diff --git a/.github/workflows/issue-label-assign.yml b/.github/workflows/issue-label-assign.yml index 17b33b8455c95..c2f5743ffb04b 100644 --- a/.github/workflows/issue-label-assign.yml +++ b/.github/workflows/issue-label-assign.yml @@ -14,7 +14,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: peterwoodworth/issue-action@main + - uses: aws-github-ops/aws-issue-triage-manager@main with: github-token: "${{ secrets.GITHUB_TOKEN }}" excluded-expressions: "[CDK CLI Version|TypeScript|Java|Python]" @@ -228,5 +228,8 @@ jobs: {"area":"@aws-cdk/region-info","keywords":["region-info","fact"],"labels":["@aws-cdk/region-info"],"assignees":["skinny85"]}, {"area":"aws-cdk-lib","keywords":["aws-cdk-lib","cdk-v2","v2","ubergen"],"labels":["aws-cdk-lib"],"assignees":["nija-at"]}, {"area":"monocdk","keywords":["monocdk","monocdk-experiment"],"labels":["monocdk"],"assignees":["nija-at"]}, - {"area":"@aws-cdk/yaml-cfn","keywords":["(aws-yaml-cfn)","(yaml-cfn)"],"labels":["@aws-cdk/aws-yaml-cfn"],"assignees":["skinny85"]} + {"area":"@aws-cdk/yaml-cfn","keywords":["(aws-yaml-cfn)","(yaml-cfn)"],"labels":["@aws-cdk/aws-yaml-cfn"],"assignees":["skinny85"]}, + {"area":"@aws-cdk/aws-apprunner","keywords":["apprunner","aws-apprunner"],"labels":["@aws-cdk/aws-apprunner"],"assignees":["corymhall"]}, + {"area":"@aws-cdk/aws-lightsail","keywords":["lightsail","aws-lightsail"],"labels":["@aws-cdk/aws-lightsail"],"assignees":["corymhall"]}, + {"area":"@aws-cdk/aws-aps","keywords":["aps","aws-aps","prometheus"],"labels":["@aws-cdk/aws-aps"],"assignees":["corymhall"]} ] diff --git a/.github/workflows/pr-linter.yml b/.github/workflows/pr-linter.yml index d88d64d89537d..8231b94fa2319 100644 --- a/.github/workflows/pr-linter.yml +++ b/.github/workflows/pr-linter.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v2 - name: Install & Build prlint - run: cd tools/@aws-cdk/prlint && yarn install --frozen-lockfile && yarn build+test + run: yarn install --frozen-lockfile && cd tools/@aws-cdk/prlint && yarn build+test - name: Validate uses: ./tools/@aws-cdk/prlint diff --git a/.mergify.yml b/.mergify.yml index c30db93044503..49320bf2385ee 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -37,10 +37,9 @@ pull_request_rules: actions: comment: message: Thank you for contributing! Your pull request will be automatically updated and merged (do not update manually, and be sure to [allow changes to be pushed to your fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork)). - merge: - strict: smart + queue: + name: default method: squash - strict_method: merge commit_message: title+body conditions: - base!=release @@ -60,11 +59,9 @@ pull_request_rules: actions: comment: message: Thank you for contributing! Your pull request will be automatically updated and merged without squashing (do not update manually, and be sure to [allow changes to be pushed to your fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork)). - merge: - strict: smart - # Merge instead of squash + queue: + name: default method: merge - strict_method: merge commit_message: title+body conditions: - -title~=(WIP|wip) @@ -106,12 +103,10 @@ pull_request_rules: actions: comment: message: Thanks Dependabot! - merge: - # 'strict: false' disables Mergify keeping the branch up-to-date from master. - # It's not necessary: Dependabot will do that itself. - # It's not dangerous: GitHub branch protection settings prevent merging stale branches. - strict: false + queue: + name: default method: squash + commit_message: title+body conditions: - -title~=(WIP|wip) - -label~=(blocked|do-not-merge) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92798df54dd59..6aa86d37bb585 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,54 @@ 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.133.0](https://github.com/aws/aws-cdk/compare/v1.132.0...v1.133.0) (2021-11-19) + + +### Features + +* **apigatewayv2:** websocket api: grant manage connections ([#16872](https://github.com/aws/aws-cdk/issues/16872)) ([10dfa60](https://github.com/aws/aws-cdk/commit/10dfa60a693db6e38a1188effc6eeebc2b5c49b8)), closes [#14828](https://github.com/aws/aws-cdk/issues/14828) +* **assertions:** support assertions over nested stacks ([#16972](https://github.com/aws/aws-cdk/issues/16972)) ([bde44e7](https://github.com/aws/aws-cdk/commit/bde44e7a767b88762ecb1370e605e6e5dfc85b52)) +* **aws-eks:** support bottlerocket managed nodegroup ([#17323](https://github.com/aws/aws-cdk/issues/17323)) ([2e6a1a9](https://github.com/aws/aws-cdk/commit/2e6a1a941dc37fdb0cffd79af4887be182eaacd1)) +* **cfnspec:** cloudformation spec v48.0.0 ([#17484](https://github.com/aws/aws-cdk/issues/17484)) ([6e8de96](https://github.com/aws/aws-cdk/commit/6e8de96c401c1a019742490850b43e398b561a62)) +* **cfnspec:** cloudformation spec v49.0.0 ([#17585](https://github.com/aws/aws-cdk/issues/17585)) ([d44d0e7](https://github.com/aws/aws-cdk/commit/d44d0e7d06bf3b420adae320e0fae4123d731451)) +* **cognito:** user pool: send emails using Amazon SES ([#17117](https://github.com/aws/aws-cdk/issues/17117)) ([503720f](https://github.com/aws/aws-cdk/commit/503720ffb90c67ac1a3a0f80faeca87c0428f2d3)), closes [#6768](https://github.com/aws/aws-cdk/issues/6768) +* **ec2:** add G5 instances ([#17499](https://github.com/aws/aws-cdk/issues/17499)) ([eed70a0](https://github.com/aws/aws-cdk/commit/eed70a0bab1885b6293ae8db4dc41b7dfd8724d8)), closes [/docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html#cfn-ec2](https://github.com/aws//docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html/issues/cfn-ec2) +* **ec2:** add m5n and m5dn instance types ([#17488](https://github.com/aws/aws-cdk/issues/17488)) ([df30d4f](https://github.com/aws/aws-cdk/commit/df30d4f7fa6c1a5c381411904526be17796f2103)) +* **ec2:** lookup security group by name ([#17246](https://github.com/aws/aws-cdk/issues/17246)) ([5bf0d07](https://github.com/aws/aws-cdk/commit/5bf0d074854ff90c5d9521f5d7f0fc9ff31c5eb5)), closes [#4241](https://github.com/aws/aws-cdk/issues/4241) +* **ec2:** vpc endpoints for codeguru ([#17498](https://github.com/aws/aws-cdk/issues/17498)) ([21c2d2b](https://github.com/aws/aws-cdk/commit/21c2d2b258f18f32c6adfbe9f3cdd3f7f2424551)), closes [#16788](https://github.com/aws/aws-cdk/issues/16788) +* **ecs:** Add SystemControls to ContainerDefinition ([#16970](https://github.com/aws/aws-cdk/issues/16970)) ([b12a2c6](https://github.com/aws/aws-cdk/commit/b12a2c68063c5739c81c032f32c82bb85c590053)), closes [#16025](https://github.com/aws/aws-cdk/issues/16025) +* **eks:** Allow passing of custom IAM role to Kube Ctl Lambda ([#17196](https://github.com/aws/aws-cdk/issues/17196)) ([8fa293a](https://github.com/aws/aws-cdk/commit/8fa293a79fc8957410637dfd3a4de2069dead36b)) +* **iot:** add Action to put objects in S3 Buckets ([#17307](https://github.com/aws/aws-cdk/issues/17307)) ([49b87db](https://github.com/aws/aws-cdk/commit/49b87dbfe5a37abad8880e0325f7aa8218705407)), closes [/github.com/aws/aws-cdk/pull/16681#issuecomment-942233029](https://github.com/aws//github.com/aws/aws-cdk/pull/16681/issues/issuecomment-942233029) +* **iot:** add Action to put records to a Firehose stream ([#17466](https://github.com/aws/aws-cdk/issues/17466)) ([7cb5f2c](https://github.com/aws/aws-cdk/commit/7cb5f2cc8402aad223eb5c50cdf5ee2e0d56150e)), closes [/github.com/aws/aws-cdk/pull/16681#issuecomment-942233029](https://github.com/aws//github.com/aws/aws-cdk/pull/16681/issues/issuecomment-942233029) +* **lambda:** singleton function: access runtime, log group and configure layers and environment ([#17372](https://github.com/aws/aws-cdk/issues/17372)) ([ec5b102](https://github.com/aws/aws-cdk/commit/ec5b102e560e241b21c63773817114fc44f7898a)) +* **rds:** validate backup retention for read replica instances ([#17569](https://github.com/aws/aws-cdk/issues/17569)) ([9b2158b](https://github.com/aws/aws-cdk/commit/9b2158bf9228a876d8f434dd5e025dbb74dbe4d5)), closes [#17356](https://github.com/aws/aws-cdk/issues/17356) +* warn users when deprecated elements are used ([#17328](https://github.com/aws/aws-cdk/issues/17328)) ([3721358](https://github.com/aws/aws-cdk/commit/3721358fa1501e42b3514b8a8f15f05c9615f149)) +* **redshift:** Add support for distStyle, distKey, sortStyle and sortKey to Table ([#17135](https://github.com/aws/aws-cdk/issues/17135)) ([a137cd1](https://github.com/aws/aws-cdk/commit/a137cd13a90cc3bfdb8207bd8764e2eb05836126)), closes [#17125](https://github.com/aws/aws-cdk/issues/17125) +* **servicecatalog:** support local launch role name in launch role constraint ([#17371](https://github.com/aws/aws-cdk/issues/17371)) ([b307b69](https://github.com/aws/aws-cdk/commit/b307b6996ed13b1f2dedeb41d29409183becb969)) +* **stepfunctions-tasks:** Support `DynamoAttributeValue.listFromJsonPath` ([#17376](https://github.com/aws/aws-cdk/issues/17376)) ([bc10e6f](https://github.com/aws/aws-cdk/commit/bc10e6ffb6164c212336ada745923e91adb8fe05)), closes [#17375](https://github.com/aws/aws-cdk/issues/17375) + + +### Bug Fixes + +* **apigateway:** SAM CLI asset metadata missing from SpecRestApi ([#17293](https://github.com/aws/aws-cdk/issues/17293)) ([841cf99](https://github.com/aws/aws-cdk/commit/841cf990001dd64605873a65b8a155e37fc4541f)), closes [#14593](https://github.com/aws/aws-cdk/issues/14593) +* **assets:** SAM asset metadata missing from log retention and custom resource provider functions ([#17551](https://github.com/aws/aws-cdk/issues/17551)) ([a90e959](https://github.com/aws/aws-cdk/commit/a90e959618fede4ea871bf5d36147a65f4ba9da8)) +* **autoscaling:** add timezone property to Scheduled Action ([#17330](https://github.com/aws/aws-cdk/issues/17330)) ([3154a58](https://github.com/aws/aws-cdk/commit/3154a58bfc5ae4b845994c7a0ab45771f5af4cd0)) +* **aws-codebuild:** add @aws-cdk/asserts to package deps ([#17435](https://github.com/aws/aws-cdk/issues/17435)) ([9c77e94](https://github.com/aws/aws-cdk/commit/9c77e941252ad16a2744577b6333ee5054302a30)) +* **aws-lambda-event-sources:** `Function.addEventSource` fails for `ManagedKafkaEventSource` typed parameters ([#17490](https://github.com/aws/aws-cdk/issues/17490)) ([a474ee8](https://github.com/aws/aws-cdk/commit/a474ee8fb6b708f4147122deeacb8fc13debaed4)) +* **aws-logs:** include new `policy.ts` exports in `index.ts` exports ([#17403](https://github.com/aws/aws-cdk/issues/17403)) ([a391468](https://github.com/aws/aws-cdk/commit/a39146840a10472c8afee71bf1a1cfc3cacb5f72)) +* **cli:** improve asset publishing times by up to 30% ([#17409](https://github.com/aws/aws-cdk/issues/17409)) ([40d6a48](https://github.com/aws/aws-cdk/commit/40d6a48eb31b09edf2ba0ea1b0a1e212156c1784)), closes [#17266](https://github.com/aws/aws-cdk/issues/17266) +* **cli:** skip bundling for the 'watch' command ([#17455](https://github.com/aws/aws-cdk/issues/17455)) ([af61b7f](https://github.com/aws/aws-cdk/commit/af61b7f2fec17d4f817e78db21d09d471d8e2baf)), closes [#17391](https://github.com/aws/aws-cdk/issues/17391) +* **cloudwatch:** render agnostic alarms in legacy style ([#17538](https://github.com/aws/aws-cdk/issues/17538)) ([7c50ef8](https://github.com/aws/aws-cdk/commit/7c50ef8de4cad7237b442c43460695518bfb1fdc)) +* **ec2:** Duplicate EIP when NatGatewayProps.eipAllocationIds is provided ([#17235](https://github.com/aws/aws-cdk/issues/17235)) ([050f6fa](https://github.com/aws/aws-cdk/commit/050f6fa74a3888fff2a495042c0ebad368775ab1)) +* **eks:** Allow specifying subnets in Pinger ([#17429](https://github.com/aws/aws-cdk/issues/17429)) ([6acee52](https://github.com/aws/aws-cdk/commit/6acee5219eef91ac3686f9b6722877cea5fff6e5)) +* **iot:** unable to add the same lambda function to two TopicRule Actions ([#17521](https://github.com/aws/aws-cdk/issues/17521)) ([eda1640](https://github.com/aws/aws-cdk/commit/eda1640fcaf6375d7edc5f8edcb5d69c82d130a1)), closes [#17508](https://github.com/aws/aws-cdk/issues/17508) +* **kinesis:** add required rights to trigger Lambda from Kinesis. Fixes issue [#17312](https://github.com/aws/aws-cdk/issues/17312). ([#17358](https://github.com/aws/aws-cdk/issues/17358)) ([0bfc15c](https://github.com/aws/aws-cdk/commit/0bfc15c991cc3373bc7c1b0cd1f5e9241398ac2c)) +* **lambda:** SAM CLI asset metadata missing from image Functions ([#17368](https://github.com/aws/aws-cdk/issues/17368)) ([f52d9bf](https://github.com/aws/aws-cdk/commit/f52d9bf13d2bb3c066ba227259a2d98a5947982b)) +* **NestedStack:** add asset metadata to NestedStack resources for local tooling ([#17343](https://github.com/aws/aws-cdk/issues/17343)) ([4ba40dc](https://github.com/aws/aws-cdk/commit/4ba40dcf275bbed0dbcca4cf6cf295edde5e9894)) +* **redshift:** tableNameSuffix evaluation ([#17213](https://github.com/aws/aws-cdk/issues/17213)) ([f7c3217](https://github.com/aws/aws-cdk/commit/f7c3217a731804f014526e10b414a9e7f27d575b)), closes [#17064](https://github.com/aws/aws-cdk/issues/17064) +* **sns-subscriptions:** enable cross region subscriptions to sqs and lambda ([#17273](https://github.com/aws/aws-cdk/issues/17273)) ([3cd8d48](https://github.com/aws/aws-cdk/commit/3cd8d481906fc4e3abdd1211908844e5b8bd2509)), closes [#7044](https://github.com/aws/aws-cdk/issues/7044) [#13707](https://github.com/aws/aws-cdk/issues/13707) +* **ssm:** fix service principals for all regions since ap-east-1 ([#17047](https://github.com/aws/aws-cdk/issues/17047)) ([5900548](https://github.com/aws/aws-cdk/commit/59005483ea1224a147db479471f541e2efb9ba23)), closes [#16188](https://github.com/aws/aws-cdk/issues/16188) + ## [1.132.0](https://github.com/aws/aws-cdk/compare/v1.131.0...v1.132.0) (2021-11-09) diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index 6def9776b6dc9..6b5d57a000a4e 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -77,4 +77,12 @@ strengthened:@aws-cdk/aws-lambda-event-sources.ManagedKafkaEventSourceProps # Remove IO2 from autoscaling EbsDeviceVolumeType. This value is not supported # at the moment and was not supported in the past. -removed:@aws-cdk/aws-autoscaling.EbsDeviceVolumeType.IO2 \ No newline at end of file +removed:@aws-cdk/aws-autoscaling.EbsDeviceVolumeType.IO2 + +# Remove autoTerminationPolicy from stepfunctions-tasks EmrCreateClusterProps. This value is not supported by stepfunctions at the moment and was not supported in the past. +removed:@aws-cdk/aws-stepfunctions-tasks.EmrCreateCluster.AutoTerminationPolicyProperty +removed:@aws-cdk/aws-stepfunctions-tasks.EmrCreateClusterProps.autoTerminationPolicy + +# Changed property securityGroupId to optional because either securityGroupId or +# securityGroupName is required. Therefore securityGroupId is no longer mandatory. +weakened:@aws-cdk/cloud-assembly-schema.SecurityGroupContextQuery diff --git a/pack.sh b/pack.sh index 81eecafabe187..4b9a13ab5aa26 100755 --- a/pack.sh +++ b/pack.sh @@ -36,10 +36,7 @@ function lerna_scopes() { done } -# Compile examples with respect to "decdk" directory, as all packages will -# be symlinked there so they can all be included. -echo "Extracting code samples" >&2 -scripts/run-rosetta.sh $TMPDIR/jsii.txt +scripts/run-rosetta.sh --infuse --pkgs-from $TMPDIR/jsii.txt # Jsii packaging (all at once using jsii-pacmak) echo "Packaging jsii modules" >&2 diff --git a/package.json b/package.json index ca79fb49d57b3..eaa90dd627a02 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,10 @@ "fs-extra": "^9.1.0", "graceful-fs": "^4.2.8", "jest-junit": "^13.0.0", - "jsii-diff": "^1.42.0", - "jsii-pacmak": "^1.42.0", - "jsii-reflect": "^1.42.0", - "jsii-rosetta": "^1.42.0", + "jsii-diff": "^1.45.0", + "jsii-pacmak": "^1.45.0", + "jsii-reflect": "^1.45.0", + "jsii-rosetta": "^1.45.0", "lerna": "^4.0.0", "patch-package": "^6.4.7", "standard-version": "^9.3.2", @@ -71,28 +71,14 @@ "nohoist": [ "**/jszip", "**/jszip/**", - "@aws-cdk/assertions-alpha/colors", - "@aws-cdk/assertions-alpha/colors/**", - "@aws-cdk/assertions-alpha/diff", - "@aws-cdk/assertions-alpha/diff/**", - "@aws-cdk/assertions-alpha/fast-deep-equal", - "@aws-cdk/assertions-alpha/fast-deep-equal/**", - "@aws-cdk/assertions-alpha/string-width", - "@aws-cdk/assertions-alpha/string-width/**", - "@aws-cdk/assertions-alpha/table", - "@aws-cdk/assertions-alpha/table/**", + "@aws-cdk/assertions-alpha/fs-extra", + "@aws-cdk/assertions-alpha/fs-extra/**", + "@aws-cdk/assertions/fs-extra", + "@aws-cdk/assertions/fs-extra/**", "@aws-cdk/aws-amplify-alpha/yaml", "@aws-cdk/aws-amplify-alpha/yaml/**", - "@aws-cdk/assertions/colors", - "@aws-cdk/assertions/colors/**", - "@aws-cdk/assertions/diff", - "@aws-cdk/assertions/diff/**", - "@aws-cdk/assertions/fast-deep-equal", - "@aws-cdk/assertions/fast-deep-equal/**", - "@aws-cdk/assertions/string-width", - "@aws-cdk/assertions/string-width/**", - "@aws-cdk/assertions/table", - "@aws-cdk/assertions/table/**", + "@aws-cdk/aws-iot-actions-alpha/case", + "@aws-cdk/aws-iot-actions-alpha/case/**", "@aws-cdk/aws-amplify/yaml", "@aws-cdk/aws-amplify/yaml/**", "@aws-cdk/aws-codebuild/yaml", @@ -107,6 +93,8 @@ "@aws-cdk/aws-eks/yaml/**", "@aws-cdk/aws-events-targets/aws-sdk", "@aws-cdk/aws-events-targets/aws-sdk/**", + "@aws-cdk/aws-iot-actions/case", + "@aws-cdk/aws-iot-actions/case/**", "@aws-cdk/aws-s3-deployment/case", "@aws-cdk/aws-s3-deployment/case/**", "@aws-cdk/cloud-assembly-schema/jsonschema", diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/extension-interfaces.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/extension-interfaces.ts index 8e70a53a59d8e..8945958ce83fb 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/extension-interfaces.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/extension-interfaces.ts @@ -1,6 +1,6 @@ import * as ecs from '@aws-cdk/aws-ecs'; import * as cdk from '@aws-cdk/core'; -import { Service } from '../service'; +import { Service, ConnectToProps } from '../service'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -225,8 +225,9 @@ export abstract class ServiceExtension { * * @param service - The other service to connect to. */ - public connectToService(service: Service) { + public connectToService(service: Service, connectToProps: ConnectToProps) { service = service; + connectToProps = connectToProps; } } diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts index 957dcef280cd7..2214f209fb935 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts @@ -10,6 +10,19 @@ import { ServiceDescription } from './service-description'; // eslint-disable-next-line no-duplicate-imports, import/order import { Construct } from '@aws-cdk/core'; +/** + * connectToProps will have all the extra parameters which are required for connecting services. + */ +export interface ConnectToProps { + /** + * local_bind_port is the local port that this application should + * use when calling the upstream service in ECS Consul Mesh Extension + * Currently, this parameter will only be used in the ECSConsulMeshExtension + * https://github.com/aws-ia/ecs-consul-mesh-extension + */ + readonly local_bind_port?: number; +} + /** * The settings for an ECS Service. */ @@ -313,10 +326,10 @@ export class Service extends Construct { * * @param service */ - public connectTo(service: Service) { + public connectTo(service: Service, connectToProps: ConnectToProps = {}) { for (const extensions in this.serviceDescription.extensions) { if (this.serviceDescription.extensions[extensions]) { - this.serviceDescription.extensions[extensions].connectToService(service); + this.serviceDescription.extensions[extensions].connectToService(service, connectToProps); } } } diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/package.json b/packages/@aws-cdk-containers/ecs-service-extensions/package.json index baa44a4772e21..49e48a3f2fa9c 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/package.json +++ b/packages/@aws-cdk-containers/ecs-service-extensions/package.json @@ -40,6 +40,7 @@ "@types/jest": "^27.0.2", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", + "@aws-cdk/aws-autoscaling": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "jest": "^27.3.1", "@aws-cdk/pkglint": "0.0.0", diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/assign-public-ip.test.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/assign-public-ip.test.ts index 630911abf254a..173a573b96e78 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/assign-public-ip.test.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/assign-public-ip.test.ts @@ -1,4 +1,5 @@ import '@aws-cdk/assert-internal/jest'; +import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as route53 from '@aws-cdk/aws-route53'; @@ -45,9 +46,13 @@ describe('assign public ip', () => { const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + cluster.addAsgCapacityProvider(new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: new autoscaling.AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + machineImage: ec2.MachineImage.latestAmazonLinux(), + instanceType: new ec2.InstanceType('t2.micro'), + }), + })); const environment = new Environment(stack, 'production', { vpc, diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/environment.test.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/environment.test.ts index 1f893305ef788..443c9fa9f9f68 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/environment.test.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/environment.test.ts @@ -1,4 +1,5 @@ import '@aws-cdk/assert-internal/jest'; +import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as cdk from '@aws-cdk/core'; @@ -148,9 +149,13 @@ describe('environment', () => { // WHEN const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + cluster.addAsgCapacityProvider(new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: new autoscaling.AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + machineImage: ec2.MachineImage.latestAmazonLinux(), + instanceType: new ec2.InstanceType('t2.micro'), + }), + })); const environment = new Environment(stack, 'production', { vpc, @@ -222,9 +227,13 @@ describe('environment', () => { const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + cluster.addAsgCapacityProvider(new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: new autoscaling.AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + machineImage: ec2.MachineImage.latestAmazonLinux(), + instanceType: new ec2.InstanceType('t2.micro'), + }), + })); // WHEN const environment = Environment.fromEnvironmentAttributes(stack, 'Environment', { diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts index 65807231679b7..39d26ef371f17 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/service.test.ts @@ -1,5 +1,6 @@ import { ABSENT } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as iam from '@aws-cdk/aws-iam'; @@ -31,9 +32,13 @@ describe('service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + cluster.addAsgCapacityProvider(new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: new autoscaling.AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + machineImage: ec2.MachineImage.latestAmazonLinux(), + instanceType: new ec2.InstanceType('t2.micro'), + }), + })); const environment = new Environment(stack, 'production', { vpc, diff --git a/packages/@aws-cdk/app-delivery/test/pipeline-deploy-stack-action.test.ts b/packages/@aws-cdk/app-delivery/test/pipeline-deploy-stack-action.test.ts index e065330c152d3..b0c8e4f941a04 100644 --- a/packages/@aws-cdk/app-delivery/test/pipeline-deploy-stack-action.test.ts +++ b/packages/@aws-cdk/app-delivery/test/pipeline-deploy-stack-action.test.ts @@ -7,6 +7,7 @@ import * as cpactions from '@aws-cdk/aws-codepipeline-actions'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; @@ -19,7 +20,7 @@ interface SelfUpdatingPipeline { } const accountId = fc.array(fc.integer(0, 9), 12, 12).map(arr => arr.join()); -describe('pipeline deploy stack action', () => { +describeDeprecated('pipeline deploy stack action', () => { test('rejects cross-environment deployment', () => { fc.assert( fc.property( diff --git a/packages/@aws-cdk/assertions/NOTICE b/packages/@aws-cdk/assertions/NOTICE index ab7c1d91ac484..9e3fd34dbe209 100644 --- a/packages/@aws-cdk/assertions/NOTICE +++ b/packages/@aws-cdk/assertions/NOTICE @@ -5,410 +5,84 @@ Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. The AWS CDK includes the following third-party software/licensing: -** colors - https://www.npmjs.com/package/colors -Original Library - - Copyright (c) Marak Squires +** fs-extra - https://www.npmjs.com/package/fs-extra +Copyright (c) 2011-2017 JP Richardson -Additional Functionality - - Copyright (c) Sindre Sorhus (sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------- - -** diff - https://www.npmjs.com/package/diff -Copyright (c) 2009-2015, Kevin Decker - -All rights reserved. - -Redistribution and use of this software in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of Kevin Decker nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER -IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------- - -** fast-deep-equal - https://www.npmjs.com/package/fast-deep-equal -Copyright (c) 2017 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** string-width - https://www.npmjs.com/package/string-width -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** emoji-regex - https://www.npmjs.com/package/emoji-regex -Copyright Mathias Bynens - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** is-fullwidth-code-point - https://www.npmjs.com/package/is-fullwidth-code-point -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +** at-least-node - https://www.npmjs.com/package/at-least-node +Copyright (c) 2020 Ryan Zimmerman -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------- -** strip-ansi - https://www.npmjs.com/package/strip-ansi -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +** graceful-fs - https://www.npmjs.com/package/graceful-fs +Copyright (c) Isaac Z. Schlueter, Ben Noordhuis, and Contributors -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------- -** ansi-regex - https://www.npmjs.com/package/ansi-regex -Copyright (c) Sindre Sorhus (https://sindresorhus.com) +** jsonfile - https://www.npmjs.com/package/jsonfile +Copyright (c) 2012-2015, JP Richardson -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** table - https://www.npmjs.com/package/table -Copyright (c) 2018, Gajus Kuizinas (http://gajus.com/) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the Gajus Kuizinas (http://gajus.com/) nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. +** universalify - https://www.npmjs.com/package/universalify +Copyright (c) 2017, Ryan Zimmerman -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ANUARY BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------- - -** ajv - https://www.npmjs.com/package/ajv -Copyright (c) 2015-2021 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the 'Software'), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** json-schema-traverse - https://www.npmjs.com/package/json-schema-traverse -Copyright (c) 2017 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** require-from-string - https://www.npmjs.com/package/require-from-string -Copyright (c) Vsevolod Strukchinsky (github.com/floatdrop) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------- - -** uri-js - https://www.npmjs.com/package/uri-js -Copyright 2011 Gary Court. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY GARY COURT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Gary Court. - ----------------- - -** punycode - https://www.npmjs.com/package/punycode -Copyright Mathias Bynens - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** lodash.truncate - https://www.npmjs.com/package/lodash -Copyright JS Foundation and other contributors - -Based on Underscore.js, copyright Jeremy Ashkenas, -DocumentCloud and Investigative Reporters & Editors - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/lodash/lodash - -The following license applies to all parts of this software except as -documented below: - -==== - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -==== - -Copyright and related rights for sample code are waived via CC0. Sample -code is defined as all source code displayed within the prose of the -documentation. - -CC0: http://creativecommons.org/publicdomain/zero/1.0/ - -==== - -Files located in the node_modules and vendor directories are externally -maintained libraries used by this software which have their own -licenses; we recommend you read them, as their terms may differ from the -terms above. - ----------------- - -** slice-ansi - https://www.npmjs.com/package/slice-ansi -Copyright (c) DC -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** ansi-styles - https://www.npmjs.com/package/ansi-styles -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** color-convert - https://www.npmjs.com/package/color-convert -Copyright (c) 2011-2016 Heather Arthur . -Copyright (c) 2016-2021 Josh Junon . - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** color-name - https://www.npmjs.com/package/color-name -Copyright (c) 2015 Dmitry Ivanov - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** astral-regex - https://www.npmjs.com/package/astral-regex -Copyright (c) Kevin Mårtensson (github.com/kevva) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/lib/template.ts b/packages/@aws-cdk/assertions/lib/template.ts index 01e0d3376dc8c..dfc830cf8d822 100644 --- a/packages/@aws-cdk/assertions/lib/template.ts +++ b/packages/@aws-cdk/assertions/lib/template.ts @@ -1,4 +1,6 @@ +import * as path from 'path'; import { Stack, Stage } from '@aws-cdk/core'; +import * as fs from 'fs-extra'; import { Match } from './match'; import { Matcher } from './matcher'; import { findMappings, hasMapping } from './private/mappings'; @@ -179,5 +181,9 @@ function toTemplate(stack: Stack): any { throw new Error('unexpected: all stacks must be part of a Stage or an App'); } const assembly = root.synth(); + if (stack.nestedStackParent) { + // if this is a nested stack (it has a parent), then just read the template as a string + return JSON.parse(fs.readFileSync(path.join(assembly.directory, stack.templateFile)).toString('utf-8')); + } return assembly.getStackArtifact(stack.artifactId).template; } \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/package.json b/packages/@aws-cdk/assertions/package.json index a780d1623b39e..1b515143d4d58 100644 --- a/packages/@aws-cdk/assertions/package.json +++ b/packages/@aws-cdk/assertions/package.json @@ -50,7 +50,7 @@ "metadata": { "jsii": { "rosetta": { - "strict": false + "strict": true } } } @@ -64,6 +64,7 @@ "devDependencies": { "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", + "@types/fs-extra": "^9.0.13", "@types/jest": "^27.0.2", "constructs": "^3.3.69", "jest": "^27.3.1", @@ -73,12 +74,8 @@ "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/cx-api": "0.0.0", - "colors": "^1.4.0", "constructs": "^3.3.69", - "diff": "^5.0.0", - "fast-deep-equal": "^3.1.3", - "string-width": "^4.2.3", - "table": "^6.7.2" + "fs-extra": "^9.1.0" }, "peerDependencies": { "@aws-cdk/cloud-assembly-schema": "0.0.0", @@ -87,11 +84,7 @@ "constructs": "^3.3.69" }, "bundledDependencies": [ - "colors", - "diff", - "fast-deep-equal", - "string-width", - "table" + "fs-extra" ], "repository": { "url": "https://github.com/aws/aws-cdk.git", diff --git a/packages/@aws-cdk/assertions/test/template.test.ts b/packages/@aws-cdk/assertions/test/template.test.ts index 3384cda21207f..64141edf52e89 100644 --- a/packages/@aws-cdk/assertions/test/template.test.ts +++ b/packages/@aws-cdk/assertions/test/template.test.ts @@ -1,11 +1,10 @@ -import { App, CfnMapping, CfnOutput, CfnResource, Stack } from '@aws-cdk/core'; +import { App, CfnMapping, CfnOutput, CfnResource, NestedStack, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { Match, Template } from '../lib'; describe('Template', () => { - describe('asObject', () => { - test('fromString', () => { - const template = Template.fromString(`{ + test('fromString', () => { + const template = Template.fromString(`{ "Resources": { "Foo": { "Type": "Baz::Qux", @@ -14,30 +13,57 @@ describe('Template', () => { } }`); + expect(template.toJSON()).toEqual({ + Resources: { + Foo: { + Type: 'Baz::Qux', + Properties: { Fred: 'Waldo' }, + }, + }, + }); + }); + + describe('fromStack', () => { + test('default', () => { + const app = new App({ + context: { + '@aws-cdk/core:newStyleStackSynthesis': false, + }, + }); + const stack = new Stack(app); + new CfnResource(stack, 'Foo', { + type: 'Foo::Bar', + properties: { + Baz: 'Qux', + }, + }); + const template = Template.fromStack(stack); + expect(template.toJSON()).toEqual({ Resources: { Foo: { - Type: 'Baz::Qux', - Properties: { Fred: 'Waldo' }, + Type: 'Foo::Bar', + Properties: { Baz: 'Qux' }, }, }, }); }); - test('fromStack', () => { + test('nested', () => { const app = new App({ context: { '@aws-cdk/core:newStyleStackSynthesis': false, }, }); const stack = new Stack(app); - new CfnResource(stack, 'Foo', { + const nested = new NestedStack(stack, 'MyNestedStack'); + new CfnResource(nested, 'Foo', { type: 'Foo::Bar', properties: { Baz: 'Qux', }, }); - const template = Template.fromStack(stack); + const template = Template.fromStack(nested); expect(template.toJSON()).toEqual({ Resources: { diff --git a/packages/@aws-cdk/assets/package.json b/packages/@aws-cdk/assets/package.json index 53d52c49cb4b4..c099d2edc4c02 100644 --- a/packages/@aws-cdk/assets/package.json +++ b/packages/@aws-cdk/assets/package.json @@ -78,7 +78,7 @@ "aws-cdk": "0.0.0", "jest": "^27.3.1", "sinon": "^9.2.4", - "ts-mock-imports": "^1.3.7" + "ts-mock-imports": "^1.3.8" }, "dependencies": { "@aws-cdk/core": "0.0.0", diff --git a/packages/@aws-cdk/assets/test/staging.test.ts b/packages/@aws-cdk/assets/test/staging.test.ts index 4c95f236f2d81..bd924e434207b 100644 --- a/packages/@aws-cdk/assets/test/staging.test.ts +++ b/packages/@aws-cdk/assets/test/staging.test.ts @@ -1,11 +1,12 @@ import * as fs from 'fs'; import * as path from 'path'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, Stack } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import '@aws-cdk/assert-internal/jest'; import { Staging } from '../lib'; -describe('staging', () => { +describeDeprecated('staging', () => { test('base case', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts b/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts index 850087920f152..96b9a5aced9e1 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts @@ -3,6 +3,10 @@ import * as s3_assets from '@aws-cdk/aws-s3-assets'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order +import * as cxapi from '@aws-cdk/cx-api'; +import { Node } from 'constructs'; +import { CfnRestApi } from './apigateway.generated'; +import { IRestApi } from './restapi'; import { Construct } from '@aws-cdk/core'; /** @@ -82,6 +86,15 @@ export abstract class ApiDefinition { * assume it's initialized. You may just use it as a construct scope. */ public abstract bind(scope: Construct): ApiDefinitionConfig; + + /** + * Called after the CFN RestApi resource has been created to allow the Api + * Definition to bind to it. Specifically it's required to allow assets to add + * metadata for tooling like SAM CLI to be able to find their origins. + */ + public bindAfterCreate(_scope: Construct, _restApi: IRestApi) { + return; + } } /** @@ -198,4 +211,18 @@ export class AssetApiDefinition extends ApiDefinition { }, }; } + + public bindAfterCreate(scope: Construct, restApi: IRestApi) { + if (!scope.node.tryGetContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT)) { + return; // not enabled + } + + if (!this.asset) { + throw new Error('bindToResource() must be called after bind()'); + } + + const child = Node.of(restApi).defaultChild as CfnRestApi; + child.addMetadata(cxapi.ASSET_RESOURCE_METADATA_PATH_KEY, this.asset.assetPath); + child.addMetadata(cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY, 'BodyS3Location'); + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts index a8065430fef4c..c6dd3e8c44dbd 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts @@ -1,5 +1,5 @@ import * as iam from '@aws-cdk/aws-iam'; -import { IResource as IResourceBase, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource as IResourceBase, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApiKey } from './apigateway.generated'; import { ResourceOptions } from './resource'; @@ -146,7 +146,7 @@ export class ApiKey extends ApiKeyBase { service: 'apigateway', account: '', resource: '/apikeys', - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, resourceName: apiKeyId, }); } @@ -177,7 +177,7 @@ export class ApiKey extends ApiKeyBase { service: 'apigateway', account: '', resource: '/apikeys', - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, resourceName: this.keyId, }); } @@ -236,12 +236,12 @@ export class RateLimitedApiKey extends ApiKeyBase { const resource = new ApiKey(this, 'Resource', props); if (props.apiStages || props.quota || props.throttle) { - new UsagePlan(this, 'UsagePlanResource', { - apiKey: resource, + const usageplan = new UsagePlan(this, 'UsagePlanResource', { apiStages: props.apiStages, quota: props.quota, throttle: props.throttle, }); + usageplan.addApiKey(resource); } this.keyId = resource.keyId; diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts index 0e607e25a21cf..8b7ef8cccd565 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts @@ -1,4 +1,5 @@ import * as cdk from '@aws-cdk/core'; +import { ArnFormat } from '@aws-cdk/core'; import { Integration, IntegrationConfig, IntegrationOptions, IntegrationType } from '../integration'; import { Method } from '../method'; import { parseAwsApiCall } from '../util'; @@ -92,7 +93,7 @@ export class AwsIntegration extends Integration { service: 'apigateway', account: backend, resource: apiType, - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, resourceName: apiValue, region: props.region, }); diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index 919d27e6573be..5a99d03270f41 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -1,4 +1,4 @@ -import { Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnMethod, CfnMethodProps } from './apigateway.generated'; import { Authorizer, IAuthorizer } from './authorizer'; @@ -275,7 +275,7 @@ export class Method extends Resource { } else if (options.credentialsPassthrough) { // arn:aws:iam::*:user/* // eslint-disable-next-line max-len - credentials = Stack.of(this).formatArn({ service: 'iam', region: '', account: '*', resource: 'user', sep: '/', resourceName: '*' }); + credentials = Stack.of(this).formatArn({ service: 'iam', region: '', account: '*', resource: 'user', arnFormat: ArnFormat.SLASH_RESOURCE_NAME, resourceName: '*' }); } return { @@ -346,7 +346,7 @@ export class Method extends Resource { } if (options.requestValidatorOptions) { - const validator = this.restApi.addRequestValidator('validator', options.requestValidatorOptions); + const validator = (this.api as RestApi).addRequestValidator('validator', options.requestValidatorOptions); return validator.requestValidatorId; } diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index e213ddad7f22f..0ed981690af3c 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -1,7 +1,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import { IVpcEndpoint } from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { CfnOutput, IResource as IResourceBase, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, CfnOutput, IResource as IResourceBase, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { ApiDefinition } from './api-definition'; import { ApiKey, ApiKeyOptions, IApiKey } from './api-key'; @@ -373,7 +373,7 @@ export abstract class RestApiBase extends Resource implements IRestApi { return Stack.of(this).formatArn({ service: 'execute-api', resource: this.restApiId, - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, resourceName: `${stage}/${method}${path}`, }); } @@ -405,7 +405,7 @@ export abstract class RestApiBase extends Resource implements IRestApi { return new cloudwatch.Metric({ namespace: 'AWS/ApiGateway', metricName, - dimensions: { ApiName: this.restApiName }, + dimensionsMap: { ApiName: this.restApiName }, ...props, }).attachTo(this); } @@ -626,6 +626,9 @@ export class SpecRestApi extends RestApiBase { endpointConfiguration: this._configureEndpoints(props), parameters: props.parameters, }); + + props.apiDefinition.bindAfterCreate(this, this); + this.node.defaultChild = resource; this.restApiId = resource.ref; this.restApiRootResourceId = resource.attrRootResourceId; diff --git a/packages/@aws-cdk/aws-apigateway/package.json b/packages/@aws-cdk/aws-apigateway/package.json index 0901cd7438a7b..d20ad7f17d5f5 100644 --- a/packages/@aws-cdk/aws-apigateway/package.json +++ b/packages/@aws-cdk/aws-apigateway/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-apigateway/test/api-definition.test.ts b/packages/@aws-cdk/aws-apigateway/test/api-definition.test.ts index 709900b36c71d..c4af056038451 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-definition.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/api-definition.test.ts @@ -1,7 +1,9 @@ import '@aws-cdk/assert-internal/jest'; import * as path from 'path'; +import { ResourcePart } from '@aws-cdk/assert-internal'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; +import * as cxapi from '@aws-cdk/cx-api'; import * as apigw from '../lib'; describe('api definition', () => { @@ -73,6 +75,23 @@ describe('api definition', () => { expect(synthesized.assets.length).toEqual(1); }); + + test('asset metadata added to RestApi resource that contains Asset Api Definition', () => { + const stack = new cdk.Stack(); + stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + const assetApiDefinition = apigw.ApiDefinition.fromAsset(path.join(__dirname, 'sample-definition.yaml')); + new apigw.SpecRestApi(stack, 'API', { + apiDefinition: assetApiDefinition, + }); + + expect(stack).toHaveResource('AWS::ApiGateway::RestApi', { + Metadata: { + 'aws:asset:path': 'asset.68497ac876de4e963fc8f7b5f1b28844c18ecc95e3f7c6e9e0bf250e03c037fb.yaml', + 'aws:asset:property': 'BodyS3Location', + }, + }, ResourcePart.CompleteDefinition); + + }); }); describe('apigateway.ApiDefinition.fromBucket', () => { diff --git a/packages/@aws-cdk/aws-apigateway/test/api-key.test.ts b/packages/@aws-cdk/aws-apigateway/test/api-key.test.ts index 6fee9378f1217..a929519d39c5a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/api-key.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/api-key.test.ts @@ -66,9 +66,8 @@ describe('api key', () => { // WHEN const importedKey = apigateway.ApiKey.fromApiKeyId(stack, 'imported', 'KeyIdabc'); - api.addUsagePlan('plan', { - apiKey: importedKey, - }); + const usagePlan = api.addUsagePlan('plan'); + usagePlan.addApiKey(importedKey); // THEN expect(stack).toHaveResourceLike('AWS::ApiGateway::UsagePlanKey', { diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json index e976e6426e0a5..e59d8e02743fc 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json @@ -72,14 +72,14 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "MyAuthorizerFunctionServiceRole8A34C19E", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "MyAuthorizerFunctionServiceRole8A34C19E" @@ -313,4 +313,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.ts b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.ts index d8160e3be6f49..066badbe4e0bb 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.ts +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.ts @@ -15,7 +15,7 @@ const app = new App(); const stack = new Stack(app, 'RequestAuthorizerInteg'); const authorizerFn = new lambda.Function(stack, 'MyAuthorizerFunction', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.AssetCode.fromAsset(path.join(__dirname, 'integ.request-authorizer.handler')), }); diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json index 1f3a157fc36b9..27e2f89e8f631 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json @@ -72,14 +72,14 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "MyAuthorizerFunctionServiceRole8A34C19E", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "MyAuthorizerFunctionServiceRole8A34C19E" diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.ts b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.ts index 47d05cf481009..5890d03f9bc3a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.ts +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.ts @@ -16,7 +16,7 @@ const app = new App(); const stack = new Stack(app, 'TokenAuthorizerIAMRoleInteg'); const authorizerFn = new lambda.Function(stack, 'MyAuthorizerFunction', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.AssetCode.fromAsset(path.join(__dirname, 'integ.token-authorizer.handler')), }); diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json index bb4c493fac04b..8a56511175436 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json @@ -72,14 +72,14 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "MyAuthorizerFunctionServiceRole8A34C19E", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "MyAuthorizerFunctionServiceRole8A34C19E" @@ -313,4 +313,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.ts b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.ts index e62e476e8cd4a..655fa91962b1a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.ts +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.ts @@ -15,7 +15,7 @@ const app = new App(); const stack = new Stack(app, 'TokenAuthorizerInteg'); const authorizerFn = new lambda.Function(stack, 'MyAuthorizerFunction', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.AssetCode.fromAsset(path.join(__dirname, 'integ.token-authorizer.handler')), }); diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.cors.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.cors.expected.json index 6d17b2e53232e..146d5220f7540 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.cors.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.cors.expected.json @@ -564,14 +564,14 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "handlerServiceRole187D5A5A", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "handlerServiceRole187D5A5A" diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.cors.ts b/packages/@aws-cdk/aws-apigateway/test/integ.cors.ts index 5b3fc8bdc36c2..e5617f4c9b202 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.cors.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.cors.ts @@ -12,7 +12,7 @@ class TestStack extends Stack { const api = new apigw.RestApi(this, 'cors-api-test'); const handler = new lambda.Function(this, 'handler', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'integ.cors.handler')), }); diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.expected.json index 17dd7ccf222e8..8cf5ef7c0e552 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.expected.json @@ -37,14 +37,14 @@ "Code": { "ZipFile": "foo" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "myfnServiceRole7822DC24", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "myfnServiceRole7822DC24" diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.ts b/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.ts index d6176fdb78301..615c934f918fe 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.ts @@ -9,7 +9,7 @@ class LateBoundDeploymentStageStack extends Stack { const fn = new Function(this, 'myfn', { code: Code.fromInline('foo'), - runtime: Runtime.NODEJS_10_X, + runtime: Runtime.NODEJS_14_X, handler: 'index.handler', }); diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json index 91af30b6ef8d4..a77027807057d 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json @@ -37,14 +37,14 @@ "Code": { "ZipFile": "exports.handler = function echoHandlerCode(event, _, callback) {\n return callback(undefined, {\n isBase64Encoded: false,\n statusCode: 200,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(event),\n });\n}" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "BooksHandlerServiceRole5B6A8847", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "BooksHandlerServiceRole5B6A8847" @@ -87,14 +87,14 @@ "Code": { "ZipFile": "exports.handler = function echoHandlerCode(event, _, callback) {\n return callback(undefined, {\n isBase64Encoded: false,\n statusCode: 200,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(event),\n });\n}" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "BookHandlerServiceRole894768AD", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "BookHandlerServiceRole894768AD" @@ -137,14 +137,14 @@ "Code": { "ZipFile": "exports.handler = function helloCode(_event, _context, callback) {\n return callback(undefined, {\n statusCode: 200,\n body: 'hello, world!',\n });\n}" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "HelloServiceRole1E55EA16", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "HelloServiceRole1E55EA16" diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.ts index 9c647c83dcebd..0ec001e3d1a3a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.ts @@ -7,19 +7,19 @@ class BookStack extends cdk.Stack { super(scope, id); const booksHandler = new apigw.LambdaIntegration(new lambda.Function(this, 'BooksHandler', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.Code.fromInline(`exports.handler = ${echoHandlerCode}`), })); const bookHandler = new apigw.LambdaIntegration(new lambda.Function(this, 'BookHandler', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.Code.fromInline(`exports.handler = ${echoHandlerCode}`), })); const hello = new apigw.LambdaIntegration(new lambda.Function(this, 'Hello', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.Code.fromInline(`exports.handler = ${helloCode}`), })); diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json index a0fb6357db3c7..606c5f2098a0d 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json @@ -651,14 +651,14 @@ "Code": { "ZipFile": "exports.handler = function handlerCode(event, _, callback) {\n return callback(undefined, {\n isBase64Encoded: false,\n statusCode: 200,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(event),\n });\n }" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "MyHandlerServiceRoleFFA06653", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "MyHandlerServiceRoleFFA06653" diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.expected.json index 5aa57732955bc..198d0e80231ae 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.expected.json @@ -38,15 +38,15 @@ "Code": { "ZipFile": "exports.handler = async function(event) {\n return {\n 'headers': { 'Content-Type': 'text/plain' },\n 'statusCode': 200\n }\n }" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "firstLambdaServiceRoleB6408C31", "Arn" ] }, - "Runtime": "nodejs10.x", - "FunctionName": "FirstLambda" + "FunctionName": "FirstLambda", + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "firstLambdaServiceRoleB6408C31" diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.ts index 0ac4e01241eba..75b6e219613de 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multistack.ts @@ -20,7 +20,7 @@ class FirstStack extends cdk.Stack { } }`), handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, }); } } diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.expected.json index 6a7cea680ef60..0ed3cb19c6aad 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.expected.json @@ -37,14 +37,14 @@ "Code": { "ZipFile": "exports.handler = function helloCode(_event, _context, callback) {\n return callback(undefined, {\n statusCode: 200,\n body: 'hello, world!',\n });\n}" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "HelloServiceRole1E55EA16", "Arn" ] }, - "Runtime": "nodejs10.x" + "Handler": "index.handler", + "Runtime": "nodejs14.x" }, "DependsOn": [ "HelloServiceRole1E55EA16" diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.ts index a248bf9041158..612bd0e83b963 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.multiuse.ts @@ -7,7 +7,7 @@ class MultiStack extends cdk.Stack { super(scope, id); const hello = new apigw.LambdaIntegration(new lambda.Function(this, 'Hello', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', code: lambda.Code.fromInline(`exports.handler = ${helloCode}`), })); diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts index 10e3d357108d7..537ec3031d58a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts @@ -23,7 +23,7 @@ class Test extends cdk.Stack { }); const handler = new lambda.Function(this, 'MyHandler', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, code: lambda.Code.fromInline(`exports.handler = ${handlerCode}`), handler: 'index.handler', }); diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.vpc-endpoint.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.vpc-endpoint.expected.json index 9051ff580c010..4279ff70893aa 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.vpc-endpoint.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.vpc-endpoint.expected.json @@ -95,15 +95,15 @@ "MyVpcPublicSubnet1NATGatewayAD3400C1": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + }, "AllocationId": { "Fn::GetAtt": [ "MyVpcPublicSubnet1EIP096967CB", "AllocationId" ] }, - "SubnetId": { - "Ref": "MyVpcPublicSubnet1SubnetF6608456" - }, "Tags": [ { "Key": "Name", @@ -192,15 +192,15 @@ "MyVpcPublicSubnet2NATGateway91BFBEC9": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + }, "AllocationId": { "Fn::GetAtt": [ "MyVpcPublicSubnet2EIP8CCBA239", "AllocationId" ] }, - "SubnetId": { - "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" - }, "Tags": [ { "Key": "Name", @@ -289,15 +289,15 @@ "MyVpcPublicSubnet3NATGatewayD4B50EBE": { "Type": "AWS::EC2::NatGateway", "Properties": { + "SubnetId": { + "Ref": "MyVpcPublicSubnet3Subnet57EEE236" + }, "AllocationId": { "Fn::GetAtt": [ "MyVpcPublicSubnet3EIPC5ACADAB", "AllocationId" ] }, - "SubnetId": { - "Ref": "MyVpcPublicSubnet3Subnet57EEE236" - }, "Tags": [ { "Key": "Name", diff --git a/packages/@aws-cdk/aws-apigateway/test/lambda-api.test.ts b/packages/@aws-cdk/aws-apigateway/test/lambda-api.test.ts index 0babe3d5671e3..7e412c549e897 100644 --- a/packages/@aws-cdk/aws-apigateway/test/lambda-api.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/lambda-api.test.ts @@ -180,7 +180,7 @@ describe('lambda api', () => { expect(() => new apigw.LambdaRestApi(stack, 'lambda-rest-api', { handler, - options: { defaultIntegration: new apigw.HttpIntegration('https://foo/bar') }, + defaultIntegration: new apigw.HttpIntegration('https://foo/bar'), })).toThrow(/Cannot specify \"defaultIntegration\" since Lambda integration is automatically defined/); expect(() => new apigw.LambdaRestApi(stack, 'lambda-rest-api', { diff --git a/packages/@aws-cdk/aws-apigateway/test/method.test.ts b/packages/@aws-cdk/aws-apigateway/test/method.test.ts index fdddb91777755..f1037e550e23a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/method.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/method.test.ts @@ -2,6 +2,7 @@ import '@aws-cdk/assert-internal/jest'; import { ABSENT } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as apigw from '../lib'; @@ -902,7 +903,7 @@ describe('method', () => { }); - test('"restApi" and "api" properties return the RestApi correctly', () => { + testDeprecated('"restApi" and "api" properties return the RestApi correctly', () => { // GIVEN const stack = new cdk.Stack(); @@ -918,7 +919,7 @@ describe('method', () => { }); - test('"restApi" throws an error on imported while "api" returns correctly', () => { + testDeprecated('"restApi" throws an error on imported while "api" returns correctly', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-apigateway/test/resource.test.ts b/packages/@aws-cdk/aws-apigateway/test/resource.test.ts index 36d3fff2ccdab..b118c328073cb 100644 --- a/packages/@aws-cdk/aws-apigateway/test/resource.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/resource.test.ts @@ -201,7 +201,7 @@ describe('resource', () => { const cResource = aResource.addResource('b').addResource('c'); // THEN - expect(stack.resolve(aResource.url)).toEqual({ + expect(stack.resolve(api.urlForPath(aResource.path))).toEqual({ 'Fn::Join': [ '', [ @@ -217,7 +217,7 @@ describe('resource', () => { ], ], }); - expect(stack.resolve(cResource.url)).toEqual({ + expect(stack.resolve(api.urlForPath(cResource.path))).toEqual({ 'Fn::Join': [ '', [ diff --git a/packages/@aws-cdk/aws-apigateway/test/restapi.test.ts b/packages/@aws-cdk/aws-apigateway/test/restapi.test.ts index 60cc707468c4c..d6f17c21df8da 100644 --- a/packages/@aws-cdk/aws-apigateway/test/restapi.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/restapi.test.ts @@ -1,6 +1,7 @@ import '@aws-cdk/assert-internal/jest'; import { ResourcePart, SynthUtils } from '@aws-cdk/assert-internal'; import { GatewayVpcEndpoint } from '@aws-cdk/aws-ec2'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, CfnElement, CfnResource, Stack } from '@aws-cdk/core'; import * as apigw from '../lib'; @@ -807,7 +808,7 @@ describe('restapi', () => { }); }); - test('"restApi" and "api" properties return the RestApi correctly', () => { + testDeprecated('"restApi" and "api" properties return the RestApi correctly', () => { // GIVEN const stack = new Stack(); @@ -821,7 +822,7 @@ describe('restapi', () => { expect(stack.resolve(method.api.restApiId)).toEqual(stack.resolve(method.restApi.restApiId)); }); - test('"restApi" throws an error on imported while "api" returns correctly', () => { + testDeprecated('"restApi" throws an error on imported while "api" returns correctly', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json index 67b23ee362f5a..8465cb5e8a9a5 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json @@ -30,7 +30,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.lambda.expected.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.lambda.expected.json index 69c407f274908..6f0d30c71ad66 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.lambda.expected.json +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.lambda.expected.json @@ -101,9 +101,6 @@ "Ref": "MyHttpApi8AEAAC21" }, "AuthorizerType": "REQUEST", - "IdentitySource": [ - "$request.header.X-API-Key" - ], "Name": "my-simple-authorizer", "AuthorizerPayloadFormatVersion": "2.0", "AuthorizerResultTtlInSeconds": 300, @@ -130,7 +127,10 @@ ] ] }, - "EnableSimpleResponses": true + "EnableSimpleResponses": true, + "IdentitySource": [ + "$request.header.X-API-Key" + ] } }, "MyHttpApiAuthorizerIntegMyHttpApimysimpleauthorizer0F14A472PermissionF37EF5C8": { diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md index dc29843d00209..d284be9491e99 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md @@ -172,7 +172,7 @@ The following example creates a new header - `header2` - as a copy of `header1` ```ts import { HttpAlbIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; -declare const lb: elbv2.NetworkLoadBalancer; +declare const lb: elbv2.ApplicationLoadBalancer; const listener = lb.addListener('listener', { port: 80 }); listener.addTargets('target', { port: 80, @@ -182,9 +182,8 @@ const httpEndpoint = new apigwv2.HttpApi(this, 'HttpProxyPrivateApi', { defaultIntegration: new HttpAlbIntegration({ listener, parameterMapping: new apigwv2.ParameterMapping() - .appendHeader('header2', apigwv2.MappingValue.header('header1')) + .appendHeader('header2', apigwv2.MappingValue.requestHeader('header1')) .removeHeader('header1'), - }), }), }); ``` @@ -194,7 +193,7 @@ To add mapping keys and values not yet supported by the CDK, use the `custom()` ```ts import { HttpAlbIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; -declare const lb: elbv2.NetworkLoadBalancer; +declare const lb: elbv2.ApplicationLoadBalancer; const listener = lb.addListener('listener', { port: 80 }); listener.addTargets('target', { port: 80, @@ -203,9 +202,7 @@ listener.addTargets('target', { const httpEndpoint = new apigwv2.HttpApi(this, 'HttpProxyPrivateApi', { defaultIntegration: new HttpAlbIntegration({ listener, - parameterMapping: new apigwv2.ParameterMapping() - .custom('myKey', 'myValue'), - }), + parameterMapping: new apigwv2.ParameterMapping().custom('myKey', 'myValue'), }), }); ``` @@ -217,7 +214,7 @@ WebSocket integrations connect a route to backend resources. The following integ ### Lambda WebSocket Integration -Lambda integrations enable integrating a WebSocket API route with a Lambda function. When a client connects/disconnects +Lambda integrations enable integrating a WebSocket API route with a Lambda function. When a client connects/disconnects or sends message specific to a route, the API Gateway service forwards the request to the Lambda function The API Gateway service will invoke the lambda function with an event payload of a specific format. diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/package.json b/packages/@aws-cdk/aws-apigatewayv2-integrations/package.json index 10cde741e9d26..203793018a92a 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index cc3132a9a072a..cc6c6f48c5827 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -40,6 +40,7 @@ Higher level constructs for Websocket APIs | ![Experimental](https://img.shields - [VPC Link](#vpc-link) - [Private Integration](#private-integration) - [WebSocket API](#websocket-api) + - [Manage Connections Permission](#manage-connections-permission) ## Introduction @@ -403,3 +404,22 @@ webSocketApi.addRoute('sendmessage', { }), }); ``` + +### Manage Connections Permission + +Grant permission to use API Gateway Management API of a WebSocket API by calling the `grantManageConnections` API. +You can use Management API to send a callback message to a connected client, get connection information, or disconnect the client. Learn more at [Use @connections commands in your backend service](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-how-to-call-websocket-api-connections.html). + +```ts +const lambda = new lambda.Function(this, 'lambda', { /* ... */ }); + +const webSocketApi = new WebSocketApi(stack, 'mywsapi'); +const stage = new WebSocketStage(stack, 'mystage', { + webSocketApi, + stageName: 'dev', +}); +// per stage permission +stage.grantManageConnections(lambda); +// for all the stages permission +webSocketApi.grantManageConnections(lambda); +``` diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/common/base.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/common/base.ts index 006df3f83cd2b..26d9ca64e391a 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/common/base.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/common/base.ts @@ -21,7 +21,7 @@ export abstract class ApiBase extends Resource implements IApi { return new cloudwatch.Metric({ namespace: 'AWS/ApiGateway', metricName, - dimensions: { ApiId: this.apiId }, + dimensionsMap: { ApiId: this.apiId }, ...props, }).attachTo(this); } @@ -66,7 +66,7 @@ export abstract class StageBase extends Resource implements IStage { public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.baseApi.metric(metricName, props).with({ - dimensions: { ApiId: this.baseApi.apiId, Stage: this.stageName }, + dimensionsMap: { ApiId: this.baseApi.apiId, Stage: this.stageName }, }).attachTo(this); } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts index 3d7d627ab4fef..fdcfbdbce6d30 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts @@ -1,3 +1,5 @@ +import { Grant, IGrantable } from '@aws-cdk/aws-iam'; +import { Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApi } from '../apigatewayv2.generated'; import { IApi } from '../common/api'; @@ -127,4 +129,23 @@ export class WebSocketApi extends ApiBase implements IWebSocketApi { ...options, }); } + + /** + * Grant access to the API Gateway management API for this WebSocket API to an IAM + * principal (Role/Group/User). + * + * @param identity The principal + */ + public grantManageConnections(identity: IGrantable): Grant { + const arn = Stack.of(this).formatArn({ + service: 'execute-api', + resource: this.apiId, + }); + + return Grant.addToPrincipal({ + grantee: identity, + actions: ['execute-api:ManageConnections'], + resourceArns: [`${arn}/*/POST/@connections/*`], + }); + } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts index f6bc91909dcba..6d5cc8527fef0 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts @@ -1,3 +1,4 @@ +import { Grant, IGrantable } from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnStage } from '../apigatewayv2.generated'; @@ -114,4 +115,23 @@ export class WebSocketStage extends StageBase implements IWebSocketStage { const urlPath = this.stageName; return `https://${this.api.apiId}.execute-api.${s.region}.${s.urlSuffix}/${urlPath}`; } + + /** + * Grant access to the API Gateway management API for this WebSocket API Stage to an IAM + * principal (Role/Group/User). + * + * @param identity The principal + */ + public grantManagementApiAccess(identity: IGrantable): Grant { + const arn = Stack.of(this.api).formatArn({ + service: 'execute-api', + resource: this.api.apiId, + }); + + return Grant.addToPrincipal({ + grantee: identity, + actions: ['execute-api:ManageConnections'], + resourceArns: [`${arn}/${this.stageName}/POST/@connections/*`], + }); + } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts index 959555a5c2b7a..24337a3f7c3f2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts @@ -1,4 +1,5 @@ -import { Template } from '@aws-cdk/assertions'; +import { Match, Template } from '@aws-cdk/assertions'; +import { User } from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; import { IWebSocketRouteIntegration, WebSocketApi, WebSocketIntegrationType, @@ -80,6 +81,49 @@ describe('WebSocketApi', () => { RouteKey: '$default', }); }); + + describe('grantManageConnections', () => { + test('adds an IAM policy to the principal', () => { + // GIVEN + const stack = new Stack(); + const api = new WebSocketApi(stack, 'api'); + const principal = new User(stack, 'user'); + + // WHEN + api.grantManageConnections(principal); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: Match.arrayWith([{ + Action: 'execute-api:ManageConnections', + Effect: 'Allow', + Resource: { + 'Fn::Join': ['', [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':execute-api:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':', + { + Ref: 'apiC8550315', + }, + '/*/POST/@connections/*', + ]], + }, + }]), + }, + }); + }); + }); }); class DummyIntegration implements IWebSocketRouteIntegration { diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts index d942eb6dc7a4e..b873f7fa74efa 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts @@ -1,4 +1,5 @@ -import { Template } from '@aws-cdk/assertions'; +import { Match, Template } from '@aws-cdk/assertions'; +import { User } from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; import { WebSocketApi, WebSocketStage } from '../../lib'; @@ -59,4 +60,51 @@ describe('WebSocketStage', () => { expect(defaultStage.callbackUrl.endsWith('/dev')).toBe(true); expect(defaultStage.callbackUrl.startsWith('https://')).toBe(true); }); + + describe('grantManageConnections', () => { + test('adds an IAM policy to the principal', () => { + // GIVEN + const stack = new Stack(); + const api = new WebSocketApi(stack, 'Api'); + const defaultStage = new WebSocketStage(stack, 'Stage', { + webSocketApi: api, + stageName: 'dev', + }); + const principal = new User(stack, 'User'); + + // WHEN + defaultStage.grantManagementApiAccess(principal); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: Match.arrayWith([{ + Action: 'execute-api:ManageConnections', + Effect: 'Allow', + Resource: { + 'Fn::Join': ['', [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':execute-api:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':', + { + Ref: 'ApiF70053CD', + }, + `/${defaultStage.stageName}/POST/@connections/*`, + ]], + }, + }]), + }, + }); + }); + }); }); diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts index ca5f973a8fa50..7937b331c018a 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts @@ -39,7 +39,8 @@ export interface ScalableTargetProps { * * This string consists of the resource type and unique identifier. * - * @example service/ecsStack-MyECSCluster-AB12CDE3F4GH/ecsStack-MyECSService-AB12CDE3F4GH + * Example value: `service/ecsStack-MyECSCluster-AB12CDE3F4GH/ecsStack-MyECSService-AB12CDE3F4GH` + * * @see https://docs.aws.amazon.com/autoscaling/application/APIReference/API_RegisterScalableTarget.html */ readonly resourceId: string; @@ -49,7 +50,7 @@ export interface ScalableTargetProps { * * Specify the service namespace, resource type, and scaling property. * - * @example ecs:service:DesiredCount + * Example value: `ecs:service:DesiredCount` * @see https://docs.aws.amazon.com/autoscaling/application/APIReference/API_ScalingPolicy.html */ readonly scalableDimension: string; @@ -82,7 +83,8 @@ export class ScalableTarget extends Resource implements IScalableTarget { /** * ID of the Scalable Target * - * @example service/ecsStack-MyECSCluster-AB12CDE3F4GH/ecsStack-MyECSService-AB12CDE3F4GH|ecs:service:DesiredCount|ecs + * Example value: `service/ecsStack-MyECSCluster-AB12CDE3F4GH/ecsStack-MyECSService-AB12CDE3F4GH|ecs:service:DesiredCount|ecs` + * * @attribute */ public readonly scalableTargetId: string; diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts index f7ccaff153ffe..de48ab5fd21e7 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts @@ -88,7 +88,7 @@ export interface BasicTargetTrackingScalingPolicyProps extends BaseTargetTrackin * * Only used for predefined metric ALBRequestCountPerTarget. * - * @example app///targetgroup// + * Example value: `app///targetgroup//` * * @default - No resource label. */ diff --git a/packages/@aws-cdk/aws-applicationautoscaling/package.json b/packages/@aws-cdk/aws-applicationautoscaling/package.json index ca025554d085f..17bfd09967f88 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/package.json +++ b/packages/@aws-cdk/aws-applicationautoscaling/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-appmesh/lib/gateway-route.ts b/packages/@aws-cdk/aws-appmesh/lib/gateway-route.ts index 2c34c1e3f6f46..088067d06763d 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/gateway-route.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/gateway-route.ts @@ -68,7 +68,7 @@ export class GatewayRoute extends cdk.Resource implements IGatewayRoute { public static fromGatewayRouteArn(scope: Construct, id: string, gatewayRouteArn: string): IGatewayRoute { return new class extends cdk.Resource implements IGatewayRoute { readonly gatewayRouteArn = gatewayRouteArn; - readonly gatewayRouteName = cdk.Fn.select(4, cdk.Fn.split('/', cdk.Stack.of(scope).parseArn(gatewayRouteArn).resourceName!)); + readonly gatewayRouteName = cdk.Fn.select(4, cdk.Fn.split('/', cdk.Stack.of(scope).splitArn(gatewayRouteArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName!)); readonly virtualGateway = VirtualGateway.fromVirtualGatewayArn(this, 'virtualGateway', gatewayRouteArn); }(scope, id); } @@ -105,7 +105,7 @@ export class GatewayRoute extends cdk.Resource implements IGatewayRoute { constructor(scope: Construct, id: string, props: GatewayRouteProps) { super(scope, id, { - physicalName: props.gatewayRouteName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), + physicalName: props.gatewayRouteName || cdk.Lazy.string({ produce: () => cdk.Names.uniqueId(this) }), }); this.virtualGateway = props.virtualGateway; diff --git a/packages/@aws-cdk/aws-appmesh/lib/mesh.ts b/packages/@aws-cdk/aws-appmesh/lib/mesh.ts index 30bb9cee8243a..3983a58c25742 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/mesh.ts @@ -136,7 +136,7 @@ export class Mesh extends MeshBase { * Import an existing mesh by arn */ public static fromMeshArn(scope: Construct, id: string, meshArn: string): IMesh { - const parts = cdk.Stack.of(scope).parseArn(meshArn); + const parts = cdk.Stack.of(scope).splitArn(meshArn, cdk.ArnFormat.SLASH_RESOURCE_NAME); class Import extends MeshBase { public meshName = parts.resourceName || ''; diff --git a/packages/@aws-cdk/aws-appmesh/lib/route.ts b/packages/@aws-cdk/aws-appmesh/lib/route.ts index 49b101acfdf9f..3e604f6d97511 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/route.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/route.ts @@ -75,7 +75,7 @@ export class Route extends cdk.Resource implements IRoute { return new class extends cdk.Resource implements IRoute { readonly routeArn = routeArn; readonly virtualRouter = VirtualRouter.fromVirtualRouterArn(this, 'VirtualRouter', routeArn); - readonly routeName = cdk.Fn.select(4, cdk.Fn.split('/', cdk.Stack.of(scope).parseArn(routeArn).resourceName!)); + readonly routeName = cdk.Fn.select(4, cdk.Fn.split('/', cdk.Stack.of(scope).splitArn(routeArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName!)); }(scope, id); } diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway.ts index e287027c7e946..a99f014df8c63 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway.ts @@ -134,7 +134,7 @@ export class VirtualGateway extends VirtualGatewayBase { */ public static fromVirtualGatewayArn(scope: Construct, id: string, virtualGatewayArn: string): IVirtualGateway { return new class extends VirtualGatewayBase { - private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).parseArn(virtualGatewayArn).resourceName!); + private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).splitArn(virtualGatewayArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName!); readonly mesh = Mesh.fromMeshName(this, 'Mesh', cdk.Fn.select(0, this.parsedArn)); readonly virtualGatewayArn = virtualGatewayArn; readonly virtualGatewayName = cdk.Fn.select(2, this.parsedArn); @@ -175,7 +175,7 @@ export class VirtualGateway extends VirtualGatewayBase { constructor(scope: Construct, id: string, props: VirtualGatewayProps) { super(scope, id, { - physicalName: props.virtualGatewayName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), + physicalName: props.virtualGatewayName || cdk.Lazy.string({ produce: () => cdk.Names.uniqueId(this) }), }); this.mesh = props.mesh; diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts index bba83feb0b3d1..c2dda44d7a7de 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts @@ -140,7 +140,7 @@ export class VirtualNode extends VirtualNodeBase { public static fromVirtualNodeArn(scope: Construct, id: string, virtualNodeArn: string): IVirtualNode { return new class extends VirtualNodeBase { readonly virtualNodeArn = virtualNodeArn; - private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).parseArn(virtualNodeArn).resourceName!); + private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).splitArn(virtualNodeArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName!); readonly mesh = Mesh.fromMeshName(this, 'Mesh', cdk.Fn.select(0, this.parsedArn)); readonly virtualNodeName = cdk.Fn.select(2, this.parsedArn); }(scope, id); diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts index bc2023f19d028..f5dda554bd387 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts @@ -102,7 +102,7 @@ export class VirtualRouter extends VirtualRouterBase { public static fromVirtualRouterArn(scope: Construct, id: string, virtualRouterArn: string): IVirtualRouter { return new class extends VirtualRouterBase { readonly virtualRouterArn = virtualRouterArn; - private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).parseArn(virtualRouterArn).resourceName!); + private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).splitArn(virtualRouterArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName!); readonly virtualRouterName = cdk.Fn.select(2, this.parsedArn); readonly mesh = Mesh.fromMeshName(this, 'Mesh', cdk.Fn.select(0, this.parsedArn)); }(scope, id); diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts index cb561eff19308..8dc31c2a56f66 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts @@ -40,7 +40,7 @@ export interface VirtualServiceProps { * It is recommended this follows the fully-qualified domain name format, * such as "my-service.default.svc.cluster.local". * - * @example service.domain.local + * Example value: `service.domain.local` * @default - A name is automatically generated */ readonly virtualServiceName?: string; @@ -65,7 +65,7 @@ export class VirtualService extends cdk.Resource implements IVirtualService { public static fromVirtualServiceArn(scope: Construct, id: string, virtualServiceArn: string): IVirtualService { return new class extends cdk.Resource implements IVirtualService { readonly virtualServiceArn = virtualServiceArn; - private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).parseArn(virtualServiceArn).resourceName!); + private readonly parsedArn = cdk.Fn.split('/', cdk.Stack.of(scope).splitArn(virtualServiceArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName!); readonly virtualServiceName = cdk.Fn.select(2, this.parsedArn); readonly mesh = Mesh.fromMeshName(this, 'Mesh', cdk.Fn.select(0, this.parsedArn)); }(scope, id); diff --git a/packages/@aws-cdk/aws-appmesh/package.json b/packages/@aws-cdk/aws-appmesh/package.json index a7024d16cbc9b..0bf1fc94a030d 100644 --- a/packages/@aws-cdk/aws-appmesh/package.json +++ b/packages/@aws-cdk/aws-appmesh/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 71cd6e32060c1..a63b757ba6b0b 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -1,7 +1,7 @@ import { IUserPool } from '@aws-cdk/aws-cognito'; import { ManagedPolicy, Role, IRole, ServicePrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam'; import { IFunction } from '@aws-cdk/aws-lambda'; -import { CfnResource, Duration, Expiration, IResolvable, Stack } from '@aws-cdk/core'; +import { ArnFormat, CfnResource, Duration, Expiration, IResolvable, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; import { IGraphqlApi, GraphqlApiBase } from './graphqlapi-base'; @@ -347,7 +347,7 @@ export class IamResource { return this.arns.map((arn) => Stack.of(api).formatArn({ service: 'appsync', resource: `apis/${api.apiId}`, - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, resourceName: `${arn}`, })); } diff --git a/packages/@aws-cdk/aws-appsync/package.json b/packages/@aws-cdk/aws-appsync/package.json index f36a7ada3d7ec..814bfb98e9600 100644 --- a/packages/@aws-cdk/aws-appsync/package.json +++ b/packages/@aws-cdk/aws-appsync/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts b/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts index 70a269bde9239..fa5d431bcf47b 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts @@ -8,6 +8,17 @@ import { Schedule } from './schedule'; * Properties for a scheduled scaling action */ export interface BasicScheduledActionProps { + /** + * Specifies the time zone for a cron expression. If a time zone is not provided, UTC is used by default. + * + * Valid values are the canonical names of the IANA time zones, derived from the IANA Time Zone Database (such as Etc/GMT+9 or Pacific/Tahiti). + * + * For more information, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. + * + * @default - UTC + * + */ + readonly timeZone?: string; /** * When to perform this action. * @@ -94,6 +105,7 @@ export class ScheduledAction extends Resource { maxSize: props.maxCapacity, desiredCapacity: props.desiredCapacity, recurrence: props.schedule.expressionString, + timeZone: props.timeZone, }); } } diff --git a/packages/@aws-cdk/aws-autoscaling/package.json b/packages/@aws-cdk/aws-autoscaling/package.json index 0fd6ce84bf75c..dfdb08ad1ac2b 100644 --- a/packages/@aws-cdk/aws-autoscaling/package.json +++ b/packages/@aws-cdk/aws-autoscaling/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts index d74860638fd30..25e84eeacaecf 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts @@ -4,6 +4,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cdk from '@aws-cdk/core'; import * as autoscaling from '../lib'; @@ -336,7 +337,7 @@ describe('auto scaling group', () => { }); - test('can configure replacing update', () => { + testDeprecated('can configure replacing update', () => { // GIVEN const stack = new cdk.Stack(undefined, 'MyStack', { env: { region: 'us-east-1', account: '1234' } }); const vpc = mockVpc(stack); @@ -367,7 +368,7 @@ describe('auto scaling group', () => { }); - test('can configure rolling update', () => { + testDeprecated('can configure rolling update', () => { // GIVEN const stack = new cdk.Stack(undefined, 'MyStack', { env: { region: 'us-east-1', account: '1234' } }); const vpc = mockVpc(stack); @@ -399,7 +400,7 @@ describe('auto scaling group', () => { }); - test('can configure resource signals', () => { + testDeprecated('can configure resource signals', () => { // GIVEN const stack = new cdk.Stack(undefined, 'MyStack', { env: { region: 'us-east-1', account: '1234' } }); const vpc = mockVpc(stack); @@ -506,11 +507,7 @@ describe('auto scaling group', () => { instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, - updateType: autoscaling.UpdateType.ROLLING_UPDATE, - rollingUpdateConfiguration: { - minSuccessfulInstancesPercent: 50, - pauseTime: cdk.Duration.seconds(345), - }, + updatePolicy: autoscaling.UpdatePolicy.rollingUpdate(), }); cdk.Tags.of(asg).add('superfood', 'acai'); @@ -960,8 +957,8 @@ describe('auto scaling group', () => { }); // THEN - expect(asg.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(asg.node.metadata[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + expect(asg.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); + expect(asg.node.metadataEntry[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); }); @@ -987,8 +984,8 @@ describe('auto scaling group', () => { }); // THEN - expect(asg.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(asg.node.metadata[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + expect(asg.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); + expect(asg.node.metadataEntry[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); }); @@ -1225,7 +1222,7 @@ describe('auto scaling group', () => { }); - test('throw if notification and notificationsTopics are both configured', () => { + testDeprecated('throw if notification and notificationsTopics are both configured', () => { // GIVEN const stack = new cdk.Stack(); const vpc = mockVpc(stack); @@ -1283,7 +1280,7 @@ describe('auto scaling group', () => { }); - test('setting notificationTopic configures all non test NotificationType', () => { + testDeprecated('setting notificationTopic configures all non test NotificationType', () => { // GIVEN const stack = new cdk.Stack(); const vpc = mockVpc(stack); diff --git a/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts b/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts index 8216968f406c1..a497efab73135 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts @@ -185,7 +185,7 @@ describe('scaling', () => { metric: new cloudwatch.Metric({ metricName: 'Henk', namespace: 'Test', - dimensions: { + dimensionsMap: { Mustache: 'Bushy', }, }), @@ -220,7 +220,7 @@ describe('scaling', () => { metric: new cloudwatch.Metric({ metricName: 'Legs', namespace: 'Henk', - dimensions: { Mustache: 'Bushy' }, + dimensionsMap: { Mustache: 'Bushy' }, }), // Adjust the number of legs to be closer to 2 scalingSteps: [ diff --git a/packages/@aws-cdk/aws-autoscaling/test/scheduled-action.test.ts b/packages/@aws-cdk/aws-autoscaling/test/scheduled-action.test.ts index 3afbd887b45ae..57789d5ae36fe 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/scheduled-action.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/scheduled-action.test.ts @@ -1,11 +1,12 @@ import '@aws-cdk/assert-internal/jest'; import { expect, haveResource, MatchStyle } from '@aws-cdk/assert-internal'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; import * as autoscaling from '../lib'; -describe('scheduled action', () => { +describeDeprecated('scheduled action', () => { test('can schedule an action', () => { // GIVEN const stack = new cdk.Stack(); @@ -46,6 +47,27 @@ describe('scheduled action', () => { }); + test('have timezone property', () => { + // GIVEN + const stack = new cdk.Stack(); + const asg = makeAutoScalingGroup(stack); + + // WHEN + asg.scaleOnSchedule('ScaleOutAtMiddaySeoul', { + schedule: autoscaling.Schedule.cron({ hour: '12', minute: '0' }), + minCapacity: 12, + timeZone: 'Asia/Seoul', + }); + + // THEN + expect(stack).to(haveResource('AWS::AutoScaling::ScheduledAction', { + MinSize: 12, + Recurrence: '0 12 * * *', + TimeZone: 'Asia/Seoul', + })); + + }); + test('autoscaling group has recommended updatepolicy for scheduled actions', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-backup/lib/backupable-resources-collector.ts b/packages/@aws-cdk/aws-backup/lib/backupable-resources-collector.ts index c363ab6424812..85d807b67b353 100644 --- a/packages/@aws-cdk/aws-backup/lib/backupable-resources-collector.ts +++ b/packages/@aws-cdk/aws-backup/lib/backupable-resources-collector.ts @@ -2,7 +2,7 @@ import * as dynamodb from '@aws-cdk/aws-dynamodb'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as efs from '@aws-cdk/aws-efs'; import * as rds from '@aws-cdk/aws-rds'; -import { IAspect, IConstruct, Stack } from '@aws-cdk/core'; +import { ArnFormat, IAspect, IConstruct, Stack } from '@aws-cdk/core'; export class BackupableResourcesCollector implements IAspect { public readonly resources: string[] = []; @@ -44,7 +44,7 @@ export class BackupableResourcesCollector implements IAspect { this.resources.push(Stack.of(node).formatArn({ service: 'rds', resource: 'db', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, resourceName: node.ref, })); } diff --git a/packages/@aws-cdk/aws-backup/lib/vault.ts b/packages/@aws-cdk/aws-backup/lib/vault.ts index 5f4f0d05c6001..059b6ed6bb17f 100644 --- a/packages/@aws-cdk/aws-backup/lib/vault.ts +++ b/packages/@aws-cdk/aws-backup/lib/vault.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as sns from '@aws-cdk/aws-sns'; -import { IResource, Lazy, Names, RemovalPolicy, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Lazy, Names, RemovalPolicy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnBackupVault } from './backup.generated'; @@ -168,7 +168,7 @@ export class BackupVault extends BackupVaultBase { service: 'backup', resource: 'backup-vault', resourceName: backupVaultName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); return BackupVault.fromBackupVaultArn(scope, id, backupVaultArn); @@ -178,7 +178,7 @@ export class BackupVault extends BackupVaultBase { * Import an existing backup vault by arn */ public static fromBackupVaultArn(scope: Construct, id: string, backupVaultArn: string): IBackupVault { - const parsedArn = Stack.of(scope).parseArn(backupVaultArn); + const parsedArn = Stack.of(scope).splitArn(backupVaultArn, ArnFormat.SLASH_RESOURCE_NAME); if (!parsedArn.resourceName) { throw new Error(`Backup Vault Arn ${backupVaultArn} does not have a resource name.`); diff --git a/packages/@aws-cdk/aws-backup/test/vault.test.ts b/packages/@aws-cdk/aws-backup/test/vault.test.ts index e72b039e75c63..ea63fb16323f2 100644 --- a/packages/@aws-cdk/aws-backup/test/vault.test.ts +++ b/packages/@aws-cdk/aws-backup/test/vault.test.ts @@ -2,7 +2,7 @@ import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as sns from '@aws-cdk/aws-sns'; -import { Stack } from '@aws-cdk/core'; +import { ArnFormat, Stack } from '@aws-cdk/core'; import { BackupVault, BackupVaultEvents } from '../lib'; let stack: Stack; @@ -267,7 +267,7 @@ test('import from arn', () => { service: 'backup', resource: 'backup-vault', resourceName: 'myVaultName', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); const vault = BackupVault.fromBackupVaultArn(stack, 'Vault', vaultArn); @@ -287,7 +287,7 @@ test('import from name', () => { service: 'backup', resource: 'backup-vault', resourceName: 'myVaultName', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, })); }); diff --git a/packages/@aws-cdk/aws-batch/README.md b/packages/@aws-cdk/aws-batch/README.md index f2900da8cda0f..67d31ea468b41 100644 --- a/packages/@aws-cdk/aws-batch/README.md +++ b/packages/@aws-cdk/aws-batch/README.md @@ -44,17 +44,17 @@ In **MANAGED** mode, AWS will handle the provisioning of compute resources to ac Below is an example of each available type of compute environment: ```ts -const defaultVpc = new ec2.Vpc(this, 'VPC'); +declare const vpc: ec2.Vpc; // default is managed -const awsManagedEnvironment = new batch.ComputeEnvironment(stack, 'AWS-Managed-Compute-Env', { +const awsManagedEnvironment = new batch.ComputeEnvironment(this, 'AWS-Managed-Compute-Env', { computeResources: { - vpc + vpc, } }); -const customerManagedEnvironment = new batch.ComputeEnvironment(stack, 'Customer-Managed-Compute-Env', { - managed: false // unmanaged environment +const customerManagedEnvironment = new batch.ComputeEnvironment(this, 'Customer-Managed-Compute-Env', { + managed: false, // unmanaged environment }); ``` @@ -65,7 +65,7 @@ It is possible to have AWS Batch submit spotfleet requests for obtaining compute ```ts const vpc = new ec2.Vpc(this, 'VPC'); -const spotEnvironment = new batch.ComputeEnvironment(stack, 'MySpotEnvironment', { +const spotEnvironment = new batch.ComputeEnvironment(this, 'MySpotEnvironment', { computeResources: { type: batch.ComputeResourceType.SPOT, bidPercentage: 75, // Bids for resources at 75% of the on-demand price @@ -81,7 +81,7 @@ It is possible to have AWS Batch submit jobs to be run on Fargate compute resour ```ts const vpc = new ec2.Vpc(this, 'VPC'); -const fargateSpotEnvironment = new batch.ComputeEnvironment(stack, 'MyFargateEnvironment', { +const fargateSpotEnvironment = new batch.ComputeEnvironment(this, 'MyFargateEnvironment', { computeResources: { type: batch.ComputeResourceType.FARGATE_SPOT, vpc, @@ -119,7 +119,8 @@ The alternative would be to use the `BEST_FIT_PROGRESSIVE` strategy in order for Simply define your Launch Template: -```ts +```text +// This example is only available in TypeScript const myLaunchTemplate = new ec2.CfnLaunchTemplate(this, 'LaunchTemplate', { launchTemplateName: 'extra-storage-template', launchTemplateData: { @@ -129,17 +130,20 @@ const myLaunchTemplate = new ec2.CfnLaunchTemplate(this, 'LaunchTemplate', { ebs: { encrypted: true, volumeSize: 100, - volumeType: 'gp2' - } - } - ] - } + volumeType: 'gp2', + }, + }, + ], + }, }); ``` and use it: ```ts +declare const vpc: ec2.Vpc; +declare const myLaunchTemplate: ec2.CfnLaunchTemplate; + const myComputeEnv = new batch.ComputeEnvironment(this, 'ComputeEnv', { computeResources: { launchTemplate: { @@ -168,6 +172,7 @@ Occasionally, you will need to deviate from the default processing AMI. ECS Optimized Amazon Linux 2 example: ```ts +declare const vpc: ec2.Vpc; const myComputeEnv = new batch.ComputeEnvironment(this, 'ComputeEnv', { computeResources: { image: new ecs.EcsOptimizedAmi({ @@ -181,11 +186,12 @@ const myComputeEnv = new batch.ComputeEnvironment(this, 'ComputeEnv', { Custom based AMI example: ```ts +declare const vpc: ec2.Vpc; const myComputeEnv = new batch.ComputeEnvironment(this, 'ComputeEnv', { computeResources: { image: ec2.MachineImage.genericLinux({ "[aws-region]": "[ami-ID]", - }) + }), vpc, } }); @@ -196,7 +202,8 @@ const myComputeEnv = new batch.ComputeEnvironment(this, 'ComputeEnv', { Jobs are always submitted to a specific queue. This means that you have to create a queue before you can start submitting jobs. Each queue is mapped to at least one (and no more than three) compute environment. When the job is scheduled for execution, AWS Batch will select the compute environment based on ordinal priority and available capacity in each environment. ```ts -const jobQueue = new batch.JobQueue(stack, 'JobQueue', { +declare const computeEnvironment: batch.ComputeEnvironment; +const jobQueue = new batch.JobQueue(this, 'JobQueue', { computeEnvironments: [ { // Defines a collection of compute resources to handle assigned batch jobs @@ -213,13 +220,20 @@ const jobQueue = new batch.JobQueue(stack, 'JobQueue', { Sometimes you might have jobs that are more important than others, and when submitted, should take precedence over the existing jobs. To achieve this, you can create a priority based execution strategy, by assigning each queue its own priority: ```ts -const highPrioQueue = new batch.JobQueue(stack, 'JobQueue', { - computeEnvironments: sharedComputeEnvs, +declare const sharedComputeEnvs: batch.ComputeEnvironment; +const highPrioQueue = new batch.JobQueue(this, 'JobQueue', { + computeEnvironments: [{ + computeEnvironment: sharedComputeEnvs, + order: 1, + }], priority: 2, }); -const lowPrioQueue = new batch.JobQueue(stack, 'JobQueue', { - computeEnvironments: sharedComputeEnvs, +const lowPrioQueue = new batch.JobQueue(this, 'JobQueue', { + computeEnvironments: [{ + computeEnvironment: sharedComputeEnvs, + order: 1, + }], priority: 1, }); ``` @@ -241,9 +255,11 @@ const jobQueue = batch.JobQueue.fromJobQueueArn(this, 'imported-job-queue', 'arn A Batch Job definition helps AWS Batch understand important details about how to run your application in the scope of a Batch Job. This involves key information like resource requirements, what containers to run, how the compute environment should be prepared, and more. Below is a simple example of how to create a job definition: ```ts -const repo = ecr.Repository.fromRepositoryName(stack, 'batch-job-repo', 'todo-list'); +import * as ecr from '@aws-cdk/aws-ecr'; -new batch.JobDefinition(stack, 'batch-job-def-from-ecr', { +const repo = ecr.Repository.fromRepositoryName(this, 'batch-job-repo', 'todo-list'); + +new batch.JobDefinition(this, 'batch-job-def-from-ecr', { container: { image: new ecs.EcrImage(repo, 'latest'), }, @@ -255,7 +271,7 @@ new batch.JobDefinition(stack, 'batch-job-def-from-ecr', { Below is an example of how you can create a Batch Job Definition from a local Docker application. ```ts -new batch.JobDefinition(stack, 'batch-job-def-from-local', { +new batch.JobDefinition(this, 'batch-job-def-from-local', { container: { // todo-list is a directory containing a Dockerfile to build the application image: ecs.ContainerImage.fromAsset('../todo-list'), @@ -268,14 +284,16 @@ new batch.JobDefinition(stack, 'batch-job-def-from-local', { You can provide custom log driver and its configuration for the container. ```ts -new batch.JobDefinition(stack, 'job-def', { +import * as ssm from '@aws-cdk/aws-ssm'; + +new batch.JobDefinition(this, 'job-def', { container: { image: ecs.EcrImage.fromRegistry('docker/whalesay'), logConfiguration: { logDriver: batch.LogDriver.AWSLOGS, options: { 'awslogs-region': 'us-east-1' }, secretOptions: [ - batch.ExposedSecret.fromParametersStore('xyz', ssm.StringParameter.fromStringParameterName(stack, 'parameter', 'xyz')), + batch.ExposedSecret.fromParametersStore('xyz', ssm.StringParameter.fromStringParameterName(this, 'parameter', 'xyz')), ], }, }, @@ -303,8 +321,8 @@ Below is an example: ```ts // Without revision -const job = batch.JobDefinition.fromJobDefinitionName(this, 'imported-job-definition', 'my-job-definition'); +const job1 = batch.JobDefinition.fromJobDefinitionName(this, 'imported-job-definition', 'my-job-definition'); // With revision -const job = batch.JobDefinition.fromJobDefinitionName(this, 'imported-job-definition', 'my-job-definition:3'); +const job2 = batch.JobDefinition.fromJobDefinitionName(this, 'imported-job-definition', 'my-job-definition:3'); ``` diff --git a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts index 408e16c7bb98a..80f2b2c4e1e6d 100644 --- a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts +++ b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts @@ -1,6 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { IResource, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnComputeEnvironment } from './batch.generated'; @@ -325,7 +325,7 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment */ public static fromComputeEnvironmentArn(scope: Construct, id: string, computeEnvironmentArn: string): IComputeEnvironment { const stack = Stack.of(scope); - const computeEnvironmentName = stack.parseArn(computeEnvironmentArn).resourceName!; + const computeEnvironmentName = stack.splitArn(computeEnvironmentArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; class Import extends Resource implements IComputeEnvironment { public readonly computeEnvironmentArn = computeEnvironmentArn; diff --git a/packages/@aws-cdk/aws-batch/lib/job-definition.ts b/packages/@aws-cdk/aws-batch/lib/job-definition.ts index dab8515acb6d1..025dea4516252 100644 --- a/packages/@aws-cdk/aws-batch/lib/job-definition.ts +++ b/packages/@aws-cdk/aws-batch/lib/job-definition.ts @@ -1,7 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as iam from '@aws-cdk/aws-iam'; -import { Duration, IResource, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, Duration, IResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnJobDefinition } from './batch.generated'; import { ExposedSecret } from './exposed-secret'; @@ -382,7 +382,7 @@ export class JobDefinition extends Resource implements IJobDefinition { */ public static fromJobDefinitionArn(scope: Construct, id: string, jobDefinitionArn: string): IJobDefinition { const stack = Stack.of(scope); - const jobDefName = stack.parseArn(jobDefinitionArn).resourceName!; + const jobDefName = stack.splitArn(jobDefinitionArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; class Import extends Resource implements IJobDefinition { public readonly jobDefinitionArn = jobDefinitionArn; @@ -405,7 +405,7 @@ export class JobDefinition extends Resource implements IJobDefinition { const jobDefArn = stack.formatArn({ service: 'batch', resource: 'job-definition', - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, resourceName: jobDefinitionName, }); diff --git a/packages/@aws-cdk/aws-batch/lib/job-queue.ts b/packages/@aws-cdk/aws-batch/lib/job-queue.ts index 0b5943b3fac91..8fb265d0ce86a 100644 --- a/packages/@aws-cdk/aws-batch/lib/job-queue.ts +++ b/packages/@aws-cdk/aws-batch/lib/job-queue.ts @@ -1,4 +1,4 @@ -import { IResource, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnJobQueue } from './batch.generated'; import { IComputeEnvironment } from './compute-environment'; @@ -93,7 +93,7 @@ export class JobQueue extends Resource implements IJobQueue { */ public static fromJobQueueArn(scope: Construct, id: string, jobQueueArn: string): IJobQueue { const stack = Stack.of(scope); - const jobQueueName = stack.parseArn(jobQueueArn).resourceName!; + const jobQueueName = stack.splitArn(jobQueueArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; class Import extends Resource implements IJobQueue { public readonly jobQueueArn = jobQueueArn; diff --git a/packages/@aws-cdk/aws-batch/package.json b/packages/@aws-cdk/aws-batch/package.json index bcbbc905cebb0..68faf56d0e656 100644 --- a/packages/@aws-cdk/aws-batch/package.json +++ b/packages/@aws-cdk/aws-batch/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-batch/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-batch/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..6fcfe8682a3ff --- /dev/null +++ b/packages/@aws-cdk/aws-batch/rosetta/default.ts-fixture @@ -0,0 +1,14 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as batch from '@aws-cdk/aws-batch'; +import * as ecs from '@aws-cdk/aws-ecs'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json index 25f6dd3260c0e..287da60856a44 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json +++ b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json @@ -36,14 +36,14 @@ "aws-sdk-mock": "^5.4.0", "eslint": "^7.32.0", "eslint-config-standard": "^14.1.1", - "eslint-plugin-import": "^2.25.2", + "eslint-plugin-import": "^2.25.3", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.3.1", "eslint-plugin-standard": "^4.1.0", "jest": "^27.3.1", "lambda-tester": "^3.6.0", "sinon": "^9.2.4", - "nock": "^13.1.4", + "nock": "^13.2.1", "ts-jest": "^27.0.7" } } diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/certificate-base.ts b/packages/@aws-cdk/aws-certificatemanager/lib/certificate-base.ts index b66a845429abf..de36b51a4ef00 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/certificate-base.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/certificate-base.ts @@ -22,7 +22,7 @@ export abstract class CertificateBase extends Resource implements ICertificate { return new cloudwatch.Metric({ period: Duration.days(1), ...props, - dimensions: { CertificateArn: this.certificateArn }, + dimensionsMap: { CertificateArn: this.certificateArn }, metricName: 'DaysToExpiry', namespace: 'AWS/CertificateManager', region: this.region, diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/util.ts b/packages/@aws-cdk/aws-certificatemanager/lib/util.ts index 219bac1fd6a80..e917213d36a06 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/util.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/util.ts @@ -1,4 +1,4 @@ -import { Arn, Stack, Token } from '@aws-cdk/core'; +import { Arn, ArnFormat, Stack, Token } from '@aws-cdk/core'; import { ICertificate } from './certificate'; import { DnsValidatedCertificate } from './dns-validated-certificate'; import { publicSuffixes } from './public-suffixes'; @@ -40,7 +40,7 @@ export function getCertificateRegion(cert: ICertificate): string | undefined { } { - const { region } = Arn.parse(certificateArn); + const { region } = Arn.split(certificateArn, ArnFormat.SLASH_RESOURCE_NAME); if (region && !Token.isUnresolved(region)) { return region; diff --git a/packages/@aws-cdk/aws-certificatemanager/test/certificate.test.ts b/packages/@aws-cdk/aws-certificatemanager/test/certificate.test.ts index 22ebc72917b2b..8edcdf95b8158 100644 --- a/packages/@aws-cdk/aws-certificatemanager/test/certificate.test.ts +++ b/packages/@aws-cdk/aws-certificatemanager/test/certificate.test.ts @@ -1,7 +1,7 @@ import '@aws-cdk/assert-internal/jest'; import * as route53 from '@aws-cdk/aws-route53'; import { Duration, Lazy, Stack } from '@aws-cdk/core'; -import { Certificate, CertificateValidation, ValidationMethod } from '../lib'; +import { Certificate, CertificateValidation } from '../lib'; test('apex domain selection by default', () => { const stack = new Stack(); @@ -43,9 +43,9 @@ test('validation domain can be overridden', () => { new Certificate(stack, 'Certificate', { domainName: 'test.example.com', - validationDomains: { + validation: CertificateValidation.fromEmail({ 'test.example.com': 'test.example.com', - }, + }), }); expect(stack).toHaveResource('AWS::CertificateManager::Certificate', { @@ -72,7 +72,7 @@ test('can configure validation method', () => { new Certificate(stack, 'Certificate', { domainName: 'test.example.com', - validationMethod: ValidationMethod.DNS, + validation: CertificateValidation.fromDns(), }); expect(stack).toHaveResource('AWS::CertificateManager::Certificate', { @@ -98,9 +98,9 @@ test('validationdomains can be given for a Token', () => { const domainName = Lazy.string({ produce: () => 'my.example.com' }); new Certificate(stack, 'Certificate', { domainName, - validationDomains: { + validation: CertificateValidation.fromEmail({ [domainName]: 'example.com', - }, + }), }); expect(stack).toHaveResource('AWS::CertificateManager::Certificate', { diff --git a/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts b/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts index d7d48f9efc9f2..3960c3675f389 100644 --- a/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts +++ b/packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts @@ -173,7 +173,7 @@ abstract class SlackChannelConfigurationBase extends cdk.Resource implements ISl return new cloudwatch.Metric({ namespace: 'AWS/Chatbot', region: 'us-east-1', - dimensions: { + dimensionsMap: { ConfigurationName: this.slackChannelConfigurationName, }, metricName, diff --git a/packages/@aws-cdk/aws-chatbot/test/slack-channel-configuration.test.ts b/packages/@aws-cdk/aws-chatbot/test/slack-channel-configuration.test.ts index 01aa5d88c2c5d..a5d2b443ed486 100644 --- a/packages/@aws-cdk/aws-chatbot/test/slack-channel-configuration.test.ts +++ b/packages/@aws-cdk/aws-chatbot/test/slack-channel-configuration.test.ts @@ -194,7 +194,7 @@ describe('SlackChannelConfiguration', () => { expect(metric).toEqual(new cloudwatch.Metric({ namespace: 'AWS/Chatbot', region: 'us-east-1', - dimensions: { + dimensionsMap: { ConfigurationName: 'ConfigurationName', }, metricName: 'MetricName', diff --git a/packages/@aws-cdk/aws-cloudformation/test/deps.test.ts b/packages/@aws-cdk/aws-cloudformation/test/deps.test.ts index 50014ed1b10e4..0354c7e9e4fa8 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/deps.test.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/deps.test.ts @@ -2,6 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { ResourcePart } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import { testDeprecated, describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, CfnResource, Stack } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { NestedStack } from '../lib'; @@ -25,7 +26,7 @@ describe('resource dependencies', () => { }); // eslint-disable-next-line jest/valid-describe - describe('resource in nested stack depends on a resource in the parent stack', matrixForResourceDependencyTest((addDep) => { + describeDeprecated('resource in nested stack depends on a resource in the parent stack', matrixForResourceDependencyTest((addDep) => { // GIVEN const parent = new Stack(undefined, 'root'); const nested = new NestedStack(parent, 'Nested'); @@ -43,7 +44,7 @@ describe('resource dependencies', () => { })); // eslint-disable-next-line jest/valid-describe - describe('resource in nested stack depends on a resource in a grandparent stack', matrixForResourceDependencyTest((addDep) => { + describeDeprecated('resource in nested stack depends on a resource in a grandparent stack', matrixForResourceDependencyTest((addDep) => { // GIVEN const grantparent = new Stack(undefined, 'Grandparent'); const parent = new NestedStack(grantparent, 'Parent'); @@ -61,7 +62,7 @@ describe('resource dependencies', () => { })); // eslint-disable-next-line jest/valid-describe - describe('resource in parent stack depends on resource in nested stack', matrixForResourceDependencyTest((addDep) => { + describeDeprecated('resource in parent stack depends on resource in nested stack', matrixForResourceDependencyTest((addDep) => { // GIVEN const parent = new Stack(undefined, 'root'); const nested = new NestedStack(parent, 'Nested'); @@ -78,7 +79,7 @@ describe('resource dependencies', () => { })); // eslint-disable-next-line jest/valid-describe - describe('resource in grantparent stack depends on resource in nested stack', matrixForResourceDependencyTest((addDep) => { + describeDeprecated('resource in grantparent stack depends on resource in nested stack', matrixForResourceDependencyTest((addDep) => { // GIVEN const grandparent = new Stack(undefined, 'Grandparent'); const parent = new NestedStack(grandparent, 'Parent'); @@ -96,7 +97,7 @@ describe('resource dependencies', () => { })); // eslint-disable-next-line jest/valid-describe - describe('resource in sibling stack depends on a resource in nested stack', matrixForResourceDependencyTest((addDep) => { + describeDeprecated('resource in sibling stack depends on a resource in nested stack', matrixForResourceDependencyTest((addDep) => { // GIVEN const app = new App(); const stack1 = new Stack(app, 'Stack1'); @@ -118,7 +119,7 @@ describe('resource dependencies', () => { })); // eslint-disable-next-line jest/valid-describe - describe('resource in nested stack depends on a resource in sibling stack', matrixForResourceDependencyTest((addDep) => { + describeDeprecated('resource in nested stack depends on a resource in sibling stack', matrixForResourceDependencyTest((addDep) => { // GIVEN const app = new App(); const stack1 = new Stack(app, 'Stack1'); @@ -140,7 +141,7 @@ describe('resource dependencies', () => { })); // eslint-disable-next-line jest/valid-describe - describe('resource in nested stack depends on a resource in nested sibling stack', matrixForResourceDependencyTest((addDep) => { + describeDeprecated('resource in nested stack depends on a resource in nested sibling stack', matrixForResourceDependencyTest((addDep) => { // GIVEN const app = new App(); const stack = new Stack(app, 'Stack1'); @@ -178,7 +179,7 @@ describe('stack dependencies', () => { assertNoDependsOn(assembly, stack); }); - test('nested stack depends on itself', () => { + testDeprecated('nested stack depends on itself', () => { // GIVEN const app = new App(); const parent = new Stack(app, 'Parent'); @@ -191,7 +192,7 @@ describe('stack dependencies', () => { assertNoDependsOn(app.synth(), parent); }); - test('nested stack cannot depend on any of its parents', () => { + testDeprecated('nested stack cannot depend on any of its parents', () => { // GIVEN const root = new Stack(); const nested1 = new NestedStack(root, 'Nested1'); @@ -203,7 +204,7 @@ describe('stack dependencies', () => { expect(() => nested2.addDependency(root)).toThrow(/Nested stack 'Default\/Nested1\/Nested2' cannot depend on a parent stack 'Default'/); }); - test('any parent stack is by definition dependent on the nested stack so dependency is ignored', () => { + testDeprecated('any parent stack is by definition dependent on the nested stack so dependency is ignored', () => { // GIVEN const root = new Stack(); const nested1 = new NestedStack(root, 'Nested1'); @@ -215,7 +216,7 @@ describe('stack dependencies', () => { nested1.addDependency(nested2); }); - test('sibling nested stacks transfer to resources', () => { + testDeprecated('sibling nested stacks transfer to resources', () => { // GIVEN const stack = new Stack(); const nested1 = new NestedStack(stack, 'Nested1'); @@ -230,7 +231,7 @@ describe('stack dependencies', () => { }, ResourcePart.CompleteDefinition); }); - test('nested stack depends on a deeply nested stack', () => { + testDeprecated('nested stack depends on a deeply nested stack', () => { // GIVEN const stack = new Stack(); const nested1 = new NestedStack(stack, 'Nested1'); @@ -246,7 +247,7 @@ describe('stack dependencies', () => { }, ResourcePart.CompleteDefinition); }); - test('deeply nested stack depends on a parent nested stack', () => { + testDeprecated('deeply nested stack depends on a parent nested stack', () => { // GIVEN const stack = new Stack(); const nested1 = new NestedStack(stack, 'Nested1'); @@ -262,7 +263,7 @@ describe('stack dependencies', () => { }, ResourcePart.CompleteDefinition); }); - test('top-level stack depends on a nested stack within a sibling', () => { + testDeprecated('top-level stack depends on a nested stack within a sibling', () => { // GIVEN const app = new App(); const stack1 = new Stack(app, 'Stack1'); @@ -281,7 +282,7 @@ describe('stack dependencies', () => { assertNoDependsOn(assembly, nested1); }); - test('nested stack within a sibling depends on top-level stack', () => { + testDeprecated('nested stack within a sibling depends on top-level stack', () => { // GIVEN const app = new App(); const stack1 = new Stack(app, 'Stack1'); diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts index 3b3a26346c2be..fdb5097463440 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts @@ -2,8 +2,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; import * as sns_subscriptions from '@aws-cdk/aws-sns-subscriptions'; import * as sqs from '@aws-cdk/aws-sqs'; -import { App, CfnParameter, Stack } from '@aws-cdk/core'; -import * as cfn from '../lib'; +import { App, CfnParameter, NestedStack, Stack } from '@aws-cdk/core'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -18,7 +17,7 @@ interface MyNestedStackProps { readonly topicNamePrefix: string; } -class MyNestedStack extends cfn.NestedStack { +class MyNestedStack extends NestedStack { constructor(scope: Construct, id: string, props: MyNestedStackProps) { const topicNamePrefixLogicalId = 'TopicNamePrefix'; diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts index 996ef461b0d2c..dc839504ef0e2 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts @@ -1,8 +1,7 @@ /// !cdk-integ pragma:ignore-assets import * as path from 'path'; import * as lambda from '@aws-cdk/aws-lambda'; -import { App, Stack } from '@aws-cdk/core'; -import * as cfn from '../lib'; +import { App, NestedStack, Stack } from '@aws-cdk/core'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -10,7 +9,7 @@ import { Construct } from '@aws-cdk/core'; /* eslint-disable @aws-cdk/no-core-construct */ -class NestedStack extends cfn.NestedStack { +class MyNestedStack extends NestedStack { constructor(scope: Construct, id: string) { super(scope, id); @@ -26,7 +25,7 @@ class ParentStack extends Stack { constructor(scope: Construct, id: string) { super(scope, id); - new NestedStack(this, 'Nested'); + new MyNestedStack(this, 'Nested'); } } diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi-refs.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi-refs.ts index b64a4d488e956..e810da5b1f1e5 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi-refs.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi-refs.ts @@ -1,7 +1,6 @@ /// !cdk-integ pragma:ignore-assets import * as sns from '@aws-cdk/aws-sns'; -import { App, Fn, Stack } from '@aws-cdk/core'; -import { NestedStack } from '../lib'; +import { App, Fn, NestedStack, Stack } from '@aws-cdk/core'; const app = new App(); const top = new Stack(app, 'nested-stacks-multi-refs'); diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts index 768f69cb93209..f16fd9a9cbad3 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts @@ -1,7 +1,6 @@ /// !cdk-integ pragma:ignore-assets import * as sns from '@aws-cdk/aws-sns'; -import { App, Stack } from '@aws-cdk/core'; -import * as cfn from '../lib'; +import { App, NestedStack, Stack } from '@aws-cdk/core'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -9,7 +8,7 @@ import { Construct } from '@aws-cdk/core'; /* eslint-disable @aws-cdk/no-core-construct */ -class YourNestedStack extends cfn.NestedStack { +class YourNestedStack extends NestedStack { constructor(scope: Construct, id: string) { super(scope, id); @@ -17,7 +16,7 @@ class YourNestedStack extends cfn.NestedStack { } } -class MyNestedStack extends cfn.NestedStack { +class MyNestedStack extends NestedStack { constructor(scope: Construct, id: string) { super(scope, id); diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-nested-export-to-sibling.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-nested-export-to-sibling.ts index 8d99bb75fc8af..b5ad58fbb6c57 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-nested-export-to-sibling.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-nested-export-to-sibling.ts @@ -1,14 +1,13 @@ /// !cdk-integ Stack1 Stack2 import * as sns from '@aws-cdk/aws-sns'; -import { App, Fn, Stack } from '@aws-cdk/core'; -import * as cfn from '../lib'; +import { App, Fn, NestedStack, Stack } from '@aws-cdk/core'; const app = new App(); const stack1 = new Stack(app, 'Stack1'); const stack2 = new Stack(app, 'Stack2'); -const nestedUnderStack1 = new cfn.NestedStack(stack1, 'NestedUnderStack1'); +const nestedUnderStack1 = new NestedStack(stack1, 'NestedUnderStack1'); const topicInNestedUnderStack1 = new sns.Topic(nestedUnderStack1, 'TopicInNestedUnderStack1'); new sns.Topic(stack2, 'TopicInStack2', { diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs1.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs1.ts index 11ab60d5c80cf..74b2c50f321fc 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs1.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs1.ts @@ -5,14 +5,13 @@ /* eslint-disable @aws-cdk/no-core-construct */ import * as sns from '@aws-cdk/aws-sns'; -import { App, Stack } from '@aws-cdk/core'; -import * as cfn from '../lib'; +import { App, NestedStack, Stack } from '@aws-cdk/core'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order import { Construct } from '@aws-cdk/core'; -class ConsumerNestedStack extends cfn.NestedStack { +class ConsumerNestedStack extends NestedStack { constructor(scope: Construct, id: string, topic: sns.Topic) { super(scope, id); diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs2.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs2.ts index 03bfc0998a30e..43d8c9e89edfb 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs2.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs2.ts @@ -1,7 +1,6 @@ /// !cdk-integ * import * as sns from '@aws-cdk/aws-sns'; -import { App, Fn, Stack } from '@aws-cdk/core'; -import * as cfn from '../lib'; +import { App, Fn, NestedStack, Stack } from '@aws-cdk/core'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -11,7 +10,7 @@ import { Construct } from '@aws-cdk/core'; /* eslint-disable @aws-cdk/no-core-construct */ -class ProducerNestedStack extends cfn.NestedStack { +class ProducerNestedStack extends NestedStack { public readonly topic: sns.Topic; constructor(scope: Construct, id: string) { diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs3.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs3.ts index 3266d08e027f0..c055e3c77137c 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs3.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs3.ts @@ -1,7 +1,6 @@ /// !cdk-integ * import * as sns from '@aws-cdk/aws-sns'; -import { App, Fn, Stack } from '@aws-cdk/core'; -import * as cfn from '../lib'; +import { App, Fn, NestedStack, Stack } from '@aws-cdk/core'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order @@ -11,7 +10,7 @@ import { Construct } from '@aws-cdk/core'; /* eslint-disable @aws-cdk/no-core-construct */ -class ProducerNestedStack extends cfn.NestedStack { +class ProducerNestedStack extends NestedStack { public readonly topic: sns.Topic; constructor(scope: Construct, id: string) { @@ -21,7 +20,7 @@ class ProducerNestedStack extends cfn.NestedStack { } } -class ConsumerNestedStack extends cfn.NestedStack { +class ConsumerNestedStack extends NestedStack { constructor(scope: Construct, id: string, topic: sns.Topic) { super(scope, id); diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-provider.py b/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-provider.py deleted file mode 100644 index f51514d0d3585..0000000000000 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-provider.py +++ /dev/null @@ -1,26 +0,0 @@ -def main(event, context): - import logging as log - import cfnresponse - log.getLogger().setLevel(log.INFO) - - # This needs to change if there are to be multiple resources in the same stack - physical_id = 'TheOnlyCustomResource' - - try: - log.info('Input event: %s', event) - - # Check if this is a Create and we're failing Creates - if event['RequestType'] == 'Create' and event['ResourceProperties'].get('FailCreate', False): - raise RuntimeError('Create failure requested') - - # Do the thing - message = event['ResourceProperties']['Message'] - attributes = { - 'Response': 'You said "%s"' % message - } - - cfnresponse.send(event, context, cfnresponse.SUCCESS, attributes, physical_id) - except Exception as e: - log.exception(e) - # cfnresponse's error message is always "see CloudWatch" - cfnresponse.send(event, context, cfnresponse.FAILED, {}, physical_id) diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.expected.json b/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.expected.json deleted file mode 100644 index f033100eeec5a..0000000000000 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.expected.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "Resources": { - "DemoResource5B5C546C": { - "Type": "AWS::CloudFormation::CustomResource", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "SingletonLambdaf7d4f7304ee111e89c2dfa7ae01bbebc492C6E5C", - "Arn" - ] - }, - "Message": "CustomResource says hello" - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, - "SingletonLambdaf7d4f7304ee111e89c2dfa7ae01bbebcServiceRoleFE9ABB04": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "SingletonLambdaf7d4f7304ee111e89c2dfa7ae01bbebc492C6E5C": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "def main(event, context):\n import logging as log\n import cfnresponse\n log.getLogger().setLevel(log.INFO)\n\n # This needs to change if there are to be multiple resources in the same stack\n physical_id = 'TheOnlyCustomResource'\n\n try:\n log.info('Input event: %s', event)\n\n # Check if this is a Create and we're failing Creates\n if event['RequestType'] == 'Create' and event['ResourceProperties'].get('FailCreate', False):\n raise RuntimeError('Create failure requested')\n\n # Do the thing\n message = event['ResourceProperties']['Message']\n attributes = {\n 'Response': 'You said \"%s\"' % message\n }\n\n cfnresponse.send(event, context, cfnresponse.SUCCESS, attributes, physical_id)\n except Exception as e:\n log.exception(e)\n # cfnresponse's error message is always \"see CloudWatch\"\n cfnresponse.send(event, context, cfnresponse.FAILED, {}, physical_id)\n" - }, - "Handler": "index.main", - "Role": { - "Fn::GetAtt": [ - "SingletonLambdaf7d4f7304ee111e89c2dfa7ae01bbebcServiceRoleFE9ABB04", - "Arn" - ] - }, - "Runtime": "python2.7", - "Timeout": 300 - }, - "DependsOn": [ - "SingletonLambdaf7d4f7304ee111e89c2dfa7ae01bbebcServiceRoleFE9ABB04" - ] - } - }, - "Outputs": { - "ResponseMessage": { - "Description": "The message that came back from the Custom Resource", - "Value": { - "Fn::GetAtt": [ - "DemoResource5B5C546C", - "Response" - ] - } - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.ts deleted file mode 100644 index e88b7113cb113..0000000000000 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.ts +++ /dev/null @@ -1,68 +0,0 @@ -import * as fs from 'fs'; -import * as lambda from '@aws-cdk/aws-lambda'; -import * as cdk from '@aws-cdk/core'; -import { CustomResource, CustomResourceProvider } from '../lib'; - -// keep this import separate from other imports to reduce chance for merge conflicts with v2-main -// eslint-disable-next-line no-duplicate-imports, import/order -import { Construct } from '@aws-cdk/core'; - -/* eslint-disable @aws-cdk/no-core-construct */ - -interface DemoResourceProps { - /** - * Message to echo - */ - message: string; - - /** - * Set this to true to fail the CREATE invocation - */ - failCreate?: boolean; -} - -class DemoResource extends Construct { - public readonly response: string; - - constructor(scope: Construct, id: string, props: DemoResourceProps) { - super(scope, id); - - const resource = new CustomResource(this, 'Resource', { - provider: CustomResourceProvider.fromLambda(new lambda.SingletonFunction(this, 'Singleton', { - uuid: 'f7d4f730-4ee1-11e8-9c2d-fa7ae01bbebc', - // This makes the demo only work as top-level TypeScript program, but that's fine for now - code: new lambda.InlineCode(fs.readFileSync('integ.trivial-lambda-provider.py', { encoding: 'utf-8' })), - handler: 'index.main', - timeout: cdk.Duration.minutes(5), - runtime: lambda.Runtime.PYTHON_2_7, - })), - properties: props, - }); - - this.response = resource.getAtt('Response').toString(); - } -} - -/** - * A stack that only sets up the CustomResource and shows how to get an attribute from it - */ -class SucceedingStack extends cdk.Stack { - constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { - super(scope, id, props); - - const resource = new DemoResource(this, 'DemoResource', { - message: 'CustomResource says hello', - }); - - // Publish the custom resource output - new cdk.CfnOutput(this, 'ResponseMessage', { - description: 'The message that came back from the Custom Resource', - value: resource.response, - }); - } -} -const app = new cdk.App(); - -new SucceedingStack(app, 'SucceedingStack'); - -app.synth(); diff --git a/packages/@aws-cdk/aws-cloudformation/test/nested-stack.test.ts b/packages/@aws-cdk/aws-cloudformation/test/nested-stack.test.ts index 90c39268ec7bb..07a43586971bd 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/nested-stack.test.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/nested-stack.test.ts @@ -4,6 +4,7 @@ import { SynthUtils } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; import * as s3_assets from '@aws-cdk/aws-s3-assets'; import * as sns from '@aws-cdk/aws-sns'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, CfnParameter, CfnResource, ContextProvider, LegacyStackSynthesizer, Names, Stack } from '@aws-cdk/core'; import { NestedStack } from '../lib/nested-stack'; @@ -14,1070 +15,1074 @@ import { Construct } from '@aws-cdk/core'; /* eslint-disable @aws-cdk/no-core-construct */ /* eslint-disable max-len */ -test('fails if defined as a root', () => { - // THEN - expect(() => new NestedStack(undefined as any, 'boom')).toThrow(/Nested stacks cannot be defined as a root construct/); -}); - -test('fails if defined without a parent stack', () => { - // GIVEN - const app = new App(); - const group = new Construct(app, 'group'); - - // THEN - expect(() => new NestedStack(app, 'boom')).toThrow(/must be defined within scope of another non-nested stack/); - expect(() => new NestedStack(group, 'bam')).toThrow(/must be defined within scope of another non-nested stack/); -}); +describeDeprecated('NestedStack', () => { -test('can be defined as a direct child or an indirect child of a Stack', () => { - // GIVEN - const parent = new Stack(); - - // THEN - expect(() => new NestedStack(parent, 'direct')).not.toThrow(); - expect(() => new NestedStack(new Construct(parent, 'group'), 'indirect')).not.toThrow(); -}); + test('fails if defined as a root', () => { + // THEN + expect(() => new NestedStack(undefined as any, 'boom')).toThrow(/Nested stacks cannot be defined as a root construct/); + }); -test('nested stack is not synthesized as a stack artifact into the assembly', () => { - // GIVEN - const app = new App(); - const parentStack = new Stack(app, 'parent-stack'); - new NestedStack(parentStack, 'nested-stack'); + test('fails if defined without a parent stack', () => { + // GIVEN + const app = new App(); + const group = new Construct(app, 'group'); - // WHEN - const assembly = app.synth(); + // THEN + expect(() => new NestedStack(app, 'boom')).toThrow(/must be defined within scope of another non-nested stack/); + expect(() => new NestedStack(group, 'bam')).toThrow(/must be defined within scope of another non-nested stack/); + }); - // THEN - expect(assembly.artifacts.length).toEqual(2); -}); + test('can be defined as a direct child or an indirect child of a Stack', () => { + // GIVEN + const parent = new Stack(); -test('the template of the nested stack is synthesized into the cloud assembly', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'parent-stack'); - const nested = new NestedStack(parent, 'nested-stack'); - new CfnResource(nested, 'ResourceInNestedStack', { type: 'AWS::Resource::Nested' }); - - // WHEN - const assembly = app.synth(); - - // THEN - const template = JSON.parse(fs.readFileSync(path.join(assembly.directory, `${Names.uniqueId(nested)}.nested.template.json`), 'utf-8')); - expect(template).toEqual({ - Resources: { - ResourceInNestedStack: { - Type: 'AWS::Resource::Nested', - }, - }, + // THEN + expect(() => new NestedStack(parent, 'direct')).not.toThrow(); + expect(() => new NestedStack(new Construct(parent, 'group'), 'indirect')).not.toThrow(); }); -}); -test('file asset metadata is associated with the parent stack', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'parent-stack'); - const nested = new NestedStack(parent, 'nested-stack'); - new CfnResource(nested, 'ResourceInNestedStack', { type: 'AWS::Resource::Nested' }); - - // WHEN - const assembly = app.synth(); - - // THEN - expect(assembly.getStackByName(parent.stackName).assets).toEqual([{ - path: 'parentstacknestedstack844892C0.nested.template.json', - id: 'c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096', - packaging: 'file', - sourceHash: 'c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096', - s3BucketParameter: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3BucketDA8C3345', - s3KeyParameter: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6', - artifactHashParameter: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096ArtifactHash8DE450C7', - }]); -}); + test('nested stack is not synthesized as a stack artifact into the assembly', () => { + // GIVEN + const app = new App(); + const parentStack = new Stack(app, 'parent-stack'); + new NestedStack(parentStack, 'nested-stack'); -test('aws::cloudformation::stack is synthesized in the parent scope', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'parent-stack'); + // WHEN + const assembly = app.synth(); - // WHEN - const nested = new NestedStack(parent, 'nested-stack'); - new CfnResource(nested, 'ResourceInNestedStack', { type: 'AWS::Resource::Nested' }); + // THEN + expect(assembly.artifacts.length).toEqual(2); + }); - // THEN - const assembly = app.synth(); + test('the template of the nested stack is synthesized into the cloud assembly', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'parent-stack'); + const nested = new NestedStack(parent, 'nested-stack'); + new CfnResource(nested, 'ResourceInNestedStack', { type: 'AWS::Resource::Nested' }); + + // WHEN + const assembly = app.synth(); + + // THEN + const template = JSON.parse(fs.readFileSync(path.join(assembly.directory, `${Names.uniqueId(nested)}.nested.template.json`), 'utf-8')); + expect(template).toEqual({ + Resources: { + ResourceInNestedStack: { + Type: 'AWS::Resource::Nested', + }, + }, + }); + }); - // assembly has one stack (the parent) - expect(assembly.stacks.length).toEqual(1); + test('file asset metadata is associated with the parent stack', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'parent-stack'); + const nested = new NestedStack(parent, 'nested-stack'); + new CfnResource(nested, 'ResourceInNestedStack', { type: 'AWS::Resource::Nested' }); - // but this stack has an asset that points to the synthesized template - expect(assembly.stacks[0].assets[0].path).toEqual('parentstacknestedstack844892C0.nested.template.json'); + // WHEN + const assembly = app.synth(); - // the template includes our resource - const filePath = path.join(assembly.directory, assembly.stacks[0].assets[0].path); - expect(JSON.parse(fs.readFileSync(filePath).toString('utf-8'))).toEqual({ - Resources: { ResourceInNestedStack: { Type: 'AWS::Resource::Nested' } }, + // THEN + expect(assembly.getStackByName(parent.stackName).assets).toEqual([{ + path: 'parentstacknestedstack844892C0.nested.template.json', + id: 'c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096', + packaging: 'file', + sourceHash: 'c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096', + s3BucketParameter: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3BucketDA8C3345', + s3KeyParameter: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6', + artifactHashParameter: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096ArtifactHash8DE450C7', + }]); }); - // the parent template includes the parameters and the nested stack resource which points to the s3 url - expect(parent).toMatchTemplate({ - Resources: { - nestedstackNestedStacknestedstackNestedStackResource71CDD241: { - Type: 'AWS::CloudFormation::Stack', - DeletionPolicy: 'Delete', - UpdateReplacePolicy: 'Delete', - Properties: { - TemplateURL: { - 'Fn::Join': [ - '', - [ - 'https://s3.', - { - Ref: 'AWS::Region', - }, - '.', - { - Ref: 'AWS::URLSuffix', - }, - '/', - { - Ref: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3BucketDA8C3345', - }, - '/', - { - 'Fn::Select': [ - 0, - { - 'Fn::Split': [ - '||', - { - Ref: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6', - }, - ], - }, - ], - }, - { - 'Fn::Select': [ - 1, - { - 'Fn::Split': [ - '||', - { - Ref: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6', - }, - ], - }, - ], - }, + test('aws::cloudformation::stack is synthesized in the parent scope', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'parent-stack'); + + // WHEN + const nested = new NestedStack(parent, 'nested-stack'); + new CfnResource(nested, 'ResourceInNestedStack', { type: 'AWS::Resource::Nested' }); + + // THEN + const assembly = app.synth(); + + // assembly has one stack (the parent) + expect(assembly.stacks.length).toEqual(1); + + // but this stack has an asset that points to the synthesized template + expect(assembly.stacks[0].assets[0].path).toEqual('parentstacknestedstack844892C0.nested.template.json'); + + // the template includes our resource + const filePath = path.join(assembly.directory, assembly.stacks[0].assets[0].path); + expect(JSON.parse(fs.readFileSync(filePath).toString('utf-8'))).toEqual({ + Resources: { ResourceInNestedStack: { Type: 'AWS::Resource::Nested' } }, + }); + + // the parent template includes the parameters and the nested stack resource which points to the s3 url + expect(parent).toMatchTemplate({ + Resources: { + nestedstackNestedStacknestedstackNestedStackResource71CDD241: { + Type: 'AWS::CloudFormation::Stack', + DeletionPolicy: 'Delete', + UpdateReplacePolicy: 'Delete', + Properties: { + TemplateURL: { + 'Fn::Join': [ + '', + [ + 'https://s3.', + { + Ref: 'AWS::Region', + }, + '.', + { + Ref: 'AWS::URLSuffix', + }, + '/', + { + Ref: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3BucketDA8C3345', + }, + '/', + { + 'Fn::Select': [ + 0, + { + 'Fn::Split': [ + '||', + { + Ref: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6', + }, + ], + }, + ], + }, + { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '||', + { + Ref: 'AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6', + }, + ], + }, + ], + }, + ], ], - ], + }, }, }, }, - }, - Parameters: { - AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3BucketDA8C3345: { - Type: 'String', - Description: 'S3 bucket for asset "c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096"', - }, - AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6: { - Type: 'String', - Description: 'S3 key for asset version "c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096"', - }, - AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096ArtifactHash8DE450C7: { - Type: 'String', - Description: 'Artifact hash for asset "c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096"', + Parameters: { + AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3BucketDA8C3345: { + Type: 'String', + Description: 'S3 bucket for asset "c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096"', + }, + AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096S3VersionKey09D03EE6: { + Type: 'String', + Description: 'S3 key for asset version "c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096"', + }, + AssetParametersc639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096ArtifactHash8DE450C7: { + Type: 'String', + Description: 'Artifact hash for asset "c639c0a5e7320758aa22589669ecebc98f185b711300b074f53998c8f9a45096"', + }, }, - }, + }); }); -}); -test('Stack.of()', () => { - class MyNestedStack extends NestedStack { - public readonly stackOfChild: Stack; + test('Stack.of()', () => { + class MyNestedStack extends NestedStack { + public readonly stackOfChild: Stack; - constructor(scope: Construct, id: string) { - super(scope, id); + constructor(scope: Construct, id: string) { + super(scope, id); - const param = new CfnParameter(this, 'param', { type: 'String' }); - this.stackOfChild = Stack.of(param); + const param = new CfnParameter(this, 'param', { type: 'String' }); + this.stackOfChild = Stack.of(param); + } } - } - const parent = new Stack(); - const nested = new MyNestedStack(parent, 'nested'); + const parent = new Stack(); + const nested = new MyNestedStack(parent, 'nested'); - expect(nested.stackOfChild).toEqual(nested); - expect(Stack.of(nested)).toEqual(nested); -}); + expect(nested.stackOfChild).toEqual(nested); + expect(Stack.of(nested)).toEqual(nested); + }); -test('references within the nested stack are not reported as cross stack references', () => { - class MyNestedStack extends NestedStack { - constructor(scope: Construct, id: string) { - super(scope, id); + test('references within the nested stack are not reported as cross stack references', () => { + class MyNestedStack extends NestedStack { + constructor(scope: Construct, id: string) { + super(scope, id); - const param = new CfnParameter(this, 'param', { type: 'String' }); - new CfnResource(this, 'resource', { - type: 'My::Resource', - properties: { - SomeProp: param.valueAsString, - }, - }); + const param = new CfnParameter(this, 'param', { type: 'String' }); + new CfnResource(this, 'resource', { + type: 'My::Resource', + properties: { + SomeProp: param.valueAsString, + }, + }); + } } - } - const app = new App(); - const parent = new Stack(app, 'parent'); + const app = new App(); + const parent = new Stack(app, 'parent'); - new MyNestedStack(parent, 'nested'); + new MyNestedStack(parent, 'nested'); - // references are added during "prepare" - const assembly = app.synth(); + // references are added during "prepare" + const assembly = app.synth(); - expect(assembly.stacks.length).toEqual(1); - expect(assembly.stacks[0].dependencies).toEqual([]); -}); + expect(assembly.stacks.length).toEqual(1); + expect(assembly.stacks[0].dependencies).toEqual([]); + }); -test('references to a resource from the parent stack in a nested stack is translated into a cfn parameter', () => { - // WHEN - class MyNestedStack extends NestedStack { + test('references to a resource from the parent stack in a nested stack is translated into a cfn parameter', () => { + // WHEN + class MyNestedStack extends NestedStack { - constructor(scope: Construct, id: string, resourceFromParent: CfnResource) { - super(scope, id); + constructor(scope: Construct, id: string, resourceFromParent: CfnResource) { + super(scope, id); - new CfnResource(this, 'resource', { - type: 'AWS::Child::Resource', - properties: { - ReferenceToResourceInParentStack: resourceFromParent.ref, - }, - }); + new CfnResource(this, 'resource', { + type: 'AWS::Child::Resource', + properties: { + ReferenceToResourceInParentStack: resourceFromParent.ref, + }, + }); - new CfnResource(this, 'resource2', { - type: 'My::Resource::2', - properties: { - Prop1: resourceFromParent.getAtt('Attr'), - Prop2: resourceFromParent.ref, - }, - }); + new CfnResource(this, 'resource2', { + type: 'My::Resource::2', + properties: { + Prop1: resourceFromParent.getAtt('Attr'), + Prop2: resourceFromParent.ref, + }, + }); + } } - } - const app = new App(); - const parentStack = new Stack(app, 'parent'); + const app = new App(); + const parentStack = new Stack(app, 'parent'); - const resource = new CfnResource(parentStack, 'parent-resource', { type: 'AWS::Parent::Resource' }); + const resource = new CfnResource(parentStack, 'parent-resource', { type: 'AWS::Parent::Resource' }); - const nested = new MyNestedStack(parentStack, 'nested', resource); + const nested = new MyNestedStack(parentStack, 'nested', resource); - // THEN - app.synth(); + // THEN + app.synth(); - // nested template should use a parameter to reference the resource from the parent stack - expect(nested).toMatchTemplate({ - Resources: - { - resource: - { - Type: 'AWS::Child::Resource', - Properties: - { ReferenceToResourceInParentStack: { Ref: 'referencetoparentparentresourceD56EA8F7Ref' } }, - }, - resource2: + // nested template should use a parameter to reference the resource from the parent stack + expect(nested).toMatchTemplate({ + Resources: { - Type: 'My::Resource::2', - Properties: + resource: { - Prop1: { Ref: 'referencetoparentparentresourceD56EA8F7Attr' }, - Prop2: { Ref: 'referencetoparentparentresourceD56EA8F7Ref' }, + Type: 'AWS::Child::Resource', + Properties: + { ReferenceToResourceInParentStack: { Ref: 'referencetoparentparentresourceD56EA8F7Ref' } }, + }, + resource2: + { + Type: 'My::Resource::2', + Properties: + { + Prop1: { Ref: 'referencetoparentparentresourceD56EA8F7Attr' }, + Prop2: { Ref: 'referencetoparentparentresourceD56EA8F7Ref' }, + }, }, }, - }, - Parameters: - { - referencetoparentparentresourceD56EA8F7Ref: { Type: 'String' }, - referencetoparentparentresourceD56EA8F7Attr: { Type: 'String' }, - }, - }); - - // parent template should pass in the value through the parameter - expect(parentStack).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - referencetoparentparentresourceD56EA8F7Ref: { - Ref: 'parentresource', + Parameters: + { + referencetoparentparentresourceD56EA8F7Ref: { Type: 'String' }, + referencetoparentparentresourceD56EA8F7Attr: { Type: 'String' }, }, - referencetoparentparentresourceD56EA8F7Attr: { - 'Fn::GetAtt': [ - 'parentresource', - 'Attr', - ], + }); + + // parent template should pass in the value through the parameter + expect(parentStack).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + referencetoparentparentresourceD56EA8F7Ref: { + Ref: 'parentresource', + }, + referencetoparentparentresourceD56EA8F7Attr: { + 'Fn::GetAtt': [ + 'parentresource', + 'Attr', + ], + }, }, - }, + }); }); -}); -test('references to a resource in the nested stack in the parent is translated into a cfn output', () => { - class MyNestedStack extends NestedStack { - public readonly resourceFromChild: CfnResource; + test('references to a resource in the nested stack in the parent is translated into a cfn output', () => { + class MyNestedStack extends NestedStack { + public readonly resourceFromChild: CfnResource; - constructor(scope: Construct, id: string) { - super(scope, id); + constructor(scope: Construct, id: string) { + super(scope, id); - this.resourceFromChild = new CfnResource(this, 'resource', { - type: 'AWS::Child::Resource', - }); + this.resourceFromChild = new CfnResource(this, 'resource', { + type: 'AWS::Child::Resource', + }); + } } - } - const app = new App(); - const parentStack = new Stack(app, 'parent'); + const app = new App(); + const parentStack = new Stack(app, 'parent'); - const nested = new MyNestedStack(parentStack, 'nested'); + const nested = new MyNestedStack(parentStack, 'nested'); - new CfnResource(parentStack, 'another-parent-resource', { - type: 'AWS::Parent::Resource', - properties: { - RefToResourceInNestedStack: nested.resourceFromChild.ref, - }, - }); + new CfnResource(parentStack, 'another-parent-resource', { + type: 'AWS::Parent::Resource', + properties: { + RefToResourceInNestedStack: nested.resourceFromChild.ref, + }, + }); - // references are added during "prepare" - app.synth(); - - // nested template should use a parameter to reference the resource from the parent stack - expect(nested).toMatchTemplate({ - Resources: { - resource: { Type: 'AWS::Child::Resource' }, - }, - Outputs: { - parentnestedresource4D680677Ref: { Value: { Ref: 'resource' } }, - }, - }); + // references are added during "prepare" + app.synth(); - // parent template should pass in the value through the parameter - expect(parentStack).toHaveResource('AWS::Parent::Resource', { - RefToResourceInNestedStack: { - 'Fn::GetAtt': [ - 'nestedNestedStacknestedNestedStackResource3DD143BF', - 'Outputs.parentnestedresource4D680677Ref', - ], - }, - }); -}); + // nested template should use a parameter to reference the resource from the parent stack + expect(nested).toMatchTemplate({ + Resources: { + resource: { Type: 'AWS::Child::Resource' }, + }, + Outputs: { + parentnestedresource4D680677Ref: { Value: { Ref: 'resource' } }, + }, + }); -test('nested stack references a resource from another non-nested stack (not the parent)', () => { - // GIVEN - const app = new App(); - const stack1 = new Stack(app, 'Stack1'); - const stack2 = new Stack(app, 'Stack2'); - const nestedUnderStack1 = new NestedStack(stack1, 'NestedUnderStack1'); - const resourceInStack2 = new CfnResource(stack2, 'ResourceInStack2', { type: 'MyResource' }); - - // WHEN - new CfnResource(nestedUnderStack1, 'ResourceInNestedStack1', { - type: 'Nested::Resource', - properties: { - RefToSibling: resourceInStack2.getAtt('MyAttribute'), - }, + // parent template should pass in the value through the parameter + expect(parentStack).toHaveResource('AWS::Parent::Resource', { + RefToResourceInNestedStack: { + 'Fn::GetAtt': [ + 'nestedNestedStacknestedNestedStackResource3DD143BF', + 'Outputs.parentnestedresource4D680677Ref', + ], + }, + }); }); - // THEN - const assembly = app.synth(); - - // producing stack should have an export - expect(stack2).toMatchTemplate({ - Resources: { - ResourceInStack2: { Type: 'MyResource' }, - }, - Outputs: { - ExportsOutputFnGetAttResourceInStack2MyAttributeC15F1009: { - Value: { 'Fn::GetAtt': ['ResourceInStack2', 'MyAttribute'] }, - Export: { Name: 'Stack2:ExportsOutputFnGetAttResourceInStack2MyAttributeC15F1009' }, + test('nested stack references a resource from another non-nested stack (not the parent)', () => { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1'); + const stack2 = new Stack(app, 'Stack2'); + const nestedUnderStack1 = new NestedStack(stack1, 'NestedUnderStack1'); + const resourceInStack2 = new CfnResource(stack2, 'ResourceInStack2', { type: 'MyResource' }); + + // WHEN + new CfnResource(nestedUnderStack1, 'ResourceInNestedStack1', { + type: 'Nested::Resource', + properties: { + RefToSibling: resourceInStack2.getAtt('MyAttribute'), }, - }, - }); + }); + + // THEN + const assembly = app.synth(); - // nested stack uses Fn::ImportValue like normal - expect(nestedUnderStack1).toMatchTemplate({ - Resources: { - ResourceInNestedStack1: { - Type: 'Nested::Resource', - Properties: { - RefToSibling: { - 'Fn::ImportValue': 'Stack2:ExportsOutputFnGetAttResourceInStack2MyAttributeC15F1009', + // producing stack should have an export + expect(stack2).toMatchTemplate({ + Resources: { + ResourceInStack2: { Type: 'MyResource' }, + }, + Outputs: { + ExportsOutputFnGetAttResourceInStack2MyAttributeC15F1009: { + Value: { 'Fn::GetAtt': ['ResourceInStack2', 'MyAttribute'] }, + Export: { Name: 'Stack2:ExportsOutputFnGetAttResourceInStack2MyAttributeC15F1009' }, + }, + }, + }); + + // nested stack uses Fn::ImportValue like normal + expect(nestedUnderStack1).toMatchTemplate({ + Resources: { + ResourceInNestedStack1: { + Type: 'Nested::Resource', + Properties: { + RefToSibling: { + 'Fn::ImportValue': 'Stack2:ExportsOutputFnGetAttResourceInStack2MyAttributeC15F1009', + }, }, }, }, - }, + }); + + // verify a depedency was established between the parents + const stack1Artifact = assembly.getStackByName(stack1.stackName); + const stack2Artifact = assembly.getStackByName(stack2.stackName); + expect(stack1Artifact.dependencies.length).toEqual(1); + expect(stack2Artifact.dependencies.length).toEqual(0); + expect(stack1Artifact.dependencies[0]).toEqual(stack2Artifact); }); - // verify a depedency was established between the parents - const stack1Artifact = assembly.getStackByName(stack1.stackName); - const stack2Artifact = assembly.getStackByName(stack2.stackName); - expect(stack1Artifact.dependencies.length).toEqual(1); - expect(stack2Artifact.dependencies.length).toEqual(0); - expect(stack1Artifact.dependencies[0]).toEqual(stack2Artifact); -}); + test('nested stack within a nested stack references a resource in a sibling top-level stack', () => { + // GIVEN + const app = new App(); + const consumerTopLevel = new Stack(app, 'ConsumerTopLevel'); + const consumerNested1 = new NestedStack(consumerTopLevel, 'ConsumerNested1'); + const consumerNested2 = new NestedStack(consumerNested1, 'ConsumerNested2'); + const producerTopLevel = new Stack(app, 'ProducerTopLevel'); + const producer = new CfnResource(producerTopLevel, 'Producer', { type: 'Producer' }); + + // WHEN + new CfnResource(consumerNested2, 'Consumer', { + type: 'Consumer', + properties: { + Ref: producer.ref, + }, + }); -test('nested stack within a nested stack references a resource in a sibling top-level stack', () => { - // GIVEN - const app = new App(); - const consumerTopLevel = new Stack(app, 'ConsumerTopLevel'); - const consumerNested1 = new NestedStack(consumerTopLevel, 'ConsumerNested1'); - const consumerNested2 = new NestedStack(consumerNested1, 'ConsumerNested2'); - const producerTopLevel = new Stack(app, 'ProducerTopLevel'); - const producer = new CfnResource(producerTopLevel, 'Producer', { type: 'Producer' }); - - // WHEN - new CfnResource(consumerNested2, 'Consumer', { - type: 'Consumer', - properties: { - Ref: producer.ref, - }, + // THEN + const manifest = app.synth(); + const consumerDeps = manifest.getStackArtifact(consumerTopLevel.artifactId).dependencies.map(d => d.id); + expect(consumerDeps).toEqual(['ProducerTopLevel']); }); - // THEN - const manifest = app.synth(); - const consumerDeps = manifest.getStackArtifact(consumerTopLevel.artifactId).dependencies.map(d => d.id); - expect(consumerDeps).toEqual(['ProducerTopLevel']); -}); - -test('another non-nested stack takes a reference on a resource within the nested stack (the parent exports)', () => { - // GIVEN - const app = new App(); - const stack1 = new Stack(app, 'Stack1'); - const stack2 = new Stack(app, 'Stack2'); - const nestedUnderStack1 = new NestedStack(stack1, 'NestedUnderStack1'); - const resourceInNestedStack = new CfnResource(nestedUnderStack1, 'ResourceInNestedStack', { type: 'MyResource' }); - - // WHEN - new CfnResource(stack2, 'ResourceInStack2', { - type: 'JustResource', - properties: { - RefToSibling: resourceInNestedStack.getAtt('MyAttribute'), - }, - }); + test('another non-nested stack takes a reference on a resource within the nested stack (the parent exports)', () => { + // GIVEN + const app = new App(); + const stack1 = new Stack(app, 'Stack1'); + const stack2 = new Stack(app, 'Stack2'); + const nestedUnderStack1 = new NestedStack(stack1, 'NestedUnderStack1'); + const resourceInNestedStack = new CfnResource(nestedUnderStack1, 'ResourceInNestedStack', { type: 'MyResource' }); + + // WHEN + new CfnResource(stack2, 'ResourceInStack2', { + type: 'JustResource', + properties: { + RefToSibling: resourceInNestedStack.getAtt('MyAttribute'), + }, + }); - // THEN - const assembly = app.synth(); + // THEN + const assembly = app.synth(); - // nested stack should output this value as if it was referenced by the parent (without the export) - expect(nestedUnderStack1).toMatchTemplate({ - Resources: { - ResourceInNestedStack: { - Type: 'MyResource', + // nested stack should output this value as if it was referenced by the parent (without the export) + expect(nestedUnderStack1).toMatchTemplate({ + Resources: { + ResourceInNestedStack: { + Type: 'MyResource', + }, }, - }, - Outputs: { - Stack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute: { - Value: { - 'Fn::GetAtt': [ - 'ResourceInNestedStack', - 'MyAttribute', - ], + Outputs: { + Stack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute: { + Value: { + 'Fn::GetAtt': [ + 'ResourceInNestedStack', + 'MyAttribute', + ], + }, }, }, - }, - }); + }); - // parent stack (stack1) should export this value - expect(assembly.getStackByName(stack1.stackName).template.Outputs).toEqual({ - ExportsOutputFnGetAttNestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305BOutputsStack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute564EECF3: { - Value: { 'Fn::GetAtt': ['NestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305B', 'Outputs.Stack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute'] }, - Export: { Name: 'Stack1:ExportsOutputFnGetAttNestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305BOutputsStack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute564EECF3' }, - }, - }); - - // consuming stack should use ImportValue to import the value from the parent stack - expect(stack2).toMatchTemplate({ - Resources: { - ResourceInStack2: { - Type: 'JustResource', - Properties: { - RefToSibling: { - 'Fn::ImportValue': 'Stack1:ExportsOutputFnGetAttNestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305BOutputsStack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute564EECF3', + // parent stack (stack1) should export this value + expect(assembly.getStackByName(stack1.stackName).template.Outputs).toEqual({ + ExportsOutputFnGetAttNestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305BOutputsStack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute564EECF3: { + Value: { 'Fn::GetAtt': ['NestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305B', 'Outputs.Stack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute'] }, + Export: { Name: 'Stack1:ExportsOutputFnGetAttNestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305BOutputsStack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute564EECF3' }, + }, + }); + + // consuming stack should use ImportValue to import the value from the parent stack + expect(stack2).toMatchTemplate({ + Resources: { + ResourceInStack2: { + Type: 'JustResource', + Properties: { + RefToSibling: { + 'Fn::ImportValue': 'Stack1:ExportsOutputFnGetAttNestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305BOutputsStack1NestedUnderStack1ResourceInNestedStack6EE9DCD2MyAttribute564EECF3', + }, }, }, }, - }, + }); + + expect(assembly.stacks.length).toEqual(2); + const stack1Artifact = assembly.getStackByName(stack1.stackName); + const stack2Artifact = assembly.getStackByName(stack2.stackName); + expect(stack1Artifact.dependencies.length).toEqual(0); + expect(stack2Artifact.dependencies.length).toEqual(1); + expect(stack2Artifact.dependencies[0]).toEqual(stack1Artifact); }); - expect(assembly.stacks.length).toEqual(2); - const stack1Artifact = assembly.getStackByName(stack1.stackName); - const stack2Artifact = assembly.getStackByName(stack2.stackName); - expect(stack1Artifact.dependencies.length).toEqual(0); - expect(stack2Artifact.dependencies.length).toEqual(1); - expect(stack2Artifact.dependencies[0]).toEqual(stack1Artifact); -}); - -test('references between sibling nested stacks should output from one and getAtt from the other', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'Parent'); - const nested1 = new NestedStack(parent, 'Nested1'); - const nested2 = new NestedStack(parent, 'Nested2'); - const resource1 = new CfnResource(nested1, 'Resource1', { type: 'Resource1' }); - - // WHEN - new CfnResource(nested2, 'Resource2', { - type: 'Resource2', - properties: { - RefToResource1: resource1.ref, - }, - }); + test('references between sibling nested stacks should output from one and getAtt from the other', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'Parent'); + const nested1 = new NestedStack(parent, 'Nested1'); + const nested2 = new NestedStack(parent, 'Nested2'); + const resource1 = new CfnResource(nested1, 'Resource1', { type: 'Resource1' }); + + // WHEN + new CfnResource(nested2, 'Resource2', { + type: 'Resource2', + properties: { + RefToResource1: resource1.ref, + }, + }); - // THEN - app.synth(); + // THEN + app.synth(); - // producing nested stack - expect(nested1).toMatchTemplate({ - Resources: { - Resource1: { - Type: 'Resource1', - }, - }, - Outputs: { - ParentNested1Resource15F3F0657Ref: { - Value: { - Ref: 'Resource1', + // producing nested stack + expect(nested1).toMatchTemplate({ + Resources: { + Resource1: { + Type: 'Resource1', }, }, - }, - }); - - // consuming nested stack - expect(nested2).toMatchTemplate({ - Resources: { - Resource2: { - Type: 'Resource2', - Properties: { - RefToResource1: { - Ref: 'referencetoParentNested1NestedStackNested1NestedStackResource9C05342COutputsParentNested1Resource15F3F0657Ref', + Outputs: { + ParentNested1Resource15F3F0657Ref: { + Value: { + Ref: 'Resource1', }, }, }, - }, - Parameters: { - referencetoParentNested1NestedStackNested1NestedStackResource9C05342COutputsParentNested1Resource15F3F0657Ref: { - Type: 'String', + }); + + // consuming nested stack + expect(nested2).toMatchTemplate({ + Resources: { + Resource2: { + Type: 'Resource2', + Properties: { + RefToResource1: { + Ref: 'referencetoParentNested1NestedStackNested1NestedStackResource9C05342COutputsParentNested1Resource15F3F0657Ref', + }, + }, + }, }, - }, - }); - - // parent - expect(parent).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - referencetoParentNested1NestedStackNested1NestedStackResource9C05342COutputsParentNested1Resource15F3F0657Ref: { - 'Fn::GetAtt': [ - 'Nested1NestedStackNested1NestedStackResourceCD0AD36B', - 'Outputs.ParentNested1Resource15F3F0657Ref', - ], + Parameters: { + referencetoParentNested1NestedStackNested1NestedStackResource9C05342COutputsParentNested1Resource15F3F0657Ref: { + Type: 'String', + }, }, - }, - }); -}); - -test('stackId returns AWS::StackId when referenced from the context of the nested stack', () => { - // GIVEN - const parent = new Stack(); - const nested = new NestedStack(parent, 'NestedStack'); + }); - // WHEN - new CfnResource(nested, 'NestedResource', { - type: 'Nested::Resource', - properties: { MyStackId: nested.stackId }, - }); - - // THEN - expect(nested).toHaveResource('Nested::Resource', { - MyStackId: { Ref: 'AWS::StackId' }, - }); -}); - -test('stackId returns the REF of the CloudFormation::Stack resource when referenced from the parent stack', () => { - // GIVEN - const parent = new Stack(); - const nested = new NestedStack(parent, 'NestedStack'); - - // WHEN - new CfnResource(parent, 'ParentResource', { - type: 'Parent::Resource', - properties: { NestedStackId: nested.stackId }, - }); - - // THEN - expect(parent).toHaveResource('Parent::Resource', { - NestedStackId: { Ref: 'NestedStackNestedStackNestedStackNestedStackResourceB70834FD' }, - }); -}); - -test('stackName returns AWS::StackName when referenced from the context of the nested stack', () => { - // GIVEN - const parent = new Stack(); - const nested = new NestedStack(parent, 'NestedStack'); - - // WHEN - new CfnResource(nested, 'NestedResource', { - type: 'Nested::Resource', - properties: { MyStackName: nested.stackName }, - }); - - // THEN - expect(nested).toHaveResource('Nested::Resource', { - MyStackName: { Ref: 'AWS::StackName' }, - }); -}); - -test('stackName returns the REF of the CloudFormation::Stack resource when referenced from the parent stack', () => { - // GIVEN - const parent = new Stack(); - const nested = new NestedStack(parent, 'NestedStack'); - - // WHEN - new CfnResource(parent, 'ParentResource', { - type: 'Parent::Resource', - properties: { NestedStackName: nested.stackName }, - }); - - // THEN - expect(parent).toHaveResource('Parent::Resource', { - NestedStackName: { - 'Fn::Select': [ - 1, - { - 'Fn::Split': [ - '/', - { - Ref: 'NestedStackNestedStackNestedStackNestedStackResourceB70834FD', - }, + // parent + expect(parent).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + referencetoParentNested1NestedStackNested1NestedStackResource9C05342COutputsParentNested1Resource15F3F0657Ref: { + 'Fn::GetAtt': [ + 'Nested1NestedStackNested1NestedStackResourceCD0AD36B', + 'Outputs.ParentNested1Resource15F3F0657Ref', ], }, - ], - }, + }, + }); }); -}); - -test('"account", "region" and "environment" are all derived from the parent', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'ParentStack', { env: { account: '1234account', region: 'us-east-44' } }); - - // WHEN - const nested = new NestedStack(parent, 'NestedStack'); - // THEN - expect(nested.environment).toEqual(parent.environment); - expect(nested.account).toEqual(parent.account); - expect(nested.region).toEqual(parent.region); -}); - -test('double-nested stack', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'stack'); - - // WHEN - const nested1 = new NestedStack(parent, 'Nested1'); - const nested2 = new NestedStack(nested1, 'Nested2'); - - new CfnResource(nested1, 'Resource1', { type: 'Resource::1' }); - new CfnResource(nested2, 'Resource2', { type: 'Resource::2' }); - - // THEN - const assembly = app.synth(); - - // nested2 is a "leaf", so it's just the resource - expect(nested2).toMatchTemplate({ - Resources: { - Resource2: { Type: 'Resource::2' }, - }, + test('stackId returns AWS::StackId when referenced from the context of the nested stack', () => { + // GIVEN + const parent = new Stack(); + const nested = new NestedStack(parent, 'NestedStack'); + + // WHEN + new CfnResource(nested, 'NestedResource', { + type: 'Nested::Resource', + properties: { MyStackId: nested.stackId }, + }); + + // THEN + expect(nested).toHaveResource('Nested::Resource', { + MyStackId: { Ref: 'AWS::StackId' }, + }); }); - const middleStackHash = '7c426f7299a739900279ac1ece040397c1913cdf786f5228677b289f4d5e4c48'; - const bucketSuffix = 'C706B101'; - const versionSuffix = '4B193AA5'; - const hashSuffix = 'E28F0693'; - - // nested1 wires the nested2 template through parameters, so we expect those - expect(nested1).toHaveResource('Resource::1'); - const nested2Template = SynthUtils.toCloudFormation(nested1); - expect(nested2Template.Parameters).toEqual({ - referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketE8768F5CRef: { Type: 'String' }, - referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey49DD83A2Ref: { Type: 'String' }, + test('stackId returns the REF of the CloudFormation::Stack resource when referenced from the parent stack', () => { + // GIVEN + const parent = new Stack(); + const nested = new NestedStack(parent, 'NestedStack'); + + // WHEN + new CfnResource(parent, 'ParentResource', { + type: 'Parent::Resource', + properties: { NestedStackId: nested.stackId }, + }); + + // THEN + expect(parent).toHaveResource('Parent::Resource', { + NestedStackId: { Ref: 'NestedStackNestedStackNestedStackNestedStackResourceB70834FD' }, + }); }); - // parent stack should have two sets of parameters. one for the first nested stack and the second - // for the second nested stack, passed in as parameters to the first - const template = SynthUtils.toCloudFormation(parent); - expect(template.Parameters).toEqual({ - AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketDE3B88D6: { Type: 'String', Description: 'S3 bucket for asset "8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235c"' }, - AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey3A62EFEA: { Type: 'String', Description: 'S3 key for asset version "8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235c"' }, - AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cArtifactHash7DC546E0: { Type: 'String', Description: 'Artifact hash for asset "8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235c"' }, - [`AssetParameters${middleStackHash}S3Bucket${bucketSuffix}`]: { Type: 'String', Description: `S3 bucket for asset "${middleStackHash}"` }, - [`AssetParameters${middleStackHash}S3VersionKey${versionSuffix}`]: { Type: 'String', Description: `S3 key for asset version "${middleStackHash}"` }, - [`AssetParameters${middleStackHash}ArtifactHash${hashSuffix}`]: { Type: 'String', Description: `Artifact hash for asset "${middleStackHash}"` }, + test('stackName returns AWS::StackName when referenced from the context of the nested stack', () => { + // GIVEN + const parent = new Stack(); + const nested = new NestedStack(parent, 'NestedStack'); + + // WHEN + new CfnResource(nested, 'NestedResource', { + type: 'Nested::Resource', + properties: { MyStackName: nested.stackName }, + }); + + // THEN + expect(nested).toHaveResource('Nested::Resource', { + MyStackName: { Ref: 'AWS::StackName' }, + }); }); - // proxy asset params to nested stack - expect(parent).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketE8768F5CRef: { Ref: 'AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketDE3B88D6' }, - referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey49DD83A2Ref: { Ref: 'AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey3A62EFEA' }, - }, + test('stackName returns the REF of the CloudFormation::Stack resource when referenced from the parent stack', () => { + // GIVEN + const parent = new Stack(); + const nested = new NestedStack(parent, 'NestedStack'); + + // WHEN + new CfnResource(parent, 'ParentResource', { + type: 'Parent::Resource', + properties: { NestedStackName: nested.stackName }, + }); + + // THEN + expect(parent).toHaveResource('Parent::Resource', { + NestedStackName: { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '/', + { + Ref: 'NestedStackNestedStackNestedStackNestedStackResourceB70834FD', + }, + ], + }, + ], + }, + }); }); - // parent stack should have 2 assets - expect(assembly.getStackByName(parent.stackName).assets.length).toEqual(2); -}); + test('"account", "region" and "environment" are all derived from the parent', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'ParentStack', { env: { account: '1234account', region: 'us-east-44' } }); -test('reference resource in a double nested stack (#15155)', () => { - // GIVEN - const app = new App(); - const producerStack = new Stack(app, 'Producer'); - const nested2 = new NestedStack(new NestedStack(producerStack, 'Nested1'), 'Nested2'); - const producerResource = new CfnResource(nested2, 'Resource', { type: 'MyResource' }); - const consumerStack = new Stack(app, 'Consumer'); - - // WHEN - new CfnResource(consumerStack, 'ConsumingResource', { - type: 'YourResource', - properties: { RefToResource: producerResource.ref }, - }); + // WHEN + const nested = new NestedStack(parent, 'NestedStack'); - // THEN - const casm = app.synth(); // before #15155 was fixed this threw an error + // THEN + expect(nested.environment).toEqual(parent.environment); + expect(nested.account).toEqual(parent.account); + expect(nested.region).toEqual(parent.region); + }); - const producerTemplate = casm.getStackArtifact(producerStack.artifactId).template; - const consumerTemplate = casm.getStackArtifact(consumerStack.artifactId).template; + test('double-nested stack', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'stack'); - // check that the consuming resource references the expected export name - const outputName = 'ExportsOutputFnGetAttNested1NestedStackNested1NestedStackResourceCD0AD36BOutputsProducerNested1Nested2NestedStackNested2NestedStackResource1E6FA3C3OutputsProducerNested1Nested238A89CC5Ref2E9E52EA'; - const exportName = producerTemplate.Outputs[outputName].Export.Name; - const importName = consumerTemplate.Resources.ConsumingResource.Properties.RefToResource['Fn::ImportValue']; - expect(exportName).toEqual(importName); -}); + // WHEN + const nested1 = new NestedStack(parent, 'Nested1'); + const nested2 = new NestedStack(nested1, 'Nested2'); -test('assets within nested stacks are proxied from the parent', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'ParentStack'); - const nested = new NestedStack(parent, 'NestedStack'); + new CfnResource(nested1, 'Resource1', { type: 'Resource::1' }); + new CfnResource(nested2, 'Resource2', { type: 'Resource::2' }); - // WHEN - const asset = new s3_assets.Asset(nested, 'asset', { - path: path.join(__dirname, 'asset-fixture.txt'), - }); + // THEN + const assembly = app.synth(); - new CfnResource(nested, 'NestedResource', { - type: 'Nested::Resource', - properties: { - AssetBucket: asset.s3BucketName, - AssetKey: asset.s3ObjectKey, - }, - }); + // nested2 is a "leaf", so it's just the resource + expect(nested2).toMatchTemplate({ + Resources: { + Resource2: { Type: 'Resource::2' }, + }, + }); + + const middleStackHash = '7c426f7299a739900279ac1ece040397c1913cdf786f5228677b289f4d5e4c48'; + const bucketSuffix = 'C706B101'; + const versionSuffix = '4B193AA5'; + const hashSuffix = 'E28F0693'; + + // nested1 wires the nested2 template through parameters, so we expect those + expect(nested1).toHaveResource('Resource::1'); + const nested2Template = SynthUtils.toCloudFormation(nested1); + expect(nested2Template.Parameters).toEqual({ + referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketE8768F5CRef: { Type: 'String' }, + referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey49DD83A2Ref: { Type: 'String' }, + }); + + // parent stack should have two sets of parameters. one for the first nested stack and the second + // for the second nested stack, passed in as parameters to the first + const template = SynthUtils.toCloudFormation(parent); + expect(template.Parameters).toEqual({ + AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketDE3B88D6: { Type: 'String', Description: 'S3 bucket for asset "8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235c"' }, + AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey3A62EFEA: { Type: 'String', Description: 'S3 key for asset version "8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235c"' }, + AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cArtifactHash7DC546E0: { Type: 'String', Description: 'Artifact hash for asset "8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235c"' }, + [`AssetParameters${middleStackHash}S3Bucket${bucketSuffix}`]: { Type: 'String', Description: `S3 bucket for asset "${middleStackHash}"` }, + [`AssetParameters${middleStackHash}S3VersionKey${versionSuffix}`]: { Type: 'String', Description: `S3 key for asset version "${middleStackHash}"` }, + [`AssetParameters${middleStackHash}ArtifactHash${hashSuffix}`]: { Type: 'String', Description: `Artifact hash for asset "${middleStackHash}"` }, + }); + + // proxy asset params to nested stack + expect(parent).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketE8768F5CRef: { Ref: 'AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketDE3B88D6' }, + referencetostackAssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey49DD83A2Ref: { Ref: 'AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey3A62EFEA' }, + }, + }); - // THEN - const assembly = app.synth(); - const template = SynthUtils.toCloudFormation(parent); - - // two sets of asset parameters: one for the nested stack itself and one as a proxy for the asset within the stack - expect(template.Parameters).toEqual({ - AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3BucketC188F637: { Type: 'String', Description: 'S3 bucket for asset "db01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281"' }, - AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3VersionKeyC7F4DBF2: { Type: 'String', Description: 'S3 key for asset version "db01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281"' }, - AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281ArtifactHash373B14D2: { Type: 'String', Description: 'Artifact hash for asset "db01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281"' }, - AssetParameters46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71S3Bucket3C4265E9: { Type: 'String', Description: 'S3 bucket for asset "46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71"' }, - AssetParameters46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71S3VersionKey8E981535: { Type: 'String', Description: 'S3 key for asset version "46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71"' }, - AssetParameters46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71ArtifactHash45A28583: { Type: 'String', Description: 'Artifact hash for asset "46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71"' }, + // parent stack should have 2 assets + expect(assembly.getStackByName(parent.stackName).assets.length).toEqual(2); }); - // asset proxy parameters are passed to the nested stack - expect(parent).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - referencetoParentStackAssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3Bucket82C55B96Ref: { Ref: 'AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3BucketC188F637' }, - referencetoParentStackAssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3VersionKeyA43C3CC6Ref: { Ref: 'AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3VersionKeyC7F4DBF2' }, - }, + test('reference resource in a double nested stack (#15155)', () => { + // GIVEN + const app = new App(); + const producerStack = new Stack(app, 'Producer'); + const nested2 = new NestedStack(new NestedStack(producerStack, 'Nested1'), 'Nested2'); + const producerResource = new CfnResource(nested2, 'Resource', { type: 'MyResource' }); + const consumerStack = new Stack(app, 'Consumer'); + + // WHEN + new CfnResource(consumerStack, 'ConsumingResource', { + type: 'YourResource', + properties: { RefToResource: producerResource.ref }, + }); + + // THEN + const casm = app.synth(); // before #15155 was fixed this threw an error + + const producerTemplate = casm.getStackArtifact(producerStack.artifactId).template; + const consumerTemplate = casm.getStackArtifact(consumerStack.artifactId).template; + + // check that the consuming resource references the expected export name + const outputName = 'ExportsOutputFnGetAttNested1NestedStackNested1NestedStackResourceCD0AD36BOutputsProducerNested1Nested2NestedStackNested2NestedStackResource1E6FA3C3OutputsProducerNested1Nested238A89CC5Ref2E9E52EA'; + const exportName = producerTemplate.Outputs[outputName].Export.Name; + const importName = consumerTemplate.Resources.ConsumingResource.Properties.RefToResource['Fn::ImportValue']; + expect(exportName).toEqual(importName); }); - // parent stack should have 2 assets - expect(assembly.getStackByName(parent.stackName).assets.length).toEqual(2); -}); - -test('docker image assets are wired through the top-level stack', () => { - // GIVEN - const app = new App(); - const parent = new Stack(app, 'my-stack'); - const nested = new NestedStack(parent, 'nested-stack'); - - // WHEN - const location = nested.synthesizer.addDockerImageAsset({ - directoryName: 'my-image', - dockerBuildArgs: { key: 'value', boom: 'bam' }, - dockerBuildTarget: 'buildTarget', - sourceHash: 'hash-of-source', - }); + test('assets within nested stacks are proxied from the parent', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'ParentStack'); + const nested = new NestedStack(parent, 'NestedStack'); + + // WHEN + const asset = new s3_assets.Asset(nested, 'asset', { + path: path.join(__dirname, 'asset-fixture.txt'), + }); + + new CfnResource(nested, 'NestedResource', { + type: 'Nested::Resource', + properties: { + AssetBucket: asset.s3BucketName, + AssetKey: asset.s3ObjectKey, + }, + }); + + // THEN + const assembly = app.synth(); + const template = SynthUtils.toCloudFormation(parent); + + // two sets of asset parameters: one for the nested stack itself and one as a proxy for the asset within the stack + expect(template.Parameters).toEqual({ + AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3BucketC188F637: { Type: 'String', Description: 'S3 bucket for asset "db01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281"' }, + AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3VersionKeyC7F4DBF2: { Type: 'String', Description: 'S3 key for asset version "db01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281"' }, + AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281ArtifactHash373B14D2: { Type: 'String', Description: 'Artifact hash for asset "db01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281"' }, + AssetParameters46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71S3Bucket3C4265E9: { Type: 'String', Description: 'S3 bucket for asset "46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71"' }, + AssetParameters46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71S3VersionKey8E981535: { Type: 'String', Description: 'S3 key for asset version "46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71"' }, + AssetParameters46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71ArtifactHash45A28583: { Type: 'String', Description: 'Artifact hash for asset "46b107d6db798ca46046b8669d057a4debcbdbaaddb6170400748c2f9e4f9d71"' }, + }); + + // asset proxy parameters are passed to the nested stack + expect(parent).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + referencetoParentStackAssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3Bucket82C55B96Ref: { Ref: 'AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3BucketC188F637' }, + referencetoParentStackAssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3VersionKeyA43C3CC6Ref: { Ref: 'AssetParametersdb01ee2eb7adc7915e364dc410d861e569543f9be3761d535a68d5c2cc181281S3VersionKeyC7F4DBF2' }, + }, + }); - // use the asset, so the parameters will be wired. - new sns.Topic(nested, 'MyTopic', { - displayName: `image location is ${location.imageUri}`, + // parent stack should have 2 assets + expect(assembly.getStackByName(parent.stackName).assets.length).toEqual(2); }); - // THEN - const asm = app.synth(); - expect(asm.getStackArtifact(parent.artifactId).assets).toEqual([ - { - repositoryName: 'aws-cdk/assets', - imageTag: 'hash-of-source', - id: 'hash-of-source', - packaging: 'container-image', - path: 'my-image', + test('docker image assets are wired through the top-level stack', () => { + // GIVEN + const app = new App(); + const parent = new Stack(app, 'my-stack'); + const nested = new NestedStack(parent, 'nested-stack'); + + // WHEN + const location = nested.synthesizer.addDockerImageAsset({ + directoryName: 'my-image', + dockerBuildArgs: { key: 'value', boom: 'bam' }, + dockerBuildTarget: 'buildTarget', sourceHash: 'hash-of-source', - buildArgs: { key: 'value', boom: 'bam' }, - target: 'buildTarget', - }, - { - path: 'mystacknestedstackFAE12FB5.nested.template.json', - id: 'fcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5', - packaging: 'file', - sourceHash: 'fcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5', - s3BucketParameter: 'AssetParametersfcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5S3Bucket67A749F8', - s3KeyParameter: 'AssetParametersfcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5S3VersionKeyE1E6A8D4', - artifactHashParameter: 'AssetParametersfcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5ArtifactHash0AEDBE8A', - }, - ]); -}); + }); -test('metadata defined in nested stacks is reported at the parent stack level in the cloud assembly', () => { - // GIVEN - const app = new App({ stackTraces: false }); - const parent = new Stack(app, 'parent'); - const child = new Stack(parent, 'child'); - const nested = new NestedStack(child, 'nested'); - const resource = new CfnResource(nested, 'resource', { type: 'foo' }); - - // WHEN - resource.node.addMetadata('foo', 'bar'); - - // THEN: the first non-nested stack records the assembly metadata - const asm = app.synth(); - expect(asm.stacks.length).toEqual(2); // only one stack is defined as an artifact - expect(asm.getStackByName(parent.stackName).findMetadataByType('foo')).toEqual([]); - expect(asm.getStackByName(child.stackName).findMetadataByType('foo')).toEqual([ - { - path: '/parent/child/nested/resource', - type: 'foo', - data: 'bar', - }, - ]); -}); + // use the asset, so the parameters will be wired. + new sns.Topic(nested, 'MyTopic', { + displayName: `image location is ${location.imageUri}`, + }); -test('referencing attributes with period across stacks', () => { - // GIVEN - const parent = new Stack(); - const nested = new NestedStack(parent, 'nested'); - const consumed = new CfnResource(nested, 'resource-in-nested', { type: 'CONSUMED' }); - - // WHEN - new CfnResource(parent, 'resource-in-parent', { - type: 'CONSUMER', - properties: { - ConsumedAttribute: consumed.getAtt('Consumed.Attribute'), - }, - }); - - // THEN - expect(nested).toMatchTemplate({ - Resources: { - resourceinnested: { - Type: 'CONSUMED', + // THEN + const asm = app.synth(); + expect(asm.getStackArtifact(parent.artifactId).assets).toEqual([ + { + repositoryName: 'aws-cdk/assets', + imageTag: 'hash-of-source', + id: 'hash-of-source', + packaging: 'container-image', + path: 'my-image', + sourceHash: 'hash-of-source', + buildArgs: { key: 'value', boom: 'bam' }, + target: 'buildTarget', }, - }, - Outputs: { - nestedresourceinnested59B1F01CConsumedAttribute: { - Value: { - 'Fn::GetAtt': [ - 'resourceinnested', - 'Consumed.Attribute', - ], - }, + { + path: 'mystacknestedstackFAE12FB5.nested.template.json', + id: 'fcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5', + packaging: 'file', + sourceHash: 'fcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5', + s3BucketParameter: 'AssetParametersfcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5S3Bucket67A749F8', + s3KeyParameter: 'AssetParametersfcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5S3VersionKeyE1E6A8D4', + artifactHashParameter: 'AssetParametersfcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5ArtifactHash0AEDBE8A', }, - }, + ]); }); - expect(parent).toHaveResource('CONSUMER', { - ConsumedAttribute: { - 'Fn::GetAtt': [ - 'nestedNestedStacknestedNestedStackResource3DD143BF', - 'Outputs.nestedresourceinnested59B1F01CConsumedAttribute', - ], - }, - }); -}); - -test('missing context in nested stack is reported if the context is not available', () => { - // GIVEN - const app = new App(); - const stack = new Stack(app, 'ParentStack', { env: { account: '1234account', region: 'us-east-44' } }); - const nestedStack = new NestedStack(stack, 'nested'); - const provider = 'availability-zones'; - const expectedKey = ContextProvider.getKey(nestedStack, { - provider, - }).key; - - // WHEN - ContextProvider.getValue(nestedStack, { - provider, - dummyValue: ['dummy1a', 'dummy1b', 'dummy1c'], + test('metadata defined in nested stacks is reported at the parent stack level in the cloud assembly', () => { + // GIVEN + const app = new App({ stackTraces: false }); + const parent = new Stack(app, 'parent'); + const child = new Stack(parent, 'child'); + const nested = new NestedStack(child, 'nested'); + const resource = new CfnResource(nested, 'resource', { type: 'foo' }); + + // WHEN + resource.node.addMetadata('foo', 'bar'); + + // THEN: the first non-nested stack records the assembly metadata + const asm = app.synth(); + expect(asm.stacks.length).toEqual(2); // only one stack is defined as an artifact + expect(asm.getStackByName(parent.stackName).findMetadataByType('foo')).toEqual([]); + expect(asm.getStackByName(child.stackName).findMetadataByType('foo')).toEqual([ + { + path: '/parent/child/nested/resource', + type: 'foo', + data: 'bar', + }, + ]); }); - // THEN: missing context is reported in the cloud assembly - const asm = app.synth(); - const missing = asm.manifest.missing; - - expect(missing && missing.find(m => { - return (m.key === expectedKey); - })).toBeTruthy(); -}); - -test('3-level stacks: legacy synthesizer parameters are added to the middle-level stack', () => { - // GIVEN - const app = new App(); - const top = new Stack(app, 'stack', { - synthesizer: new LegacyStackSynthesizer(), - }); - const middle = new NestedStack(top, 'nested1'); - const bottom = new NestedStack(middle, 'nested2'); + test('referencing attributes with period across stacks', () => { + // GIVEN + const parent = new Stack(); + const nested = new NestedStack(parent, 'nested'); + const consumed = new CfnResource(nested, 'resource-in-nested', { type: 'CONSUMED' }); + + // WHEN + new CfnResource(parent, 'resource-in-parent', { + type: 'CONSUMER', + properties: { + ConsumedAttribute: consumed.getAtt('Consumed.Attribute'), + }, + }); - // WHEN - new CfnResource(bottom, 'Something', { - type: 'BottomLevel', - }); + // THEN + expect(nested).toMatchTemplate({ + Resources: { + resourceinnested: { + Type: 'CONSUMED', + }, + }, + Outputs: { + nestedresourceinnested59B1F01CConsumedAttribute: { + Value: { + 'Fn::GetAtt': [ + 'resourceinnested', + 'Consumed.Attribute', + ], + }, + }, + }, + }); - // THEN - const asm = app.synth(); - const middleTemplate = JSON.parse(fs.readFileSync(path.join(asm.directory, middle.templateFile), { encoding: 'utf-8' })); - - const hash = 'bc3c51e4d3545ee0a0069401e5a32c37b66d044b983f12de416ba1576ecaf0a4'; - expect(middleTemplate.Parameters ?? {}).toEqual({ - [`referencetostackAssetParameters${hash}S3BucketD7C30435Ref`]: { - Type: 'String', - }, - [`referencetostackAssetParameters${hash}S3VersionKeyB667DBE1Ref`]: { - Type: 'String', - }, + expect(parent).toHaveResource('CONSUMER', { + ConsumedAttribute: { + 'Fn::GetAtt': [ + 'nestedNestedStacknestedNestedStackResource3DD143BF', + 'Outputs.nestedresourceinnested59B1F01CConsumedAttribute', + ], + }, + }); }); -}); -test('references to a resource from a deeply nested stack', () => { - // GIVEN - const app = new App(); - const top = new Stack(app, 'stack'); - const topLevel = new CfnResource(top, 'toplevel', { type: 'TopLevel' }); - const nested1 = new NestedStack(top, 'nested1'); - const nested2 = new NestedStack(nested1, 'nested2'); - - // WHEN - new CfnResource(nested2, 'refToTopLevel', { - type: 'BottomLevel', - properties: { RefToTopLevel: topLevel.ref }, + test('missing context in nested stack is reported if the context is not available', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'ParentStack', { env: { account: '1234account', region: 'us-east-44' } }); + const nestedStack = new NestedStack(stack, 'nested'); + const provider = 'availability-zones'; + const expectedKey = ContextProvider.getKey(nestedStack, { + provider, + }).key; + + // WHEN + ContextProvider.getValue(nestedStack, { + provider, + dummyValue: ['dummy1a', 'dummy1b', 'dummy1c'], + }); + + // THEN: missing context is reported in the cloud assembly + const asm = app.synth(); + const missing = asm.manifest.missing; + + expect(missing && missing.find(m => { + return (m.key === expectedKey); + })).toBeTruthy(); }); - // THEN - expect(top).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - referencetostackAssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3Bucket5DA5D2E7Ref: { - Ref: 'AssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3BucketDD4D96B5', - }, - referencetostackAssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3VersionKey8FBE5C12Ref: { - Ref: 'AssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3VersionKey83E381F3', + test('3-level stacks: legacy synthesizer parameters are added to the middle-level stack', () => { + // GIVEN + const app = new App(); + const top = new Stack(app, 'stack', { + synthesizer: new LegacyStackSynthesizer(), + }); + const middle = new NestedStack(top, 'nested1'); + const bottom = new NestedStack(middle, 'nested2'); + + // WHEN + new CfnResource(bottom, 'Something', { + type: 'BottomLevel', + }); + + // THEN + const asm = app.synth(); + const middleTemplate = JSON.parse(fs.readFileSync(path.join(asm.directory, middle.templateFile), { encoding: 'utf-8' })); + + const hash = 'bc3c51e4d3545ee0a0069401e5a32c37b66d044b983f12de416ba1576ecaf0a4'; + expect(middleTemplate.Parameters ?? {}).toEqual({ + [`referencetostackAssetParameters${hash}S3BucketD7C30435Ref`]: { + Type: 'String', }, - referencetostacktoplevelBB16BF13Ref: { - Ref: 'toplevel', + [`referencetostackAssetParameters${hash}S3VersionKeyB667DBE1Ref`]: { + Type: 'String', }, - }, + }); }); - expect(nested1).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - referencetostacktoplevelBB16BF13Ref: { - Ref: 'referencetostacktoplevelBB16BF13Ref', + test('references to a resource from a deeply nested stack', () => { + // GIVEN + const app = new App(); + const top = new Stack(app, 'stack'); + const topLevel = new CfnResource(top, 'toplevel', { type: 'TopLevel' }); + const nested1 = new NestedStack(top, 'nested1'); + const nested2 = new NestedStack(nested1, 'nested2'); + + // WHEN + new CfnResource(nested2, 'refToTopLevel', { + type: 'BottomLevel', + properties: { RefToTopLevel: topLevel.ref }, + }); + + // THEN + expect(top).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + referencetostackAssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3Bucket5DA5D2E7Ref: { + Ref: 'AssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3BucketDD4D96B5', + }, + referencetostackAssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3VersionKey8FBE5C12Ref: { + Ref: 'AssetParameters842982bd421cce9742ba27151ef12ed699d44d22801f41e8029f63f2358a3f2fS3VersionKey83E381F3', + }, + referencetostacktoplevelBB16BF13Ref: { + Ref: 'toplevel', + }, }, - }, - }); + }); - expect(nested2).toMatchTemplate({ - Resources: { - refToTopLevel: { - Type: 'BottomLevel', - Properties: { - RefToTopLevel: { - Ref: 'referencetostacktoplevelBB16BF13Ref', + expect(nested1).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + referencetostacktoplevelBB16BF13Ref: { + Ref: 'referencetostacktoplevelBB16BF13Ref', + }, + }, + }); + + expect(nested2).toMatchTemplate({ + Resources: { + refToTopLevel: { + Type: 'BottomLevel', + Properties: { + RefToTopLevel: { + Ref: 'referencetostacktoplevelBB16BF13Ref', + }, }, }, }, - }, - Parameters: { - referencetostacktoplevelBB16BF13Ref: { - Type: 'String', + Parameters: { + referencetostacktoplevelBB16BF13Ref: { + Type: 'String', + }, }, - }, + }); }); -}); -test('bottom nested stack consumes value from a top-level stack through a parameter in a middle nested stack', () => { - // GIVEN - const app = new App(); - const top = new Stack(app, 'Grandparent'); - const middle = new NestedStack(top, 'Parent'); - const bottom = new NestedStack(middle, 'Child'); - const resourceInGrandparent = new CfnResource(top, 'ResourceInGrandparent', { type: 'ResourceInGrandparent' }); - - // WHEN - new CfnResource(bottom, 'ResourceInChild', { - type: 'ResourceInChild', - properties: { - RefToGrandparent: resourceInGrandparent.ref, - }, - }); + test('bottom nested stack consumes value from a top-level stack through a parameter in a middle nested stack', () => { + // GIVEN + const app = new App(); + const top = new Stack(app, 'Grandparent'); + const middle = new NestedStack(top, 'Parent'); + const bottom = new NestedStack(middle, 'Child'); + const resourceInGrandparent = new CfnResource(top, 'ResourceInGrandparent', { type: 'ResourceInGrandparent' }); + + // WHEN + new CfnResource(bottom, 'ResourceInChild', { + type: 'ResourceInChild', + properties: { + RefToGrandparent: resourceInGrandparent.ref, + }, + }); - // THEN + // THEN - // this is the name allocated for the parameter that's propagated through - // the hierarchy. - const paramName = 'referencetoGrandparentResourceInGrandparent010E997ARef'; + // this is the name allocated for the parameter that's propagated through + // the hierarchy. + const paramName = 'referencetoGrandparentResourceInGrandparent010E997ARef'; - // child (bottom) references through a parameter. - expect(bottom).toMatchTemplate({ - Resources: { - ResourceInChild: { - Type: 'ResourceInChild', - Properties: { - RefToGrandparent: { Ref: paramName }, + // child (bottom) references through a parameter. + expect(bottom).toMatchTemplate({ + Resources: { + ResourceInChild: { + Type: 'ResourceInChild', + Properties: { + RefToGrandparent: { Ref: paramName }, + }, }, }, - }, - Parameters: { - [paramName]: { Type: 'String' }, - }, - }); + Parameters: { + [paramName]: { Type: 'String' }, + }, + }); - // the parent (middle) sets the value of this parameter to be a reference to another parameter - expect(middle).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - [paramName]: { Ref: paramName }, - }, - }); + // the parent (middle) sets the value of this parameter to be a reference to another parameter + expect(middle).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + [paramName]: { Ref: paramName }, + }, + }); - // grandparent (top) assigns the actual value to the parameter - expect(top).toHaveResource('AWS::CloudFormation::Stack', { - Parameters: { - [paramName]: { Ref: 'ResourceInGrandparent' }, + // grandparent (top) assigns the actual value to the parameter + expect(top).toHaveResource('AWS::CloudFormation::Stack', { + Parameters: { + [paramName]: { Ref: 'ResourceInGrandparent' }, - // these are for the asset of the bottom nested stack - referencetoGrandparentAssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3Bucket06EEE58DRef: { - Ref: 'AssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3Bucket01877C2E', - }, - referencetoGrandparentAssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3VersionKeyD3B04909Ref: { - Ref: 'AssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3VersionKey5765F084', + // these are for the asset of the bottom nested stack + referencetoGrandparentAssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3Bucket06EEE58DRef: { + Ref: 'AssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3Bucket01877C2E', + }, + referencetoGrandparentAssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3VersionKeyD3B04909Ref: { + Ref: 'AssetParameters3208f43b793a1dbe28ca02cf31fb975489071beb42c492b22dc3d32decc3b4b7S3VersionKey5765F084', + }, }, - }, + }); }); + }); diff --git a/packages/@aws-cdk/aws-cloudformation/test/resource.test.ts b/packages/@aws-cdk/aws-cloudformation/test/resource.test.ts index a3bd1944625ae..e241171e43df5 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/resource.test.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/resource.test.ts @@ -2,6 +2,7 @@ import { ResourcePart } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; +import { describeDeprecated, testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import { CustomResource, CustomResourceProvider } from '../lib'; @@ -12,7 +13,7 @@ import { Construct } from '@aws-cdk/core'; /* eslint-disable @aws-cdk/no-core-construct */ /* eslint-disable quote-props */ -describe('custom resources honor removalPolicy', () => { +describeDeprecated('custom resources honor removalPolicy', () => { test('unspecified (aka .Destroy)', () => { // GIVEN const app = new cdk.App(); @@ -55,7 +56,7 @@ describe('custom resources honor removalPolicy', () => { }); }); -test('custom resource is added twice, lambda is added once', () => { +testDeprecated('custom resource is added twice, lambda is added once', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'Test'); @@ -141,7 +142,7 @@ test('custom resource is added twice, lambda is added once', () => { }); }); -test('custom resources can specify a resource type that starts with Custom::', () => { +testDeprecated('custom resources can specify a resource type that starts with Custom::', () => { const app = new cdk.App(); const stack = new cdk.Stack(app, 'Test'); new CustomResource(stack, 'MyCustomResource', { @@ -151,7 +152,7 @@ test('custom resources can specify a resource type that starts with Custom::', ( expect(stack).toHaveResource('Custom::MyCustomResourceType'); }); -describe('fails if custom resource type is invalid', () => { +describeDeprecated('fails if custom resource type is invalid', () => { test('does not start with "Custom::"', () => { const app = new cdk.App(); const stack = new cdk.Stack(app, 'Test'); @@ -189,7 +190,7 @@ describe('fails if custom resource type is invalid', () => { }); }); -test('.ref returns the intrinsic reference (physical name)', () => { +testDeprecated('.ref returns the intrinsic reference (physical name)', () => { // GIVEN const stack = new cdk.Stack(); const res = new TestCustomResource(stack, 'myResource'); diff --git a/packages/@aws-cdk/aws-cloudfront/README.md b/packages/@aws-cdk/aws-cloudfront/README.md index 10c42d68efcfd..e10f5c39d596b 100644 --- a/packages/@aws-cdk/aws-cloudfront/README.md +++ b/packages/@aws-cdk/aws-cloudfront/README.md @@ -39,10 +39,7 @@ An S3 bucket can be added as an origin. If the bucket is configured as a website documents. ```ts -import * as cloudfront from '@aws-cdk/aws-cloudfront'; -import * as origins from '@aws-cdk/aws-cloudfront-origins'; - -// Creates a distribution for a S3 bucket. +// Creates a distribution from an S3 bucket. const myBucket = new s3.Bucket(this, 'myBucket'); new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.S3Origin(myBucket) }, @@ -61,15 +58,13 @@ An Elastic Load Balancing (ELB) v2 load balancer may be used as an origin. In or accessible (`internetFacing` is true). Both Application and Network load balancers are supported. ```ts -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; - -const vpc = new ec2.Vpc(...); +// Creates a distribution from an ELBv2 load balancer +declare const vpc: ec2.Vpc; // Create an application load balancer in a VPC. 'internetFacing' must be 'true' // for CloudFront to access the load balancer and use it as an origin. const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', { vpc, - internetFacing: true + internetFacing: true, }); new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.LoadBalancerV2Origin(lb) }, @@ -81,6 +76,7 @@ new cloudfront.Distribution(this, 'myDist', { Origins can also be created from any other HTTP endpoint, given the domain name, and optionally, other origin properties. ```ts +// Creates a distribution from an HTTP endpoint new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.HttpOrigin('www.example.com') }, }); @@ -98,10 +94,17 @@ may either be created by ACM, or created elsewhere and imported into ACM. When a from SNI only and a minimum protocol version of TLSv1.2_2021 if the '@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021' feature flag is set, and TLSv1.2_2019 otherwise. ```ts +// To use your own domain name in a Distribution, you must associate a certificate +import * as acm from '@aws-cdk/aws-certificatemanager'; +import * as route53 from '@aws-cdk/aws-route53'; + +declare const hostedZone: route53.HostedZone; const myCertificate = new acm.DnsValidatedCertificate(this, 'mySiteCert', { domainName: 'www.example.com', hostedZone, }); + +declare const myBucket: s3.Bucket; new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.S3Origin(myBucket) }, domainNames: ['www.example.com'], @@ -112,10 +115,12 @@ new cloudfront.Distribution(this, 'myDist', { However, you can customize the minimum protocol version for the certificate while creating the distribution using `minimumProtocolVersion` property. ```ts +// Create a Distribution with a custom domain name and a minimum protocol version. +declare const myBucket: s3.Bucket; new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.S3Origin(myBucket) }, domainNames: ['www.example.com'], - minimumProtocolVersion: SecurityPolicyProtocol.TLS_V1_2016 + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2016, }); ``` @@ -129,12 +134,14 @@ The properties of the default behavior can be adjusted as part of the distributi methods and viewer protocol policy of the cache. ```ts +// Create a Distribution with configured HTTP methods and viewer protocol policy of the cache. +declare const myBucket: s3.Bucket; const myWebDistribution = new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.S3Origin(myBucket), - allowedMethods: AllowedMethods.ALLOW_ALL, - viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, - } + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + }, }); ``` @@ -143,25 +150,30 @@ and enable customization for a specific set of resources based on a URL path pat override the default viewer protocol policy for all of the images. ```ts +// Add a behavior to a Distribution after initial creation. +declare const myBucket: s3.Bucket; +declare const myWebDistribution: cloudfront.Distribution; myWebDistribution.addBehavior('/images/*.jpg', new origins.S3Origin(myBucket), { - viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, }); ``` These behaviors can also be specified at distribution creation time. ```ts +// Create a Distribution with additional behaviors at creation time. +declare const myBucket: s3.Bucket; const bucketOrigin = new origins.S3Origin(myBucket); new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: bucketOrigin, - allowedMethods: AllowedMethods.ALLOW_ALL, - viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, }, additionalBehaviors: { '/images/*.jpg': { origin: bucketOrigin, - viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, }, }, }); @@ -176,15 +188,19 @@ or you can create your own cache policy that’s specific to your needs. See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/controlling-the-cache-key.html for more details. ```ts -// Using an existing cache policy +// Using an existing cache policy for a Distribution +declare const bucketOrigin: origins.S3Origin; new cloudfront.Distribution(this, 'myDistManagedPolicy', { defaultBehavior: { origin: bucketOrigin, cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED, }, }); +``` -// Creating a custom cache policy -- all parameters optional +```ts +// Creating a custom cache policy for a Distribution -- all parameters optional +declare const bucketOrigin: origins.S3Origin; const myCachePolicy = new cloudfront.CachePolicy(this, 'myCachePolicy', { cachePolicyName: 'MyPolicy', comment: 'A default policy', @@ -215,25 +231,30 @@ or you can create your own origin request policy that’s specific to your needs See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/controlling-origin-requests.html for more details. ```ts -// Using an existing origin request policy +// Using an existing origin request policy for a Distribution +declare const bucketOrigin: origins.S3Origin; new cloudfront.Distribution(this, 'myDistManagedPolicy', { defaultBehavior: { origin: bucketOrigin, originRequestPolicy: cloudfront.OriginRequestPolicy.CORS_S3_ORIGIN, }, }); -// Creating a custom origin request policy -- all parameters optional -const myOriginRequestPolicy = new cloudfront.OriginRequestPolicy(stack, 'OriginRequestPolicy', { +``` + +```ts +// Creating a custom origin request policy for a Distribution -- all parameters optional +declare const bucketOrigin: origins.S3Origin; +const myOriginRequestPolicy = new cloudfront.OriginRequestPolicy(this, 'OriginRequestPolicy', { originRequestPolicyName: 'MyPolicy', comment: 'A default policy', cookieBehavior: cloudfront.OriginRequestCookieBehavior.none(), headerBehavior: cloudfront.OriginRequestHeaderBehavior.all('CloudFront-Is-Android-Viewer'), queryStringBehavior: cloudfront.OriginRequestQueryStringBehavior.allowList('username'), }); + new cloudfront.Distribution(this, 'myDistCustomPolicy', { defaultBehavior: { origin: bucketOrigin, - cachePolicy: myCachePolicy, originRequestPolicy: myOriginRequestPolicy, }, }); @@ -241,23 +262,26 @@ new cloudfront.Distribution(this, 'myDistCustomPolicy', { ### Validating signed URLs or signed cookies with Trusted Key Groups -CloudFront Distribution now supports validating signed URLs or signed cookies using key groups. When a cache behavior contains trusted key groups, CloudFront requires signed URLs or signed cookies for all requests that match the cache behavior. - -Example: +CloudFront Distribution supports validating signed URLs or signed cookies using key groups. +When a cache behavior contains trusted key groups, CloudFront requires signed URLs or signed +cookies for all requests that match the cache behavior. ```ts +// Validating signed URLs or signed cookies with Trusted Key Groups + // public key in PEM format -const pubKey = new PublicKey(stack, 'MyPubKey', { +declare const publicKey: string; +const pubKey = new cloudfront.PublicKey(this, 'MyPubKey', { encodedKey: publicKey, }); -const keyGroup = new KeyGroup(stack, 'MyKeyGroup', { +const keyGroup = new cloudfront.KeyGroup(this, 'MyKeyGroup', { items: [ pubKey, ], }); -new cloudfront.Distribution(stack, 'Dist', { +new cloudfront.Distribution(this, 'Dist', { defaultBehavior: { origin: new origins.HttpOrigin('www.example.com'), trustedKeyGroups: [ @@ -269,23 +293,27 @@ new cloudfront.Distribution(stack, 'Dist', { ### Lambda@Edge -Lambda@Edge is an extension of AWS Lambda, a compute service that lets you execute functions that customize the content that CloudFront delivers. -You can author Node.js or Python functions in the US East (N. Virginia) region, -and then execute them in AWS locations globally that are closer to the viewer, -without provisioning or managing servers. -Lambda@Edge functions are associated with a specific behavior and event type. -Lambda@Edge can be used to rewrite URLs, -alter responses based on headers or cookies, -or authorize requests based on headers or authorization tokens. +Lambda@Edge is an extension of AWS Lambda, a compute service that lets you execute +functions that customize the content that CloudFront delivers. You can author Node.js +or Python functions in the US East (N. Virginia) region, and then execute them in AWS +locations globally that are closer to the viewer, without provisioning or managing servers. +Lambda@Edge functions are associated with a specific behavior and event type. Lambda@Edge +can be used to rewrite URLs, alter responses based on headers or cookies, or authorize +requests based on headers or authorization tokens. -The following shows a Lambda@Edge function added to the default behavior and triggered on every request: +The following shows a Lambda@Edge function added to the default behavior and triggered +on every request: ```ts +// A Lambda@Edge function added to default behavior of a Distribution +// and triggered on every request const myFunc = new cloudfront.experimental.EdgeFunction(this, 'MyFunction', { runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), }); + +declare const myBucket: s3.Bucket; new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.S3Origin(myBucket), @@ -309,6 +337,7 @@ new cloudfront.Distribution(this, 'myDist', { If the stack is in `us-east-1`, a "normal" `lambda.Function` can be used instead of an `EdgeFunction`. ```ts +// Using a lambda Function instead of an EdgeFunction for stacks in `us-east-`. const myFunc = new lambda.Function(this, 'MyFunction', { runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', @@ -320,18 +349,20 @@ If the stack is not in `us-east-1`, and you need references from different appli you can also set a specific stack ID for each Lambda@Edge. ```ts +// Setting stackIds for EdgeFunctions that can be referenced from different applications +// on the same account. const myFunc1 = new cloudfront.experimental.EdgeFunction(this, 'MyFunction1', { runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler1')), - stackId: 'edge-lambda-stack-id-1' + stackId: 'edge-lambda-stack-id-1', }); const myFunc2 = new cloudfront.experimental.EdgeFunction(this, 'MyFunction2', { runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler2')), - stackId: 'edge-lambda-stack-id-2' + stackId: 'edge-lambda-stack-id-2', }); ``` @@ -339,9 +370,13 @@ Lambda@Edge functions can also be associated with additional behaviors, either at or after Distribution creation time. ```ts +// Associating a Lambda@Edge function with additional behaviors. + +declare const myFunc: cloudfront.experimental.EdgeFunction; // assigning at Distribution creation +declare const myBucket: s3.Bucket; const myOrigin = new origins.S3Origin(myBucket); -new cloudfront.Distribution(this, 'myDist', { +const myDistribution = new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: myOrigin }, additionalBehaviors: { 'images/*': { @@ -371,16 +406,19 @@ myDistribution.addBehavior('images/*', myOrigin, { Adding an existing Lambda@Edge function created in a different stack to a CloudFront distribution. ```ts +// Adding an existing Lambda@Edge function created in a different stack +// to a CloudFront distribution. +declare const s3Bucket: s3.Bucket; const functionVersion = lambda.Version.fromVersionArn(this, 'Version', 'arn:aws:lambda:us-east-1:123456789012:function:functionName:1'); new cloudfront.Distribution(this, 'distro', { defaultBehavior: { origin: new origins.S3Origin(s3Bucket), edgeLambdas: [ - { - functionVersion, - eventType: cloudfront.LambdaEdgeEventType.VIEWER_REQUEST - }, + { + functionVersion, + eventType: cloudfront.LambdaEdgeEventType.VIEWER_REQUEST, + }, ], }, }); @@ -391,11 +429,13 @@ new cloudfront.Distribution(this, 'distro', { You can also deploy CloudFront functions and add them to a CloudFront distribution. ```ts -const cfFunction = new cloudfront.Function(stack, 'Function', { +// Add a cloudfront Function to a Distribution +const cfFunction = new cloudfront.Function(this, 'Function', { code: cloudfront.FunctionCode.fromInline('function handler(event) { return event.request }'), }); -new cloudfront.Distribution(stack, 'distro', { +declare const s3Bucket: s3.Bucket; +new cloudfront.Distribution(this, 'distro', { defaultBehavior: { origin: new origins.S3Origin(s3Bucket), functionAssociations: [{ @@ -416,6 +456,8 @@ You can configure CloudFront to create log files that contain detailed informati The logs can go to either an existing bucket, or a bucket will be created for you. ```ts +// Configure logging for Distributions + // Simplest form - creates a new bucket and logs to it. new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { origin: new origins.HttpOrigin('www.example.com') }, @@ -438,7 +480,8 @@ Existing distributions can be imported as well; note that like most imported con However, it can be used as a reference for other higher-level constructs. ```ts -const distribution = cloudfront.Distribution.fromDistributionAttributes(scope, 'ImportedDist', { +// Using a reference to an imported Distribution +const distribution = cloudfront.Distribution.fromDistributionAttributes(this, 'ImportedDist', { domainName: 'd111111abcdef8.cloudfront.net', distributionId: '012345ABCDEF', }); @@ -452,23 +495,25 @@ const distribution = cloudfront.Distribution.fromDistributionAttributes(scope, ' Example usage: ```ts -const sourceBucket = new Bucket(this, 'Bucket'); +// Using a CloudFrontWebDistribution construct. -const distribution = new CloudFrontWebDistribution(this, 'MyDistribution', { - originConfigs: [ - { - s3OriginSource: { - s3BucketSource: sourceBucket - }, - behaviors : [ {isDefaultBehavior: true}] - } - ] - }); +declare const sourceBucket: s3.Bucket; +const distribution = new cloudfront.CloudFrontWebDistribution(this, 'MyDistribution', { + originConfigs: [ + { + s3OriginSource: { + s3BucketSource: sourceBucket, + }, + behaviors : [ {isDefaultBehavior: true}], + }, + ], +}); ``` ### Viewer certificate -By default, CloudFront Web Distributions will answer HTTPS requests with CloudFront's default certificate, only containing the distribution `domainName` (e.g. d111111abcdef8.cloudfront.net). +By default, CloudFront Web Distributions will answer HTTPS requests with CloudFront's default certificate, +only containing the distribution `domainName` (e.g. d111111abcdef8.cloudfront.net). You can customize the viewer certificate property to provide a custom certificate and/or list of domain name aliases to fit your needs. See [Using Alternate Domain Names and HTTPS](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https-alternate-domain-names.html) in the CloudFront User Guide. @@ -486,7 +531,10 @@ Example: You can change the default certificate by one stored AWS Certificate Manager, or ACM. Those certificate can either be generated by AWS, or purchased by another CA imported into ACM. -For more information, see [the aws-certificatemanager module documentation](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-certificatemanager-readme.html) or [Importing Certificates into AWS Certificate Manager](https://docs.aws.amazon.com/acm/latest/userguide/import-certificate.html) in the AWS Certificate Manager User Guide. +For more information, see +[the aws-certificatemanager module documentation](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-certificatemanager-readme.html) +or [Importing Certificates into AWS Certificate Manager](https://docs.aws.amazon.com/acm/latest/userguide/import-certificate.html) +in the AWS Certificate Manager User Guide. Example: @@ -504,22 +552,26 @@ Example: ### Trusted Key Groups -CloudFront Web Distributions supports validating signed URLs or signed cookies using key groups. When a cache behavior contains trusted key groups, CloudFront requires signed URLs or signed cookies for all requests that match the cache behavior. +CloudFront Web Distributions supports validating signed URLs or signed cookies using key groups. +When a cache behavior contains trusted key groups, CloudFront requires signed URLs or signed cookies for all requests that match the cache behavior. Example: ```ts -const pubKey = new PublicKey(stack, 'MyPubKey', { +// Using trusted key groups for Cloudfront Web Distributions. +declare const sourceBucket: s3.Bucket; +declare const publicKey: string; +const pubKey = new cloudfront.PublicKey(this, 'MyPubKey', { encodedKey: publicKey, }); -const keyGroup = new KeyGroup(stack, 'MyKeyGroup', { +const keyGroup = new cloudfront.KeyGroup(this, 'MyKeyGroup', { items: [ pubKey, ], }); -new CloudFrontWebDistribution(stack, 'AnAmazingWebsiteProbably', { +new cloudfront.CloudFrontWebDistribution(this, 'AnAmazingWebsiteProbably', { originConfigs: [ { s3OriginSource: { @@ -547,15 +599,25 @@ See [Restricting the Geographic Distribution of Your Content](https://docs.aws.a Example: ```ts -new cloudfront.CloudFrontWebDistribution(stack, 'MyDistribution', { - //... - geoRestriction: GeoRestriction.whitelist('US', 'UK') +// Adding restrictions to a Cloudfront Web Distribution. +declare const sourceBucket: s3.Bucket; +new cloudfront.CloudFrontWebDistribution(this, 'MyDistribution', { + originConfigs: [ + { + s3OriginSource: { + s3BucketSource: sourceBucket, + }, + behaviors : [ {isDefaultBehavior: true}], + }, + ], + geoRestriction: cloudfront.GeoRestriction.whitelist('US', 'UK'), }); ``` ### Connection behaviors between CloudFront and your origin -CloudFront provides you even more control over the connection behaviors between CloudFront and your origin. You can now configure the number of connection attempts CloudFront will make to your origin and the origin connection timeout for each attempt. +CloudFront provides you even more control over the connection behaviors between CloudFront and your origin. +You can now configure the number of connection attempts CloudFront will make to your origin and the origin connection timeout for each attempt. See [Origin Connection Attempts](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html#origin-connection-attempts) @@ -564,43 +626,49 @@ See [Origin Connection Timeout](https://docs.aws.amazon.com/AmazonCloudFront/lat Example usage: ```ts -const distribution = new CloudFrontWebDistribution(this, 'MyDistribution', { - originConfigs: [ +// Configuring connection behaviors between Cloudfront and your origin +const distribution = new cloudfront.CloudFrontWebDistribution(this, 'MyDistribution', { + originConfigs: [ + { + connectionAttempts: 3, + connectionTimeout: Duration.seconds(10), + behaviors: [ { - ..., - connectionAttempts: 3, - connectionTimeout: cdk.Duration.seconds(10), - } - ] + isDefaultBehavior: true, + }, + ], + }, + ], }); ``` #### Origin Fallback In case the origin source is not available and answers with one of the -specified status code the failover origin source will be used. +specified status codes the failover origin source will be used. ```ts -new CloudFrontWebDistribution(stack, 'ADistribution', { +// Configuring origin fallback options for the CloudFrontWebDistribution +new cloudfront.CloudFrontWebDistribution(this, 'ADistribution', { originConfigs: [ { s3OriginSource: { - s3BucketSource: s3.Bucket.fromBucketName(stack, 'aBucket', 'myoriginbucket'), + s3BucketSource: s3.Bucket.fromBucketName(this, 'aBucket', 'myoriginbucket'), originPath: '/', originHeaders: { 'myHeader': '42', }, - originShieldRegion: 'us-west-2' + originShieldRegion: 'us-west-2', }, failoverS3OriginSource: { - s3BucketSource: s3.Bucket.fromBucketName(stack, 'aBucketFallback', 'myoriginbucketfallback'), + s3BucketSource: s3.Bucket.fromBucketName(this, 'aBucketFallback', 'myoriginbucketfallback'), originPath: '/somewhere', originHeaders: { 'myHeader2': '21', }, - originShieldRegion: 'us-east-1' + originShieldRegion: 'us-east-1', }, - failoverCriteriaStatusCodes: [FailoverStatusCode.INTERNAL_SERVER_ERROR], + failoverCriteriaStatusCodes: [cloudfront.FailoverStatusCode.INTERNAL_SERVER_ERROR], behaviors: [ { isDefaultBehavior: true, @@ -613,7 +681,8 @@ new CloudFrontWebDistribution(stack, 'ADistribution', { ## KeyGroup & PublicKey API -Now you can create a key group to use with CloudFront signed URLs and signed cookies. You can add public keys to use with CloudFront features such as signed URLs, signed cookies, and field-level encryption. +You can create a key group to use with CloudFront signed URLs and signed cookies +You can add public keys to use with CloudFront features such as signed URLs, signed cookies, and field-level encryption. The following example command uses OpenSSL to generate an RSA key pair with a length of 2048 bits and save to the file named `private_key.pem`. @@ -632,15 +701,16 @@ Note: Don't forget to copy/paste the contents of `public_key.pem` file including Example: ```ts - new cloudfront.KeyGroup(stack, 'MyKeyGroup', { - items: [ - new cloudfront.PublicKey(stack, 'MyPublicKey', { - encodedKey: '...', // contents of public_key.pem file - // comment: 'Key is expiring on ...', - }), - ], - // comment: 'Key group containing public keys ...', - }); +// Create a key group to use with CloudFront signed URLs and signed cookies. +new cloudfront.KeyGroup(this, 'MyKeyGroup', { + items: [ + new cloudfront.PublicKey(this, 'MyPublicKey', { + encodedKey: '...', // contents of public_key.pem file + // comment: 'Key is expiring on ...', + }), + ], + // comment: 'Key group containing public keys ...', +}); ``` See: diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index b8926bad4c88f..517a73bcc8917 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -1,7 +1,7 @@ import * as acm from '@aws-cdk/aws-certificatemanager'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; -import { IResource, Lazy, Resource, Stack, Token, Duration, Names, FeatureFlags } from '@aws-cdk/core'; +import { ArnFormat, IResource, Lazy, Resource, Stack, Token, Duration, Names, FeatureFlags } from '@aws-cdk/core'; import { CLOUDFRONT_DEFAULT_SECURITY_POLICY_TLS_V1_2_2021 } from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import { ICachePolicy } from './cache-policy'; @@ -260,7 +260,7 @@ export class Distribution extends Resource implements IDistribution { super(scope, id); if (props.certificate) { - const certificateRegion = Stack.of(this).parseArn(props.certificate.certificateArn).region; + const certificateRegion = Stack.of(this).splitArn(props.certificate.certificateArn, ArnFormat.SLASH_RESOURCE_NAME).region; if (!Token.isUnresolved(certificateRegion) && certificateRegion !== 'us-east-1') { throw new Error(`Distribution certificates must be in the us-east-1 region and the certificate you provided is in ${certificateRegion}.`); } diff --git a/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts b/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts index 4a3974c2af632..1fe9e745e8079 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts @@ -144,13 +144,13 @@ export class EdgeFunction extends Resource implements lambda.IVersion { /** Create a support stack and function in us-east-1, and a SSM reader in-region */ private createCrossRegionFunction(id: string, props: EdgeFunctionProps): FunctionConfig { - const parameterNamePrefix = '/cdk/EdgeFunctionArn'; + const parameterNamePrefix = 'cdk/EdgeFunctionArn'; if (Token.isUnresolved(this.env.region)) { throw new Error('stacks which use EdgeFunctions must have an explicitly set region'); } // SSM parameter names must only contain letters, numbers, ., _, -, or /. const sanitizedPath = this.node.path.replace(/[^\/\w.-]/g, '_'); - const parameterName = `${parameterNamePrefix}/${this.env.region}/${sanitizedPath}`; + const parameterName = `/${parameterNamePrefix}/${this.env.region}/${sanitizedPath}`; const functionStack = this.edgeStack(props.stackId); const edgeFunction = new lambda.Function(functionStack, id, props); @@ -177,7 +177,6 @@ export class EdgeFunction extends Resource implements lambda.IVersion { region: EdgeFunction.EDGE_REGION, resource: 'parameter', resourceName: parameterNamePrefix + '/*', - sep: '', }); const resourceType = 'Custom::CrossRegionStringParameterReader'; diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts index ab2fbbd44b03c..c0a332a2e1b89 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts @@ -721,19 +721,17 @@ export interface CloudFrontWebDistributionAttributes { * Here's how you can use this construct: * * ```ts - * import { CloudFrontWebDistribution } from '@aws-cdk/aws-cloudfront' + * const sourceBucket = new s3.Bucket(this, 'Bucket'); * - * const sourceBucket = new Bucket(this, 'Bucket'); - * - * const distribution = new CloudFrontWebDistribution(this, 'MyDistribution', { - * originConfigs: [ - * { - * s3OriginSource: { - * s3BucketSource: sourceBucket - * }, - * behaviors : [ {isDefaultBehavior: true}] - * } - * ] + * const distribution = new cloudfront.CloudFrontWebDistribution(this, 'MyDistribution', { + * originConfigs: [ + * { + * s3OriginSource: { + * s3BucketSource: sourceBucket, + * }, + * behaviors : [ {isDefaultBehavior: true}], + * }, + * ], * }); * ``` * diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index 8a43d54731a76..b0fdf92f48da2 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-cloudfront/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-cloudfront/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..1ac8c7fec5da3 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/rosetta/default.ts-fixture @@ -0,0 +1,17 @@ +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as cloudfront from '@aws-cdk/aws-cloudfront'; +import * as origins from '@aws-cdk/aws-cloudfront-origins'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as path from 'path'; + +class Context extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.ts index c06a78d16e714..7f90fff25740e 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-geo-restrictions.ts @@ -19,7 +19,7 @@ new cloudfront.CloudFrontWebDistribution(stack, 'MyDistribution', { behaviors: [{ isDefaultBehavior: true }], }, ], - geoRestriction: cloudfront.GeoRestriction.whitelist('US', 'UK'), + geoRestriction: cloudfront.GeoRestriction.allowlist('US', 'UK'), }); app.synth(); diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.ts index e44ffa5ba138c..4a8d60efd5219 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.ts @@ -1,4 +1,3 @@ - import * as cdk from '@aws-cdk/core'; import * as cloudfront from '../lib'; diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.expected.json index f02507fb5fa05..bc5f2284b77ca 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.expected.json @@ -12,7 +12,7 @@ }, "Region": "us-east-1", "ParameterName": "/cdk/EdgeFunctionArn/eu-west-1/integ-distribution-lambda-cross-region/Lambda", - "RefreshToken": "LambdaCurrentVersionDF706F6A25bf7d67df4eb614ea2e1ea69c8759b6" + "RefreshToken": "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" @@ -138,7 +138,7 @@ }, "Region": "us-east-1", "ParameterName": "/cdk/EdgeFunctionArn/eu-west-1/integ-distribution-lambda-cross-region/Lambda2", - "RefreshToken": "Lambda2CurrentVersion72012B74ae3cccfdad93f3cb5c0d547683bcfea9" + "RefreshToken": "Lambda2CurrentVersion72012B74da2ca4572056a1112d9804f75b5b7491" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" @@ -257,13 +257,13 @@ ] }, "Handler": "index.handler", - "Runtime": "nodejs10.x" + "Runtime": "nodejs14.x" }, "DependsOn": [ "LambdaServiceRoleA8ED4D3B" ] }, - "LambdaCurrentVersionDF706F6A25bf7d67df4eb614ea2e1ea69c8759b6": { + "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -276,7 +276,7 @@ "Properties": { "Type": "String", "Value": { - "Ref": "LambdaCurrentVersionDF706F6A25bf7d67df4eb614ea2e1ea69c8759b6" + "Ref": "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d" }, "Name": "/cdk/EdgeFunctionArn/eu-west-1/integ-distribution-lambda-cross-region/Lambda" } @@ -289,7 +289,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "LambdaCurrentVersionDF706F6A25bf7d67df4eb614ea2e1ea69c8759b6", + "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d", "Version" ] }, @@ -351,13 +351,13 @@ ] }, "Handler": "index.handler", - "Runtime": "nodejs10.x" + "Runtime": "nodejs14.x" }, "DependsOn": [ "Lambda2ServiceRole31A072E1" ] }, - "Lambda2CurrentVersion72012B74ae3cccfdad93f3cb5c0d547683bcfea9": { + "Lambda2CurrentVersion72012B74da2ca4572056a1112d9804f75b5b7491": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -370,7 +370,7 @@ "Properties": { "Type": "String", "Value": { - "Ref": "Lambda2CurrentVersion72012B74ae3cccfdad93f3cb5c0d547683bcfea9" + "Ref": "Lambda2CurrentVersion72012B74da2ca4572056a1112d9804f75b5b7491" }, "Name": "/cdk/EdgeFunctionArn/eu-west-1/integ-distribution-lambda-cross-region/Lambda2" } @@ -383,7 +383,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "Lambda2CurrentVersion72012B74ae3cccfdad93f3cb5c0d547683bcfea9", + "Lambda2CurrentVersion72012B74da2ca4572056a1112d9804f75b5b7491", "Version" ] }, diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts index b1aed3d79da21..64c27f6535594 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.ts @@ -12,13 +12,13 @@ const stack = new cdk.Stack(app, 'integ-distribution-lambda-cross-region', { env const lambdaFunction = new cloudfront.experimental.EdgeFunction(stack, 'Lambda', { code: lambda.Code.fromInline('foo'), handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, }); const lambdaFunction2 = new cloudfront.experimental.EdgeFunction(stack, 'Lambda2', { code: lambda.Code.fromInline('foo'), handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.NODEJS_14_X, stackId: `edge-lambda-stack-${region}-2`, }); diff --git a/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts index 600750bc4deca..d7612633cf93c 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts @@ -3,6 +3,7 @@ import { ABSENT } from '@aws-cdk/assert-internal'; import * as certificatemanager from '@aws-cdk/aws-certificatemanager'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import { CfnDistribution, @@ -1008,7 +1009,7 @@ added the ellipsis so a user would know there was more to ...`, }); - test('allows multiple aliasConfiguration CloudFrontWebDistribution per stack', () => { + testDeprecated('allows multiple aliasConfiguration CloudFrontWebDistribution per stack', () => { const stack = new cdk.Stack(); const s3BucketSource = new s3.Bucket(stack, 'Bucket'); @@ -1248,7 +1249,7 @@ added the ellipsis so a user would know there was more to ...`, }); }); describe('errors', () => { - test('throws if both deprecated aliasConfiguration and viewerCertificate', () => { + testDeprecated('throws if both deprecated aliasConfiguration and viewerCertificate', () => { const stack = new cdk.Stack(); const sourceBucket = new s3.Bucket(stack, 'Bucket'); diff --git a/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts b/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts index 89bd89d84c31c..a53a45b2c97ce 100644 --- a/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts +++ b/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts @@ -6,6 +6,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import { LogGroup, RetentionDays } from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as sns from '@aws-cdk/aws-sns'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import { ManagementEventSources, ReadWriteType, Trail } from '../lib'; @@ -192,16 +193,11 @@ describe('cloudtrail', () => { }); new Trail(stack, 'KmsKeyTrail', { trailName: 'KmsKeyTrail', - kmsKey: key, + encryptionKey: key, }); new Trail(stack, 'UnencryptedTrail', { trailName: 'UnencryptedTrail', }); - expect(() => new Trail(stack, 'ErrorTrail', { - trailName: 'ErrorTrail', - encryptionKey: key, - kmsKey: key, - })).toThrow(/Both kmsKey and encryptionKey must not be specified/); expect(stack).toHaveResource('AWS::CloudTrail::Trail', { TrailName: 'EncryptionKeyTrail', @@ -221,6 +217,17 @@ describe('cloudtrail', () => { }); }); + testDeprecated('Both kmsKey and encryptionKey must not be specified', () => { + const stack = new Stack(); + const key = new kms.Key(stack, 'key'); + + expect(() => new Trail(stack, 'ErrorTrail', { + trailName: 'ErrorTrail', + encryptionKey: key, + kmsKey: key, + })).toThrow(/Both kmsKey and encryptionKey must not be specified/); + }); + describe('with cloud watch logs', () => { test('enabled', () => { const stack = getTestStack(); diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts b/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts index d4dd9781f8370..1d6dd47793796 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts +++ b/packages/@aws-cdk/aws-cloudwatch-actions/test/ec2.test.ts @@ -10,7 +10,7 @@ test('can use instance reboot as alarm action', () => { metric: new cloudwatch.Metric({ namespace: 'AWS/EC2', metricName: 'StatusCheckFailed', - dimensions: { + dimensionsMap: { InstanceId: 'i-03cb889aaaafffeee', }, }), diff --git a/packages/@aws-cdk/aws-cloudwatch/README.md b/packages/@aws-cdk/aws-cloudwatch/README.md index 67b68ed46e741..37a31f636b3ad 100644 --- a/packages/@aws-cdk/aws-cloudwatch/README.md +++ b/packages/@aws-cdk/aws-cloudwatch/README.md @@ -206,6 +206,27 @@ const topic = new sns.Topic(this, 'Topic'); alarm.addAlarmAction(new cw_actions.SnsAction(topic)); ``` +#### Notification formats + +Alarms can be created in one of two "formats": + +- With "top-level parameters" (these are the classic style of CloudWatch Alarms). +- With a list of metrics specifications (these are the modern style of CloudWatch Alarms). + +For backwards compatibility, CDK will try to create classic, top-level CloudWatch alarms +as much as possible, unless you are using features that cannot be expressed in that format. +Features that require the new-style alarm format are: + +- Metric math +- Cross-account metrics +- Labels + +The difference between these two does not impact the functionality of the alarm +in any way, *except* that the format of the notifications the Alarm generates is +different between them. This affects both the notifications sent out over SNS, +as well as the EventBridge events generated by this Alarm. If you are writing +code to consume these notifications, be sure to handle both formats. + ### Composite Alarms [Composite Alarms](https://aws.amazon.com/about-aws/whats-new/2020/03/amazon-cloudwatch-now-allows-you-to-combine-multiple-alarms/) diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts index 9169e155bd696..62d717f33dc54 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts @@ -1,4 +1,4 @@ -import { Lazy, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, Lazy, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IAlarmAction } from './alarm-action'; import { AlarmBase, IAlarm } from './alarm-base'; @@ -114,7 +114,7 @@ export class Alarm extends AlarmBase { public static fromAlarmArn(scope: Construct, id: string, alarmArn: string): IAlarm { class Import extends AlarmBase implements IAlarm { public readonly alarmArn = alarmArn; - public readonly alarmName = Stack.of(scope).parseArn(alarmArn, ':').resourceName!; + public readonly alarmName = Stack.of(scope).splitArn(alarmArn, ArnFormat.COLON_RESOURCE_NAME).resourceName!; } return new Import(scope, id); } @@ -192,7 +192,7 @@ export class Alarm extends AlarmBase { service: 'cloudwatch', resource: 'alarm', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); this.alarmName = this.getResourceNameAttribute(alarm.ref); @@ -381,19 +381,10 @@ export class Alarm extends AlarmBase { return false; } - // if this is a region-agnostic stack, we can't assume anything about stat.account - // and therefore we assume its a cross-account call - if (Token.isUnresolved(stackAccount)) { - return true; - } - - // ok, we can compare the two concrete values directly - if they are the same we - // can omit the account ID from the metric. - if (stackAccount === stat.account) { - return false; - } - - return true; + // Return true if they're different. The ACCOUNT_ID token is interned + // so will always have the same string value (and even if we guess wrong + // it will still work). + return stackAccount !== stat.account; } } diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts index 8b9d61c99ea82..08f0db1b0880c 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts @@ -1,4 +1,4 @@ -import { Lazy, Names, Stack } from '@aws-cdk/core'; +import { ArnFormat, Lazy, Names, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { AlarmBase, IAlarm, IAlarmRule } from './alarm-base'; import { CfnCompositeAlarm } from './cloudwatch.generated'; @@ -68,7 +68,7 @@ export class CompositeAlarm extends AlarmBase { public static fromCompositeAlarmArn(scope: Construct, id: string, compositeAlarmArn: string): IAlarm { class Import extends AlarmBase implements IAlarm { public readonly alarmArn = compositeAlarmArn; - public readonly alarmName = Stack.of(scope).parseArn(compositeAlarmArn).resourceName!; + public readonly alarmName = Stack.of(scope).splitArn(compositeAlarmArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; } return new Import(scope, id); } @@ -115,7 +115,7 @@ export class CompositeAlarm extends AlarmBase { service: 'cloudwatch', resource: 'alarm', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts index d306978c93733..03d9e4fe5ca7d 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts @@ -288,8 +288,7 @@ export class Metric implements IMetric { } return new Metric({ - dimensions: ifUndefined(props.dimensions, this.dimensions), - dimensionsMap: props.dimensionsMap, + dimensionsMap: props.dimensionsMap ?? props.dimensions ?? this.dimensions, namespace: this.namespace, metricName: this.metricName, period: ifUndefined(props.period, this.period), diff --git a/packages/@aws-cdk/aws-cloudwatch/package.json b/packages/@aws-cdk/aws-cloudwatch/package.json index 5466e925c6663..b02f9e882b51a 100644 --- a/packages/@aws-cdk/aws-cloudwatch/package.json +++ b/packages/@aws-cdk/aws-cloudwatch/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-cloudwatch/test/alarm.test.ts b/packages/@aws-cdk/aws-cloudwatch/test/alarm.test.ts index dcde88284aadd..85e6460bb0e08 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/alarm.test.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/alarm.test.ts @@ -78,8 +78,7 @@ describe('Alarm', () => { // WHEN new Alarm(stack, 'Alarm', { - metric: testMetric, - period: Duration.minutes(10), + metric: testMetric.with({ period: Duration.minutes(10) }), threshold: 1000, evaluationPeriods: 3, }); @@ -102,8 +101,7 @@ describe('Alarm', () => { // WHEN new Alarm(stack, 'Alarm', { - metric: testMetric, - statistic: 'max', + metric: testMetric.with({ statistic: 'max' }), threshold: 1000, evaluationPeriods: 3, }); @@ -127,8 +125,7 @@ describe('Alarm', () => { // WHEN new Alarm(stack, 'Alarm', { - metric: testMetric, - statistic: 'P99', + metric: testMetric.with({ statistic: 'P99' }), threshold: 1000, evaluationPeriods: 3, }); @@ -199,11 +196,12 @@ describe('Alarm', () => { const stack = new Stack(); // WHEN - testMetric.createAlarm(stack, 'Alarm', { - threshold: 1000, - evaluationPeriods: 2, + testMetric.with({ statistic: 'min', period: Duration.seconds(10), + }).createAlarm(stack, 'Alarm', { + threshold: 1000, + evaluationPeriods: 2, }); // THEN @@ -223,10 +221,11 @@ describe('Alarm', () => { const stack = new Stack(); // WHEN - testMetric.createAlarm(stack, 'Alarm', { + testMetric.with({ + statistic: 'p99.9', + }).createAlarm(stack, 'Alarm', { threshold: 1000, evaluationPeriods: 2, - statistic: 'p99.9', }); // THEN @@ -240,10 +239,11 @@ describe('Alarm', () => { const stack = new Stack(); // WHEN - testMetric.createAlarm(stack, 'Alarm', { + testMetric.with({ + statistic: 'tm99.9999999999', + }).createAlarm(stack, 'Alarm', { threshold: 1000, evaluationPeriods: 2, - statistic: 'tm99.9999999999', }); // THEN diff --git a/packages/@aws-cdk/aws-cloudwatch/test/cross-environment.test.ts b/packages/@aws-cdk/aws-cloudwatch/test/cross-environment.test.ts index 61b006e9d8f89..00b9ef07b8aa8 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/cross-environment.test.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/cross-environment.test.ts @@ -1,5 +1,5 @@ import { Match, Template } from '@aws-cdk/assertions'; -import { Duration, Stack } from '@aws-cdk/core'; +import { Duration, Stack, Token, Aws } from '@aws-cdk/core'; import { Alarm, GraphWidget, IWidget, MathExpression, Metric } from '../lib'; const a = new Metric({ namespace: 'Test', metricName: 'ACount' }); @@ -468,11 +468,30 @@ describe('cross environment', () => { }); }); - test('metric account === stack account, but both are tokens', () => { + test('metric account === stack account, both are the AccountID token', () => { const metric = new Metric({ namespace: 'Test', metricName: 'ACount', - account: stack4.account, + account: Aws.ACCOUNT_ID, + }); + + new Alarm(stack4, 'Alarm', { + threshold: 1, + evaluationPeriods: 1, + metric, + }); + + // Alarm will be defined as legacy alarm + Template.fromStack(stack4).hasResourceProperties('AWS::CloudWatch::Alarm', { + MetricName: 'ACount', + }); + }); + + test('metric account and stack account are different tokens', () => { + const metric = new Metric({ + namespace: 'Test', + metricName: 'ACount', + account: Token.asString('asdf'), }); new Alarm(stack4, 'Alarm', { diff --git a/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.ts index 1ca941d8519b4..799dad893e227 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.ts @@ -16,13 +16,13 @@ const queue = new cdk.CfnResource(stack, 'queue', { type: 'AWS::SQS::Queue' }); const numberOfMessagesVisibleMetric = new cloudwatch.Metric({ namespace: 'AWS/SQS', metricName: 'ApproximateNumberOfMessagesVisible', - dimensions: { QueueName: queue.getAtt('QueueName') }, + dimensionsMap: { QueueName: queue.getAtt('QueueName').toString() }, }); const sentMessageSizeMetric = new cloudwatch.Metric({ namespace: 'AWS/SQS', metricName: 'SentMessageSize', - dimensions: { QueueName: queue.getAtt('QueueName') }, + dimensionsMap: { QueueName: queue.getAtt('QueueName').toString() }, }); const alarm = numberOfMessagesVisibleMetric.createAlarm(stack, 'Alarm', { diff --git a/packages/@aws-cdk/aws-cloudwatch/test/integ.math-alarm-and-dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/test/integ.math-alarm-and-dashboard.ts index 5a3285d873fe8..9f966feb03b61 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/integ.math-alarm-and-dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/integ.math-alarm-and-dashboard.ts @@ -16,7 +16,7 @@ const queue = new cdk.CfnResource(stack, 'queue', { type: 'AWS::SQS::Queue' }); const metricA = new cloudwatch.Metric({ namespace: 'AWS/SQS', metricName: 'ApproximateNumberOfMessagesVisible', - dimensions: { QueueName: queue.getAtt('QueueName') }, + dimensionsMap: { QueueName: queue.getAtt('QueueName').toString() }, period: cdk.Duration.seconds(10), label: 'Visible Messages', }); @@ -24,7 +24,7 @@ const metricA = new cloudwatch.Metric({ const metricB = new cloudwatch.Metric({ namespace: 'AWS/SQS', metricName: 'ApproximateNumberOfMessagesNotVisible', - dimensions: { QueueName: queue.getAtt('QueueName') }, + dimensionsMap: { QueueName: queue.getAtt('QueueName').toString() }, period: cdk.Duration.seconds(30), label: 'NotVisible Messages', }); diff --git a/packages/@aws-cdk/aws-cloudwatch/test/metrics.test.ts b/packages/@aws-cdk/aws-cloudwatch/test/metrics.test.ts index d13d3a8c4e0c6..49b9384df870d 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/metrics.test.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/metrics.test.ts @@ -1,5 +1,6 @@ import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import { Alarm, Metric } from '../lib'; @@ -51,7 +52,7 @@ describe('Metrics', () => { }); - test('cannot use null dimension value', () => { + testDeprecated('cannot use null dimension value', () => { expect(() => { new Metric({ namespace: 'Test', @@ -66,7 +67,7 @@ describe('Metrics', () => { }); - test('cannot use undefined dimension value', () => { + testDeprecated('cannot use undefined dimension value', () => { expect(() => { new Metric({ namespace: 'Test', @@ -81,7 +82,7 @@ describe('Metrics', () => { }); - test('cannot use long dimension values', () => { + testDeprecated('cannot use long dimension values', () => { const arr = new Array(256); const invalidDimensionValue = arr.fill('A', 0).join(''); @@ -117,7 +118,7 @@ describe('Metrics', () => { }); - test('throws error when there are more than 10 dimensions', () => { + testDeprecated('throws error when there are more than 10 dimensions', () => { expect(() => { new Metric({ namespace: 'Test', diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 29d06892bc682..4bccbbc68cfa1 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -243,7 +243,7 @@ new codebuild.Project(this, 'Project', { }), // Enable Docker AND custom caching - cache: codebuild.Cache.local(codebuild.LocalCacheMode.DOCKER_LAYER, codebuild.LocalCacheMode.CUSTOM) + cache: codebuild.Cache.local(codebuild.LocalCacheMode.DOCKER_LAYER, codebuild.LocalCacheMode.CUSTOM), // BuildSpec with a 'cache' section necessary for 'CUSTOM' caching. This can // also come from 'buildspec.yml' in your source. diff --git a/packages/@aws-cdk/aws-codebuild/lib/file-location.ts b/packages/@aws-cdk/aws-codebuild/lib/file-location.ts index eabfd95570b06..b836fc92bc22a 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/file-location.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/file-location.ts @@ -71,7 +71,8 @@ export interface EfsFileSystemLocationProps { /** * A string that specifies the location of the file system, like Amazon EFS. - * @example 'fs-abcd1234.efs.us-west-2.amazonaws.com:/my-efs-mount-directory'. + * + * This value looks like `fs-abcd1234.efs.us-west-2.amazonaws.com:/my-efs-mount-directory`. */ readonly location: string; diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 186c58f84138f..f7a503d335cbc 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -8,7 +8,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { Aws, Duration, IResource, Lazy, Names, PhysicalName, Reference, Resource, SecretValue, Stack, Token, TokenComparison, Tokenization } from '@aws-cdk/core'; +import { ArnFormat, Aws, Duration, IResource, Lazy, Names, PhysicalName, Reference, Resource, SecretValue, Stack, Token, TokenComparison, Tokenization } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IArtifacts } from './artifacts'; import { BuildSpec } from './build-spec'; @@ -412,7 +412,7 @@ abstract class ProjectBase extends Resource implements IProject { return new cloudwatch.Metric({ namespace: 'AWS/CodeBuild', metricName, - dimensions: { ProjectName: this.projectName }, + dimensionsMap: { ProjectName: this.projectName }, ...props, }).attachTo(this); } @@ -751,7 +751,7 @@ export interface BindToCodePipelineOptions { export class Project extends ProjectBase { public static fromProjectArn(scope: Construct, id: string, projectArn: string): IProject { - const parsedArn = Stack.of(scope).parseArn(projectArn); + const parsedArn = Stack.of(scope).splitArn(projectArn, ArnFormat.SLASH_RESOURCE_NAME); class Import extends ProjectBase { public readonly grantPrincipal: iam.IPrincipal; @@ -874,7 +874,7 @@ export class Project extends ProjectBase { // 2. A Token. // 3. A simple value, like 'secret-id'. if (envVariableValue.startsWith('arn:')) { - const parsedArn = stack.parseArn(envVariableValue, ':'); + const parsedArn = stack.splitArn(envVariableValue, ArnFormat.COLON_RESOURCE_NAME); if (!parsedArn.resourceName) { throw new Error('SecretManager ARN is missing the name of the secret: ' + envVariableValue); } @@ -889,7 +889,7 @@ export class Project extends ProjectBase { // (CodeBuild supports both), // stick a "*" at the end, which makes it work for both resourceName: `${secretName}*`, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, partition: parsedArn.partition, account: parsedArn.account, region: parsedArn.region, @@ -903,7 +903,7 @@ export class Project extends ProjectBase { // We do not know the ID of the key, but since this is a cross-account access, // the key policies have to allow this access, so a wildcard is safe here resourceName: '*', - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, partition: parsedArn.partition, account: parsedArn.account, region: parsedArn.region, @@ -931,7 +931,7 @@ export class Project extends ProjectBase { // We do not know the ID of the key, but since this is a cross-account access, // the key policies have to allow this access, so a wildcard is safe here resourceName: '*', - sep: '/', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, partition: resourceStack.partition, account: resourceStack.account, region: resourceStack.region, @@ -957,7 +957,7 @@ export class Project extends ProjectBase { service: 'secretsmanager', resource: 'secret', resourceName: `${secretName}-??????`, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, })); } } @@ -1257,7 +1257,7 @@ export class Project extends ProjectBase { const logGroupArn = Stack.of(this).formatArn({ service: 'logs', resource: 'log-group', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, resourceName: `/aws/codebuild/${this.projectName}`, }); diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index 50ac7d24ef78a..3d1609d9665b4 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -88,6 +95,7 @@ "jest": "^27.3.1" }, "dependencies": { + "@aws-cdk/assets": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", "@aws-cdk/aws-codestarnotifications": "0.0.0", diff --git a/packages/@aws-cdk/aws-codecommit/lib/repository.ts b/packages/@aws-cdk/aws-codecommit/lib/repository.ts index f9cd4c318cb19..69781b04f793c 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/repository.ts +++ b/packages/@aws-cdk/aws-codecommit/lib/repository.ts @@ -1,7 +1,7 @@ import * as notifications from '@aws-cdk/aws-codestarnotifications'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; -import { IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnRepository } from './codecommit.generated'; @@ -501,7 +501,7 @@ export class Repository extends RepositoryBase { */ public static fromRepositoryArn(scope: Construct, id: string, repositoryArn: string): IRepository { const stack = Stack.of(scope); - const arn = stack.parseArn(repositoryArn); + const arn = stack.splitArn(repositoryArn, ArnFormat.NO_RESOURCE_NAME); const repositoryName = arn.resource; const region = arn.region; diff --git a/packages/@aws-cdk/aws-codedeploy/README.md b/packages/@aws-cdk/aws-codedeploy/README.md index e8f878973ee6a..9b6aa5cda5d8d 100644 --- a/packages/@aws-cdk/aws-codedeploy/README.md +++ b/packages/@aws-cdk/aws-codedeploy/README.md @@ -22,10 +22,8 @@ The CDK currently supports Amazon EC2, on-premise and AWS Lambda applications. To create a new CodeDeploy Application that deploys to EC2/on-premise instances: ```ts -import * as codedeploy from '@aws-cdk/aws-codedeploy'; - const application = new codedeploy.ServerApplication(this, 'CodeDeployApplication', { - applicationName: 'MyApplication', // optional property + applicationName: 'MyApplication', // optional property }); ``` @@ -33,7 +31,9 @@ To import an already existing Application: ```ts const application = codedeploy.ServerApplication.fromServerApplicationName( - this, 'ExistingCodeDeployApplication', 'MyExistingApplication' + this, + 'ExistingCodeDeployApplication', + 'MyExistingApplication', ); ``` @@ -42,47 +42,51 @@ const application = codedeploy.ServerApplication.fromServerApplicationName( To create a new CodeDeploy Deployment Group that deploys to EC2/on-premise instances: ```ts +import * as autoscaling from '@aws-cdk/aws-autoscaling'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; + +declare const application: codedeploy.ServerApplication; +declare const asg: autoscaling.AutoScalingGroup; +declare const alarm: cloudwatch.Alarm; const deploymentGroup = new codedeploy.ServerDeploymentGroup(this, 'CodeDeployDeploymentGroup', { - application, - deploymentGroupName: 'MyDeploymentGroup', - autoScalingGroups: [asg1, asg2], - // adds User Data that installs the CodeDeploy agent on your auto-scaling groups hosts - // default: true - installAgent: true, - // adds EC2 instances matching tags - ec2InstanceTags: new codedeploy.InstanceTagSet( - { - // any instance with tags satisfying - // key1=v1 or key1=v2 or key2 (any value) or value v3 (any key) - // will match this group - 'key1': ['v1', 'v2'], - 'key2': [], - '': ['v3'], - }, - ), - // adds on-premise instances matching tags - onPremiseInstanceTags: new codedeploy.InstanceTagSet( - // only instances with tags (key1=v1 or key1=v2) AND key2=v3 will match this set - { - 'key1': ['v1', 'v2'], - }, - { - 'key2': ['v3'], - }, - ), - // CloudWatch alarms - alarms: [ - new cloudwatch.Alarm(/* ... */), - ], - // whether to ignore failure to fetch the status of alarms from CloudWatch - // default: false - ignorePollAlarmsFailure: false, - // auto-rollback configuration - autoRollback: { - failedDeployment: true, // default: true - stoppedDeployment: true, // default: false - deploymentInAlarm: true, // default: true if you provided any alarms, false otherwise + application, + deploymentGroupName: 'MyDeploymentGroup', + autoScalingGroups: [asg], + // adds User Data that installs the CodeDeploy agent on your auto-scaling groups hosts + // default: true + installAgent: true, + // adds EC2 instances matching tags + ec2InstanceTags: new codedeploy.InstanceTagSet( + { + // any instance with tags satisfying + // key1=v1 or key1=v2 or key2 (any value) or value v3 (any key) + // will match this group + 'key1': ['v1', 'v2'], + 'key2': [], + '': ['v3'], + }, + ), + // adds on-premise instances matching tags + onPremiseInstanceTags: new codedeploy.InstanceTagSet( + // only instances with tags (key1=v1 or key1=v2) AND key2=v3 will match this set + { + 'key1': ['v1', 'v2'], + }, + { + 'key2': ['v3'], }, + ), + // CloudWatch alarms + alarms: [alarm], + // whether to ignore failure to fetch the status of alarms from CloudWatch + // default: false + ignorePollAlarmsFailure: false, + // auto-rollback configuration + autoRollback: { + failedDeployment: true, // default: true + stoppedDeployment: true, // default: false + deploymentInAlarm: true, // default: true if you provided any alarms, false otherwise + }, }); ``` @@ -92,10 +96,14 @@ one will be automatically created. To import an already existing Deployment Group: ```ts -const deploymentGroup = codedeploy.ServerDeploymentGroup.fromLambdaDeploymentGroupAttributes(this, 'ExistingCodeDeployDeploymentGroup', { +declare const application: codedeploy.ServerApplication; +const deploymentGroup = codedeploy.ServerDeploymentGroup.fromServerDeploymentGroupAttributes( + this, + 'ExistingCodeDeployDeploymentGroup', { application, deploymentGroupName: 'MyExistingDeploymentGroup', -}); + }, +); ``` ### Load balancers @@ -108,18 +116,15 @@ with the `loadBalancer` property when creating a Deployment Group. With Classic Elastic Load Balancer, you provide it directly: ```ts -import * as lb from '@aws-cdk/aws-elasticloadbalancing'; +import * as elb from '@aws-cdk/aws-elasticloadbalancing'; -const elb = new lb.LoadBalancer(this, 'ELB', { - // ... -}); -elb.addTarget(/* ... */); -elb.addListener({ - // ... +declare const lb: elb.LoadBalancer; +lb.addListener({ + externalPort: 80, }); const deploymentGroup = new codedeploy.ServerDeploymentGroup(this, 'DeploymentGroup', { - loadBalancer: codedeploy.LoadBalancer.classic(elb), + loadBalancer: codedeploy.LoadBalancer.classic(lb), }); ``` @@ -127,17 +132,11 @@ With Application Load Balancer or Network Load Balancer, you provide a Target Group as the load balancer: ```ts -import * as lbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; -const alb = new lbv2.ApplicationLoadBalancer(this, 'ALB', { - // ... -}); -const listener = alb.addListener('Listener', { - // ... -}); -const targetGroup = listener.addTargets('Fleet', { - // ... -}); +declare const alb: elbv2.ApplicationLoadBalancer; +const listener = alb.addListener('Listener', { port: 80 }); +const targetGroup = listener.addTargets('Fleet', { port: 80 }); const deploymentGroup = new codedeploy.ServerDeploymentGroup(this, 'DeploymentGroup', { loadBalancer: codedeploy.LoadBalancer.application(targetGroup), @@ -150,7 +149,7 @@ You can also pass a Deployment Configuration when creating the Deployment Group: ```ts const deploymentGroup = new codedeploy.ServerDeploymentGroup(this, 'CodeDeployDeploymentGroup', { - deploymentConfig: codedeploy.ServerDeploymentConfig.ALL_AT_ONCE, + deploymentConfig: codedeploy.ServerDeploymentConfig.ALL_AT_ONCE, }); ``` @@ -160,10 +159,10 @@ You can also create a custom Deployment Configuration: ```ts const deploymentConfig = new codedeploy.ServerDeploymentConfig(this, 'DeploymentConfiguration', { - deploymentConfigName: 'MyDeploymentConfiguration', // optional property - // one of these is required, but both cannot be specified at the same time - minHealthyHostCount: 2, - minHealthyHostPercentage: 75, + deploymentConfigName: 'MyDeploymentConfiguration', // optional property + // one of these is required, but both cannot be specified at the same time + minimumHealthyHosts: codedeploy.MinimumHealthyHosts.count(2), + // minimumHealthyHosts: codedeploy.MinimumHealthyHosts.percentage(75), }); ``` @@ -171,7 +170,9 @@ Or import an existing one: ```ts const deploymentConfig = codedeploy.ServerDeploymentConfig.fromServerDeploymentConfigName( - this, 'ExistingDeploymentConfiguration', 'MyExistingDeploymentConfiguration' + this, + 'ExistingDeploymentConfiguration', + 'MyExistingDeploymentConfiguration', ); ``` @@ -180,10 +181,8 @@ const deploymentConfig = codedeploy.ServerDeploymentConfig.fromServerDeploymentC To create a new CodeDeploy Application that deploys to a Lambda function: ```ts -import * as codedeploy from '@aws-cdk/aws-codedeploy'; - const application = new codedeploy.LambdaApplication(this, 'CodeDeployApplication', { - applicationName: 'MyApplication', // optional property + applicationName: 'MyApplication', // optional property }); ``` @@ -191,7 +190,9 @@ To import an already existing Application: ```ts const application = codedeploy.LambdaApplication.fromLambdaApplicationName( - this, 'ExistingCodeDeployApplication', 'MyExistingApplication' + this, + 'ExistingCodeDeployApplication', + 'MyExistingApplication', ); ``` @@ -204,18 +205,15 @@ When you publish a new version of the function to your stack, CodeDeploy will se To create a new CodeDeploy Deployment Group that deploys to a Lambda function: ```ts -import * as codedeploy from '@aws-cdk/aws-codedeploy'; -import * as lambda from '@aws-cdk/aws-lambda'; - -const myApplication = new codedeploy.LambdaApplication(..); -const func = new lambda.Function(..); +declare const myApplication: codedeploy.LambdaApplication; +declare const func: lambda.Function; const version = func.addVersion('1'); const version1Alias = new lambda.Alias(this, 'alias', { aliasName: 'prod', - version + version, }); -const deploymentGroup = new codedeploy.LambdaDeploymentGroup(stack, 'BlueGreenDeployment', { +const deploymentGroup = new codedeploy.LambdaDeploymentGroup(this, 'BlueGreenDeployment', { application: myApplication, // optional property: one will be created for you if not provided alias: version1Alias, deploymentConfig: codedeploy.LambdaDeploymentConfig.LINEAR_10PERCENT_EVERY_1MINUTE, @@ -237,12 +235,15 @@ you can do so with the CustomLambdaDeploymentConfig construct, letting you specify precisely how fast a new function version is deployed. ```ts -const config = new codedeploy.CustomLambdaDeploymentConfig(stack, 'CustomConfig', { +const config = new codedeploy.CustomLambdaDeploymentConfig(this, 'CustomConfig', { type: codedeploy.CustomLambdaDeploymentConfigType.CANARY, interval: Duration.minutes(1), percentage: 5, }); -const deploymentGroup = new codedeploy.LambdaDeploymentGroup(stack, 'BlueGreenDeployment', { + +declare const application: codedeploy.LambdaApplication; +declare const alias: lambda.Alias; +const deploymentGroup = new codedeploy.LambdaDeploymentGroup(this, 'BlueGreenDeployment', { application, alias, deploymentConfig: config, @@ -252,7 +253,7 @@ const deploymentGroup = new codedeploy.LambdaDeploymentGroup(stack, 'BlueGreenDe You can specify a custom name for your deployment config, but if you do you will not be able to update the interval/percentage through CDK. ```ts -const config = new codedeploy.CustomLambdaDeploymentConfig(stack, 'CustomConfig', { +const config = new codedeploy.CustomLambdaDeploymentConfig(this, 'CustomConfig', { type: codedeploy.CustomLambdaDeploymentConfigType.CANARY, interval: Duration.minutes(1), percentage: 5, @@ -265,26 +266,31 @@ const config = new codedeploy.CustomLambdaDeploymentConfig(stack, 'CustomConfig' CodeDeploy will roll back if the deployment fails. You can optionally trigger a rollback when one or more alarms are in a failed state: ```ts -const deploymentGroup = new codedeploy.LambdaDeploymentGroup(stack, 'BlueGreenDeployment', { +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; + +declare const alias: lambda.Alias; +const alarm = new cloudwatch.Alarm(this, 'Errors', { + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, + threshold: 1, + evaluationPeriods: 1, + metric: alias.metricErrors(), +}); +const deploymentGroup = new codedeploy.LambdaDeploymentGroup(this, 'BlueGreenDeployment', { alias, deploymentConfig: codedeploy.LambdaDeploymentConfig.LINEAR_10PERCENT_EVERY_1MINUTE, alarms: [ // pass some alarms when constructing the deployment group - new cloudwatch.Alarm(stack, 'Errors', { - comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, - threshold: 1, - evaluationPeriods: 1, - metric: alias.metricErrors() - }) - ] + alarm, + ], }); // or add alarms to an existing group -deploymentGroup.addAlarm(new cloudwatch.Alarm(stack, 'BlueGreenErrors', { +declare const blueGreenAlias: lambda.Alias; +deploymentGroup.addAlarm(new cloudwatch.Alarm(this, 'BlueGreenErrors', { comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, threshold: 1, evaluationPeriods: 1, - metric: blueGreenAlias.metricErrors() + metric: blueGreenAlias.metricErrors(), })); ``` @@ -295,18 +301,19 @@ With either hook, you have the opportunity to run logic that determines whether For example, with PreTraffic hook you could run integration tests against the newly created Lambda version (but not serving traffic). With PostTraffic hook, you could run end-to-end validation checks. ```ts -const warmUpUserCache = new lambda.Function(..); -const endToEndValidation = new lambda.Function(..); +declare const warmUpUserCache: lambda.Function; +declare const endToEndValidation: lambda.Function; +declare const alias: lambda.Alias; // pass a hook whe creating the deployment group -const deploymentGroup = new codedeploy.LambdaDeploymentGroup(stack, 'BlueGreenDeployment', { +const deploymentGroup = new codedeploy.LambdaDeploymentGroup(this, 'BlueGreenDeployment', { alias: alias, deploymentConfig: codedeploy.LambdaDeploymentConfig.LINEAR_10PERCENT_EVERY_1MINUTE, preHook: warmUpUserCache, }); // or configure one on an existing deployment group -deploymentGroup.onPostHook(endToEndValidation); +deploymentGroup.addPostHook(endToEndValidation); ``` ### Import an existing Deployment Group @@ -314,8 +321,9 @@ deploymentGroup.onPostHook(endToEndValidation); To import an already existing Deployment Group: ```ts -const deploymentGroup = codedeploy.LambdaDeploymentGroup.import(this, 'ExistingCodeDeployDeploymentGroup', { - application, - deploymentGroupName: 'MyExistingDeploymentGroup', +declare const application: codedeploy.LambdaApplication; +const deploymentGroup = codedeploy.LambdaDeploymentGroup.fromLambdaDeploymentGroupAttributes(this, 'ExistingCodeDeployDeploymentGroup', { + application, + deploymentGroupName: 'MyExistingDeploymentGroup', }); ``` diff --git a/packages/@aws-cdk/aws-codedeploy/lib/ecs/application.ts b/packages/@aws-cdk/aws-codedeploy/lib/ecs/application.ts index e8a06374523b3..dc136abb87ee4 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/ecs/application.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/ecs/application.ts @@ -1,4 +1,4 @@ -import { IResource, Resource } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApplication } from '../codedeploy.generated'; import { arnForApplication } from '../utils'; @@ -74,7 +74,7 @@ export class EcsApplication extends Resource implements IEcsApplication { service: 'codedeploy', resource: 'application', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts index 309bd02ba3647..03449cf00b229 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts @@ -1,4 +1,4 @@ -import { IResource, Resource } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApplication } from '../codedeploy.generated'; import { arnForApplication } from '../utils'; @@ -74,7 +74,7 @@ export class LambdaApplication extends Resource implements ILambdaApplication { service: 'codedeploy', resource: 'application', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts index 0d6c6a347886a..2449ff87f31fd 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts @@ -179,7 +179,7 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy service: 'codedeploy', resource: 'deploymentgroup', resourceName: `${this.application.applicationName}/${this.physicalName}`, - sep: ':', + arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME, }); if (props.preHook) { diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts index 6de47a5fff381..b6f7324ef5985 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts @@ -1,4 +1,4 @@ -import { IResource, Resource } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApplication } from '../codedeploy.generated'; import { arnForApplication } from '../utils'; @@ -75,7 +75,7 @@ export class ServerApplication extends Resource implements IServerApplication { service: 'codedeploy', resource: 'application', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts index 4e1a436ef9b77..f4f3cad0774cc 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts @@ -4,6 +4,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; +import { ArnFormat } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnDeploymentGroup } from '../codedeploy.generated'; import { AutoRollbackConfig } from '../rollback-config'; @@ -311,7 +312,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { service: 'codedeploy', resource: 'deploymentgroup', resourceName: `${this.application.applicationName}/${this.physicalName}`, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } diff --git a/packages/@aws-cdk/aws-codedeploy/package.json b/packages/@aws-cdk/aws-codedeploy/package.json index f79f5dc10a70d..52636610a2453 100644 --- a/packages/@aws-cdk/aws-codedeploy/package.json +++ b/packages/@aws-cdk/aws-codedeploy/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/monocdk/rosetta/with-channel.ts-fixture b/packages/@aws-cdk/aws-codedeploy/rosetta/default.ts-fixture similarity index 55% rename from packages/monocdk/rosetta/with-channel.ts-fixture rename to packages/@aws-cdk/aws-codedeploy/rosetta/default.ts-fixture index 44da118b81afa..4baee63065a45 100644 --- a/packages/monocdk/rosetta/with-channel.ts-fixture +++ b/packages/@aws-cdk/aws-codedeploy/rosetta/default.ts-fixture @@ -1,15 +1,13 @@ // Fixture with packages imported, but nothing else -import { Duration, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import * as ivs from '@aws-cdk/aws-ivs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as codedeploy from '@aws-cdk/aws-codedeploy'; +import * as lambda from '@aws-cdk/aws-lambda'; class Fixture extends Stack { constructor(scope: Construct, id: string) { super(scope, id); - const myChannelArn = 'arn:aws:ivs:us-west-2:123456789012:channel/abcdABCDefgh'; - const myChannel = ivs.Channel.fromChannelArn(this, 'Channel', myChannelArn); - /// here } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts b/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts index 0537b469c17d5..0c878278b31bf 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts +++ b/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts @@ -1,5 +1,5 @@ import { Grant, IGrantable } from '@aws-cdk/aws-iam'; -import { IResource, Lazy, Names, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Lazy, Names, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnProfilingGroup } from './codeguruprofiler.generated'; @@ -154,7 +154,7 @@ export class ProfilingGroup extends ProfilingGroupBase { */ public static fromProfilingGroupArn(scope: Construct, id: string, profilingGroupArn: string): IProfilingGroup { class Import extends ProfilingGroupBase { - public readonly profilingGroupName = Stack.of(scope).parseArn(profilingGroupArn).resource; + public readonly profilingGroupName = Stack.of(scope).splitArn(profilingGroupArn, ArnFormat.SLASH_RESOURCE_NAME).resource; public readonly profilingGroupArn = profilingGroupArn; } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/README.md b/packages/@aws-cdk/aws-codepipeline-actions/README.md index 05860cfe20233..7625ed08bfb8e 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/README.md +++ b/packages/@aws-cdk/aws-codepipeline-actions/README.md @@ -987,7 +987,7 @@ The Lambda Action supports custom user parameters that pipeline will pass to the Lambda function: ```ts -import * as lambda from '@aws-cdk/aws-lambda'; +declare const fn: lambda.Function; const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); const lambdaAction = new codepipeline_actions.LambdaInvokeAction({ diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts index bf6c34ab505a8..2181f198ca012 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/stepfunctions/invoke-action.ts @@ -128,19 +128,19 @@ export class StepFunctionInvokeAction extends Action { protected bound(_scope: Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): codepipeline.ActionConfig { // allow pipeline to invoke this step function - options.role.addToPolicy(new iam.PolicyStatement({ + options.role.addToPrincipalPolicy(new iam.PolicyStatement({ actions: ['states:StartExecution', 'states:DescribeStateMachine'], resources: [this.props.stateMachine.stateMachineArn], })); // allow state machine executions to be inspected - options.role.addToPolicy(new iam.PolicyStatement({ + options.role.addToPrincipalPolicy(new iam.PolicyStatement({ actions: ['states:DescribeExecution'], resources: [cdk.Stack.of(this.props.stateMachine).formatArn({ service: 'states', resource: 'execution', - resourceName: `${cdk.Stack.of(this.props.stateMachine).parseArn(this.props.stateMachine.stateMachineArn, ':').resourceName}:${this.props.executionNamePrefix ?? ''}*`, - sep: ':', + resourceName: `${cdk.Stack.of(this.props.stateMachine).splitArn(this.props.stateMachine.stateMachineArn, cdk.ArnFormat.COLON_RESOURCE_NAME).resourceName}:${this.props.executionNamePrefix ?? ''}*`, + arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME, })], })); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package.json b/packages/@aws-cdk/aws-codepipeline-actions/package.json index f1624f676dc9c..aa3c6a125a362 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -76,7 +83,7 @@ "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.0.2", - "@types/lodash": "^4.14.176", + "@types/lodash": "^4.14.177", "jest": "^27.3.1", "lodash": "^4.17.21" }, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/bitbucket-source-action.test.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/bitbucket-source-action.test.ts index a4767fa1a61bc..4e3fd6045a251 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/bitbucket-source-action.test.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/bitbucket/bitbucket-source-action.test.ts @@ -2,12 +2,13 @@ import '@aws-cdk/assert-internal/jest'; import { arrayWith, objectLike } from '@aws-cdk/assert-internal'; import * as codebuild from '@aws-cdk/aws-codebuild'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import * as cpactions from '../../lib'; /* eslint-disable quote-props */ -describe('BitBucket source Action', () => { +describeDeprecated('BitBucket source Action', () => { describe('BitBucket source Action', () => { test('produces the correct configuration when added to a pipeline', () => { const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-codepipeline/README.md b/packages/@aws-cdk/aws-codepipeline/README.md index 976c9932ca210..b9e5e3954980e 100644 --- a/packages/@aws-cdk/aws-codepipeline/README.md +++ b/packages/@aws-cdk/aws-codepipeline/README.md @@ -16,14 +16,14 @@ To construct an empty Pipeline: ```ts -import * as codepipeline from '@aws-cdk/aws-codepipeline'; - +// Construct an empty Pipeline const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline'); ``` To give the Pipeline a nice, human-readable name: ```ts +// Give the Pipeline a nice, human-readable name const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { pipelineName: 'MyPipeline', }); @@ -40,6 +40,7 @@ the creation of the Customer Master Keys by passing `crossAccountKeys: false` when defining the Pipeline: ```ts +// Don't create Customer Master Keys const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { crossAccountKeys: false, }); @@ -50,6 +51,7 @@ you can configure it by passing `enableKeyRotation: true` when creating the pipe Note that key rotation will incur an additional cost of **$1/month**. ```ts +// Enable key rotation for the generated KMS key const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { // ... enableKeyRotation: true, @@ -61,6 +63,7 @@ const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { You can provide Stages when creating the Pipeline: ```ts +// Provide a Stage when creating a pipeline const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { stages: [ { @@ -76,6 +79,8 @@ const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { Or append a Stage to an existing Pipeline: ```ts +// Append a Stage to an existing Pipeline +declare const pipeline: codepipeline.Pipeline; const sourceStage = pipeline.addStage({ stageName: 'Source', actions: [ // optional property @@ -87,12 +92,17 @@ const sourceStage = pipeline.addStage({ You can insert the new Stage at an arbitrary point in the Pipeline: ```ts +// Insert a new Stage at an arbitrary point +declare const pipeline: codepipeline.Pipeline; +declare const anotherStage: codepipeline.IStage; +declare const yetAnotherStage: codepipeline.IStage; + const someStage = pipeline.addStage({ stageName: 'SomeStage', placement: { // note: you can only specify one of the below properties rightBefore: anotherStage, - justAfter: anotherStage + justAfter: yetAnotherStage, } }); ``` @@ -106,6 +116,9 @@ in the `actions` property, or you can use the `IStage.addAction()` method to mutate an existing Stage: ```ts +// Use the `IStage.addAction()` method to mutate an existing Stage. +declare const sourceStage: codepipeline.IStage; +declare const someAction: codepipeline.Action; sourceStage.addAction(someAction); ``` @@ -114,6 +127,7 @@ sourceStage.addAction(someAction); To make your own custom CodePipeline Action requires registering the action provider. Look to the `JenkinsProvider` in `@aws-cdk/aws-codepipeline-actions` for an implementation example. ```ts +// Make a custom CodePipeline Action new codepipeline.CustomActionRegistration(this, 'GenericGitSourceProviderResource', { category: codepipeline.ActionCategory.SOURCE, artifactBounds: { minInputs: 0, maxInputs: 0, minOutputs: 1, maxOutputs: 1 }, @@ -140,7 +154,8 @@ new codepipeline.CustomActionRegistration(this, 'GenericGitSourceProviderResourc description: 'SSH git clone URL', type: 'String', }, - ] + ], +}); ``` ## Cross-account CodePipelines @@ -163,11 +178,16 @@ example, the following action deploys to an imported S3 bucket from a different account: ```ts +// Deploy an imported S3 bucket from a different account +declare const stage: codepipeline.IStage; +declare const input: codepipeline.Artifact; stage.addAction(new codepipeline_actions.S3DeployAction({ bucket: s3.Bucket.fromBucketAttributes(this, 'Bucket', { account: '123456789012', // ... }), + input: input, + actionName: 's3-deploy-action', // ... })); ``` @@ -175,8 +195,15 @@ stage.addAction(new codepipeline_actions.S3DeployAction({ Actions that don't accept a resource object accept an explicit `account` parameter: ```ts +// Actions that don't accept a resource objet accept an explicit `account` parameter +declare const stage: codepipeline.IStage; +declare const templatePath: codepipeline.ArtifactPath; stage.addAction(new codepipeline_actions.CloudFormationCreateUpdateStackAction({ account: '123456789012', + templatePath, + adminPermissions: false, + stackName: Stack.of(this).stackName, + actionName: 'cloudformation-create-update', // ... })); ``` @@ -192,7 +219,14 @@ If you do not want to use the generated role, you can also explicitly pass a account the role belongs to: ```ts +// Explicitly pass in a `role` when creating an action. +declare const stage: codepipeline.IStage; +declare const templatePath: codepipeline.ArtifactPath; stage.addAction(new codepipeline_actions.CloudFormationCreateUpdateStackAction({ + templatePath, + adminPermissions: false, + stackName: Stack.of(this).stackName, + actionName: 'cloudformation-create-update', // ... role: iam.Role.fromRoleArn(this, 'ActionRole', '...'), })); @@ -205,11 +239,16 @@ pass to actions can also be in different *Regions*. For example, the following Action deploys to an imported S3 bucket from a different Region: ```ts +// Deploy to an imported S3 bucket from a different Region. +declare const stage: codepipeline.IStage; +declare const input: codepipeline.Artifact; stage.addAction(new codepipeline_actions.S3DeployAction({ bucket: s3.Bucket.fromBucketAttributes(this, 'Bucket', { region: 'us-west-1', // ... }), + input: input, + actionName: 's3-deploy-action', // ... })); ``` @@ -218,7 +257,14 @@ Actions that don't take an AWS resource will accept an explicit `region` parameter: ```ts +// Actions that don't take an AWS resource will accept an explicit `region` parameter. +declare const stage: codepipeline.IStage; +declare const templatePath: codepipeline.ArtifactPath; stage.addAction(new codepipeline_actions.CloudFormationCreateUpdateStackAction({ + templatePath, + adminPermissions: false, + stackName: Stack.of(this).stackName, + actionName: 'cloudformation-create-update', // ... region: 'us-west-1', })); @@ -235,6 +281,7 @@ place to serve as replication buckets, you can supply these at Pipeline definiti time using the `crossRegionReplicationBuckets` parameter. Example: ```ts +// Supply replication buckets for the Pipeline instead of using the generated support stack const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { // ... @@ -260,6 +307,8 @@ If you're passing a replication bucket created in a different stack, like this: ```ts +// Passing a replication bucket created in a different stack. +const app = new App(); const replicationStack = new Stack(app, 'ReplicationStack', { env: { region: 'us-west-1', @@ -273,7 +322,7 @@ const replicationBucket = new s3.Bucket(replicationStack, 'ReplicationBucket', { }); // later... -new codepipeline.Pipeline(pipelineStack, 'Pipeline', { +new codepipeline.Pipeline(replicationStack, 'Pipeline', { crossRegionReplicationBuckets: { 'us-west-1': replicationBucket, }, @@ -289,6 +338,13 @@ and so you can't reference them across environments. In this case, you need to use an alias in place of the key when creating the bucket: ```ts +// Passing an encrypted replication bucket created in a different stack. +const app = new App(); +const replicationStack = new Stack(app, 'ReplicationStack', { + env: { + region: 'us-west-1', + }, +}); const key = new kms.Key(replicationStack, 'ReplicationKey'); const alias = new kms.Alias(replicationStack, 'ReplicationAlias', { // aliasName is required @@ -312,14 +368,16 @@ you access the appropriate property of the interface returned from `variables`, which represents a single variable. Example: -```ts -// MyAction is some action type that produces variables +```ts fixture=action +// MyAction is some action type that produces variables, like EcrSourceAction const myAction = new MyAction({ // ... + actionName: 'myAction', }); new OtherAction({ // ... config: myAction.variables.myVariable, + actionName: 'otherAction', }); ``` @@ -327,10 +385,12 @@ The namespace name that will be used will be automatically generated by the pipe based on the stage and action name; you can pass a custom name when creating the action instance: -```ts +```ts fixture=action +// MyAction is some action type that produces variables, like EcrSourceAction const myAction = new MyAction({ // ... variablesNamespace: 'MyNamespace', + actionName: 'myAction', }); ``` @@ -338,10 +398,12 @@ There are also global variables available, not tied to any action; these are accessed through static properties of the `GlobalVariables` class: -```ts +```ts fixture=action +// OtherAction is some action type that produces variables, like EcrSourceAction new OtherAction({ // ... config: codepipeline.GlobalVariables.executionId, + actionName: 'otherAction', }); ``` @@ -358,6 +420,7 @@ for more details on how to use the variables feature. A pipeline can be used as a target for a CloudWatch event rule: ```ts +// A pipeline being used as a target for a CloudWatch event rule. import * as targets from '@aws-cdk/aws-events-targets'; import * as events from '@aws-cdk/aws-events'; @@ -366,6 +429,7 @@ const rule = new events.Rule(this, 'Daily', { schedule: events.Schedule.rate(Duration.days(1)), }); +declare const pipeline: codepipeline.Pipeline; rule.addTarget(new targets.CodePipeline(pipeline)); ``` @@ -380,7 +444,14 @@ the pipeline, stages or action, use the `onXxx` methods on the respective construct: ```ts -myPipeline.onStateChange('MyPipelineStateChange', target); +// Define event rules for events emitted by the pipeline +import * as events from '@aws-cdk/aws-events'; + +declare const myPipeline: codepipeline.Pipeline; +declare const myStage: codepipeline.IStage; +declare const myAction: codepipeline.Action; +declare const target: events.IRuleTarget; +myPipeline.onStateChange('MyPipelineStateChange', { target: target } ); myStage.onStateChange('MyStageStateChange', target); myAction.onStateChange('MyActionStateChange', target); ``` @@ -391,11 +462,14 @@ To define CodeStar Notification rules for Pipelines, use one of the `notifyOnXxx They are very similar to `onXxx()` methods for CloudWatch events: ```ts -const target = new chatbot.SlackChannelConfiguration(stack, 'MySlackChannel', { +// Define CodeStar Notification rules for Pipelines +import * as chatbot from '@aws-cdk/aws-chatbot'; +const target = new chatbot.SlackChannelConfiguration(this, 'MySlackChannel', { slackChannelConfigurationName: 'YOUR_CHANNEL_NAME', slackWorkspaceId: 'YOUR_SLACK_WORKSPACE_ID', slackChannelId: 'YOUR_SLACK_CHANNEL_ID', }); +declare const pipeline: codepipeline.Pipeline; const rule = pipeline.notifyOnExecutionStateChange('NotifyOnExecutionStateChange', target); ``` diff --git a/packages/@aws-cdk/aws-codepipeline/lib/custom-action-registration.ts b/packages/@aws-cdk/aws-codepipeline/lib/custom-action-registration.ts index f32a952a24d83..207d69d02e268 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/custom-action-registration.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/custom-action-registration.ts @@ -79,7 +79,7 @@ export interface CustomActionRegistrationProps { /** * The provider of the Action. - * @example 'MyCustomActionProvider' + * For example, `'MyCustomActionProvider'` */ readonly provider: string; diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index f453b0c5748ae..1dc98ccca51d4 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -4,7 +4,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import { - App, BootstraplessSynthesizer, DefaultStackSynthesizer, + App, ArnFormat, BootstraplessSynthesizer, DefaultStackSynthesizer, IStackSynthesizer, Lazy, Names, PhysicalName, RemovalPolicy, Resource, Stack, Token, } from '@aws-cdk/core'; import { Construct } from 'constructs'; @@ -263,15 +263,19 @@ abstract class PipelineBase extends Resource implements IPipeline { * * @example * // create a pipeline - * const pipeline = new Pipeline(this, 'Pipeline'); + * import * as codecommit from '@aws-cdk/aws-codecommit'; + * + * const pipeline = new codepipeline.Pipeline(this, 'Pipeline'); * * // add a stage * const sourceStage = pipeline.addStage({ stageName: 'Source' }); * * // add a source action to the stage + * declare const repo: codecommit.Repository; + * declare const sourceArtifact: codepipeline.Artifact; * sourceStage.addAction(new codepipeline_actions.CodeCommitSourceAction({ * actionName: 'Source', - * outputArtifactName: 'SourceArtifact', + * output: sourceArtifact, * repository: repo, * })); * @@ -287,7 +291,7 @@ export class Pipeline extends PipelineBase { */ public static fromPipelineArn(scope: Construct, id: string, pipelineArn: string): IPipeline { class Import extends PipelineBase { - public readonly pipelineName = Stack.of(scope).parseArn(pipelineArn).resource; + public readonly pipelineName = Stack.of(scope).splitArn(pipelineArn, ArnFormat.SLASH_RESOURCE_NAME).resource; public readonly pipelineArn = pipelineArn; } diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index 24dec6803d121..84f28a8804685 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-codepipeline/rosetta/action.ts-fixture b/packages/@aws-cdk/aws-codepipeline/rosetta/action.ts-fixture new file mode 100644 index 0000000000000..43b0b75b3788e --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/rosetta/action.ts-fixture @@ -0,0 +1,61 @@ +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as codepipeline from '@aws-cdk/aws-codepipeline'; + +interface MyActionProps { + variablesNamespace?: string; + actionName: string; +} + +class MyAction extends codepipeline.Action { + public variables: { [key: string]: string }; + protected readonly providedActionProperties: codepipeline.ActionProperties; + + constructor(props: MyActionProps) { + super(); + this.providedActionProperties = { + ...props, + category: codepipeline.ActionCategory.SOURCE, + provider: 'Fake', + artifactBounds: { minInputs: 0, maxInputs: 0, minOutputs: 1, maxOutputs: 4 }, + }; + this.variables = { 'myVariable': 'var' }; + } + + public bound(_scope: Construct, _stage: codepipeline.IStage, _options: codepipeline.ActionBindOptions): + codepipeline.ActionConfig { + return {}; + } +} + +interface OtherActionProps { + config: string; + actionName: string; +} + +class OtherAction extends codepipeline.Action { + protected readonly providedActionProperties: codepipeline.ActionProperties; + + constructor(props: OtherActionProps) { + super(); + this.providedActionProperties = { + ...props, + category: codepipeline.ActionCategory.SOURCE, + provider: 'Fake', + artifactBounds: { minInputs: 0, maxInputs: 0, minOutputs: 1, maxOutputs: 4 }, + }; + } + + public bound(_scope: Construct, _stage: codepipeline.IStage, _options: codepipeline.ActionBindOptions): + codepipeline.ActionConfig { + return {}; + } +} + +class Context extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-codepipeline/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-codepipeline/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..b46720fa572c4 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/rosetta/default.ts-fixture @@ -0,0 +1,15 @@ +import { Construct } from 'constructs'; +import { App, Duration, PhysicalName, Stack } from '@aws-cdk/core'; +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; + +class Context extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-codepipeline/test/artifacts.test.ts b/packages/@aws-cdk/aws-codepipeline/test/artifacts.test.ts index cf4f733d811be..4570ea850df3a 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/artifacts.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/artifacts.test.ts @@ -17,7 +17,7 @@ describe('artifacts', () => { test('without a name, when used as an input without being used as an output first - should fail validation', () => { const stack = new cdk.Stack(); const sourceOutput = new codepipeline.Artifact(); - const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { + new codepipeline.Pipeline(stack, 'Pipeline', { stages: [ { stageName: 'Source', @@ -44,8 +44,7 @@ describe('artifacts', () => { expect(errors.length).toEqual(1); const error = errors[0]; - expect(error.source).toEqual(pipeline); - expect(error.message).toEqual("Action 'Build' is using an unnamed input Artifact, which is not being produced in this pipeline"); + expect(error).toMatch(/Action 'Build' is using an unnamed input Artifact, which is not being produced in this pipeline/); }); @@ -53,7 +52,7 @@ describe('artifacts', () => { test('with a name, when used as an input without being used as an output first - should fail validation', () => { const stack = new cdk.Stack(); const sourceOutput = new codepipeline.Artifact(); - const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { + new codepipeline.Pipeline(stack, 'Pipeline', { stages: [ { stageName: 'Source', @@ -80,8 +79,7 @@ describe('artifacts', () => { expect(errors.length).toEqual(1); const error = errors[0]; - expect(error.source).toEqual(pipeline); - expect(error.message).toEqual("Action 'Build' is using input Artifact 'named', which is not being produced in this pipeline"); + expect(error).toMatch(/Action 'Build' is using input Artifact 'named', which is not being produced in this pipeline/); }); @@ -89,7 +87,7 @@ describe('artifacts', () => { test('without a name, when used as an output multiple times - should fail validation', () => { const stack = new cdk.Stack(); const sourceOutput = new codepipeline.Artifact(); - const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { + new codepipeline.Pipeline(stack, 'Pipeline', { stages: [ { stageName: 'Source', @@ -117,8 +115,7 @@ describe('artifacts', () => { expect(errors.length).toEqual(1); const error = errors[0]; - expect(error.source).toEqual(pipeline); - expect(error.message).toEqual("Both Actions 'Source' and 'Build' are producting Artifact 'Artifact_Source_Source'. Every artifact can only be produced once."); + expect(error).toMatch(/Both Actions 'Source' and 'Build' are producting Artifact 'Artifact_Source_Source'. Every artifact can only be produced once./); }); @@ -177,7 +174,7 @@ describe('artifacts', () => { const buildOutput1 = new codepipeline.Artifact('buildOutput1'); const sourceOutput2 = new codepipeline.Artifact('sourceOutput2'); - const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { + new codepipeline.Pipeline(stack, 'Pipeline', { stages: [ { stageName: 'Source', @@ -217,8 +214,7 @@ describe('artifacts', () => { expect(errors.length).toEqual(1); const error = errors[0]; - expect(error.source).toEqual(pipeline); - expect(error.message).toEqual("Stage 2 Action 2 ('Build'/'build2') is consuming input Artifact 'buildOutput1' before it is being produced at Stage 2 Action 3 ('Build'/'build1')"); + expect(error).toMatch(/Stage 2 Action 2 \('Build'\/'build2'\) is consuming input Artifact 'buildOutput1' before it is being produced at Stage 2 Action 3 \('Build'\/'build1'\)/); }); @@ -283,8 +279,16 @@ describe('artifacts', () => { }); /* eslint-disable @aws-cdk/no-core-construct */ -function validate(construct: cdk.IConstruct): cdk.ValidationError[] { - cdk.ConstructNode.prepare(construct.node); - return cdk.ConstructNode.validate(construct.node); +function validate(construct: cdk.IConstruct): string[] { + try { + (construct.node.root as cdk.App).synth(); + return []; + } catch (e) { + const err = e as any; // coerce unknown to any + if (!('message' in err) || !err.message.startsWith('Validation failed')) { + throw e; + } + return err.message.split('\n').slice(1); + } } /* eslint-enable @aws-cdk/no-core-construct */ diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index 2315662f49d10..906480586b769 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -314,29 +314,55 @@ new cognito.UserPool(this, 'UserPool', { The default for account recovery is by phone if available and by email otherwise. A user will not be allowed to reset their password via phone if they are also using it for MFA. + ### Emails Cognito sends emails to users in the user pool, when particular actions take place, such as welcome emails, invitation emails, password resets, etc. The address from which these emails are sent can be configured on the user pool. -Read more about [email settings here](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html). +Read more at [Email settings for User Pools](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html). + +By default, user pools are configured to use Cognito's built in email capability, which will send emails +from `no-reply@verificationemail.com`. If you want to use a custom email address you can configure +Cognito to send emails through Amazon SES, which is detailed below. ```ts new cognito.UserPool(this, 'myuserpool', { - // ... - emailSettings: { - from: 'noreply@myawesomeapp.com', + email: UserPoolEmail.withCognito('support@myawesomeapp.com'), +}); +``` + +For typical production environments, the default email limit is below the required delivery volume. +To enable a higher delivery volume, you can configure the UserPool to send emails through Amazon SES. To do +so, follow the steps in the [Cognito Developer Guide](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html#user-pool-email-developer) +to verify an email address, move the account out of the SES sandbox, and grant Cognito email permissions via an +authorization policy. + +Once the SES setup is complete, the UserPool can be configured to use the SES email. + +```ts +new cognito.UserPool(this, 'myuserpool', { + email: UserPoolEmail.withSES({ + fromEmail: 'noreply@myawesomeapp.com', + fromName: 'Awesome App', replyTo: 'support@myawesomeapp.com', - }, + }), }); ``` -By default, user pools are configured to use Cognito's built-in email capability, but it can also be configured to use -Amazon SES, however, support for Amazon SES is not available in the CDK yet. If you would like this to be implemented, -give [this issue](https://github.com/aws/aws-cdk/issues/6768) a +1. Until then, you can use the [cfn -layer](https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html) to configure this. +Sending emails through SES requires that SES be configured (as described above) in one of the regions - `us-east-1`, `us-west-1`, or `eu-west-1`. +If the UserPool is being created in a different region, `sesRegion` must be used to specify the correct SES region. + +```ts +new cognito.UserPool(this, 'myuserpool', { + email: UserPoolEmail.withSES({ + sesRegion: 'us-east-1', + fromEmail: 'noreply@myawesomeapp.com', + fromName: 'Awesome App', + replyTo: 'support@myawesomeapp.com', + }), +}); -If an email address contains non-ASCII characters, it will be encoded using the [punycode -encoding](https://en.wikipedia.org/wiki/Punycode) when generating the template for Cloudformation. +``` ### Device Tracking diff --git a/packages/@aws-cdk/aws-cognito/lib/index.ts b/packages/@aws-cdk/aws-cognito/lib/index.ts index cab56671c2b9e..7d5ce97fc2c76 100644 --- a/packages/@aws-cdk/aws-cognito/lib/index.ts +++ b/packages/@aws-cdk/aws-cognito/lib/index.ts @@ -4,6 +4,7 @@ export * from './user-pool'; export * from './user-pool-attr'; export * from './user-pool-client'; export * from './user-pool-domain'; +export * from './user-pool-email'; export * from './user-pool-idp'; export * from './user-pool-idps'; export * from './user-pool-resource-server'; diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-email.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-email.ts new file mode 100644 index 0000000000000..2d5b8af06447f --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-email.ts @@ -0,0 +1,203 @@ +import { Stack, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { toASCII as punycodeEncode } from 'punycode/'; + +/** + * The valid Amazon SES configuration regions + */ +const REGIONS = ['us-east-1', 'us-west-2', 'eu-west-1']; + +/** + * Configuration for Cognito sending emails via Amazon SES + */ +export interface UserPoolSESOptions { + /** + * The verified Amazon SES email address that Cognito should + * use to send emails. + * + * The email address used must be a verified email address + * in Amazon SES and must be configured to allow Cognito to + * send emails. + * + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html + */ + readonly fromEmail: string; + + /** + * An optional name that should be used as the sender's name + * along with the email. + * + * @default - no name + */ + readonly fromName?: string; + + /** + * The destination to which the receiver of the email should reploy to. + * + * @default - same as the fromEmail + */ + readonly replyTo?: string; + + /** + * The name of a configuration set in Amazon SES that should + * be applied to emails sent via Cognito. + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cognito-userpool-emailconfiguration.html#cfn-cognito-userpool-emailconfiguration-configurationset + * + * @default - no configuration set + */ + readonly configurationSetName?: string; + + /** + * Required if the UserPool region is different than the SES region. + * + * If sending emails with a Amazon SES verified email address, + * and the region that SES is configured is different than the + * region in which the UserPool is deployed, you must specify that + * region here. + * + * Must be 'us-east-1', 'us-west-2', or 'eu-west-1' + * + * @default - The same region as the Cognito UserPool + */ + readonly sesRegion?: string; +} + +/** + * Result of binding email settings with a user pool + */ +interface UserPoolEmailConfig { + /** + * The name of the configuration set in SES. + * + * @default - none + */ + readonly configurationSet?: string; + + /** + * Specifies whether to use Cognito's built in email functionality + * or SES. + * + * @default - Cognito built in email functionality + */ + readonly emailSendingAccount?: string; + + /** + * Identifies either the sender's email address or the sender's + * name with their email address. + * + * If emailSendingAccount is DEVELOPER then this cannot be specified. + * + * @default 'no-reply@verificationemail.com' + */ + readonly from?: string; + + /** + * The destination to which the receiver of the email should reply to. + * + * @default - same as `from` + */ + readonly replyToEmailAddress?: string; + + /** + * The ARN of a verified email address in Amazon SES. + * + * required if emailSendingAccount is DEVELOPER or if + * 'from' is provided. + * + * @default - none + */ + readonly sourceArn?: string; +} + +/** + * Configure how Cognito sends emails + */ +export abstract class UserPoolEmail { + /** + * Send email using Cognito + */ + public static withCognito(replyTo?: string): UserPoolEmail { + return new CognitoEmail(replyTo); + } + + /** + * Send email using SES + */ + public static withSES(options: UserPoolSESOptions): UserPoolEmail { + return new SESEmail(options); + } + + + /** + * Returns the email configuration for a Cognito UserPool + * that controls how Cognito will send emails + * @internal + */ + public abstract _bind(scope: Construct): UserPoolEmailConfig; + +} + +class CognitoEmail extends UserPoolEmail { + constructor(private readonly replyTo?: string) { + super(); + } + + public _bind(_scope: Construct): UserPoolEmailConfig { + return { + replyToEmailAddress: encodeAndTest(this.replyTo), + emailSendingAccount: 'COGNITO_DEFAULT', + }; + + } +} + +class SESEmail extends UserPoolEmail { + constructor(private readonly options: UserPoolSESOptions) { + super(); + } + + public _bind(scope: Construct): UserPoolEmailConfig { + const region = Stack.of(scope).region; + + if (Token.isUnresolved(region) && !this.options.sesRegion) { + throw new Error('Your stack region cannot be determined so "sesRegion" is required in SESOptions'); + } + + if (this.options.sesRegion && !REGIONS.includes(this.options.sesRegion)) { + throw new Error(`sesRegion must be one of 'us-east-1', 'us-west-2', 'eu-west-1'. received ${this.options.sesRegion}`); + } else if (!this.options.sesRegion && !REGIONS.includes(region)) { + throw new Error(`Your stack is in ${region}, which is not a SES Region. Please provide a valid value for 'sesRegion'`); + } + + let from = this.options.fromEmail; + if (this.options.fromName) { + from = `${this.options.fromName} <${this.options.fromEmail}>`; + } + + return { + from: encodeAndTest(from), + replyToEmailAddress: encodeAndTest(this.options.replyTo), + configurationSet: this.options.configurationSetName, + emailSendingAccount: 'DEVELOPER', + sourceArn: Stack.of(scope).formatArn({ + service: 'ses', + resource: 'identity', + resourceName: encodeAndTest(this.options.fromEmail), + region: this.options.sesRegion ?? region, + }), + }; + } +} + +function encodeAndTest(input: string | undefined): string | undefined { + if (input) { + const local = input.split('@')[0]; + if (!/[\p{ASCII}]+/u.test(local)) { + throw new Error('the local part of the email address must use ASCII characters only'); + } + return punycodeEncode(input); + } else { + return undefined; + } +} diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index adc3f51bf37a2..731b382e20f76 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -1,6 +1,6 @@ import { IRole, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Duration, IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, Duration, IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { toASCII as punycodeEncode } from 'punycode/'; import { CfnUserPool } from './cognito.generated'; @@ -8,6 +8,7 @@ import { StandardAttributeNames } from './private/attr-names'; import { ICustomAttribute, StandardAttribute, StandardAttributes } from './user-pool-attr'; import { UserPoolClient, UserPoolClientOptions } from './user-pool-client'; import { UserPoolDomain, UserPoolDomainOptions } from './user-pool-domain'; +import { UserPoolEmail } from './user-pool-email'; import { IUserPoolIdentityProvider } from './user-pool-idp'; import { UserPoolResourceServer, UserPoolResourceServerOptions } from './user-pool-resource-server'; @@ -570,10 +571,18 @@ export interface UserPoolProps { /** * Email settings for a user pool. + * * @default - see defaults on each property of EmailSettings. + * @deprecated Use 'email' instead. */ readonly emailSettings?: EmailSettings; + /** + * Email settings for a user pool. + * @default - cognito will use the default email configuration + */ + readonly email?: UserPoolEmail; + /** * Lambda functions to use for supported Cognito triggers. * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html @@ -706,7 +715,7 @@ export class UserPool extends UserPoolBase { * Import an existing user pool based on its ARN. */ public static fromUserPoolArn(scope: Construct, id: string, userPoolArn: string): IUserPool { - const arnParts = Stack.of(scope).parseArn(userPoolArn); + const arnParts = Stack.of(scope).splitArn(userPoolArn, ArnFormat.SLASH_RESOURCE_NAME); if (!arnParts.resourceName) { throw new Error('invalid user pool ARN'); @@ -788,6 +797,14 @@ export class UserPool extends UserPoolBase { const passwordPolicy = this.configurePasswordPolicy(props); + if (props.email && props.emailSettings) { + throw new Error('you must either provide "email" or "emailSettings", but not both'); + } + const emailConfiguration = props.email ? props.email._bind(this) : undefinedIfNoKeys({ + from: encodePuny(props.emailSettings?.from), + replyToEmailAddress: encodePuny(props.emailSettings?.replyTo), + }); + const userPool = new CfnUserPool(this, 'Resource', { userPoolName: props.userPoolName, usernameAttributes: signIn.usernameAttrs, @@ -805,10 +822,7 @@ export class UserPool extends UserPoolBase { mfaConfiguration: props.mfa, enabledMfas: this.mfaConfiguration(props), policies: passwordPolicy !== undefined ? { passwordPolicy } : undefined, - emailConfiguration: undefinedIfNoKeys({ - from: encodePuny(props.emailSettings?.from), - replyToEmailAddress: encodePuny(props.emailSettings?.replyTo), - }), + emailConfiguration, usernameConfiguration: undefinedIfNoKeys({ caseSensitive: props.signInCaseSensitive, }), 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 5aa6c48ee99fc..9d89be13d6fef 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts @@ -1,9 +1,10 @@ import { Match, Template } from '@aws-cdk/assertions'; import { Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { CfnParameter, Duration, Stack, Tags } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { AccountRecovery, Mfa, NumberAttribute, StringAttribute, UserPool, UserPoolIdentityProvider, UserPoolOperation, VerificationEmailStyle } from '../lib'; +import { AccountRecovery, Mfa, NumberAttribute, StringAttribute, UserPool, UserPoolIdentityProvider, UserPoolOperation, VerificationEmailStyle, UserPoolEmail } from '../lib'; describe('User Pool', () => { test('default setup', () => { @@ -903,7 +904,7 @@ describe('User Pool', () => { })).toThrow(/minLength for password must be between/); }); - test('email transmission settings are recognized correctly', () => { + testDeprecated('email transmission settings are recognized correctly', () => { // GIVEN const stack = new Stack(); @@ -1368,7 +1369,7 @@ describe('User Pool', () => { }); }); - test('email transmission with cyrillic characters are encoded', () => { + testDeprecated('email transmission with cyrillic characters are encoded', () => { // GIVEN const stack = new Stack(); @@ -1388,6 +1389,285 @@ describe('User Pool', () => { }, }); }); + + test('email transmission with cyrillic characters in the domain are encoded', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + sesRegion: 'us-east-1', + fromEmail: 'user@домен.рф', + replyTo: 'user@домен.рф', + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPool', { + EmailConfiguration: { + From: 'user@xn--d1acufc.xn--p1ai', + ReplyToEmailAddress: 'user@xn--d1acufc.xn--p1ai', + }, + }); + }); + + test('email transmission with cyrillic characters in the local part throw error', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + expect(() => new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + sesRegion: 'us-east-1', + fromEmail: 'от@домен.рф', + replyTo: 'user@домен.рф', + }), + })).toThrow(/the local part of the email address must use ASCII characters only/); + }); + + test('email transmission with cyrillic characters in the local part of replyTo throw error', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + expect(() => new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + sesRegion: 'us-east-1', + fromEmail: 'user@домен.рф', + replyTo: 'от@домен.рф', + }), + })).toThrow(/the local part of the email address must use ASCII characters only/); + }); + + test('email withCognito transmission with cyrillic characters in the local part of replyTo throw error', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + expect(() => new UserPool(stack, 'Pool', { + email: UserPoolEmail.withCognito('от@домен.рф'), + })).toThrow(/the local part of the email address must use ASCII characters only/); + }); + + test('email withCognito', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new UserPool(stack, 'Pool', { + email: UserPoolEmail.withCognito(), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPool', { + EmailConfiguration: { + EmailSendingAccount: 'COGNITO_DEFAULT', + }, + }); + }); + + test('email withCognito and replyTo', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new UserPool(stack, 'Pool', { + email: UserPoolEmail.withCognito('reply@example.com'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPool', { + EmailConfiguration: { + EmailSendingAccount: 'COGNITO_DEFAULT', + ReplyToEmailAddress: 'reply@example.com', + }, + }); + }); + + test('email withSES with custom email and no region', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + expect(() => new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + fromEmail: 'mycustomemail@example.com', + replyTo: 'reply@example.com', + }), + })).toThrow(/Your stack region cannot be determined/); + + }); + + test('email withSES with no name', () => { + // GIVEN + const stack = new Stack(undefined, undefined, { + env: { + region: 'us-east-1', + account: '11111111111', + }, + }); + + // WHEN + new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + fromEmail: 'mycustomemail@example.com', + replyTo: 'reply@example.com', + configurationSetName: 'default', + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPool', { + EmailConfiguration: { + EmailSendingAccount: 'DEVELOPER', + From: 'mycustomemail@example.com', + ReplyToEmailAddress: 'reply@example.com', + ConfigurationSet: 'default', + SourceArn: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ses:us-east-1:11111111111:identity/mycustomemail@example.com', + ], + ], + }, + }, + }); + + }); + + test('email withSES', () => { + // GIVEN + const stack = new Stack(undefined, undefined, { + env: { + region: 'us-east-1', + account: '11111111111', + }, + }); + + // WHEN + new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + fromEmail: 'mycustomemail@example.com', + fromName: 'My Custom Email', + replyTo: 'reply@example.com', + configurationSetName: 'default', + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPool', { + EmailConfiguration: { + EmailSendingAccount: 'DEVELOPER', + From: 'My Custom Email ', + ReplyToEmailAddress: 'reply@example.com', + ConfigurationSet: 'default', + SourceArn: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ses:us-east-1:11111111111:identity/mycustomemail@example.com', + ], + ], + }, + }, + }); + + }); + + test('email withSES with valid region', () => { + // GIVEN + const stack = new Stack(undefined, undefined, { + env: { + region: 'us-east-2', + account: '11111111111', + }, + }); + + // WHEN + new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + fromEmail: 'mycustomemail@example.com', + fromName: 'My Custom Email', + sesRegion: 'us-east-1', + replyTo: 'reply@example.com', + configurationSetName: 'default', + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPool', { + EmailConfiguration: { + EmailSendingAccount: 'DEVELOPER', + From: 'My Custom Email ', + ReplyToEmailAddress: 'reply@example.com', + ConfigurationSet: 'default', + SourceArn: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ses:us-east-1:11111111111:identity/mycustomemail@example.com', + ], + ], + }, + }, + }); + + }); + test('email withSES invalid region throws error', () => { + // GIVEN + const stack = new Stack(undefined, undefined, { + env: { + region: 'us-east-2', + account: '11111111111', + }, + }); + + // WHEN + expect(() => new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + fromEmail: 'mycustomemail@example.com', + fromName: 'My Custom Email', + replyTo: 'reply@example.com', + configurationSetName: 'default', + }), + })).toThrow(/Please provide a valid value/); + + }); + + test('email withSES invalid sesRegion throws error', () => { + // GIVEN + const stack = new Stack(undefined, undefined, { + env: { + account: '11111111111', + }, + }); + + // WHEN + expect(() => new UserPool(stack, 'Pool', { + email: UserPoolEmail.withSES({ + sesRegion: 'us-east-2', + fromEmail: 'mycustomemail@example.com', + fromName: 'My Custom Email', + replyTo: 'reply@example.com', + configurationSetName: 'default', + }), + })).toThrow(/sesRegion must be one of/); + + }); }); test('device tracking is configured correctly', () => { diff --git a/packages/@aws-cdk/aws-docdb/lib/instance.ts b/packages/@aws-cdk/aws-docdb/lib/instance.ts index a7563cb601388..1a791b99a0bf5 100644 --- a/packages/@aws-cdk/aws-docdb/lib/instance.ts +++ b/packages/@aws-cdk/aws-docdb/lib/instance.ts @@ -1,5 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; +import { ArnFormat } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IDatabaseCluster } from './cluster-ref'; import { CfnDBInstance } from './docdb.generated'; @@ -102,7 +103,7 @@ abstract class DatabaseInstanceBase extends cdk.Resource implements IDatabaseIns return cdk.Stack.of(this).formatArn({ service: 'docdb', resource: 'db', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, resourceName: this.instanceIdentifier, }); } diff --git a/packages/@aws-cdk/aws-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 b81fb81ec34e2..6e16e263c4129 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json @@ -33,12 +33,12 @@ "aws-sdk-mock": "^5.4.0", "eslint": "^7.32.0", "eslint-config-standard": "^14.1.1", - "eslint-plugin-import": "^2.25.2", + "eslint-plugin-import": "^2.25.3", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.3.1", "eslint-plugin-standard": "^4.1.0", "jest": "^27.3.1", "lambda-tester": "^3.6.0", - "nock": "^13.1.4" + "nock": "^13.2.1" } } diff --git a/packages/@aws-cdk/aws-dynamodb-global/test/dynamodb-global.test.ts b/packages/@aws-cdk/aws-dynamodb-global/test/dynamodb-global.test.ts index ed84a3f8a6145..5a6a45d5be3a3 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/test/dynamodb-global.test.ts +++ b/packages/@aws-cdk/aws-dynamodb-global/test/dynamodb-global.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import { Attribute, AttributeType, StreamViewType, Table } from '@aws-cdk/aws-dynamodb'; +import { describeDeprecated, testDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, CfnOutput, Stack } from '@aws-cdk/core'; import { GlobalTable, GlobalTableProps } from '../lib'; @@ -18,7 +19,7 @@ const STACK_PROPS: GlobalTableProps = { regions: ['us-east-1', 'us-east-2', 'us-west-2'], }; -describe('Default Global DynamoDB stack', () => { +describeDeprecated('Default Global DynamoDB stack', () => { test('global dynamo', () => { const stack = new Stack(); new GlobalTable(stack, CONSTRUCT_NAME, STACK_PROPS); @@ -58,7 +59,7 @@ describe('Default Global DynamoDB stack', () => { }); }); -test('GlobalTable generated stacks inherit their account from the parent stack', () => { +testDeprecated('GlobalTable generated stacks inherit their account from the parent stack', () => { const app = new App({ context: { '@aws-cdk/core:stackRelativeExports': true } }); const stack = new Stack(app, 'GlobalTableStack', { env: { account: '123456789012', region: 'us-east-1' } }); @@ -85,7 +86,7 @@ test('GlobalTable generated stacks inherit their account from the parent stack', }); }); -describe('Enforce StreamSpecification', () => { +describeDeprecated('Enforce StreamSpecification', () => { test('global dynamo should only allow NEW_AND_OLD_IMAGES', () => { const stack = new Stack(); @@ -100,7 +101,7 @@ describe('Enforce StreamSpecification', () => { }); }); -describe('Check getting tables', () => { +describeDeprecated('Check getting tables', () => { test('global dynamo should only allow NEW_AND_OLD_IMAGES', () => { const stack = new Stack(); const regTables = new GlobalTable(stack, CONSTRUCT_NAME, { diff --git a/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.ts b/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.ts index 90965454a6828..85ecc746b8ae5 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.ts +++ b/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.ts @@ -5,6 +5,10 @@ import { GlobalTable } from '../lib'; const app = new App(); const stack = new Stack(app, 'Default'); + +const deprecated = process.env.JSII_DEPRECATED; +process.env.JSII_DEPRECATED = 'quiet'; + new GlobalTable(stack, 'globdynamodbinteg', { partitionKey: { name: 'hashKey', type: AttributeType.STRING }, tableName: 'integrationtest', @@ -12,3 +16,9 @@ new GlobalTable(stack, 'globdynamodbinteg', { removalPolicy: RemovalPolicy.DESTROY, }); app.synth(); + +if (deprecated) { + process.env.JSII_DEPRECATED = deprecated; +} else { + delete process.env.JSII_DEPRECATED; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index 10a7472be3c0d..6e6b79d253d1a 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -4,6 +4,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kinesis from '@aws-cdk/aws-kinesis'; import * as kms from '@aws-cdk/aws-kms'; import { + ArnFormat, Aws, CfnCondition, CfnCustomResource, CfnResource, CustomResource, Duration, Fn, IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token, } from '@aws-cdk/core'; @@ -739,7 +740,7 @@ abstract class TableBase extends Resource implements ITable { return new cloudwatch.Metric({ namespace: 'AWS/DynamoDB', metricName, - dimensions: { + dimensionsMap: { TableName: this.tableName, }, ...props, @@ -772,17 +773,18 @@ abstract class TableBase extends Resource implements ITable { * @deprecated use `metricSystemErrorsForOperations`. */ public metricSystemErrors(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - if (!props?.dimensions?.Operation) { + if (!props?.dimensions?.Operation && !props?.dimensionsMap?.Operation) { // 'Operation' must be passed because its an operational metric. throw new Error("'Operation' dimension must be passed for the 'SystemErrors' metric."); } - const dimensions = { + const dimensionsMap = { TableName: this.tableName, ...props?.dimensions ?? {}, + ...props?.dimensionsMap ?? {}, }; - return this.metric('SystemErrors', { statistic: 'sum', ...props, dimensions }); + return this.metric('SystemErrors', { statistic: 'sum', ...props, dimensionsMap }); } /** @@ -799,7 +801,7 @@ abstract class TableBase extends Resource implements ITable { // overriding 'dimensions' here because this metric is an account metric. // see 'UserErrors' in https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/metrics-dimensions.html - return this.metric('UserErrors', { statistic: 'sum', ...props, dimensions: {} }); + return this.metric('UserErrors', { statistic: 'sum', ...props, dimensionsMap: {} }); } /** @@ -828,19 +830,19 @@ abstract class TableBase extends Resource implements ITable { * You can customize this by using the `statistic` and `period` properties. */ public metricSuccessfulRequestLatency(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - if (!props?.dimensions?.Operation) { + if (!props?.dimensions?.Operation && !props?.dimensionsMap?.Operation) { throw new Error("'Operation' dimension must be passed for the 'SuccessfulRequestLatency' metric."); } - const dimensions = { + const dimensionsMap = { TableName: this.tableName, - Operation: props.dimensions.Operation, + Operation: props.dimensionsMap?.Operation ?? props.dimensions?.Operation, }; return new cloudwatch.Metric({ - ...DynamoDBMetrics.successfulRequestLatencyAverage(dimensions), + ...DynamoDBMetrics.successfulRequestLatencyAverage(dimensionsMap), ...props, - dimensions, + dimensionsMap, }).attachTo(this); } @@ -898,7 +900,7 @@ abstract class TableBase extends Resource implements ITable { const metric = this.metric(metricName, { ...props, - dimensions: { + dimensionsMap: { TableName: this.tableName, Operation: operation, ...props?.dimensions, @@ -1048,7 +1050,7 @@ export class Table extends TableBase { if (!attrs.tableArn) { throw new Error('One of tableName or tableArn is required!'); } arn = attrs.tableArn; - const maybeTableName = stack.parseArn(attrs.tableArn).resourceName; + const maybeTableName = stack.splitArn(attrs.tableArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName; if (!maybeTableName) { throw new Error('ARN for DynamoDB table must be in the form: ...'); } name = maybeTableName; } else { diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index f1fdf012ab0b8..d63167cf3d5de 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts index a095bff84ca25..ded635da9983f 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts @@ -4,9 +4,10 @@ import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as iam from '@aws-cdk/aws-iam'; import * as kinesis from '@aws-cdk/aws-kinesis'; import * as kms from '@aws-cdk/aws-kms'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; +import { testLegacyBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import { App, Aws, CfnDeletionPolicy, ConstructNode, Duration, PhysicalName, RemovalPolicy, Resource, Stack, Tags } from '@aws-cdk/core'; import * as cr from '@aws-cdk/custom-resources'; -import { testLegacyBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import { Construct } from 'constructs'; import { Attribute, @@ -39,7 +40,7 @@ const GSI_NAME = 'MyGSI'; const GSI_PARTITION_KEY: Attribute = { name: 'gsiHashKey', type: AttributeType.STRING }; const GSI_SORT_KEY: Attribute = { name: 'gsiSortKey', type: AttributeType.BINARY }; const GSI_NON_KEY = 'gsiNonKey'; -function* GSI_GENERATOR(): Generator { +function * GSI_GENERATOR(): Generator { let n = 0; while (true) { const globalSecondaryIndexProps: GlobalSecondaryIndexProps = { @@ -50,7 +51,7 @@ function* GSI_GENERATOR(): Generator { n++; } } -function* NON_KEY_ATTRIBUTE_GENERATOR(nonKeyPrefix: string): Generator { +function * NON_KEY_ATTRIBUTE_GENERATOR(nonKeyPrefix: string): Generator { let n = 0; while (true) { yield `${nonKeyPrefix}${n}`; @@ -62,7 +63,7 @@ function* NON_KEY_ATTRIBUTE_GENERATOR(nonKeyPrefix: string): Generator { +function * LSI_GENERATOR(): Generator { let n = 0; while (true) { const localSecondaryIndexProps: LocalSecondaryIndexProps = { @@ -318,7 +319,7 @@ describe('default properties', () => { }); }); -test('when specifying every property', () => { +testDeprecated('when specifying every property', () => { const stack = new Stack(); const stream = new kinesis.Stream(stack, 'MyStream'); const table = new Table(stack, CONSTRUCT_NAME, { @@ -469,7 +470,7 @@ test('fails if encryption key is used with default encryption', () => { })).toThrow('`encryptionKey cannot be specified unless encryption is set to TableEncryption.CUSTOMER_MANAGED (it was set to ${encryptionType})`'); }); -test('fails if encryption key is used with serverSideEncryption', () => { +testDeprecated('fails if encryption key is used with serverSideEncryption', () => { const stack = new Stack(); const encryptionKey = new kms.Key(stack, 'Key', { enableKeyRotation: true, @@ -482,7 +483,7 @@ test('fails if encryption key is used with serverSideEncryption', () => { })).toThrow(/encryptionKey cannot be specified when serverSideEncryption is specified. Use encryption instead/); }); -test('fails if both encryption and serverSideEncryption is specified', () => { +testDeprecated('fails if both encryption and serverSideEncryption is specified', () => { const stack = new Stack(); expect(() => new Table(stack, 'Table A', { tableName: TABLE_NAME, @@ -1603,7 +1604,7 @@ describe('metrics', () => { }); - test('Can use metricSystemErrors without the TableName dimension', () => { + testDeprecated('Can use metricSystemErrors without the TableName dimension', () => { const stack = new Stack(); const table = new Table(stack, 'Table', { @@ -1617,7 +1618,7 @@ describe('metrics', () => { }); - test('Using metricSystemErrors without the Operation dimension will fail', () => { + testDeprecated('Using metricSystemErrors without the Operation dimension will fail', () => { const stack = new Stack(); const table = new Table(stack, 'Table', { @@ -1672,7 +1673,7 @@ describe('metrics', () => { }); - test('Can use metricSystemErrors on a Dynamodb Table', () => { + testDeprecated('Can use metricSystemErrors on a Dynamodb Table', () => { // GIVEN const stack = new Stack(); const table = new Table(stack, 'Table', { @@ -1680,7 +1681,7 @@ describe('metrics', () => { }); // THEN - expect(stack.resolve(table.metricSystemErrors({ dimensions: { TableName: table.tableName, Operation: 'GetItem' } }))).toEqual({ + expect(stack.resolve(table.metricSystemErrors({ dimensionsMap: { TableName: table.tableName, Operation: 'GetItem' } }))).toEqual({ period: Duration.minutes(5), dimensions: { TableName: { Ref: 'TableCD117FA1' }, Operation: 'GetItem' }, namespace: 'AWS/DynamoDB', @@ -1741,7 +1742,7 @@ describe('metrics', () => { partitionKey: { name: 'id', type: AttributeType.STRING }, }); - expect(table.metricSuccessfulRequestLatency({ dimensions: { Operation: 'GetItem' } }).dimensions).toEqual({ + expect(table.metricSuccessfulRequestLatency({ dimensionsMap: { Operation: 'GetItem' } }).dimensions).toEqual({ TableName: table.tableName, Operation: 'GetItem', }); @@ -1755,7 +1756,7 @@ describe('metrics', () => { partitionKey: { name: 'id', type: AttributeType.STRING }, }); - expect(() => table.metricSuccessfulRequestLatency({ dimensions: { TableName: table.tableName } })) + expect(() => table.metricSuccessfulRequestLatency({ dimensionsMap: { TableName: table.tableName } })) .toThrow(/'Operation' dimension must be passed for the 'SuccessfulRequestLatency' metric./); }); @@ -1769,7 +1770,7 @@ describe('metrics', () => { // THEN expect(stack.resolve(table.metricSuccessfulRequestLatency({ - dimensions: { + dimensionsMap: { TableName: table.tableName, Operation: 'GetItem', }, @@ -1859,7 +1860,7 @@ describe('grants', () => { testGrant(['*'], (p, t) => t.grantFullAccess(p)); }); - test('"Table.grantListStreams" allows principal to list all streams', () => { + testDeprecated('"Table.grantListStreams" allows principal to list all streams', () => { // GIVEN const stack = new Stack(); const user = new iam.User(stack, 'user'); diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts index 83495c01355cd..53e0bd7b20f17 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts @@ -1,5 +1,5 @@ import { App, RemovalPolicy, Stack, Tags } from '@aws-cdk/core'; -import { Attribute, AttributeType, BillingMode, ProjectionType, StreamViewType, Table } from '../lib'; +import { Attribute, AttributeType, BillingMode, ProjectionType, StreamViewType, Table, TableEncryption } from '../lib'; // CDK parameters const STACK_NAME = 'aws-cdk-dynamodb'; @@ -49,7 +49,7 @@ new Table(stack, TABLE, { const tableWithGlobalAndLocalSecondaryIndex = new Table(stack, TABLE_WITH_GLOBAL_AND_LOCAL_SECONDARY_INDEX, { pointInTimeRecovery: true, - serverSideEncryption: true, + encryption: TableEncryption.AWS_MANAGED, stream: StreamViewType.KEYS_ONLY, billingMode: BillingMode.PAY_PER_REQUEST, timeToLiveAttribute: 'timeToLive', diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts index e01d3dd996101..f016561c441db 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import { App, RemovalPolicy, Stack, Tags } from '@aws-cdk/core'; -import { Attribute, AttributeType, ProjectionType, StreamViewType, Table } from '../lib'; +import { Attribute, AttributeType, ProjectionType, StreamViewType, Table, TableEncryption } from '../lib'; // CDK parameters const STACK_NAME = 'aws-cdk-dynamodb'; @@ -48,7 +48,7 @@ const table = new Table(stack, TABLE, { const tableWithGlobalAndLocalSecondaryIndex = new Table(stack, TABLE_WITH_GLOBAL_AND_LOCAL_SECONDARY_INDEX, { pointInTimeRecovery: true, - serverSideEncryption: true, + encryption: TableEncryption.AWS_MANAGED, stream: StreamViewType.KEYS_ONLY, timeToLiveAttribute: 'timeToLive', partitionKey: TABLE_PARTITION_KEY, diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index ca30cc28fe9bd..f673e24111e9f 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -577,6 +577,30 @@ const mySecurityGroupWithoutInlineRules = new ec2.SecurityGroup(this, 'SecurityG mySecurityGroupWithoutInlineRules.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'allow ssh access from the world'); ``` +### Importing an existing security group + +If you know the ID and the configuration of the security group to import, you can use `SecurityGroup.fromSecurityGroupId`: + +```ts +const sg = ec2.SecurityGroup.fromSecurityGroupId(this, 'SecurityGroupImport', 'sg-1234', { + allowAllOutbound: true, +}); +``` + +Alternatively, use lookup methods to import security groups if you do not know the ID or the configuration details. Method `SecurityGroup.fromLookupByName` looks up a security group if the secruity group ID is unknown. + +```ts fixture=with-vpc +const sg = ec2.SecurityGroup.fromLookupByName(this, 'SecurityGroupLookup', 'security-group-name', vpc); +``` + +If the security group ID is known and configuration details are unknown, use method `SecurityGroup.fromLookupById` instead. This method will lookup property `allowAllOutbound` from the current configuration of the security group. + +```ts +const sg = ec2.SecurityGroup.fromLookupById(this, 'SecurityGroupLookup', 'sg-1234'); +``` + +The result of `SecurityGroup.fromLookupByName` and `SecurityGroup.fromLookupById` operations will be written to a file called `cdk.context.json`. You must commit this file to source control so that the lookup values are available in non-privileged environments such as CI build steps, and to ensure your template builds are repeatable. + ## Machine Images (AMIs) AMIs control the OS that gets launched when you start your EC2 instance. The EC2 diff --git a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts index 1fc4e02f25daa..ec5f2b91e17e7 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts @@ -68,6 +68,26 @@ export enum InstanceClass { */ M5AD = 'm5ad', + /** + * Standard instances for high performance computing, 5th generation + */ + STANDARD5_HIGH_PERFORMANCE = 'm5n', + + /** + * Standard instances for high performance computing, 5th generation + */ + M5N = 'm5n', + + /** + * Standard instances with local NVME drive for high performance computing, 5th generation + */ + STANDARD5_NVME_DRIVE_HIGH_PERFORMANCE = 'm5dn', + + /** + * Standard instances with local NVME drive for high performance computing, 5th generation + */ + M5DN = 'm5dn', + /** * Memory optimized instances, 3rd generation */ @@ -446,6 +466,16 @@ export enum InstanceClass { */ G4DN = 'g4dn', + /** + * Graphics-optimized instances, 5th generation + */ + GRAPHICS5 = 'g5', + + /** + * Graphics-optimized instances, 5th generation + */ + G5 = 'g5', + /** * Parallel-processing optimized instances, 2nd generation */ @@ -646,6 +676,11 @@ export enum InstanceSize { */ XLARGE32 = '32xlarge', + /** + * Instance size XLARGE48 (48xlarge) + */ + XLARGE48 = '48xlarge', + /** * Instance size METAL (metal) */ diff --git a/packages/@aws-cdk/aws-ec2/lib/nat.ts b/packages/@aws-cdk/aws-ec2/lib/nat.ts index 4743799762b51..f4ce8aa242053 100644 --- a/packages/@aws-cdk/aws-ec2/lib/nat.ts +++ b/packages/@aws-cdk/aws-ec2/lib/nat.ts @@ -233,10 +233,8 @@ class NatGatewayProvider extends NatProvider { // Create the NAT gateways let i = 0; for (const sub of options.natSubnets) { - const gateway = sub.addNatGateway(); - if (this.props.eipAllocationIds) { - gateway.allocationId = pickN(i, this.props.eipAllocationIds); - } + const eipAllocationId = this.props.eipAllocationIds ? pickN(i, this.props.eipAllocationIds) : undefined; + const gateway = sub.addNatGateway(eipAllocationId); this.gateways.add(sub.availabilityZone, gateway.ref); i++; } diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index 692df3629ebbf..163cb8079ccdb 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -325,25 +325,25 @@ export interface SecurityGroupImportOptions { export class SecurityGroup extends SecurityGroupBase { /** * Look up a security group by id. + * + * @deprecated Use `fromLookupById()` instead */ public static fromLookup(scope: Construct, id: string, securityGroupId: string) { - if (Token.isUnresolved(securityGroupId)) { - throw new Error('All arguments to look up a security group must be concrete (no Tokens)'); - } + return this.fromLookupAttributes(scope, id, { securityGroupId }); + } - const attributes: cxapi.SecurityGroupContextResponse = ContextProvider.getValue(scope, { - provider: cxschema.ContextProvider.SECURITY_GROUP_PROVIDER, - props: { securityGroupId }, - dummyValue: { - securityGroupId: 'sg-12345', - allowAllOutbound: true, - } as cxapi.SecurityGroupContextResponse, - }).value; + /** + * Look up a security group by id. + */ + public static fromLookupById(scope: Construct, id: string, securityGroupId: string) { + return this.fromLookupAttributes(scope, id, { securityGroupId }); + } - return SecurityGroup.fromSecurityGroupId(scope, id, attributes.securityGroupId, { - allowAllOutbound: attributes.allowAllOutbound, - mutable: true, - }); + /** + * Look up a security group by name. + */ + public static fromLookupByName(scope: Construct, id: string, securityGroupName: string, vpc: IVpc) { + return this.fromLookupAttributes(scope, id, { securityGroupName, vpc }); } /** @@ -387,6 +387,33 @@ export class SecurityGroup extends SecurityGroupBase { : new ImmutableImport(scope, id); } + /** + * Look up a security group. + */ + private static fromLookupAttributes(scope: Construct, id: string, options: SecurityGroupLookupOptions) { + if (Token.isUnresolved(options.securityGroupId) || Token.isUnresolved(options.securityGroupName) || Token.isUnresolved(options.vpc?.vpcId)) { + throw new Error('All arguments to look up a security group must be concrete (no Tokens)'); + } + + const attributes: cxapi.SecurityGroupContextResponse = ContextProvider.getValue(scope, { + provider: cxschema.ContextProvider.SECURITY_GROUP_PROVIDER, + props: { + securityGroupId: options.securityGroupId, + securityGroupName: options.securityGroupName, + vpcId: options.vpc?.vpcId, + }, + dummyValue: { + securityGroupId: 'sg-12345', + allowAllOutbound: true, + } as cxapi.SecurityGroupContextResponse, + }).value; + + return SecurityGroup.fromSecurityGroupId(scope, id, attributes.securityGroupId, { + allowAllOutbound: attributes.allowAllOutbound, + mutable: true, + }); + } + /** * An attribute that represents the security group name. * @@ -696,3 +723,37 @@ function egressRulesEqual(a: CfnSecurityGroup.EgressProperty, b: CfnSecurityGrou function isAllTrafficRule(rule: any) { return rule.cidrIp === '0.0.0.0/0' && rule.ipProtocol === '-1'; } + +/** + * Properties for looking up an existing SecurityGroup. + * + * Either `securityGroupName` or `securityGroupId` has to be specified. + */ +interface SecurityGroupLookupOptions { + /** + * The name of the security group + * + * If given, will import the SecurityGroup with this name. + * + * @default Don't filter on securityGroupName + */ + readonly securityGroupName?: string; + + /** + * The ID of the security group + * + * If given, will import the SecurityGroup with this ID. + * + * @default Don't filter on securityGroupId + */ + readonly securityGroupId?: string; + + /** + * The VPC of the security group + * + * If given, will filter the SecurityGroup based on the VPC. + * + * @default Don't filter on VPC + */ + readonly vpc?: IVpc, +} diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index 00c59bbd2a022..1f454f9d63f65 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -266,6 +266,8 @@ export class InterfaceVpcEndpointAwsService implements IInterfaceVpcEndpointServ public static readonly CODEBUILD_FIPS = new InterfaceVpcEndpointAwsService('codebuild-fips'); public static readonly CODECOMMIT = new InterfaceVpcEndpointAwsService('codecommit'); public static readonly CODECOMMIT_FIPS = new InterfaceVpcEndpointAwsService('codecommit-fips'); + public static readonly CODEGURU_PROFILER = new InterfaceVpcEndpointAwsService('codeguru-profiler'); + public static readonly CODEGURU_REVIEWER = new InterfaceVpcEndpointAwsService('codeguru-reviewer'); public static readonly CODEPIPELINE = new InterfaceVpcEndpointAwsService('codepipeline'); public static readonly CONFIG = new InterfaceVpcEndpointAwsService('config'); public static readonly EC2 = new InterfaceVpcEndpointAwsService('ec2'); diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index cd70f71582718..6d1722b3135d8 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -1840,11 +1840,11 @@ export class PublicSubnet extends Subnet implements IPublicSubnet { * Also adds the EIP for the managed NAT. * @returns A ref to the the NAT Gateway ID */ - public addNatGateway() { + public addNatGateway(eipAllocationId?: string) { // Create a NAT Gateway in this public subnet const ngw = new CfnNatGateway(this, 'NATGateway', { subnetId: this.subnetId, - allocationId: new CfnEIP(this, 'EIP', { + allocationId: eipAllocationId ?? new CfnEIP(this, 'EIP', { domain: 'vpc', }).attrAllocationId, }); diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index f8664e445ec97..00190b44dd5f8 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -226,6 +233,8 @@ "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.CODECOMMIT_FIPS", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.CODECOMMIT_GIT", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.CODECOMMIT_GIT_FIPS", + "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.CODEGURU_PROFILER", + "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.CODEGURU_REVIEWER", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.CODEPIPELINE", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.CONFIG", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.EC2", diff --git a/packages/@aws-cdk/aws-ec2/test/client-vpn-authorization-rule.test.ts b/packages/@aws-cdk/aws-ec2/test/client-vpn-authorization-rule.test.ts index 5390eb43adb85..8570fa4fafacc 100644 --- a/packages/@aws-cdk/aws-ec2/test/client-vpn-authorization-rule.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/client-vpn-authorization-rule.test.ts @@ -1,4 +1,5 @@ import '@aws-cdk/assert-internal/jest'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, Stack } from '@aws-cdk/core'; import { Connections, IClientVpnEndpoint } from '../lib'; import { ClientVpnAuthorizationRule } from '../lib/client-vpn-authorization-rule'; @@ -41,7 +42,7 @@ describe('ClientVpnAuthorizationRule constructor', () => { ), ); }); - test('specifying both clientVpnEndoint (deprecated, typo) and clientVpnEndpoint is not allowed', () => { + testDeprecated('specifying both clientVpnEndoint (deprecated, typo) and clientVpnEndpoint is not allowed', () => { const clientVpnEndoint: IClientVpnEndpoint = { endpointId: 'typoTypo', targetNetworksAssociated: [], @@ -79,7 +80,7 @@ describe('ClientVpnAuthorizationRule constructor', () => { }).toThrow(); expect(stack.node.children.length).toBe(0); }); - test('supplying clientVpnEndoint (deprecated due to typo) should still work', () => { + testDeprecated('supplying clientVpnEndoint (deprecated due to typo) should still work', () => { const clientVpnEndoint: IClientVpnEndpoint = { endpointId: 'myClientVpnEndpoint', targetNetworksAssociated: [], diff --git a/packages/@aws-cdk/aws-ec2/test/client-vpn-route.test.ts b/packages/@aws-cdk/aws-ec2/test/client-vpn-route.test.ts index fde37f49335ae..5bb1ee9722139 100644 --- a/packages/@aws-cdk/aws-ec2/test/client-vpn-route.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/client-vpn-route.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import { SamlMetadataDocument, SamlProvider } from '@aws-cdk/aws-iam'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, Stack } from '@aws-cdk/core'; import * as ec2 from '../lib'; import { @@ -46,7 +47,7 @@ describe('ClientVpnRoute constructor', () => { expect(stack).toCountResources('AWS::EC2::ClientVpnRoute', 1); expect(stack.node.children.length).toBe(3); }); - test('either clientVpnEndoint (deprecated, typo) or clientVpnEndpoint is required', () => { + testDeprecated('either clientVpnEndoint (deprecated, typo) or clientVpnEndpoint is required', () => { expect(() => { new ClientVpnRoute(stack, 'RouteNoEndointOrEndpoint', { cidr: '0.0.0.0/0', @@ -58,7 +59,7 @@ describe('ClientVpnRoute constructor', () => { ), ); }); - test('specifying both clientVpnEndoint (deprecated, typo) and clientVpnEndpoint is not allowed', () => { + testDeprecated('specifying both clientVpnEndoint (deprecated, typo) and clientVpnEndpoint is not allowed', () => { const samlProvider = new SamlProvider(stack, 'Provider', { metadataDocument: SamlMetadataDocument.fromXml('xml'), }); @@ -98,7 +99,7 @@ describe('ClientVpnRoute constructor', () => { expect(stack).toCountResources('AWS::EC2::VPC', 1); expect(stack.node.children.length).toBe(1); }); - test('supplying clientVpnEndoint (deprecated due to typo) should still work', () => { + testDeprecated('supplying clientVpnEndoint (deprecated due to typo) should still work', () => { const samlProvider = new SamlProvider(stack, 'Provider', { metadataDocument: SamlMetadataDocument.fromXml('xml'), }); diff --git a/packages/@aws-cdk/aws-ec2/test/instance.test.ts b/packages/@aws-cdk/aws-ec2/test/instance.test.ts index a3a389d94aa9d..4a0f40ae1df8f 100644 --- a/packages/@aws-cdk/aws-ec2/test/instance.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/instance.test.ts @@ -314,8 +314,8 @@ describe('instance', () => { }); // THEN - expect(instance.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(instance.node.metadata[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + expect(instance.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); + expect(instance.node.metadataEntry[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); }); @@ -337,8 +337,8 @@ describe('instance', () => { }); // THEN - expect(instance.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(instance.node.metadata[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + expect(instance.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); + expect(instance.node.metadataEntry[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); }); diff --git a/packages/@aws-cdk/aws-ec2/test/launch-template.test.ts b/packages/@aws-cdk/aws-ec2/test/launch-template.test.ts index 6243a409bc007..6196a60541a12 100644 --- a/packages/@aws-cdk/aws-ec2/test/launch-template.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/launch-template.test.ts @@ -566,7 +566,7 @@ describe('LaunchTemplate marketOptions', () => { }); // THEN - expect(template.node.metadata).toHaveLength(expectedErrors); + expect(template.node.metadataEntry).toHaveLength(expectedErrors); }); test('for bad duration', () => { diff --git a/packages/@aws-cdk/aws-ec2/test/security-group.test.ts b/packages/@aws-cdk/aws-ec2/test/security-group.test.ts index 70dbe64cef335..c7c3662f8592f 100644 --- a/packages/@aws-cdk/aws-ec2/test/security-group.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/security-group.test.ts @@ -1,4 +1,5 @@ import '@aws-cdk/assert-internal/jest'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, Intrinsic, Lazy, Stack, Token } from '@aws-cdk/core'; import { Peer, Port, SecurityGroup, SecurityGroupProps, Vpc } from '../lib'; @@ -336,7 +337,7 @@ describe('security group', () => { }); }); - test('can look up a security group', () => { + testDeprecated('can look up a security group', () => { const app = new App(); const stack = new Stack(app, 'stack', { env: { @@ -350,8 +351,131 @@ describe('security group', () => { expect(securityGroup.securityGroupId).toEqual('sg-12345'); expect(securityGroup.allowAllOutbound).toEqual(true); + }); + + test('can look up a security group by id', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack', { + env: { + account: '1234', + region: 'us-east-1', + }, + }); + + // WHEN + const securityGroup = SecurityGroup.fromLookupById(stack, 'SG1', 'sg-12345'); + + // THEN + expect(securityGroup.securityGroupId).toEqual('sg-12345'); + expect(securityGroup.allowAllOutbound).toEqual(true); + + }); + + test('can look up a security group by name and vpc', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack', { + env: { + account: '1234', + region: 'us-east-1', + }, + }); + + const vpc = Vpc.fromVpcAttributes(stack, 'VPC', { + vpcId: 'vpc-1234', + availabilityZones: ['dummy1a', 'dummy1b', 'dummy1c'], + }); + + // WHEN + const securityGroup = SecurityGroup.fromLookupByName(stack, 'SG1', 'sg-12345', vpc); + + // THEN + expect(securityGroup.securityGroupId).toEqual('sg-12345'); + expect(securityGroup.allowAllOutbound).toEqual(true); }); + + test('can look up a security group by id and vpc', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack', { + env: { + account: '1234', + region: 'us-east-1', + }, + }); + + const vpc = Vpc.fromVpcAttributes(stack, 'VPC', { + vpcId: 'vpc-1234', + availabilityZones: ['dummy1a', 'dummy1b', 'dummy1c'], + }); + + // WHEN + const securityGroup = SecurityGroup.fromLookupByName(stack, 'SG1', 'my-security-group', vpc); + + // THEN + expect(securityGroup.securityGroupId).toEqual('sg-12345'); + expect(securityGroup.allowAllOutbound).toEqual(true); + + }); + + test('throws if securityGroupId is tokenized', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack', { + env: { + account: '1234', + region: 'us-east-1', + }, + }); + + // WHEN + expect(() => { + SecurityGroup.fromLookupById(stack, 'stack', Lazy.string({ produce: () => 'sg-12345' })); + }).toThrow('All arguments to look up a security group must be concrete (no Tokens)'); + + }); + + test('throws if securityGroupName is tokenized', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack', { + env: { + account: '1234', + region: 'us-east-1', + }, + }); + + // WHEN + expect(() => { + SecurityGroup.fromLookupById(stack, 'stack', Lazy.string({ produce: () => 'my-security-group' })); + }).toThrow('All arguments to look up a security group must be concrete (no Tokens)'); + + }); + + test('throws if vpc id is tokenized', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack', { + env: { + account: '1234', + region: 'us-east-1', + }, + }); + + const vpc = Vpc.fromVpcAttributes(stack, 'VPC', { + vpcId: Lazy.string({ produce: () => 'vpc-1234' }), + availabilityZones: ['dummy1a', 'dummy1b', 'dummy1c'], + }); + + // WHEN + expect(() => { + SecurityGroup.fromLookupByName(stack, 'stack', 'my-security-group', vpc); + }).toThrow('All arguments to look up a security group must be concrete (no Tokens)'); + + }); + }); function testRulesAreInlined(contextDisableInlineRules: boolean | undefined | null, optionsDisableInlineRules: boolean | undefined) { diff --git a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts index eedc950ab584b..b7922e877c64b 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import { isSuperObject, MatchStyle, SynthUtils } from '@aws-cdk/assert-internal'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { CfnOutput, CfnResource, Fn, Lazy, Stack, Tags } from '@aws-cdk/core'; import { AclCidr, @@ -585,6 +586,31 @@ describe('vpc', () => { }); + test('EIP passed with NAT gateway does not create duplicate EIP', () => { + const stack = getTestStack(); + new Vpc(stack, 'VPC', { + cidr: '10.0.0.0/16', + subnetConfiguration: [ + { + cidrMask: 24, + name: 'ingress', + subnetType: SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'application', + subnetType: SubnetType.PRIVATE_WITH_NAT, + }, + ], + natGatewayProvider: NatProvider.gateway({ eipAllocationIds: ['b'] }), + natGateways: 1, + }); + expect(stack).toCountResources('AWS::EC2::EIP', 0); + expect(stack).toHaveResource('AWS::EC2::NatGateway', { + AllocationId: 'b', + }); + }); + test('with mis-matched nat and subnet configs it throws', () => { const stack = getTestStack(); expect(() => new Vpc(stack, 'VPC', { @@ -997,7 +1023,7 @@ describe('vpc', () => { }); - test('can configure Security Groups of NAT instances with allowAllTraffic false', () => { + testDeprecated('can configure Security Groups of NAT instances with allowAllTraffic false', () => { // GIVEN const stack = getTestStack(); diff --git a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts index e579baf55ac41..78e706587dd16 100644 --- a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts +++ b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as ecr from '@aws-cdk/aws-ecr'; -import { Annotations, AssetStaging, FeatureFlags, FileFingerprintOptions, IgnoreMode, Stack, SymlinkFollowMode, Token, Stage } from '@aws-cdk/core'; +import { Annotations, AssetStaging, FeatureFlags, FileFingerprintOptions, IgnoreMode, Stack, SymlinkFollowMode, Token, Stage, CfnResource } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; @@ -147,6 +147,30 @@ export class DockerImageAsset extends CoreConstruct implements IAsset { */ public readonly assetHash: string; + /** + * The path to the asset, relative to the current Cloud Assembly + * + * If asset staging is disabled, this will just be the original path. + * + * If asset staging is enabled it will be the staged path. + */ + private readonly assetPath: string; + + /** + * The path to the Dockerfile, relative to the assetPath + */ + private readonly dockerfilePath?: string; + + /** + * Build args to pass to the `docker build` command. + */ + private readonly dockerBuildArgs?: { [key: string]: string }; + + /** + * Docker target to build to + */ + private readonly dockerBuildTarget?: string; + constructor(scope: Construct, id: string, props: DockerImageAssetProps) { super(scope, id); @@ -160,7 +184,8 @@ export class DockerImageAsset extends CoreConstruct implements IAsset { } // validate the docker file exists - const file = path.join(dir, props.file || 'Dockerfile'); + this.dockerfilePath = props.file || 'Dockerfile'; + const file = path.join(dir, this.dockerfilePath); if (!fs.existsSync(file)) { throw new Error(`Cannot find file at ${file}`); } @@ -223,10 +248,14 @@ export class DockerImageAsset extends CoreConstruct implements IAsset { this.assetHash = staging.assetHash; const stack = Stack.of(this); + this.assetPath = staging.relativeStagedPath(stack); + this.dockerBuildArgs = props.buildArgs; + this.dockerBuildTarget = props.target; + const location = stack.synthesizer.addDockerImageAsset({ - directoryName: staging.relativeStagedPath(stack), - dockerBuildArgs: props.buildArgs, - dockerBuildTarget: props.target, + directoryName: this.assetPath, + dockerBuildArgs: this.dockerBuildArgs, + dockerBuildTarget: this.dockerBuildTarget, dockerFile: props.file, sourceHash: staging.assetHash, }); @@ -234,6 +263,38 @@ export class DockerImageAsset extends CoreConstruct implements IAsset { this.repository = ecr.Repository.fromRepositoryName(this, 'Repository', location.repositoryName); this.imageUri = location.imageUri; } + + /** + * Adds CloudFormation template metadata to the specified resource with + * information that indicates which resource property is mapped to this local + * asset. This can be used by tools such as SAM CLI to provide local + * experience such as local invocation and debugging of Lambda functions. + * + * Asset metadata will only be included if the stack is synthesized with the + * "aws:cdk:enable-asset-metadata" context key defined, which is the default + * behavior when synthesizing via the CDK Toolkit. + * + * @see https://github.com/aws/aws-cdk/issues/1432 + * + * @param resource The CloudFormation resource which is using this asset [disable-awslint:ref-via-interface] + * @param resourceProperty The property name where this asset is referenced + */ + public addResourceMetadata(resource: CfnResource, resourceProperty: string) { + if (!this.node.tryGetContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT)) { + return; // not enabled + } + + // tell tools such as SAM CLI that the resourceProperty of this resource + // points to a local path and include the path to de dockerfile, docker build args, and target, + // in order to enable local invocation of this function. + resource.cfnOptions.metadata = resource.cfnOptions.metadata || { }; + resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PATH_KEY] = this.assetPath; + resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKERFILE_PATH_KEY] = this.dockerfilePath; + resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_ARGS_KEY] = this.dockerBuildArgs; + resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_TARGET_KEY] = this.dockerBuildTarget; + resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY] = resourceProperty; + } + } function validateProps(props: DockerImageAssetProps) { diff --git a/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts b/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts index e08ef41d2d868..a42fd2451d8e3 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts +++ b/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts @@ -2,10 +2,10 @@ import * as fs from 'fs'; import * as path from 'path'; import { expect as ourExpect, haveResource } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; +import { describeDeprecated, testDeprecated, testFutureBehavior } from '@aws-cdk/cdk-build-tools'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { App, DefaultStackSynthesizer, IgnoreMode, Lazy, LegacyStackSynthesizer, Stack, Stage } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import { DockerImageAsset } from '../lib'; /* eslint-disable quote-props */ @@ -50,7 +50,7 @@ describe('image asset', () => { }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); expect(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).buildArgs).toEqual({ a: 'b' }); }); @@ -126,7 +126,7 @@ describe('image asset', () => { }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); expect(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).target).toEqual('a-target'); }); @@ -142,7 +142,7 @@ describe('image asset', () => { }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); expect(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).file).toEqual('Dockerfile.Custom'); }); @@ -256,12 +256,20 @@ describe('image asset', () => { }); - testFutureBehavior('docker directory is staged without files specified in .dockerignore', flags, App, (app) => { - testDockerDirectoryIsStagedWithoutFilesSpecifiedInDockerignore(app); - }); + describeDeprecated('docker ignore option', () => { + // The 'ignoreMode' property is both deprecated and not deprecated in DockerImageAssetProps interface. + // The interface through a complex set of inheritance chain has a 'ignoreMode' prop that is deprecated + // and another 'ignoreMode' prop that is not deprecated. + // Using a 'describeDeprecated' block here since there's no way to work around this craziness. + // When the deprecated property is removed source code, this block can be dropped. + + testFutureBehavior('docker directory is staged without files specified in .dockerignore', flags, App, (app) => { + testDockerDirectoryIsStagedWithoutFilesSpecifiedInDockerignore(app); + }); - testFutureBehavior('docker directory is staged without files specified in .dockerignore with IgnoreMode.GLOB', flags, App, (app) => { - testDockerDirectoryIsStagedWithoutFilesSpecifiedInDockerignore(app, IgnoreMode.GLOB); + testFutureBehavior('docker directory is staged without files specified in .dockerignore with IgnoreMode.GLOB', flags, App, (app) => { + testDockerDirectoryIsStagedWithoutFilesSpecifiedInDockerignore(app, IgnoreMode.GLOB); + }); }); testFutureBehavior('docker directory is staged with allow-listed files specified in .dockerignore', flags, App, (app) => { @@ -315,7 +323,7 @@ describe('image asset', () => { }); - test('fails if using token as repositoryName', () => { + testDeprecated('fails if using token as repositoryName', () => { // GIVEN const stack = new Stack(); const token = Lazy.string({ produce: () => 'foo' }); @@ -340,7 +348,6 @@ describe('image asset', () => { const asset4 = new DockerImageAsset(stack, 'Asset4', { directory, buildArgs: { opt1: '123', opt2: 'boom' } }); const asset5 = new DockerImageAsset(stack, 'Asset5', { directory, file: 'Dockerfile.Custom', target: 'NonDefaultTarget' }); const asset6 = new DockerImageAsset(stack, 'Asset6', { directory, extraHash: 'random-extra' }); - const asset7 = new DockerImageAsset(stack, 'Asset7', { directory, repositoryName: 'foo' }); expect(asset1.assetHash).toEqual('365b5d951fc5f725f78093a07e3e1cc7819b4cbe582ca71a4c344752c23bf409'); expect(asset2.assetHash).toEqual('9560a36f786f317c5e1abb986b58269b2453ed1cab16c36fd9b76646c837078c'); @@ -348,9 +355,19 @@ describe('image asset', () => { expect(asset4.assetHash).toEqual('72b961f96e358b8dad935719cfc2704c3d14a46434871825ac81e3b94caa4853'); expect(asset5.assetHash).toEqual('c23d34b3a1dac5a80c42e8fa6c88a0ac697eb709a6f36ebdb6e36ee8c75edc75'); expect(asset6.assetHash).toEqual('7e950a9b08c58d371c1658e04d377c0ec59d89a47fc245a86a50525b36a8949b'); - expect(asset7.assetHash).toEqual('313dd1f45a939b77fa8a4eb7780190aa7a20a40c95f503eca9e099186643d717'); }); + + testDeprecated('repositoryName is included in the asset id', () => { + const stack = new Stack(); + const directory = path.join(__dirname, 'demo-image-custom-docker-file'); + + const asset1 = new DockerImageAsset(stack, 'Asset1', { directory }); + const asset2 = new DockerImageAsset(stack, 'Asset2', { directory, repositoryName: 'foo' }); + + expect(asset1.assetHash).toEqual('b5d181eb114c889020f9d59961ac4ad5d65f49c571c0aafd5ce2be9464bc2d13'); + expect(asset2.assetHash).toEqual('0b48fa3f7f75365962e6e18f52608ec4e4451f8ecc0b58abdb063c5381569471'); + }); }); function testDockerDirectoryIsStagedWithoutFilesSpecifiedInDockerignore(app: App, ignoreMode?: IgnoreMode) { diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index 0511290e113a5..74df965ff5d58 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -1,7 +1,7 @@ import { EOL } from 'os'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; -import { IResource, Lazy, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, IResource, Lazy, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; import { IConstruct, Construct } from 'constructs'; import { CfnRepository } from './ecr.generated'; import { LifecycleRule, TagStatus } from './lifecycle'; @@ -167,7 +167,7 @@ export abstract class RepositoryBase extends Resource implements IRepository { * @private */ private repositoryUriWithSuffix(suffix?: string): string { - const parts = this.stack.parseArn(this.repositoryArn); + const parts = this.stack.splitArn(this.repositoryArn, ArnFormat.SLASH_RESOURCE_NAME); return `${parts.account}.dkr.ecr.${parts.region}.${this.stack.urlSuffix}/${this.repositoryName}${suffix}`; } diff --git a/packages/@aws-cdk/aws-ecr/package.json b/packages/@aws-cdk/aws-ecr/package.json index 704de9272f366..c9039866abcc0 100644 --- a/packages/@aws-cdk/aws-ecr/package.json +++ b/packages/@aws-cdk/aws-ecr/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts index e2cafab14e71a..f9a18d7c88bde 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts @@ -4,7 +4,14 @@ import { AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerDefinition, ContainerImage, ICluster, LogDriver, PropagatedTagSource, Protocol, Secret, } from '@aws-cdk/aws-ecs'; -import { ApplicationListener, ApplicationLoadBalancer, ApplicationProtocol, ApplicationTargetGroup, SslPolicy } from '@aws-cdk/aws-elasticloadbalancingv2'; +import { + ApplicationListener, + ApplicationLoadBalancer, + ApplicationProtocol, + ApplicationTargetGroup, ListenerCertificate, + ListenerCondition, + SslPolicy, +} from '@aws-cdk/aws-elasticloadbalancingv2'; import { IRole } from '@aws-cdk/aws-iam'; import { ARecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53'; import { LoadBalancerTarget } from '@aws-cdk/aws-route53-targets'; @@ -469,6 +476,14 @@ export abstract class ApplicationMultipleTargetGroupsServiceBase extends CoreCon protected registerECSTargets(service: BaseService, container: ContainerDefinition, targets: ApplicationTargetProps[]): ApplicationTargetGroup { for (const targetProps of targets) { + const conditions: Array = []; + if (targetProps.hostHeader) { + conditions.push(ListenerCondition.hostHeaders([targetProps.hostHeader])); + } + if (targetProps.pathPattern) { + conditions.push(ListenerCondition.pathPatterns([targetProps.pathPattern])); + } + const targetGroup = this.findListener(targetProps.listener).addTargets(`ECSTargetGroup${container.containerName}${targetProps.containerPort}`, { port: 80, targets: [ @@ -478,8 +493,7 @@ export abstract class ApplicationMultipleTargetGroupsServiceBase extends CoreCon protocol: targetProps.protocol, }), ], - hostHeader: targetProps.hostHeader, - pathPattern: targetProps.pathPattern, + conditions, priority: targetProps.priority, }); this.targetGroups.push(targetGroup); @@ -519,7 +533,7 @@ export abstract class ApplicationMultipleTargetGroupsServiceBase extends CoreCon certificate = undefined; } if (certificate !== undefined) { - listener.addCertificateArns(`Arns${props.listenerName}`, [certificate.certificateArn]); + listener.addCertificates(`Arns${props.listenerName}`, [ListenerCertificate.fromArn(certificate.certificateArn)]); } return listener; diff --git a/packages/@aws-cdk/aws-ecs-patterns/package.json b/packages/@aws-cdk/aws-ecs-patterns/package.json index e399ff853d46b..96a5eccecc75f 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/package.json +++ b/packages/@aws-cdk/aws-ecs-patterns/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.multiple-application-load-balanced-ecs-service.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.multiple-application-load-balanced-ecs-service.expected.json index 4d4e00fe664e4..2364b79c8b0b5 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.multiple-application-load-balanced-ecs-service.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.multiple-application-load-balanced-ecs-service.expected.json @@ -997,9 +997,11 @@ "Conditions": [ { "Field": "path-pattern", - "Values": [ - "a/b/c" - ] + "PathPatternConfig": { + "Values": [ + "a/b/c" + ] + } } ], "ListenerArn": { diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s-v2.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s-v2.test.ts index 575ab92b40cfb..767c7a13f0df6 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s-v2.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s-v2.test.ts @@ -1,8 +1,18 @@ import { SynthUtils } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; import { Certificate } from '@aws-cdk/aws-certificatemanager'; -import { InstanceType, Vpc } from '@aws-cdk/aws-ec2'; -import { AwsLogDriver, Cluster, ContainerImage, Ec2TaskDefinition, PropagatedTagSource, Protocol } from '@aws-cdk/aws-ecs'; +import { MachineImage, Vpc } from '@aws-cdk/aws-ec2'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import { + AsgCapacityProvider, + AwsLogDriver, + Cluster, + ContainerImage, + Ec2TaskDefinition, + PropagatedTagSource, + Protocol, +} from '@aws-cdk/aws-ecs'; import { ApplicationProtocol, SslPolicy } from '@aws-cdk/aws-elasticloadbalancingv2'; import { CompositePrincipal, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import { PublicHostedZone } from '@aws-cdk/aws-route53'; @@ -16,7 +26,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ApplicationMultipleTargetGroupsEc2Service(stack, 'Service', { @@ -74,7 +90,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); // WHEN @@ -273,7 +295,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const taskDefinition = new Ec2TaskDefinition(stack, 'Ec2TaskDef'); const container = taskDefinition.addContainer('web', { @@ -320,7 +348,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); // WHEN @@ -442,7 +476,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const taskDefinition = new Ec2TaskDefinition(stack, 'Ec2TaskDef'); @@ -530,7 +570,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'MyVpc', {}); const cluster = new Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN cluster.addDefaultCloudMapNamespace({ @@ -599,7 +645,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const taskDefinition = new Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('test', { @@ -624,7 +676,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => { @@ -639,7 +697,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => { @@ -817,7 +881,13 @@ describe('When Application Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => @@ -838,7 +908,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new NetworkMultipleTargetGroupsEc2Service(stack, 'Service', { @@ -910,7 +986,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); // WHEN @@ -1104,7 +1186,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const taskDefinition = new Ec2TaskDefinition(stack, 'Ec2TaskDef'); const container = taskDefinition.addContainer('web', { @@ -1151,7 +1239,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const taskDefinition = new Ec2TaskDefinition(stack, 'Ec2TaskDef'); @@ -1237,7 +1331,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'MyVpc', {}); const cluster = new Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN cluster.addDefaultCloudMapNamespace({ @@ -1306,7 +1406,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const taskDefinition = new Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('test', { @@ -1331,7 +1437,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => { @@ -1346,7 +1458,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => { @@ -1465,7 +1583,13 @@ describe('When Network Load Balancer', () => { const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s.test.ts index 6248c9c4d14dd..c8f29cfbc695c 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s.test.ts @@ -1,8 +1,11 @@ import { ABSENT, arrayWith, objectLike, SynthUtils } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; import { Certificate } from '@aws-cdk/aws-certificatemanager'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { MachineImage } from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; +import { AsgCapacityProvider } from '@aws-cdk/aws-ecs'; import { ApplicationLoadBalancer, ApplicationProtocol, ApplicationProtocolVersion, NetworkLoadBalancer, SslPolicy } from '@aws-cdk/aws-elasticloadbalancingv2'; import { PublicHostedZone } from '@aws-cdk/aws-route53'; import * as cloudmap from '@aws-cdk/aws-servicediscovery'; @@ -15,7 +18,13 @@ test('test ECS loadbalanced construct', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { @@ -70,7 +79,13 @@ test('ApplicationLoadBalancedEc2Service desiredCount can be undefined when featu const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { @@ -114,7 +129,13 @@ test('NetworkLoadBalancedEc2Service desiredCount can be undefined when feature f const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { @@ -197,7 +218,13 @@ test('test ECS loadbalanced construct with memoryReservationMiB', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { @@ -225,7 +252,13 @@ test('creates AWS Cloud Map service for Private DNS namespace with application l const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN cluster.addDefaultCloudMapNamespace({ @@ -295,7 +328,13 @@ test('creates AWS Cloud Map service for Private DNS namespace with network load const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN cluster.addDefaultCloudMapNamespace({ @@ -851,7 +890,13 @@ test('ALB - throws if desiredTaskCount is 0', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => @@ -871,7 +916,13 @@ test('NLB - throws if desiredTaskCount is 0', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => @@ -943,7 +994,13 @@ test('ALB - having *HealthyPercent properties', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { @@ -971,7 +1028,13 @@ test('ALB - includes provided protocol version properties', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); // WHEN @@ -999,7 +1062,13 @@ test('NLB - having *HealthyPercent properties', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { @@ -1027,7 +1096,13 @@ test('ALB - having deployment controller', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { @@ -1054,7 +1129,13 @@ test('NLB - having deployment controller', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { @@ -1081,7 +1162,13 @@ test('ALB with circuit breaker', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { @@ -1112,7 +1199,13 @@ test('NLB with circuit breaker', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { @@ -1143,7 +1236,13 @@ test('NetworkLoadbalancedEC2Service accepts previously created load balancer', ( const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); - cluster.addCapacity('Capacity', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const nlb = new NetworkLoadBalancer(stack, 'NLB', { vpc }); const taskDef = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); const container = taskDef.addContainer('Container', { @@ -1174,7 +1273,13 @@ test('NetworkLoadBalancedEC2Service accepts imported load balancer', () => { const nlbArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const vpc = new ec2.Vpc(stack, 'Vpc'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); - cluster.addCapacity('Capacity', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const nlb = NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'NLB', { loadBalancerArn: nlbArn, vpc, @@ -1213,7 +1318,13 @@ test('ApplicationLoadBalancedEC2Service accepts previously created load balancer const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); - cluster.addCapacity('Capacity', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); const alb = new ApplicationLoadBalancer(stack, 'NLB', { vpc, @@ -1248,7 +1359,13 @@ test('ApplicationLoadBalancedEC2Service accepts imported load balancer', () => { const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const vpc = new ec2.Vpc(stack, 'Vpc'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); - cluster.addCapacity('Capacity', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); const alb = ApplicationLoadBalancer.fromApplicationLoadBalancerAttributes(stack, 'ALB', { loadBalancerArn: albArn, @@ -1287,7 +1404,13 @@ test('test ECS loadbalanced construct default/open security group', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { @@ -1314,7 +1437,13 @@ test('test ECS loadbalanced construct closed security group', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); // WHEN diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/queue-processing-ecs-service.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/queue-processing-ecs-service.test.ts index 60e6a7d0f6969..9a64cfd40428e 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/queue-processing-ecs-service.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/queue-processing-ecs-service.test.ts @@ -1,9 +1,13 @@ import { ABSENT } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; import * as autoscaling from '@aws-cdk/aws-autoscaling'; +import { MachineImage } from '@aws-cdk/aws-ec2'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { AsgCapacityProvider } from '@aws-cdk/aws-ecs'; import * as ecs from '@aws-cdk/aws-ecs'; import * as sqs from '@aws-cdk/aws-sqs'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import * as ecsPatterns from '../../lib'; @@ -13,7 +17,13 @@ test('test ECS queue worker service construct - with only required props', () => const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingEc2Service(stack, 'Service', { @@ -86,7 +96,13 @@ test('test ECS queue worker service construct - with remove default desiredCount const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingEc2Service(stack, 'Service', { @@ -107,7 +123,13 @@ test('test ECS queue worker service construct - with optional props for queues', const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingEc2Service(stack, 'Service', { @@ -177,12 +199,18 @@ test('test ECS queue worker service construct - with optional props for queues', }); }); -test('test ECS queue worker service construct - with optional props', () => { +testDeprecated('test ECS queue worker service construct - with optional props', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const queue = new sqs.Queue(stack, 'ecs-test-queue', { queueName: 'ecs-test-sqs-queue', }); @@ -272,12 +300,18 @@ test('test ECS queue worker service construct - with optional props', () => { }); }); -test('can set desiredTaskCount to 0', () => { +testDeprecated('can set desiredTaskCount to 0', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingEc2Service(stack, 'Service', { @@ -295,12 +329,18 @@ test('can set desiredTaskCount to 0', () => { }); }); -test('throws if desiredTaskCount and maxScalingCapacity are 0', () => { +testDeprecated('throws if desiredTaskCount and maxScalingCapacity are 0', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => @@ -318,7 +358,13 @@ test('can set custom containerName', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingEc2Service(stack, 'Service', { diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts index 745d6a17460bc..9524d7a4d145c 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts @@ -1,6 +1,9 @@ import '@aws-cdk/assert-internal/jest'; +import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { MachineImage } from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; +import { AsgCapacityProvider } from '@aws-cdk/aws-ecs'; import * as events from '@aws-cdk/aws-events'; import * as cdk from '@aws-cdk/core'; import { ScheduledEc2Task } from '../../lib'; @@ -10,9 +13,14 @@ test('Can create a scheduled Ec2 Task - with only required props', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); new ScheduledEc2Task(stack, 'ScheduledEc2Task', { cluster, @@ -70,9 +78,13 @@ test('Can create a scheduled Ec2 Task - with optional props', () => { const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); new ScheduledEc2Task(stack, 'ScheduledEc2Task', { cluster, @@ -195,9 +207,13 @@ test('Scheduled Ec2 Task - with MemoryReservation defined', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); new ScheduledEc2Task(stack, 'ScheduledEc2Task', { cluster, @@ -238,9 +254,13 @@ test('Scheduled Ec2 Task - with Command defined', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); new ScheduledEc2Task(stack, 'ScheduledEc2Task', { cluster, @@ -287,9 +307,13 @@ test('throws if desiredTaskCount is 0', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // THEN expect(() => diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts index 765d86788277e..0f4c0ab29ba86 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts @@ -1,8 +1,11 @@ import { SynthUtils } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; import { DnsValidatedCertificate } from '@aws-cdk/aws-certificatemanager'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { MachineImage } from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; +import { AsgCapacityProvider } from '@aws-cdk/aws-ecs'; import { ApplicationLoadBalancer, ApplicationProtocol, NetworkLoadBalancer } from '@aws-cdk/aws-elasticloadbalancingv2'; import * as iam from '@aws-cdk/aws-iam'; import * as route53 from '@aws-cdk/aws-route53'; @@ -318,7 +321,13 @@ test('test load balanced service with family defined', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts index 916bcb43cdf63..6f91b633d1249 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts @@ -1,8 +1,12 @@ import { ABSENT } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; +import { MachineImage } from '@aws-cdk/aws-ec2'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { AsgCapacityProvider } from '@aws-cdk/aws-ecs'; import * as ecs from '@aws-cdk/aws-ecs'; import * as sqs from '@aws-cdk/aws-sqs'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import * as ecsPatterns from '../../lib'; @@ -12,7 +16,13 @@ test('test fargate queue worker service construct - with only required props', ( const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingFargateService(stack, 'Service', { @@ -127,7 +137,13 @@ test('test fargate queue worker service construct - with optional props for queu const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingFargateService(stack, 'Service', { @@ -224,7 +240,13 @@ test('test Fargate queue worker service construct - without desiredCount specifi const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const queue = new sqs.Queue(stack, 'fargate-test-queue', { queueName: 'fargate-test-sqs-queue', }); @@ -308,12 +330,18 @@ test('test Fargate queue worker service construct - without desiredCount specifi }); }); -test('test Fargate queue worker service construct - with optional props', () => { +testDeprecated('test Fargate queue worker service construct - with optional props', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); const queue = new sqs.Queue(stack, 'fargate-test-queue', { queueName: 'fargate-test-sqs-queue', }); @@ -400,7 +428,13 @@ test('can set custom containerName', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); // WHEN new ecsPatterns.QueueProcessingFargateService(stack, 'Service', { diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 6a27855e031f5..2c4f04e64b257 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -427,6 +427,25 @@ The task execution role is automatically granted read permissions on the secrets files is restricted to the EC2 launch type for files hosted on S3. Further details provided in the AWS documentation about [specifying environment variables](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/taskdef-envfiles.html). +### System controls + +To set system controls (kernel parameters) on the container, use the `systemControls` prop: + +```ts +declare const taskDefinition: ecs.TaskDefinition; + +taskDefinition.addContainer('container', { + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), + memoryLimitMiB: 1024, + systemControls: [ + { + namespace: 'net', + value: 'ipv4.tcp_tw_recycle', + }, + ], +}); +``` + ## Service A `Service` instantiates a `TaskDefinition` on a `Cluster` a given number of 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 6bac2c663b82d..1809000064f12 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -750,7 +750,7 @@ export abstract class BaseService extends Resource return new cloudwatch.Metric({ namespace: 'AWS/ECS', metricName, - dimensions: { ClusterName: this.cluster.clusterName, ServiceName: this.serviceName }, + dimensionsMap: { ClusterName: this.cluster.clusterName, ServiceName: this.serviceName }, ...props, }).attachTo(this); } diff --git a/packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts b/packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts index cfaff590b3eb1..8dfc272300d41 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts @@ -1,4 +1,4 @@ -import { Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IBaseService } from '../base/base-service'; import { ICluster } from '../cluster'; @@ -47,7 +47,7 @@ export function fromServiceAtrributes(scope: Construct, id: string, attrs: Servi }); } else { arn = attrs.serviceArn as string; - name = stack.parseArn(arn).resourceName as string; + name = stack.splitArn(arn, ArnFormat.SLASH_RESOURCE_NAME).resourceName as string; } class Import extends Resource implements IBaseService { public readonly serviceArn = arn; @@ -57,4 +57,4 @@ export function fromServiceAtrributes(scope: Construct, id: string, attrs: Servi return new Import(scope, id, { environmentFromArn: arn, }); -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index aae5d320b20af..cf9e67960e10e 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -555,7 +555,7 @@ export class Cluster extends Resource implements ICluster { return new cloudwatch.Metric({ namespace: 'AWS/ECS', metricName, - dimensions: { ClusterName: this.clusterName }, + dimensionsMap: { ClusterName: this.clusterName }, ...props, }).attachTo(this); } @@ -1137,4 +1137,4 @@ class MaybeCreateCapacityProviderAssociations implements IAspect { function isBottleRocketImage(image: ec2.IMachineImage) { return image instanceof BottleRocketImage; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts index f817a5280315e..625041468a0a4 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts @@ -307,6 +307,15 @@ export interface ContainerDefinitionOptions { * @default - No inference accelerators assigned. */ readonly inferenceAcceleratorResources?: string[]; + + /** + * A list of namespaced kernel parameters to set in the container. + * + * @default - No system controls are set. + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-systemcontrol.html + * @see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definition_systemcontrols + */ + readonly systemControls?: SystemControl[]; } /** @@ -669,6 +678,7 @@ export class ContainerDefinition extends CoreConstruct { linuxParameters: this.linuxParameters && this.linuxParameters.renderLinuxParameters(), resourceRequirements: (!this.props.gpuCount && this.inferenceAcceleratorResources.length == 0 ) ? undefined : renderResourceRequirements(this.props.gpuCount, this.inferenceAcceleratorResources), + systemControls: this.props.systemControls && renderSystemControls(this.props.systemControls), }; } } @@ -1040,3 +1050,25 @@ function renderVolumeFrom(vf: VolumeFrom): CfnTaskDefinition.VolumeFromProperty readOnly: vf.readOnly, }; } + +/** + * Kernel parameters to set in the container + */ +export interface SystemControl { + /** + * The namespaced kernel parameter for which to set a value. + */ + readonly namespace: string; + + /** + * The value for the namespaced kernel parameter specified in namespace. + */ + readonly value: string; +} + +function renderSystemControls(systemControls: SystemControl[]): CfnTaskDefinition.SystemControlProperty[] { + return systemControls.map(sc => ({ + namespace: sc.namespace, + value: sc.value, + })); +} diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index 0a98809dff403..eca66a4db6ff3 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -1,5 +1,5 @@ import * as ec2 from '@aws-cdk/aws-ec2'; -import { Lazy, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { BaseService, BaseServiceOptions, DeploymentControllerType, IBaseService, IService, LaunchType } from '../base/base-service'; import { fromServiceAtrributes } from '../base/from-service-attributes'; @@ -128,7 +128,7 @@ export class Ec2Service extends BaseService implements IEc2Service { public static fromEc2ServiceArn(scope: Construct, id: string, ec2ServiceArn: string): IEc2Service { class Import extends Resource implements IEc2Service { public readonly serviceArn = ec2ServiceArn; - public readonly serviceName = Stack.of(scope).parseArn(ec2ServiceArn).resourceName as string; + public readonly serviceName = Stack.of(scope).splitArn(ec2ServiceArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName as string; } return new Import(scope, id); } diff --git a/packages/@aws-cdk/aws-ecs/lib/external/external-service.ts b/packages/@aws-cdk/aws-ecs/lib/external/external-service.ts index 2c1c6c9f59534..9bb1eaf0b8cef 100644 --- a/packages/@aws-cdk/aws-ecs/lib/external/external-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/external/external-service.ts @@ -2,7 +2,7 @@ import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import * as cloudmap from '@aws-cdk/aws-servicediscovery'; -import { Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { AssociateCloudMapServiceOptions, BaseService, BaseServiceOptions, CloudMapOptions, DeploymentControllerType, EcsTarget, IBaseService, IEcsLoadBalancerTarget, IService, LaunchType, PropagatedTagSource } from '../base/base-service'; import { fromServiceAtrributes } from '../base/from-service-attributes'; @@ -73,7 +73,7 @@ export class ExternalService extends BaseService implements IExternalService { public static fromExternalServiceArn(scope: Construct, id: string, externalServiceArn: string): IExternalService { class Import extends Resource implements IExternalService { public readonly serviceArn = externalServiceArn; - public readonly serviceName = Stack.of(scope).parseArn(externalServiceArn).resourceName as string; + public readonly serviceName = Stack.of(scope).splitArn(externalServiceArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName as string; } return new Import(scope, id); } @@ -187,4 +187,4 @@ export class ExternalService extends BaseService implements IExternalService { public associateCloudMapService(_options: AssociateCloudMapServiceOptions): void { throw new Error ('Cloud map service association is not supported for an external service'); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts index a996aca37cc23..b654c87887dda 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts @@ -1,5 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; +import { ArnFormat } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { BaseService, BaseServiceOptions, DeploymentControllerType, IBaseService, IService, LaunchType } from '../base/base-service'; import { fromServiceAtrributes } from '../base/from-service-attributes'; @@ -104,7 +105,7 @@ export class FargateService extends BaseService implements IFargateService { public static fromFargateServiceArn(scope: Construct, id: string, fargateServiceArn: string): IFargateService { class Import extends cdk.Resource implements IFargateService { public readonly serviceArn = fargateServiceArn; - public readonly serviceName = cdk.Stack.of(scope).parseArn(fargateServiceArn).resourceName as string; + public readonly serviceName = cdk.Stack.of(scope).splitArn(fargateServiceArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName as string; } return new Import(scope, id); } diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index 7cdcc0b9b0934..dc6c57ffe9285 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts index 594a59d0380a0..51edbb3aca17d 100644 --- a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts @@ -6,12 +6,13 @@ import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as cloudmap from '@aws-cdk/aws-servicediscovery'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as ecs from '../lib'; describe('cluster', () => { describe('When creating an ECS Cluster', () => { - test('with no properties set, it correctly sets default properties', () => { + testDeprecated('with no properties set, it correctly sets default properties', () => { // GIVEN const stack = new cdk.Stack(); const cluster = new ecs.Cluster(stack, 'EcsCluster'); @@ -179,7 +180,7 @@ describe('cluster', () => { }); - test('with only vpc set, it correctly sets default properties', () => { + testDeprecated('with only vpc set, it correctly sets default properties', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -350,7 +351,7 @@ describe('cluster', () => { }); - test('multiple clusters with default capacity', () => { + testDeprecated('multiple clusters with default capacity', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -366,7 +367,7 @@ describe('cluster', () => { }); - test('lifecycle hook is automatically added', () => { + testDeprecated('lifecycle hook is automatically added', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -505,7 +506,7 @@ describe('cluster', () => { }); - test('lifecycle hook with encrypted SNS is added correctly', () => { + testDeprecated('lifecycle hook with encrypted SNS is added correctly', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -533,7 +534,7 @@ describe('cluster', () => { }); - test('with capacity and cloudmap namespace properties set', () => { + testDeprecated('with capacity and cloudmap namespace properties set', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -715,7 +716,7 @@ describe('cluster', () => { }); }); - test('allows specifying instance type', () => { + testDeprecated('allows specifying instance type', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -733,7 +734,7 @@ describe('cluster', () => { }); - test('allows specifying cluster size', () => { + testDeprecated('allows specifying cluster size', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -752,7 +753,7 @@ describe('cluster', () => { }); - test('configures userdata with powershell if windows machine image is specified', () => { + testDeprecated('configures userdata with powershell if windows machine image is specified', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -808,7 +809,7 @@ describe('cluster', () => { /* * TODO:v2.0.0 BEGINNING OF OBSOLETE BLOCK */ - test('allows specifying special HW AMI Type', () => { + testDeprecated('allows specifying special HW AMI Type', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -841,7 +842,7 @@ describe('cluster', () => { }); - test('errors if amazon linux given with special HW type', () => { + testDeprecated('errors if amazon linux given with special HW type', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -862,7 +863,7 @@ describe('cluster', () => { }); - test('allows specifying windows image', () => { + testDeprecated('allows specifying windows image', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -889,7 +890,7 @@ describe('cluster', () => { }); - test('errors if windows given with special HW type', () => { + testDeprecated('errors if windows given with special HW type', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -910,7 +911,7 @@ describe('cluster', () => { }); - test('errors if windowsVersion and linux generation are set', () => { + testDeprecated('errors if windowsVersion and linux generation are set', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -931,7 +932,7 @@ describe('cluster', () => { }); - test('allows returning the correct image for windows for EcsOptimizedAmi', () => { + testDeprecated('allows returning the correct image for windows for EcsOptimizedAmi', () => { // GIVEN const stack = new cdk.Stack(); const ami = new ecs.EcsOptimizedAmi({ @@ -943,7 +944,7 @@ describe('cluster', () => { }); - test('allows returning the correct image for linux for EcsOptimizedAmi', () => { + testDeprecated('allows returning the correct image for linux for EcsOptimizedAmi', () => { // GIVEN const stack = new cdk.Stack(); const ami = new ecs.EcsOptimizedAmi({ @@ -955,7 +956,7 @@ describe('cluster', () => { }); - test('allows returning the correct image for linux 2 for EcsOptimizedAmi', () => { + testDeprecated('allows returning the correct image for linux 2 for EcsOptimizedAmi', () => { // GIVEN const stack = new cdk.Stack(); const ami = new ecs.EcsOptimizedAmi({ @@ -1012,7 +1013,7 @@ describe('cluster', () => { * TODO:v2.0.0 END OF OBSOLETE BLOCK */ - test('allows specifying special HW AMI Type v2', () => { + testDeprecated('allows specifying special HW AMI Type v2', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1043,7 +1044,7 @@ describe('cluster', () => { }); - test('allows specifying Amazon Linux v1 AMI', () => { + testDeprecated('allows specifying Amazon Linux v1 AMI', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1074,7 +1075,7 @@ describe('cluster', () => { }); - test('allows specifying windows image v2', () => { + testDeprecated('allows specifying windows image v2', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1099,7 +1100,7 @@ describe('cluster', () => { }); - test('allows specifying spot fleet', () => { + testDeprecated('allows specifying spot fleet', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1118,7 +1119,7 @@ describe('cluster', () => { }); - test('allows specifying drain time', () => { + testDeprecated('allows specifying drain time', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1137,7 +1138,7 @@ describe('cluster', () => { }); - test('allows specifying automated spot draining', () => { + testDeprecated('allows specifying automated spot draining', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1170,7 +1171,7 @@ describe('cluster', () => { }); - test('allows containers access to instance metadata service', () => { + testDeprecated('allows containers access to instance metadata service', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1202,7 +1203,7 @@ describe('cluster', () => { }); - test('allows adding default service discovery namespace', () => { + testDeprecated('allows adding default service discovery namespace', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1228,7 +1229,7 @@ describe('cluster', () => { }); - test('allows adding public service discovery namespace', () => { + testDeprecated('allows adding public service discovery namespace', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1254,7 +1255,7 @@ describe('cluster', () => { }); - test('throws if default service discovery namespace added more than once', () => { + testDeprecated('throws if default service discovery namespace added more than once', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1380,7 +1381,7 @@ describe('cluster', () => { }); - test('ASG with a public VPC without NAT Gateways', () => { + testDeprecated('ASG with a public VPC without NAT Gateways', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyPublicVpc', { @@ -1580,7 +1581,7 @@ describe('cluster', () => { }); - test('cluster capacity with bottlerocket AMI, by setting machineImageType', () => { + testDeprecated('cluster capacity with bottlerocket AMI, by setting machineImageType', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1672,7 +1673,7 @@ describe('cluster', () => { }); - test('correct bottlerocket AMI for ARM64 architecture', () => { + testDeprecated('correct bottlerocket AMI for ARM64 architecture', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1701,7 +1702,7 @@ describe('cluster', () => { }); - test('throws when machineImage and machineImageType both specified', () => { + testDeprecated('throws when machineImage and machineImageType both specified', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1732,7 +1733,7 @@ describe('cluster', () => { }); - test('allows specifying capacityProviders (deprecated)', () => { + testDeprecated('allows specifying capacityProviders (deprecated)', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1795,7 +1796,7 @@ describe('cluster', () => { }); - test('allows adding capacityProviders post-construction (deprecated)', () => { + testDeprecated('allows adding capacityProviders post-construction (deprecated)', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1817,7 +1818,7 @@ describe('cluster', () => { }); - test('allows adding capacityProviders post-construction', () => { + testDeprecated('allows adding capacityProviders post-construction', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -1839,7 +1840,7 @@ describe('cluster', () => { }); - test('throws for unsupported capacity providers', () => { + testDeprecated('throws for unsupported capacity providers', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'test'); @@ -2245,4 +2246,4 @@ test('can add ASG capacity via Capacity Provider by not specifying machineImageT DefaultCapacityProviderStrategy: [], }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts index a5153b82d331a..a384294900342 100644 --- a/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts @@ -85,6 +85,9 @@ describe('container definition', () => { secrets: { SECRET: ecs.Secret.fromSecretsManager(secret), }, + systemControls: [ + { namespace: 'SomeNamespace', value: 'SomeValue' }, + ], }); // THEN @@ -218,6 +221,12 @@ describe('container definition', () => { ], StartTimeout: 2, StopTimeout: 5, + SystemControls: [ + { + Namespace: 'SomeNamespace', + Value: 'SomeValue', + }, + ], User: 'rootUser', WorkingDirectory: 'a/b/c', }, @@ -753,6 +762,40 @@ describe('container definition', () => { }); }); + test('can specify system controls', () => { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + + // WHEN + taskDefinition.addContainer('cont', { + image: ecs.ContainerImage.fromRegistry('test'), + memoryLimitMiB: 1024, + systemControls: [ + { namespace: 'SomeNamespace1', value: 'SomeValue1' }, + { namespace: 'SomeNamespace2', value: 'SomeValue2' }, + ], + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + SystemControls: [ + { + Namespace: 'SomeNamespace1', + Value: 'SomeValue1', + }, + { + Namespace: 'SomeNamespace2', + Value: 'SomeValue2', + }, + ], + }, + ], + }); + }); + describe('Environment Files', () => { describe('with EC2 task definitions', () => { test('can add asset environment file to the container definition', () => { diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/cross-stack.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/cross-stack.test.ts index 7decd0047975e..aebf98820d885 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/cross-stack.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/cross-stack.test.ts @@ -3,6 +3,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import { App, Stack } from '@aws-cdk/core'; import * as ecs from '../../lib'; +import { addDefaultCapacityProvider } from '../util'; // Test various cross-stack Cluster/Service/ALB scenario's @@ -20,8 +21,8 @@ describe('cross stack', () => { const vpc = new ec2.Vpc(stack1, 'Vpc'); cluster = new ecs.Cluster(stack1, 'Cluster', { vpc, - capacity: { instanceType: new ec2.InstanceType('t2.micro') }, }); + addDefaultCapacityProvider(cluster, stack1, vpc); stack2 = new Stack(app, 'Stack2'); const taskDefinition = new ecs.Ec2TaskDefinition(stack2, 'TD'); @@ -94,6 +95,6 @@ function expectIngress(stack: Stack) { expect(stack).toHaveResource('AWS::EC2::SecurityGroupIngress', { FromPort: 32768, ToPort: 65535, - GroupId: { 'Fn::ImportValue': 'Stack1:ExportsOutputFnGetAttClusterDefaultAutoScalingGroupInstanceSecurityGroup1D15236AGroupIdEAB9C5E1' }, + GroupId: { 'Fn::ImportValue': 'Stack1:ExportsOutputFnGetAttDefaultAutoScalingGroupInstanceSecurityGroupFBA881D0GroupId2F7C804A' }, }); -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts index 21d4292346974..7e1f5532fc207 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts @@ -8,10 +8,12 @@ import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as cloudmap from '@aws-cdk/aws-servicediscovery'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as ecs from '../../lib'; import { DeploymentControllerType, LaunchType, PropagatedTagSource } from '../../lib/base/base-service'; import { PlacementConstraint, PlacementStrategy } from '../../lib/placement'; +import { addDefaultCapacityProvider } from '../util'; describe('ec2 service', () => { describe('When creating an EC2 Service', () => { @@ -20,7 +22,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -60,7 +62,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -145,7 +147,7 @@ describe('ec2 service', () => { logging: ecs.ExecuteCommandLogging.NONE, }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); const logGroup = new logs.LogGroup(stack, 'LogGroup'); @@ -214,7 +216,7 @@ describe('ec2 service', () => { logging: ecs.ExecuteCommandLogging.OVERRIDE, }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -334,7 +336,7 @@ describe('ec2 service', () => { }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -540,7 +542,7 @@ describe('ec2 service', () => { }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -780,7 +782,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); const container = taskDefinition.addContainer('web', { @@ -848,7 +850,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC, }); @@ -882,12 +884,12 @@ describe('ec2 service', () => { deploymentController: { type: ecs.DeploymentControllerType.CODE_DEPLOY, }, - securityGroup: new ec2.SecurityGroup(stack, 'SecurityGroup1', { + securityGroups: [new ec2.SecurityGroup(stack, 'SecurityGroup1', { allowAllOutbound: true, description: 'Example', securityGroupName: 'Bob', vpc, - }), + })], serviceName: 'bonjour', vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, }); @@ -1019,7 +1021,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC, }); @@ -1128,12 +1130,12 @@ describe('ec2 service', () => { }); - test('throws when both securityGroup and securityGroups are supplied', () => { + testDeprecated('throws when both securityGroup and securityGroups are supplied', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC, }); @@ -1203,7 +1205,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1220,7 +1222,7 @@ describe('ec2 service', () => { }); // THEN - expect(service.node.metadata[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); + expect(service.node.metadataEntry[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); expect(stack).toHaveResource('AWS::ECS::Service', { Cluster: { Ref: 'EcsCluster97242B84', @@ -1241,7 +1243,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('BaseContainer', { image: ecs.ContainerImage.fromRegistry('test'), @@ -1266,7 +1268,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('BaseContainer', { image: ecs.ContainerImage.fromRegistry('test'), @@ -1291,7 +1293,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('BaseContainer', { image: ecs.ContainerImage.fromRegistry('test'), @@ -1338,7 +1340,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); new ecs.Ec2Service(stack, 'FargateService', { @@ -1369,7 +1371,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1401,7 +1403,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.BRIDGE, }); @@ -1431,7 +1433,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.BRIDGE, }); @@ -1464,7 +1466,7 @@ describe('ec2 service', () => { cidrBlock: '10.10.0.0/20', }); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.BRIDGE, }); @@ -1494,7 +1496,7 @@ describe('ec2 service', () => { const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const securityGroup = new ec2.SecurityGroup(stack, 'MySG', { vpc }); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.BRIDGE, }); @@ -1508,7 +1510,7 @@ describe('ec2 service', () => { new ecs.Ec2Service(stack, 'Ec2Service', { cluster, taskDefinition, - securityGroup, + securityGroups: [securityGroup], }); }).toThrow(/vpcSubnets, securityGroup\(s\) and assignPublicIp can only be used in AwsVpc networking mode/); @@ -1525,7 +1527,7 @@ describe('ec2 service', () => { new ec2.SecurityGroup(stack, 'MySecondSG', { vpc }), ]; const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.BRIDGE, }); @@ -1554,7 +1556,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC, }); @@ -1602,7 +1604,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC, }); @@ -1630,7 +1632,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1659,7 +1661,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1690,7 +1692,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1722,7 +1724,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1772,7 +1774,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1798,7 +1800,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1825,7 +1827,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1846,12 +1848,12 @@ describe('ec2 service', () => { }); - test('with both propagateTags and propagateTaskTagsFrom defined', () => { + testDeprecated('with both propagateTags and propagateTaskTagsFrom defined', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1875,7 +1877,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1902,7 +1904,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc'); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1932,7 +1934,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc'); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1959,7 +1961,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -1990,7 +1992,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -2021,7 +2023,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -2052,7 +2054,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); taskDefinition.addContainer('web', { @@ -2081,7 +2083,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TD', { networkMode: ecs.NetworkMode.HOST }); const container = taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('test'), @@ -2105,7 +2107,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TD', { networkMode: ecs.NetworkMode.BRIDGE }); const container = taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('test'), @@ -2129,7 +2131,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TD', { networkMode: ecs.NetworkMode.AWS_VPC }); const container = taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('test'), @@ -2155,7 +2157,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TD', { networkMode: ecs.NetworkMode.NONE }); const container = taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('test'), @@ -2243,7 +2245,8 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); + cluster.connections.addSecurityGroup(); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode }); const container = taskDefinition.addContainer('MainContainer', { image: ecs.ContainerImage.fromRegistry('hello'), @@ -2291,7 +2294,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode }); const container = taskDefinition.addContainer('MainContainer', { image: ecs.ContainerImage.fromRegistry('hello'), @@ -2338,7 +2341,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.HOST }); const container = taskDefinition.addContainer('MainContainer', { image: ecs.ContainerImage.fromRegistry('hello'), @@ -2384,7 +2387,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC }); const container = taskDefinition.addContainer('MainContainer', { image: ecs.ContainerImage.fromRegistry('hello'), @@ -2493,7 +2496,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TD', { networkMode: ecs.NetworkMode.HOST }); const container = taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('test'), @@ -2534,7 +2537,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TD', { networkMode: ecs.NetworkMode.HOST }); const container = taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('test'), @@ -2575,7 +2578,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); // default network mode is bridge const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); @@ -2604,7 +2607,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.NONE, }); @@ -2635,7 +2638,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); // default network mode is bridge const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); @@ -2711,7 +2714,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.HOST, @@ -2788,7 +2791,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); // default network mode is bridge const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); @@ -2822,7 +2825,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC, @@ -2897,7 +2900,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode: ecs.NetworkMode.AWS_VPC, @@ -2975,7 +2978,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); cluster.addDefaultCloudMapNamespace({ name: 'foo.com', type: cloudmap.NamespaceType.DNS_PRIVATE, @@ -3026,7 +3029,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); cluster.addDefaultCloudMapNamespace({ name: 'foo.com', type: cloudmap.NamespaceType.DNS_PRIVATE, @@ -3068,7 +3071,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); cluster.addDefaultCloudMapNamespace({ name: 'foo.com', type: cloudmap.NamespaceType.DNS_PRIVATE, @@ -3112,7 +3115,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); cluster.addDefaultCloudMapNamespace({ name: 'foo.com', type: cloudmap.NamespaceType.DNS_PRIVATE, @@ -3159,7 +3162,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); cluster.addDefaultCloudMapNamespace({ name: 'foo.com', type: cloudmap.NamespaceType.DNS_PRIVATE, @@ -3201,7 +3204,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); cluster.addDefaultCloudMapNamespace({ name: 'foo.com', type: cloudmap.NamespaceType.DNS_PRIVATE, @@ -3238,7 +3241,7 @@ describe('ec2 service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'FargateTaskDef'); taskDefinition.addContainer('Container', { image: ecs.ContainerImage.fromRegistry('hello'), diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts index 966473e5cc6cb..9217e68a412c5 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-task-definition.test.ts @@ -702,7 +702,7 @@ describe('ec2 task definition', () => { }); // THEN - expect(container.node.metadata[0].data).toEqual("Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + expect(container.node.metadataEntry[0].data).toEqual("Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); }); @@ -721,7 +721,7 @@ describe('ec2 task definition', () => { }); // THEN - expect(container.node.metadata[0].data).toEqual("Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + expect(container.node.metadataEntry[0].data).toEqual("Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); }); diff --git a/packages/@aws-cdk/aws-ecs/test/external/external-service.test.ts b/packages/@aws-cdk/aws-ecs/test/external/external-service.test.ts index 94384a464bb47..b4a3edf73ea0d 100644 --- a/packages/@aws-cdk/aws-ecs/test/external/external-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/external/external-service.test.ts @@ -6,6 +6,7 @@ import * as cloudmap from '@aws-cdk/aws-servicediscovery'; import * as cdk from '@aws-cdk/core'; import * as ecs from '../../lib'; import { LaunchType } from '../../lib/base/base-service'; +import { addDefaultCapacityProvider } from '../util'; describe('external service', () => { describe('When creating an External Service', () => { @@ -14,7 +15,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'ExternalTaskDef'); taskDefinition.addContainer('web', { @@ -44,8 +45,6 @@ describe('external service', () => { }); expect(service.node.defaultChild).toBeDefined(); - - }); }); @@ -54,7 +53,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'ExternalTaskDef'); taskDefinition.addContainer('web', { @@ -104,7 +103,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'ExternalTaskDef'); cluster.addDefaultCloudMapNamespace({ @@ -137,7 +136,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'ExternalTaskDef'); taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), @@ -234,7 +233,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'ExternalTaskDef'); taskDefinition.addContainer('BaseContainer', { image: ecs.ContainerImage.fromRegistry('test'), @@ -257,7 +256,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -283,7 +282,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -307,7 +306,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -345,7 +344,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -376,7 +375,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -403,7 +402,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -437,7 +436,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -465,7 +464,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('web', { @@ -490,7 +489,7 @@ describe('external service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef'); const container = taskDefinition.addContainer('web', { diff --git a/packages/@aws-cdk/aws-ecs/test/external/external-task-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/external/external-task-definition.test.ts index c4e296b2c6831..3b692f73fe8cf 100644 --- a/packages/@aws-cdk/aws-ecs/test/external/external-task-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/external/external-task-definition.test.ts @@ -602,7 +602,7 @@ describe('external task definition', () => { }); // THEN - expect(container.node.metadata[0].data).toEqual("Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + expect(container.node.metadataEntry[0].data).toEqual("Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); }); @@ -634,4 +634,4 @@ describe('external task definition', () => { }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index 71866a116443f..a8dbb29c98b10 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts @@ -9,9 +9,11 @@ import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import * as cloudmap from '@aws-cdk/aws-servicediscovery'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as ecs from '../../lib'; import { DeploymentControllerType, LaunchType, PropagatedTagSource } from '../../lib/base/base-service'; +import { addDefaultCapacityProvider } from '../util'; describe('fargate service', () => { describe('When creating a Fargate Service', () => { @@ -115,7 +117,7 @@ describe('fargate service', () => { }); - test('does not set launchType when capacity provider strategies specified (deprecated)', () => { + testDeprecated('does not set launchType when capacity provider strategies specified (deprecated)', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -492,12 +494,12 @@ describe('fargate service', () => { type: ecs.DeploymentControllerType.ECS, }, circuitBreaker: { rollback: true }, - securityGroup: new ec2.SecurityGroup(stack, 'SecurityGroup1', { + securityGroups: [new ec2.SecurityGroup(stack, 'SecurityGroup1', { allowAllOutbound: true, description: 'Example', securityGroupName: 'Bob', vpc, - }), + })], serviceName: 'bonjour', vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, }); @@ -631,7 +633,7 @@ describe('fargate service', () => { }); // THEN - expect(service.node.metadata[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); + expect(service.node.metadataEntry[0].data).toEqual('taskDefinition and launchType are blanked out when using external deployment controller.'); expect(stack).toHaveResource('AWS::ECS::Service', { Cluster: { Ref: 'EcsCluster97242B84', @@ -776,7 +778,7 @@ describe('fargate service', () => { }); - test('throws when securityGroup and securityGroups are supplied', () => { + testDeprecated('throws when securityGroup and securityGroups are supplied', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -1912,7 +1914,7 @@ describe('fargate service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); const container = taskDefinition.addContainer('MainContainer', { @@ -1973,7 +1975,7 @@ describe('fargate service', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); const container = taskDefinition.addContainer('MainContainer', { @@ -2321,7 +2323,7 @@ describe('fargate service', () => { logging: ecs.ExecuteCommandLogging.NONE, }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); const logGroup = new logs.LogGroup(stack, 'LogGroup'); @@ -2390,7 +2392,7 @@ describe('fargate service', () => { logging: ecs.ExecuteCommandLogging.OVERRIDE, }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); taskDefinition.addContainer('web', { @@ -2510,7 +2512,7 @@ describe('fargate service', () => { }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); taskDefinition.addContainer('web', { @@ -2716,7 +2718,7 @@ describe('fargate service', () => { }, }); - cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + addDefaultCapacityProvider(cluster, stack, vpc); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); taskDefinition.addContainer('web', { @@ -2951,7 +2953,7 @@ describe('fargate service', () => { }); - test('with both propagateTags and propagateTaskTagsFrom defined', () => { + testDeprecated('with both propagateTags and propagateTaskTagsFrom defined', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); diff --git a/packages/@aws-cdk/aws-ecs/test/splunk-log-driver.test.ts b/packages/@aws-cdk/aws-ecs/test/splunk-log-driver.test.ts index d9902cb9728e3..cb7678d9bc6e4 100644 --- a/packages/@aws-cdk/aws-ecs/test/splunk-log-driver.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/splunk-log-driver.test.ts @@ -6,14 +6,14 @@ import * as ecs from '../lib'; let stack: cdk.Stack; let td: ecs.TaskDefinition; +let secret: secretsmanager.ISecret; const image = ecs.ContainerImage.fromRegistry('test-image'); describe('splunk log driver', () => { beforeEach(() => { stack = new cdk.Stack(); td = new ecs.Ec2TaskDefinition(stack, 'TaskDefinition'); - - + secret = secretsmanager.Secret.fromSecretNameV2(stack, 'Secret', 'my-splunk-token'); }); test('create a splunk log driver with minimum options', () => { @@ -21,7 +21,7 @@ describe('splunk log driver', () => { td.addContainer('Container', { image, logging: new ecs.SplunkLogDriver({ - token: cdk.SecretValue.secretsManager('my-splunk-token'), + secretToken: ecs.Secret.fromSecretsManager(secret), url: 'my-splunk-url', }), memoryLimitMiB: 128, @@ -34,9 +34,16 @@ describe('splunk log driver', () => { LogConfiguration: { LogDriver: 'splunk', Options: { - 'splunk-token': '{{resolve:secretsmanager:my-splunk-token:SecretString:::}}', 'splunk-url': 'my-splunk-url', }, + SecretOptions: [{ + Name: 'splunk-token', + ValueFrom: { + 'Fn::Join': ['', ['arn:', + { Ref: 'AWS::Partition' }, ':secretsmanager:', { Ref: 'AWS::Region' }, ':', + { Ref: 'AWS::AccountId' }, ':secret:my-splunk-token']], + }, + }], }, }, ], @@ -50,7 +57,7 @@ describe('splunk log driver', () => { td.addContainer('Container', { image, logging: ecs.LogDrivers.splunk({ - token: cdk.SecretValue.secretsManager('my-splunk-token'), + secretToken: ecs.Secret.fromSecretsManager(secret), url: 'my-splunk-url', }), memoryLimitMiB: 128, @@ -63,9 +70,16 @@ describe('splunk log driver', () => { LogConfiguration: { LogDriver: 'splunk', Options: { - 'splunk-token': '{{resolve:secretsmanager:my-splunk-token:SecretString:::}}', 'splunk-url': 'my-splunk-url', }, + SecretOptions: [{ + Name: 'splunk-token', + ValueFrom: { + 'Fn::Join': ['', ['arn:', + { Ref: 'AWS::Partition' }, ':secretsmanager:', { Ref: 'AWS::Region' }, ':', + { Ref: 'AWS::AccountId' }, ':secret:my-splunk-token']], + }, + }], }, }, ], @@ -79,7 +93,7 @@ describe('splunk log driver', () => { td.addContainer('Container', { image, logging: ecs.LogDrivers.splunk({ - token: cdk.SecretValue.secretsManager('my-splunk-token'), + secretToken: ecs.Secret.fromSecretsManager(secret), url: 'my-splunk-url', sourceType: 'my-source-type', }), @@ -93,10 +107,17 @@ describe('splunk log driver', () => { LogConfiguration: { LogDriver: 'splunk', Options: { - 'splunk-token': '{{resolve:secretsmanager:my-splunk-token:SecretString:::}}', 'splunk-url': 'my-splunk-url', 'splunk-sourcetype': 'my-source-type', }, + SecretOptions: [{ + Name: 'splunk-token', + ValueFrom: { + 'Fn::Join': ['', ['arn:', + { Ref: 'AWS::Partition' }, ':secretsmanager:', { Ref: 'AWS::Region' }, ':', + { Ref: 'AWS::AccountId' }, ':secret:my-splunk-token']], + }, + }], }, }, ], @@ -105,13 +126,13 @@ describe('splunk log driver', () => { }); - test('create a splunk log driver using secret splunk token from secrets manager', () => { - const secret = new secretsmanager.Secret(stack, 'Secret'); + test('create a splunk log driver using secret splunk token from a new secret', () => { + const secret2 = new secretsmanager.Secret(stack, 'Secret2'); // WHEN td.addContainer('Container', { image, logging: ecs.LogDrivers.splunk({ - secretToken: ecs.Secret.fromSecretsManager(secret), + secretToken: ecs.Secret.fromSecretsManager(secret2), url: 'my-splunk-url', }), memoryLimitMiB: 128, @@ -130,7 +151,7 @@ describe('splunk log driver', () => { { Name: 'splunk-token', ValueFrom: { - Ref: 'SecretA720EF05', + Ref: 'Secret244EA3BB5', }, }, ], diff --git a/packages/@aws-cdk/aws-ecs/test/util.ts b/packages/@aws-cdk/aws-ecs/test/util.ts new file mode 100644 index 0000000000000..cdfe38403422e --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/util.ts @@ -0,0 +1,21 @@ +import * as autoscaling from '@aws-cdk/aws-autoscaling'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as cdk from '@aws-cdk/core'; +import * as ecs from '../lib'; + +export function addDefaultCapacityProvider(cluster: ecs.Cluster, + stack: cdk.Stack, + vpc: ec2.Vpc, + props?: Omit) { + const autoScalingGroup = new autoscaling.AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + machineImage: ecs.EcsOptimizedImage.amazonLinux2(), + instanceType: new ec2.InstanceType('t2.micro'), + }); + const provider = new ecs.AsgCapacityProvider(stack, 'AsgCapacityProvider', { + ...props, + autoScalingGroup, + }); + cluster.addAsgCapacityProvider(provider); + cluster.connections.addSecurityGroup(...autoScalingGroup.connections.securityGroups); +} diff --git a/packages/@aws-cdk/aws-efs/lib/access-point.ts b/packages/@aws-cdk/aws-efs/lib/access-point.ts index 6a0e6973cc6c2..07039df8d6a6e 100644 --- a/packages/@aws-cdk/aws-efs/lib/access-point.ts +++ b/packages/@aws-cdk/aws-efs/lib/access-point.ts @@ -1,4 +1,4 @@ -import { IResource, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IFileSystem } from './efs-file-system'; import { CfnAccessPoint } from './efs.generated'; @@ -242,7 +242,7 @@ class ImportedAccessPoint extends AccessPointBase { } this.accessPointArn = attrs.accessPointArn; - let maybeApId = Stack.of(scope).parseArn(attrs.accessPointArn).resourceName; + let maybeApId = Stack.of(scope).splitArn(attrs.accessPointArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName; if (!maybeApId) { throw new Error('ARN for AccessPoint must provide the resource name.'); diff --git a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts index aac4636ed9fa9..e5f92e6e7bca0 100644 --- a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts +++ b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts @@ -1,7 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; -import { ConcreteDependable, IDependable, IResource, RemovalPolicy, Resource, Size, Stack, Tags } from '@aws-cdk/core'; +import { ArnFormat, ConcreteDependable, IDependable, IResource, RemovalPolicy, Resource, Size, Stack, Tags } from '@aws-cdk/core'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports import { FeatureFlags } from '@aws-cdk/core'; @@ -409,7 +409,7 @@ class ImportedFileSystem extends FileSystemBase { resourceName: attrs.fileSystemId, }); - const parsedArn = Stack.of(scope).parseArn(this.fileSystemArn); + const parsedArn = Stack.of(scope).splitArn(this.fileSystemArn, ArnFormat.SLASH_RESOURCE_NAME); if (!parsedArn.resourceName) { throw new Error(`Invalid FileSystem Arn ${this.fileSystemArn}`); diff --git a/packages/@aws-cdk/aws-eks-legacy/README.md b/packages/@aws-cdk/aws-eks-legacy/README.md index 35db7f23efb1e..2260236537399 100644 --- a/packages/@aws-cdk/aws-eks-legacy/README.md +++ b/packages/@aws-cdk/aws-eks-legacy/README.md @@ -42,10 +42,10 @@ cluster.addResource('mypod', { { name: 'hello', image: 'paulbouwer/hello-kubernetes:1.5', - ports: [ { containerPort: 8080 } ] - } - ] - } + ports: [ { containerPort: 8080 } ], + }, + ], + }, }); ``` @@ -65,7 +65,7 @@ the `defaultCapacity` and `defaultCapacityInstance` props: ```ts new eks.Cluster(this, 'cluster', { defaultCapacity: 10, - defaultCapacityInstance: new ec2.InstanceType('m2.xlarge') + defaultCapacityInstance: new ec2.InstanceType('m2.xlarge'), }); ``` @@ -82,7 +82,7 @@ is set to `0`: ```ts const cluster = new eks.Cluster(this, 'my-cluster'); cluster.defaultCapacity!.scaleOnCpuUtilization('up', { - targetUtilizationPercent: 80 + targetUtilizationPercent: 80, }); ``` @@ -90,10 +90,11 @@ You can add customized capacity through `cluster.addCapacity()` or `cluster.addAutoScalingGroup()`: ```ts +declare const cluster: eks.Cluster; cluster.addCapacity('frontend-nodes', { instanceType: new ec2.InstanceType('t2.medium'), desiredCapacity: 3, - vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC } + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, }); ``` @@ -102,10 +103,11 @@ cluster.addCapacity('frontend-nodes', { If `spotPrice` is specified, the capacity will be purchased from spot instances: ```ts +declare const cluster: eks.Cluster; cluster.addCapacity('spot', { spotPrice: '0.1094', instanceType: new ec2.InstanceType('t3.large'), - maxCapacity: 10 + maxCapacity: 10, }); ``` @@ -126,13 +128,14 @@ you can use `kubeletExtraArgs` to add custom node labels or taints. ```ts // up to ten spot instances +declare const cluster: eks.Cluster; cluster.addCapacity('spot', { instanceType: new ec2.InstanceType('t3.large'), desiredCapacity: 2, bootstrapOptions: { kubeletExtraArgs: '--node-labels foo=bar,goo=far', - awsApiRetryAttempts: 5 - } + awsApiRetryAttempts: 5, + }, }); ``` @@ -154,12 +157,12 @@ role to the Kubernetes `system:masters` group: ```ts // first define the role const clusterAdmin = new iam.Role(this, 'AdminRole', { - assumedBy: new iam.AccountRootPrincipal() + assumedBy: new iam.AccountRootPrincipal(), }); // now define the cluster and map role to "masters" RBAC group new eks.Cluster(this, 'Cluster', { - mastersRole: clusterAdmin + mastersRole: clusterAdmin, }); ``` @@ -247,12 +250,12 @@ const deployment = { { name: "hello-kubernetes", image: "paulbouwer/hello-kubernetes:1.5", - ports: [ { containerPort: 8080 } ] - } - ] - } - } - } + ports: [ { containerPort: 8080 } ], + }, + ], + }, + }, + }, }; const service = { @@ -262,14 +265,15 @@ const service = { spec: { type: "LoadBalancer", ports: [ { port: 80, targetPort: 8080 } ], - selector: appLabel - } + selector: appLabel, + }, }; +declare const cluster: eks.Cluster; // option 1: use a construct -new KubernetesResource(this, 'hello-kub', { +new eks.KubernetesResource(this, 'hello-kub', { cluster, - manifest: [ deployment, service ] + manifest: [ deployment, service ], }); // or, option2: use `addResource` @@ -301,6 +305,7 @@ For example, let's say you want to grant an IAM user administrative privileges on your cluster: ```ts +declare const cluster: eks.Cluster; const adminUser = new iam.User(this, 'Admin'); cluster.awsAuth.addUserMapping(adminUser, { groups: [ 'system:masters' ]}); ``` @@ -308,7 +313,9 @@ cluster.awsAuth.addUserMapping(adminUser, { groups: [ 'system:masters' ]}); A convenience method for mapping a role to the `system:masters` group is also available: ```ts -cluster.awsAuth.addMastersRole(role) +declare const cluster: eks.Cluster; +declare const role: iam.Role; +cluster.awsAuth.addMastersRole(role); ``` ### Node ssh Access @@ -369,7 +376,7 @@ the cluster: ```ts new eks.Cluster(this, 'cluster', { - kubectlEnabled: false + kubectlEnabled: false, }); ``` @@ -395,19 +402,20 @@ The following example will install the [NGINX Ingress Controller](https://kubern to you cluster using Helm. ```ts +declare const cluster: eks.Cluster; // option 1: use a construct -new HelmChart(this, 'NginxIngress', { +new eks.HelmChart(this, 'NginxIngress', { cluster, chart: 'nginx-ingress', repository: 'https://helm.nginx.com/stable', - namespace: 'kube-system' + namespace: 'kube-system', }); // or, option2: use `addChart` cluster.addChart('NginxIngress', { chart: 'nginx-ingress', repository: 'https://helm.nginx.com/stable', - namespace: 'kube-system' + namespace: 'kube-system', }); ``` diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts b/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts index 1f8c699180aad..8c364f7268b3a 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts @@ -109,8 +109,8 @@ export interface ClusterProps { * For example, to only select private subnets, supply the following: * * ```ts - * vpcSubnets: [ - * { subnetType: ec2.SubnetType.Private } + * const vpcSubnets = [ + * { subnetType: ec2.SubnetType.PRIVATE } * ] * ``` * @@ -261,7 +261,7 @@ export class Cluster extends Resource implements ICluster { /** * The AWS generated ARN for the Cluster resource * - * @example arn:aws:eks:us-west-2:666666666666:cluster/prod + * For example, `arn:aws:eks:us-west-2:666666666666:cluster/prod` */ public readonly clusterArn: string; @@ -270,7 +270,7 @@ export class Cluster extends Resource implements ICluster { * * This is the URL inside the kubeconfig file to use with kubectl * - * @example https://5E1D0CEXAMPLEA591B746AFC5AB30262.yl4.us-west-2.eks.amazonaws.com + * For example, `https://5E1D0CEXAMPLEA591B746AFC5AB30262.yl4.us-west-2.eks.amazonaws.com` */ public readonly clusterEndpoint: string; @@ -726,7 +726,7 @@ export interface BootstrapOptions { /** * Extra arguments to add to the kubelet. Useful for adding labels or taints. * - * @example --node-labels foo=bar,goo=far + * For example, `--node-labels foo=bar,goo=far` * @default - none */ readonly kubeletExtraArgs?: string; diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts b/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts index f28901d125cc9..1760c2cf5bb92 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts @@ -22,9 +22,8 @@ export interface KubernetesResourceProps { * cluster through `kubectl apply` and when the resource or the stack is * deleted, the manifest will be deleted through `kubectl delete`. * - * @example - * - * { + * ``` + * const manifest = { * apiVersion: 'v1', * kind: 'Pod', * metadata: { name: 'mypod' }, @@ -32,7 +31,7 @@ export interface KubernetesResourceProps { * containers: [ { name: 'hello', image: 'paulbouwer/hello-kubernetes:1.5', ports: [ { containerPort: 8080 } ] } ] * } * } - * + * ``` */ readonly manifest: any[]; } diff --git a/packages/@aws-cdk/aws-eks-legacy/package.json b/packages/@aws-cdk/aws-eks-legacy/package.json index f93c8a2e9003a..919122fed89d2 100644 --- a/packages/@aws-cdk/aws-eks-legacy/package.json +++ b/packages/@aws-cdk/aws-eks-legacy/package.json @@ -29,7 +29,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-eks-legacy/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-eks-legacy/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..9cac2b0852e12 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/rosetta/default.ts-fixture @@ -0,0 +1,14 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as eks from '@aws-cdk/aws-eks-legacy'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts index d9eacb3f183d1..3a5f28f648441 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts @@ -1,4 +1,5 @@ import '@aws-cdk/assert-internal/jest'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as iam from '@aws-cdk/aws-iam'; import { Cluster, KubernetesResource } from '../lib'; import { AwsAuth } from '../lib/aws-auth'; @@ -6,7 +7,7 @@ import { testFixtureNoVpc } from './util'; /* eslint-disable max-len */ -describe('awsauth', () => { +describeDeprecated('awsauth', () => { test('empty aws-auth', () => { // GIVEN const { stack } = testFixtureNoVpc(); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts index f395348cc6f1e..b7f1666fb79b0 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts @@ -1,6 +1,7 @@ import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as eks from '../lib'; import { spotInterruptHandler } from '../lib/spot-interrupt-handler'; @@ -8,7 +9,7 @@ import { testFixture, testFixtureNoVpc } from './util'; /* eslint-disable max-len */ -describe('cluster', () => { +describeDeprecated('cluster', () => { test('a default cluster spans all subnets', () => { // GIVEN const { stack, vpc } = testFixture(); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts index 4101ce02168d9..9606f892d5f1c 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts @@ -1,10 +1,11 @@ import '@aws-cdk/assert-internal/jest'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as eks from '../lib'; import { testFixtureCluster } from './util'; /* eslint-disable max-len */ -describe('helm chart', () => { +describeDeprecated('helm chart', () => { describe('add Helm chart', () => { test('should have default namespace', () => { // GIVEN diff --git a/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts index 962e0c0129821..9a3de8f587f4c 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts @@ -1,10 +1,11 @@ import '@aws-cdk/assert-internal/jest'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Cluster, KubernetesResource } from '../lib'; import { testFixtureNoVpc } from './util'; /* eslint-disable max-len */ -describe('manifest', () => { +describeDeprecated('manifest', () => { test('basic usage', () => { // GIVEN const { stack } = testFixtureNoVpc(); @@ -73,4 +74,4 @@ describe('manifest', () => { }); }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/user-data.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/user-data.test.ts index 4189e720d2f97..748acf2b4198e 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/user-data.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/user-data.test.ts @@ -1,11 +1,12 @@ import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { App, Stack } from '@aws-cdk/core'; import { renderUserData } from '../lib/user-data'; /* eslint-disable max-len */ -describe('user data', () => { +describeDeprecated('user data', () => { test('default user data', () => { // GIVEN const { asg, stack } = newFixtures(); diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index e1d78b774450d..ee082134b51f0 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -62,10 +62,10 @@ cluster.addManifest('mypod', { { name: 'hello', image: 'paulbouwer/hello-kubernetes:1.5', - ports: [ { containerPort: 8080 } ] - } - ] - } + ports: [ { containerPort: 8080 } ], + }, + ], + }, }); ``` @@ -102,8 +102,8 @@ The following is a qualitative diagram of the various possible components involv ```text +-----------------------------------------------+ +-----------------+ - | EKS Cluster | kubectl | | - | ----------- |<-------------+| Kubectl Handler | + | EKS Cluster | kubectl | | + |-----------------------------------------------|<-------------+| Kubectl Handler | | | | | | | +-----------------+ | +--------------------+ +-----------------+ | @@ -195,23 +195,22 @@ cluster.addNodegroupCapacity('custom-node-group', { minSize: 4, diskSize: 100, amiType: eks.NodegroupAmiType.AL2_X86_64_GPU, - ... }); ``` To set node taints, you can set `taints` option. ```ts +declare const cluster: eks.Cluster; cluster.addNodegroupCapacity('custom-node-group', { instanceTypes: [new ec2.InstanceType('m5.large')], taints: [ { - effect: TaintEffect.NO_SCHEDULE, + effect: eks.TaintEffect.NO_SCHEDULE, key: 'foo', value: 'bar', - } - ] - ... + }, + ], }); ``` @@ -224,6 +223,7 @@ Spot Instances, we recommend that you configure a Spot managed node group to use ```ts +declare const cluster: eks.Cluster; cluster.addNodegroupCapacity('extra-ng-spot', { instanceTypes: [ new ec2.InstanceType('c5.large'), @@ -245,6 +245,8 @@ When supplying a custom user data script, it must be encoded in the MIME multi-p for mode details. ```ts +declare const cluster: eks.Cluster; + const userData = `MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="==MYBOUNDARY==" @@ -262,6 +264,7 @@ const lt = new ec2.CfnLaunchTemplate(this, 'LaunchTemplate', { userData: Fn.base64(userData), }, }); + cluster.addNodegroupCapacity('extra-ng', { launchTemplateSpec: { id: lt.ref, @@ -275,6 +278,7 @@ Note that when using a custom AMI, Amazon EKS doesn't merge any user data. Which In the following example, `/ect/eks/bootstrap.sh` from the AMI will be used to bootstrap the node. ```ts +declare const cluster: eks.Cluster; const userData = ec2.UserData.forLinux(); userData.addCommands( 'set -o xtrace', @@ -319,17 +323,19 @@ through the `addFargateProfile()` method. The following example adds a profile that will match all pods from the "default" namespace: ```ts +declare const cluster: eks.Cluster; cluster.addFargateProfile('MyProfile', { - selectors: [ { namespace: 'default' } ] + selectors: [ { namespace: 'default' } ], }); ``` You can also directly use the `FargateProfile` construct to create profiles under different scopes: ```ts -new eks.FargateProfile(scope, 'MyProfile', { +declare const cluster: eks.Cluster; +new eks.FargateProfile(this, 'MyProfile', { cluster, - ... + selectors: [ { namespace: 'default' } ], }); ``` @@ -360,30 +366,33 @@ For a detailed overview please visit [Self Managed Nodes](https://docs.aws.amazo Creating an auto-scaling group and connecting it to the cluster is done using the `cluster.addAutoScalingGroupCapacity` method: ```ts +declare const cluster: eks.Cluster; cluster.addAutoScalingGroupCapacity('frontend-nodes', { instanceType: new ec2.InstanceType('t2.medium'), minCapacity: 3, - vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC } + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, }); ``` To connect an already initialized auto-scaling group, use the `cluster.connectAutoScalingGroupCapacity()` method: ```ts -const asg = new ec2.AutoScalingGroup(...); -cluster.connectAutoScalingGroupCapacity(asg); +declare const cluster: eks.Cluster; +declare const asg: autoscaling.AutoScalingGroup; +cluster.connectAutoScalingGroupCapacity(asg, {}); ``` To connect a self-managed node group to an imported cluster, use the `cluster.connectAutoScalingGroupCapacity()` method: ```ts -const importedCluster = eks.Cluster.fromClusterAttributes(stack, 'ImportedCluster', { +declare const cluster: eks.Cluster; +declare const asg: autoscaling.AutoScalingGroup; +const importedCluster = eks.Cluster.fromClusterAttributes(this, 'ImportedCluster', { clusterName: cluster.clusterName, clusterSecurityGroupId: cluster.clusterSecurityGroupId, }); -const asg = new ec2.AutoScalingGroup(...); -importedCluster.connectAutoScalingGroupCapacity(asg); +importedCluster.connectAutoScalingGroupCapacity(asg, {}); ``` In both cases, the [cluster security group](https://docs.aws.amazon.com/eks/latest/userguide/sec-group-reqs.html#cluster-sg) will be automatically attached to @@ -396,13 +405,14 @@ You can customize the [/etc/eks/boostrap.sh](https://github.com/awslabs/amazon-e for bootstrapping the node to the EKS cluster. For example, you can use `kubeletExtraArgs` to add custom node labels or taints. ```ts +declare const cluster: eks.Cluster; cluster.addAutoScalingGroupCapacity('spot', { instanceType: new ec2.InstanceType('t3.large'), minCapacity: 2, bootstrapOptions: { kubeletExtraArgs: '--node-labels foo=bar,goo=far', - awsApiRetryAttempts: 5 - } + awsApiRetryAttempts: 5, + }, }); ``` @@ -410,7 +420,7 @@ To disable bootstrapping altogether (i.e. to fully customize user-data), set `bo You can also configure the cluster to use an auto-scaling group as the default capacity: ```ts -cluster = new eks.Cluster(this, 'HelloEKS', { +const cluster = new eks.Cluster(this, 'HelloEKS', { version: eks.KubernetesVersion.V1_21, defaultCapacityType: eks.DefaultCapacityType.EC2, }); @@ -421,8 +431,9 @@ To access the `AutoScalingGroup` that was created on your behalf, you can use `c You can also independently create an `AutoScalingGroup` and connect it to the cluster using the `cluster.connectAutoScalingGroupCapacity` method: ```ts -const asg = new ec2.AutoScalingGroup(...) -cluster.connectAutoScalingGroupCapacity(asg); +declare const cluster: eks.Cluster; +declare const asg: autoscaling.AutoScalingGroup; +cluster.connectAutoScalingGroupCapacity(asg, {}); ``` This will add the necessary user-data to access the apiserver and configure all connections, roles, and tags needed for the instances in the auto-scaling group to properly join the cluster. @@ -433,10 +444,11 @@ When using self-managed nodes, you can configure the capacity to use spot instan To enable spot capacity, use the `spotPrice` property: ```ts +declare const cluster: eks.Cluster; cluster.addAutoScalingGroupCapacity('spot', { spotPrice: '0.1094', instanceType: new ec2.InstanceType('t3.large'), - maxCapacity: 10 + maxCapacity: 10, }); ``` @@ -458,17 +470,26 @@ To disable the installation of the termination handler, set the `spotInterruptHa #### Bottlerocket [Bottlerocket](https://aws.amazon.com/bottlerocket/) is a Linux-based open-source operating system that is purpose-built by Amazon Web Services for running containers on virtual machines or bare metal hosts. -At this moment, `Bottlerocket` is only supported when using self-managed auto-scaling groups. -> **NOTICE**: Bottlerocket is only available in [some supported AWS regions](https://github.com/bottlerocket-os/bottlerocket/blob/develop/QUICKSTART-EKS.md#finding-an-ami). +`Bottlerocket` is supported when using managed nodegroups or self-managed auto-scaling groups. + +To create a Bottlerocket managed nodegroup: + +```ts +declare const cluster: eks.Cluster; +cluster.addNodegroupCapacity('BottlerocketNG', { + amiType: eks.NodegroupAmiType.BOTTLEROCKET_X86_64, +}); +``` The following example will create an auto-scaling group of 2 `t3.small` Linux instances running with the `Bottlerocket` AMI. ```ts +declare const cluster: eks.Cluster; cluster.addAutoScalingGroupCapacity('BottlerocketNodes', { instanceType: new ec2.InstanceType('t3.small'), minCapacity: 2, - machineImageType: eks.MachineImageType.BOTTLEROCKET + machineImageType: eks.MachineImageType.BOTTLEROCKET, }); ``` @@ -480,6 +501,8 @@ For example, if the Amazon EKS cluster version is `1.17`, the Bottlerocket AMI v Please note Bottlerocket does not allow to customize bootstrap options and `bootstrapOptions` properties is not supported when you create the `Bottlerocket` capacity. +For more details about Bottlerocket, see [Bottlerocket FAQs](https://aws.amazon.com/bottlerocket/faqs/) and [Bottlerocket Open Source Blog](https://aws.amazon.com/blogs/opensource/announcing-the-general-availability-of-bottlerocket-an-open-source-linux-distribution-purpose-built-to-run-containers/). + ### Endpoint Access When you create a new cluster, Amazon EKS creates an endpoint for the managed Kubernetes API server that you use to communicate with your cluster (using Kubernetes management tools such as `kubectl`) @@ -492,7 +515,7 @@ You can configure the [cluster endpoint access](https://docs.aws.amazon.com/eks/ ```ts const cluster = new eks.Cluster(this, 'hello-eks', { version: eks.KubernetesVersion.V1_21, - endpointAccess: eks.EndpointAccess.PRIVATE // No access outside of your VPC. + endpointAccess: eks.EndpointAccess.PRIVATE, // No access outside of your VPC. }); ``` @@ -503,12 +526,12 @@ The default value is `eks.EndpointAccess.PUBLIC_AND_PRIVATE`. Which means the cl You can specify the VPC of the cluster using the `vpc` and `vpcSubnets` properties: ```ts -const vpc = new ec2.Vpc(this, 'Vpc'); +declare const vpc: ec2.Vpc; new eks.Cluster(this, 'HelloEKS', { version: eks.KubernetesVersion.V1_21, vpc, - vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE }] + vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE }], }); ``` @@ -516,12 +539,17 @@ new eks.Cluster(this, 'HelloEKS', { If you do not specify a VPC, one will be created on your behalf, which you can then access via `cluster.vpc`. The cluster VPC will be associated to any EKS managed capacity (i.e Managed Node Groups and Fargate Profiles). +Please note that the `vpcSubnets` property defines the subnets where EKS will place the _control plane_ ENIs. To choose +the subnets where EKS will place the worker nodes, please refer to the **Provisioning clusters** section above. + If you allocate self managed capacity, you can specify which subnets should the auto-scaling group use: ```ts -const vpc = new ec2.Vpc(this, 'Vpc'); +declare const vpc: ec2.Vpc; +declare const cluster: eks.Cluster; cluster.addAutoScalingGroupCapacity('nodes', { - vpcSubnets: { subnets: vpc.privateSubnets } + vpcSubnets: { subnets: vpc.privateSubnets }, + instanceType: new ec2.InstanceType('t2.medium'), }); ``` @@ -537,6 +565,8 @@ Breaking this down, it means that if the endpoint exposes private access (via `E If the endpoint does not expose private access (via `EndpointAccess.PUBLIC`) **or** the VPC does not contain private subnets, the function will not be provisioned within the VPC. +If your use-case requires control over the IAM role that the KubeCtl Handler assumes, a custom role can be passed through the ClusterProps (as `kubectlLambdaRole`) of the EKS Cluster construct. + #### Cluster Handler The `ClusterHandler` is a set of Lambda functions (`onEventHandler`, `isCompleteHandler`) responsible for interacting with the EKS API in order to control the cluster lifecycle. To provision these functions inside the VPC, set the `placeClusterHandlerInVpc` property to `true`. This will place the functions inside the private subnets of the VPC based on the selection strategy specified in the [`vpcSubnets`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-eks.Cluster.html#vpcsubnetsspan-classapi-icon-api-icon-experimental-titlethis-api-element-is-experimental-it-may-change-without-noticespan) property. @@ -544,16 +574,17 @@ The `ClusterHandler` is a set of Lambda functions (`onEventHandler`, `isComplete You can configure the environment of the Cluster Handler functions by specifying it at cluster instantiation. For example, this can be useful in order to configure an http proxy: ```ts +declare const proxyInstanceSecurityGroup: ec2.SecurityGroup; const cluster = new eks.Cluster(this, 'hello-eks', { version: eks.KubernetesVersion.V1_21, clusterHandlerEnvironment: { - https_proxy: 'http://proxy.myproxy.com' + https_proxy: 'http://proxy.myproxy.com', }, /** * If the proxy is not open publicly, you can pass a security group to the * Cluster Handler Lambdas so that it can reach the proxy. */ - clusterHandlerSecurityGroup: proxyInstanceSecurityGroup + clusterHandlerSecurityGroup: proxyInstanceSecurityGroup, }); ``` @@ -569,8 +600,8 @@ You can configure the environment of this function by specifying it at cluster i const cluster = new eks.Cluster(this, 'hello-eks', { version: eks.KubernetesVersion.V1_21, kubectlEnvironment: { - 'http_proxy': 'http://proxy.myproxy.com' - } + 'http_proxy': 'http://proxy.myproxy.com', + }, }); ``` @@ -604,13 +635,21 @@ const layer = new lambda.LayerVersion(this, 'KubectlLayer', { Now specify when the cluster is defined: ```ts -const cluster = new eks.Cluster(this, 'MyCluster', { +declare const layer: lambda.LayerVersion; +declare const vpc: ec2.Vpc; + +const cluster1 = new eks.Cluster(this, 'MyCluster', { kubectlLayer: layer, + vpc, + clusterName: 'cluster-name', + version: eks.KubernetesVersion.V1_21, }); // or -const cluster = eks.Cluster.fromClusterAttributes(this, 'MyCluster', { +const cluster2 = eks.Cluster.fromClusterAttributes(this, 'MyCluster', { kubectlLayer: layer, + vpc, + clusterName: 'cluster-name', }); ``` @@ -619,15 +658,17 @@ const cluster = eks.Cluster.fromClusterAttributes(this, 'MyCluster', { By default, the kubectl provider is configured with 1024MiB of memory. You can use the `kubectlMemory` option to specify the memory size for the AWS Lambda function: ```ts -import { Size } from '@aws-cdk/core'; - new eks.Cluster(this, 'MyCluster', { - kubectlMemory: Size.gibibytes(4) + kubectlMemory: Size.gibibytes(4), + version: eks.KubernetesVersion.V1_21, }); // or +declare const vpc: ec2.Vpc; eks.Cluster.fromClusterAttributes(this, 'MyCluster', { - kubectlMemory: Size.gibibytes(4) + kubectlMemory: Size.gibibytes(4), + vpc, + clusterName: 'cluster-name', }); ``` @@ -637,6 +678,7 @@ Instance types with `ARM64` architecture are supported in both managed nodegroup Amazon Linux 2 AMI for ARM64 will be automatically selected. ```ts +declare const cluster: eks.Cluster; // add a managed ARM64 nodegroup cluster.addNodegroupCapacity('extra-ng-arm', { instanceTypes: [new ec2.InstanceType('m6g.medium')], @@ -655,7 +697,7 @@ cluster.addAutoScalingGroupCapacity('self-ng-arm', { When you create a cluster, you can specify a `mastersRole`. The `Cluster` construct will associate this role with the `system:masters` [RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) group, giving it super-user access to the cluster. ```ts -const role = new iam.Role(...); +declare const role: iam.Role; new eks.Cluster(this, 'HelloEKS', { version: eks.KubernetesVersion.V1_21, mastersRole: role, @@ -685,7 +727,7 @@ You can use the `secretsEncryptionKey` to configure which key the cluster will u const secretsKey = new kms.Key(this, 'SecretsKey'); const cluster = new eks.Cluster(this, 'MyCluster', { secretsEncryptionKey: secretsKey, - // ... + version: eks.KubernetesVersion.V1_21, }); ``` @@ -694,13 +736,15 @@ You can also use a similar configuration for running a cluster built using the F ```ts const secretsKey = new kms.Key(this, 'SecretsKey'); const cluster = new eks.FargateCluster(this, 'MyFargateCluster', { - secretsEncryptionKey: secretsKey + secretsEncryptionKey: secretsKey, + version: eks.KubernetesVersion.V1_21, }); ``` The Amazon Resource Name (ARN) for that CMK can be retrieved. ```ts +declare const cluster: eks.Cluster; const clusterEncryptionConfigKeyArn = cluster.clusterEncryptionConfigKeyArn; ``` @@ -720,6 +764,7 @@ Furthermore, when auto-scaling group capacity is added to the cluster, the IAM i For example, let's say you want to grant an IAM user administrative privileges on your cluster: ```ts +declare const cluster: eks.Cluster; const adminUser = new iam.User(this, 'Admin'); cluster.awsAuth.addUserMapping(adminUser, { groups: [ 'system:masters' ]}); ``` @@ -727,7 +772,9 @@ cluster.awsAuth.addUserMapping(adminUser, { groups: [ 'system:masters' ]}); A convenience method for mapping a role to the `system:masters` group is also available: ```ts -cluster.awsAuth.addMastersRole(role) +declare const cluster: eks.Cluster; +declare const role: iam.Role; +cluster.awsAuth.addMastersRole(role); ``` ### Cluster Security Group @@ -739,6 +786,7 @@ between each other. The ID for that security group can be retrieved after creating the cluster. ```ts +declare const cluster: eks.Cluster; const clusterSecurityGroupId = cluster.clusterSecurityGroupId; ``` @@ -758,10 +806,11 @@ unfortunately beyond the scope of this documentation. With services account you can provide Kubernetes Pods access to AWS resources. ```ts +declare const cluster: eks.Cluster; // add service account const serviceAccount = cluster.addServiceAccount('MyServiceAccount'); -const bucket = new Bucket(this, 'Bucket'); +const bucket = new s3.Bucket(this, 'Bucket'); bucket.grantReadWrite(serviceAccount); const mypod = cluster.addManifest('mypod', { @@ -769,23 +818,22 @@ const mypod = cluster.addManifest('mypod', { kind: 'Pod', metadata: { name: 'mypod' }, spec: { - serviceAccountName: serviceAccount.serviceAccountName + serviceAccountName: serviceAccount.serviceAccountName, containers: [ { name: 'hello', image: 'paulbouwer/hello-kubernetes:1.5', ports: [ { containerPort: 8080 } ], - - } - ] - } + }, + ], + }, }); // create the resource after the service account. mypod.node.addDependency(serviceAccount); // print the IAM role arn for this service account -new cdk.CfnOutput(this, 'ServiceAccountIamRole', { value: serviceAccount.role.roleArn }) +new CfnOutput(this, 'ServiceAccountIamRole', { value: serviceAccount.role.roleArn }); ``` Note that using `serviceAccount.serviceAccountName` above **does not** translate into a resource dependency. @@ -799,9 +847,12 @@ To do so, pass the `openIdConnectProvider` property when you import the cluster const provider = eks.OpenIdConnectProvider.fromOpenIdConnectProviderArn(this, 'Provider', 'arn:aws:iam::123456:oidc-provider/oidc.eks.eu-west-1.amazonaws.com/id/AB123456ABC'); // or create a new one using an existing issuer url -const provider = new eks.OpenIdConnectProvider(this, 'Provider', issuerUrl); +declare const issuerUrl: string; +const provider2 = new eks.OpenIdConnectProvider(this, 'Provider', { + url: issuerUrl, +}); -const cluster = eks.Cluster.fromClusterAttributes({ +const cluster = eks.Cluster.fromClusterAttributes(this, 'MyCluster', { clusterName: 'Cluster', openIdConnectProvider: provider, kubectlRoleArn: 'arn:aws:iam::123456:role/service-role/k8sservicerole', @@ -809,10 +860,8 @@ const cluster = eks.Cluster.fromClusterAttributes({ const serviceAccount = cluster.addServiceAccount('MyServiceAccount'); -const bucket = new Bucket(this, 'Bucket'); +const bucket = new s3.Bucket(this, 'Bucket'); bucket.grantReadWrite(serviceAccount); - -// ... ``` Note that adding service accounts requires running `kubectl` commands against the cluster. @@ -836,6 +885,7 @@ The following examples will deploy the [paulbouwer/hello-kubernetes](https://git service on the cluster: ```ts +declare const cluster: eks.Cluster; const appLabel = { app: "hello-kubernetes" }; const deployment = { @@ -852,12 +902,12 @@ const deployment = { { name: "hello-kubernetes", image: "paulbouwer/hello-kubernetes:1.5", - ports: [ { containerPort: 8080 } ] - } - ] - } - } - } + ports: [ { containerPort: 8080 } ], + }, + ], + }, + }, + }, }; const service = { @@ -867,14 +917,14 @@ const service = { spec: { type: "LoadBalancer", ports: [ { port: 80, targetPort: 8080 } ], - selector: appLabel + selector: appLabel, } }; // option 1: use a construct -new KubernetesManifest(this, 'hello-kub', { +new eks.KubernetesManifest(this, 'hello-kub', { cluster, - manifest: [ deployment, service ] + manifest: [ deployment, service ], }); // or, option2: use `addManifest` @@ -885,13 +935,16 @@ cluster.addManifest('hello-kub', service, deployment); The following example will deploy the resource manifest hosting on remote server: -```ts +```text +// This example is only available in TypeScript + import * as yaml from 'js-yaml'; import * as request from 'sync-request'; +declare const cluster: eks.Cluster; const manifestUrl = 'https://url/of/manifest.yaml'; const manifest = yaml.safeLoadAll(request('GET', manifestUrl).getBody()); -cluster.addManifest('my-resource', ...manifest); +cluster.addManifest('my-resource', manifest); ``` #### Dependencies @@ -904,18 +957,19 @@ You can represent dependencies between `KubernetesManifest`s using `resource.node.addDependency()`: ```ts +declare const cluster: eks.Cluster; const namespace = cluster.addManifest('my-namespace', { apiVersion: 'v1', kind: 'Namespace', - metadata: { name: 'my-app' } + metadata: { name: 'my-app' }, }); const service = cluster.addManifest('my-service', { metadata: { name: 'myservice', - namespace: 'my-app' + namespace: 'my-app', }, - spec: // ... + spec: { }, // ... }); service.node.addDependency(namespace); // will apply `my-namespace` before `my-service`. @@ -945,8 +999,9 @@ Pruning is enabled by default but can be disabled through the `prune` option when a cluster is defined: ```ts -new Cluster(this, 'MyCluster', { - prune: false +new eks.Cluster(this, 'MyCluster', { + version: eks.KubernetesVersion.V1_21, + prune: false, }); ``` @@ -956,9 +1011,10 @@ The `kubectl` CLI supports applying a manifest by skipping the validation. This can be accomplished by setting the `skipValidation` flag to `true` in the `KubernetesManifest` props. ```ts +declare const cluster: eks.Cluster; new eks.KubernetesManifest(this, 'HelloAppWithoutValidation', { - cluster: this.cluster, - manifest: [ deployment, service ], + cluster, + manifest: [{ foo: 'bar' }], skipValidation: true, }); ``` @@ -975,19 +1031,20 @@ to add Kubernetes resources to this cluster using Helm. The following example will install the [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/) to your cluster using Helm. ```ts +declare const cluster: eks.Cluster; // option 1: use a construct -new HelmChart(this, 'NginxIngress', { +new eks.HelmChart(this, 'NginxIngress', { cluster, chart: 'nginx-ingress', repository: 'https://helm.nginx.com/stable', - namespace: 'kube-system' + namespace: 'kube-system', }); // or, option2: use `addHelmChart` cluster.addHelmChart('NginxIngress', { chart: 'nginx-ingress', repository: 'https://helm.nginx.com/stable', - namespace: 'kube-system' + namespace: 'kube-system', }); ``` @@ -1011,8 +1068,13 @@ resource or if Helm charts depend on each other. You can use charts: ```ts -const chart1 = cluster.addHelmChart(...); -const chart2 = cluster.addHelmChart(...); +declare const cluster: eks.Cluster; +const chart1 = cluster.addHelmChart('MyChart', { + chart: 'foo', +}); +const chart2 = cluster.addHelmChart('MyChart', { + chart: 'bar', +}); chart2.node.addDependency(chart1); ``` @@ -1050,7 +1112,7 @@ For this reason, to avoid possible confusion, we will create the chart in a sepa `+ my-chart.ts` -```ts +```ts nofixture import * as s3 from '@aws-cdk/aws-s3'; import * as constructs from 'constructs'; import * as cdk8s from 'cdk8s'; @@ -1065,16 +1127,14 @@ export class MyChart extends cdk8s.Chart { super(scope, id); new kplus.Pod(this, 'Pod', { - spec: { - containers: [ - new kplus.Container({ - image: 'my-image', - env: { - BUCKET_NAME: kplus.EnvValue.fromValue(props.bucket.bucketName), - }, - }), - ], - }, + containers: [ + new kplus.Container({ + image: 'my-image', + env: { + BUCKET_NAME: kplus.EnvValue.fromValue(props.bucket.bucketName), + }, + }), + ], }); } } @@ -1082,10 +1142,8 @@ export class MyChart extends cdk8s.Chart { Then, in your AWS CDK app: -```ts -import * as s3 from '@aws-cdk/aws-s3'; -import * as cdk8s from 'cdk8s'; -import { MyChart } from './my-chart'; +```ts fixture=cdk8schart +declare const cluster: eks.Cluster; // some bucket.. const bucket = new s3.Bucket(this, 'Bucket'); @@ -1103,7 +1161,7 @@ You can also compose a few stock `cdk8s+` constructs into your own custom constr you'll need to use is the one from the [`constructs`](https://github.com/aws/constructs) module, and not from `@aws-cdk/core` like you normally would. This is why we used `new cdk8s.App()` as the scope of the chart above. -```ts +```ts nofixture import * as constructs from 'constructs'; import * as cdk8s from 'cdk8s'; import * as kplus from 'cdk8s-plus'; @@ -1114,21 +1172,21 @@ export interface LoadBalancedWebService { readonly replicas: number; } +const app = new cdk8s.App(); +const chart = new cdk8s.Chart(app, 'my-chart'); + export class LoadBalancedWebService extends constructs.Construct { constructor(scope: constructs.Construct, id: string, props: LoadBalancedWebService) { super(scope, id); const deployment = new kplus.Deployment(chart, 'Deployment', { - spec: { - replicas: props.replicas, - podSpecTemplate: { - containers: [ new kplus.Container({ image: props.image }) ] - } - }, + replicas: props.replicas, + containers: [ new kplus.Container({ image: props.image }) ], }); - deployment.expose({port: props.port, serviceType: kplus.ServiceType.LOAD_BALANCER}) - + deployment.expose(props.port, { + serviceType: kplus.ServiceType.LOAD_BALANCER, + }); } } ``` @@ -1146,11 +1204,12 @@ resources. The following example can be used to patch the `hello-kubernetes` deployment from the example above with 5 replicas. ```ts -new KubernetesPatch(this, 'hello-kub-deployment-label', { +declare const cluster: eks.Cluster; +new eks.KubernetesPatch(this, 'hello-kub-deployment-label', { cluster, resourceName: "deployment/hello-kubernetes", applyPatch: { spec: { replicas: 5 } }, - restorePatch: { spec: { replicas: 3 } } + restorePatch: { spec: { replicas: 3 } }, }) ``` @@ -1162,8 +1221,9 @@ and use that as part of your CDK application. For example, you can fetch the address of a [`LoadBalancer`](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer) type service: ```ts +declare const cluster: eks.Cluster; // query the load balancer address -const myServiceAddress = new KubernetesObjectValue(this, 'LoadBalancerAttribute', { +const myServiceAddress = new eks.KubernetesObjectValue(this, 'LoadBalancerAttribute', { cluster: cluster, objectType: 'service', objectName: 'my-service', @@ -1172,9 +1232,11 @@ const myServiceAddress = new KubernetesObjectValue(this, 'LoadBalancerAttribute' // pass the address to a lambda function const proxyFunction = new lambda.Function(this, 'ProxyFunction', { - ... + handler: 'index.handler', + code: lambda.Code.fromInline('my-code'), + runtime: lambda.Runtime.NODEJS_14_X, environment: { - myServiceAddress: myServiceAddress.value + myServiceAddress: myServiceAddress.value, }, }) ``` @@ -1182,6 +1244,7 @@ const proxyFunction = new lambda.Function(this, 'ProxyFunction', { Specifically, since the above use-case is quite common, there is an easier way to access that information: ```ts +declare const cluster: eks.Cluster; const loadBalancerAddress = cluster.getServiceLoadBalancerAddress('my-service'); ``` @@ -1205,6 +1268,7 @@ Then, you can use `addManifest` or `addHelmChart` to define resources inside your Kubernetes cluster. For example: ```ts +declare const cluster: eks.Cluster; cluster.addManifest('Test', { apiVersion: 'v1', kind: 'ConfigMap', diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 2c762ab666327..7aa18e7aa5fce 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -117,6 +117,15 @@ export interface ICluster extends IResource, ec2.IConnectable { */ readonly kubectlPrivateSubnets?: ec2.ISubnet[]; + /** + * An IAM role that can perform kubectl operations against this cluster. + * + * The role should be mapped to the `system:masters` Kubernetes RBAC role. + * + * This role is directly passed to the lambda handler that sends Kube Ctl commands to the cluster. + */ + readonly kubectlLambdaRole?: iam.IRole; + /** * An AWS Lambda layer that includes `kubectl`, `helm` and the `aws` CLI. * @@ -271,6 +280,18 @@ export interface ClusterAttributes { */ readonly kubectlRoleArn?: string; + /** + * An IAM role that can perform kubectl operations against this cluster. + * + * The role should be mapped to the `system:masters` Kubernetes RBAC role. + * + * This role is directly passed to the lambda handler that sends Kube Ctl commands + * to the cluster. + * @default - if not specified, the default role created by a lambda function will + * be used. + */ + readonly kubectlLambdaRole?: iam.IRole; + /** * Environment variables to use when running `kubectl` against this cluster. * @default - no additional variables @@ -369,11 +390,7 @@ export interface CommonClusterOptions { * * For example, to only select private subnets, supply the following: * - * ```ts - * vpcSubnets: [ - * { subnetType: ec2.SubnetType.Private } - * ] - * ``` + * `vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE }]` * * @default - All public and private subnets */ @@ -485,9 +502,9 @@ export interface ClusterOptions extends CommonClusterOptions { * * ```ts * const layer = new lambda.LayerVersion(this, 'kubectl-layer', { - * code: lambda.Code.fromAsset(`${__dirname}/layer.zip`)), - * compatibleRuntimes: [lambda.Runtime.PROVIDED] - * }) + * code: lambda.Code.fromAsset(`${__dirname}/layer.zip`), + * compatibleRuntimes: [lambda.Runtime.PROVIDED], + * }); * ``` * * @default - the layer provided by the `aws-lambda-layer-kubectl` SAR app. @@ -531,9 +548,9 @@ export interface ClusterOptions extends CommonClusterOptions { * * ```ts * const layer = new lambda.LayerVersion(this, 'proxy-agent-layer', { - * code: lambda.Code.fromAsset(`${__dirname}/layer.zip`)), - * compatibleRuntimes: [lambda.Runtime.NODEJS_12_X] - * }) + * code: lambda.Code.fromAsset(`${__dirname}/layer.zip`), + * compatibleRuntimes: [lambda.Runtime.NODEJS_12_X], + * }); * ``` * * @default - a layer bundled with this module. @@ -702,6 +719,14 @@ export interface ClusterProps extends ClusterOptions { * @default NODEGROUP */ readonly defaultCapacityType?: DefaultCapacityType; + + + /** + * The IAM role to pass to the Kubectl Lambda Handler. + * + * @default - Default Lambda IAM Execution Role + */ + readonly kubectlLambdaRole?: iam.IRole; } /** @@ -771,6 +796,7 @@ abstract class ClusterBase extends Resource implements ICluster { public abstract readonly clusterSecurityGroup: ec2.ISecurityGroup; public abstract readonly clusterEncryptionConfigKeyArn: string; public abstract readonly kubectlRole?: iam.IRole; + public abstract readonly kubectlLambdaRole?: iam.IRole; public abstract readonly kubectlEnvironment?: { [key: string]: string }; public abstract readonly kubectlSecurityGroup?: ec2.ISecurityGroup; public abstract readonly kubectlPrivateSubnets?: ec2.ISubnet[]; @@ -1010,7 +1036,7 @@ export class Cluster extends ClusterBase { /** * The AWS generated ARN for the Cluster resource * - * @example arn:aws:eks:us-west-2:666666666666:cluster/prod + * For example, `arn:aws:eks:us-west-2:666666666666:cluster/prod` */ public readonly clusterArn: string; @@ -1019,7 +1045,7 @@ export class Cluster extends ClusterBase { * * This is the URL inside the kubeconfig file to use with kubectl * - * @example https://5E1D0CEXAMPLEA591B746AFC5AB30262.yl4.us-west-2.eks.amazonaws.com + * For example, `https://5E1D0CEXAMPLEA591B746AFC5AB30262.yl4.us-west-2.eks.amazonaws.com` */ public readonly clusterEndpoint: string; @@ -1077,6 +1103,18 @@ export class Cluster extends ClusterBase { */ public readonly kubectlRole?: iam.IRole; + /** + * An IAM role that can perform kubectl operations against this cluster. + * + * The role should be mapped to the `system:masters` Kubernetes RBAC role. + * + * This role is directly passed to the lambda handler that sends Kube Ctl commands to the cluster. + * @default - if not specified, the default role created by a lambda function will + * be used. + */ + + public readonly kubectlLambdaRole?: iam.IRole; + /** * Custom environment variables when running `kubectl` against this cluster. */ @@ -1195,6 +1233,7 @@ export class Cluster extends ClusterBase { this.prune = props.prune ?? true; this.vpc = props.vpc || new ec2.Vpc(this, 'DefaultVpc'); this.version = props.version; + this.kubectlLambdaRole = props.kubectlLambdaRole ? props.kubectlLambdaRole : undefined; this.tagSubnets(); @@ -1441,8 +1480,6 @@ export class Cluster extends ClusterBase { cpuArch: cpuArchForInstanceType(options.instanceType), kubernetesVersion: this.version.version, }), - updateType: options.updateType, - instanceType: options.instanceType, }); this.connectAutoScalingGroupCapacity(asg, { @@ -1796,7 +1833,8 @@ export interface BootstrapOptions { /** * Extra arguments to add to the kubelet. Useful for adding labels or taints. * - * @example --node-labels foo=bar,goo=far + * For example, `--node-labels foo=bar,goo=far`. + * * @default - none */ readonly kubeletExtraArgs?: string; @@ -1867,6 +1905,7 @@ class ImportedCluster extends ClusterBase { public readonly clusterArn: string; public readonly connections = new ec2.Connections(); public readonly kubectlRole?: iam.IRole; + public readonly kubectlLambdaRole?: iam.IRole; public readonly kubectlEnvironment?: { [key: string]: string; } | undefined; public readonly kubectlSecurityGroup?: ec2.ISecurityGroup | undefined; public readonly kubectlPrivateSubnets?: ec2.ISubnet[] | undefined; diff --git a/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts b/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts index b5bd8ed51b876..0e5db3c6a51e3 100644 --- a/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts +++ b/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts @@ -77,6 +77,7 @@ export class KubectlProvider extends NestedStack { description: 'onEvent handler for EKS kubectl resource provider', memorySize, environment: cluster.kubectlEnvironment, + role: cluster.kubectlLambdaRole ? cluster.kubectlLambdaRole : undefined, // defined only when using private access vpc: cluster.kubectlPrivateSubnets ? cluster.vpc : undefined, diff --git a/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts b/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts index 69dd8223edc09..ec91d54abb610 100644 --- a/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts +++ b/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts @@ -34,7 +34,15 @@ export enum NodegroupAmiType { /** * Amazon Linux 2 (ARM-64) */ - AL2_ARM_64 = 'AL2_ARM_64' + AL2_ARM_64 = 'AL2_ARM_64', + /** + * Bottlerocket Linux(ARM-64) + */ + BOTTLEROCKET_ARM_64 = 'BOTTLEROCKET_ARM_64', + /** + * Bottlerocket(x86-64) + */ + BOTTLEROCKET_X86_64 = 'BOTTLEROCKET_x86_64', } /** diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index ac9ed3f9f1fe2..48d5dad161a32 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-eks/rosetta/cdk8schart.ts-fixture b/packages/@aws-cdk/aws-eks/rosetta/cdk8schart.ts-fixture new file mode 100644 index 0000000000000..d0e854aa4b57d --- /dev/null +++ b/packages/@aws-cdk/aws-eks/rosetta/cdk8schart.ts-fixture @@ -0,0 +1,35 @@ +import { Construct } from 'constructs'; +import { CfnOutput, Fn, Size, Stack } from '@aws-cdk/core'; +import * as eks from '@aws-cdk/aws-eks'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk8s from 'cdk8s'; +import * as kplus from 'cdk8s-plus'; + +interface MyChartProps { + readonly bucket: s3.Bucket; +} + +class MyChart extends cdk8s.Chart { + constructor(scope: Construct, id: string, props: MyChartProps) { + super(scope, id); + + new kplus.Pod(this, 'Pod', { + containers: [ + new kplus.Container({ + image: 'my-image', + env: { + BUCKET_NAME: kplus.EnvValue.fromValue(props.bucket.bucketName), + }, + }), + ], + }); + } +} + +class Context extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-eks/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-eks/rosetta/default.ts-fixture index 65a6aabb9e5b4..a18b3f87d0f3d 100644 --- a/packages/@aws-cdk/aws-eks/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-eks/rosetta/default.ts-fixture @@ -1,8 +1,15 @@ -import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnOutput, Fn, Size, Stack } from '@aws-cdk/core'; import * as eks from '@aws-cdk/aws-eks'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as autoscaling from '@aws-cdk/aws-autoscaling'; -class Context extends cdk.Construct { - constructor(scope: cdk.Construct, id: string) { +class Context extends Stack { + constructor(scope: Construct, id: string) { super(scope, id); /// here diff --git a/packages/@aws-cdk/aws-eks/test/cluster.test.ts b/packages/@aws-cdk/aws-eks/test/cluster.test.ts index 63a17d9c5d64d..5c6050a63682c 100644 --- a/packages/@aws-cdk/aws-eks/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-eks/test/cluster.test.ts @@ -1571,7 +1571,7 @@ describe('cluster', () => { prune: false, defaultCapacityInstance: new ec2.InstanceType('m6g.medium'), }).addNodegroupCapacity('ng', { - instanceType: new ec2.InstanceType('m6g.medium'), + instanceTypes: [new ec2.InstanceType('m6g.medium')], }); // THEN @@ -1592,7 +1592,7 @@ describe('cluster', () => { prune: false, defaultCapacityInstance: new ec2.InstanceType('t4g.medium'), }).addNodegroupCapacity('ng', { - instanceType: new ec2.InstanceType('t4g.medium'), + instanceTypes: [new ec2.InstanceType('t4g.medium')], }); // THEN @@ -2202,6 +2202,42 @@ describe('cluster', () => { }, }); + }); + + test('kubectl provider passes iam role environment to kube ctl lambda', () => { + + const { stack } = testFixture(); + + const kubectlRole = new iam.Role(stack, 'KubectlIamRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + + // using _ syntax to silence warning about _cluster not being used, when it is + const cluster = new eks.Cluster(stack, 'Cluster1', { + version: CLUSTER_VERSION, + prune: false, + endpointAccess: eks.EndpointAccess.PRIVATE, + kubectlLambdaRole: kubectlRole, + }); + + cluster.addManifest('resource', { + kind: 'ConfigMap', + apiVersion: 'v1', + data: { + hello: 'world', + }, + metadata: { + name: 'config-map', + }, + }); + + // the kubectl provider is inside a nested stack. + const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; + expect(nested).toHaveResourceLike('AWS::Lambda::Function', { + Role: { + Ref: 'referencetoStackKubectlIamRole02F8947EArn', + }, + }); }); diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-bottlerocket-ng.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-bottlerocket-ng.expected.json new file mode 100644 index 0000000000000..7755a615e42e5 --- /dev/null +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-bottlerocket-ng.expected.json @@ -0,0 +1,1432 @@ +{ + "Resources": { + "AdminRole38563C57": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet3SubnetBE12F0B6": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3RouteTable93458DBB": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3RouteTableAssociation1F1EDF02": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + } + } + }, + "VpcPublicSubnet3DefaultRoute4697774F": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet3SubnetF258B56E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableD98824C7": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + }, + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableAssociation16BDDC43": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + } + }, + "VpcPrivateSubnet3DefaultRoute94B74F0D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "ClusterRoleFA261979": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "eks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSClusterPolicy" + ] + ] + } + ] + } + }, + "ClusterControlPlaneSecurityGroupD274242C": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "EKS Control Plane Security Group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "ClusterCreationRole360249B6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + }, + "DependsOn": [ + "VpcIGWD7BA715C", + "VpcPrivateSubnet1DefaultRouteBE02A9ED", + "VpcPrivateSubnet1RouteTableB2C5B500", + "VpcPrivateSubnet1RouteTableAssociation70C59FA6", + "VpcPrivateSubnet1Subnet536B997A", + "VpcPrivateSubnet2DefaultRoute060D2087", + "VpcPrivateSubnet2RouteTableA678073B", + "VpcPrivateSubnet2RouteTableAssociationA89CAD56", + "VpcPrivateSubnet2Subnet3788AAA1", + "VpcPrivateSubnet3DefaultRoute94B74F0D", + "VpcPrivateSubnet3RouteTableD98824C7", + "VpcPrivateSubnet3RouteTableAssociation16BDDC43", + "VpcPrivateSubnet3SubnetF258B56E", + "VpcPublicSubnet1DefaultRoute3DA9E72A", + "VpcPublicSubnet1EIPD7E02669", + "VpcPublicSubnet1NATGateway4D7517AA", + "VpcPublicSubnet1RouteTable6C95E38E", + "VpcPublicSubnet1RouteTableAssociation97140677", + "VpcPublicSubnet1Subnet5C2D37C4", + "VpcPublicSubnet2DefaultRoute97F91067", + "VpcPublicSubnet2RouteTable94F7E489", + "VpcPublicSubnet2RouteTableAssociationDD5762D8", + "VpcPublicSubnet2Subnet691E08A3", + "VpcPublicSubnet3DefaultRoute4697774F", + "VpcPublicSubnet3RouteTable93458DBB", + "VpcPublicSubnet3RouteTableAssociation1F1EDF02", + "VpcPublicSubnet3SubnetBE12F0B6", + "Vpc8378EB38", + "VpcVPCGWBF912B6E" + ] + }, + "ClusterCreationRoleDefaultPolicyE8BDFC7B": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "ClusterRoleFA261979", + "Arn" + ] + } + }, + { + "Action": [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DescribeUpdate", + "eks:DeleteCluster", + "eks:UpdateClusterVersion", + "eks:UpdateClusterConfig", + "eks:CreateFargateProfile", + "eks:TagResource", + "eks:UntagResource" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "eks:DescribeFargateProfile", + "eks:DeleteFargateProfile" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "iam:GetRole", + "iam:listAttachedRolePolicies" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:CreateServiceLinkedRole", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ec2:DescribeInstances", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeRouteTables", + "ec2:DescribeDhcpOptions", + "ec2:DescribeVpcs" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ClusterCreationRoleDefaultPolicyE8BDFC7B", + "Roles": [ + { + "Ref": "ClusterCreationRole360249B6" + } + ] + }, + "DependsOn": [ + "VpcIGWD7BA715C", + "VpcPrivateSubnet1DefaultRouteBE02A9ED", + "VpcPrivateSubnet1RouteTableB2C5B500", + "VpcPrivateSubnet1RouteTableAssociation70C59FA6", + "VpcPrivateSubnet1Subnet536B997A", + "VpcPrivateSubnet2DefaultRoute060D2087", + "VpcPrivateSubnet2RouteTableA678073B", + "VpcPrivateSubnet2RouteTableAssociationA89CAD56", + "VpcPrivateSubnet2Subnet3788AAA1", + "VpcPrivateSubnet3DefaultRoute94B74F0D", + "VpcPrivateSubnet3RouteTableD98824C7", + "VpcPrivateSubnet3RouteTableAssociation16BDDC43", + "VpcPrivateSubnet3SubnetF258B56E", + "VpcPublicSubnet1DefaultRoute3DA9E72A", + "VpcPublicSubnet1EIPD7E02669", + "VpcPublicSubnet1NATGateway4D7517AA", + "VpcPublicSubnet1RouteTable6C95E38E", + "VpcPublicSubnet1RouteTableAssociation97140677", + "VpcPublicSubnet1Subnet5C2D37C4", + "VpcPublicSubnet2DefaultRoute97F91067", + "VpcPublicSubnet2RouteTable94F7E489", + "VpcPublicSubnet2RouteTableAssociationDD5762D8", + "VpcPublicSubnet2Subnet691E08A3", + "VpcPublicSubnet3DefaultRoute4697774F", + "VpcPublicSubnet3RouteTable93458DBB", + "VpcPublicSubnet3RouteTableAssociation1F1EDF02", + "VpcPublicSubnet3SubnetBE12F0B6", + "Vpc8378EB38", + "VpcVPCGWBF912B6E" + ] + }, + "Cluster9EE0221C": { + "Type": "Custom::AWSCDK-EKS-Cluster", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.awscdkeksclustertestawscdkawseksClusterResourceProviderframeworkonEvent503C1667Arn" + ] + }, + "Config": { + "version": "1.21", + "roleArn": { + "Fn::GetAtt": [ + "ClusterRoleFA261979", + "Arn" + ] + }, + "resourcesVpcConfig": { + "subnetIds": [ + { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + }, + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ], + "securityGroupIds": [ + { + "Fn::GetAtt": [ + "ClusterControlPlaneSecurityGroupD274242C", + "GroupId" + ] + } + ], + "endpointPublicAccess": true, + "endpointPrivateAccess": true + } + }, + "AssumeRoleArn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] + }, + "AttributesRevision": 2 + }, + "DependsOn": [ + "ClusterCreationRoleDefaultPolicyE8BDFC7B", + "ClusterCreationRole360249B6", + "VpcIGWD7BA715C", + "VpcPrivateSubnet1DefaultRouteBE02A9ED", + "VpcPrivateSubnet1RouteTableB2C5B500", + "VpcPrivateSubnet1RouteTableAssociation70C59FA6", + "VpcPrivateSubnet1Subnet536B997A", + "VpcPrivateSubnet2DefaultRoute060D2087", + "VpcPrivateSubnet2RouteTableA678073B", + "VpcPrivateSubnet2RouteTableAssociationA89CAD56", + "VpcPrivateSubnet2Subnet3788AAA1", + "VpcPrivateSubnet3DefaultRoute94B74F0D", + "VpcPrivateSubnet3RouteTableD98824C7", + "VpcPrivateSubnet3RouteTableAssociation16BDDC43", + "VpcPrivateSubnet3SubnetF258B56E", + "VpcPublicSubnet1DefaultRoute3DA9E72A", + "VpcPublicSubnet1EIPD7E02669", + "VpcPublicSubnet1NATGateway4D7517AA", + "VpcPublicSubnet1RouteTable6C95E38E", + "VpcPublicSubnet1RouteTableAssociation97140677", + "VpcPublicSubnet1Subnet5C2D37C4", + "VpcPublicSubnet2DefaultRoute97F91067", + "VpcPublicSubnet2RouteTable94F7E489", + "VpcPublicSubnet2RouteTableAssociationDD5762D8", + "VpcPublicSubnet2Subnet691E08A3", + "VpcPublicSubnet3DefaultRoute4697774F", + "VpcPublicSubnet3RouteTable93458DBB", + "VpcPublicSubnet3RouteTableAssociation1F1EDF02", + "VpcPublicSubnet3SubnetBE12F0B6", + "Vpc8378EB38", + "VpcVPCGWBF912B6E" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ClusterKubectlReadyBarrier200052AF": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "aws:cdk:eks:kubectl-ready" + }, + "DependsOn": [ + "ClusterCreationRoleDefaultPolicyE8BDFC7B", + "ClusterCreationRole360249B6", + "Cluster9EE0221C" + ] + }, + "ClusterAwsAuthmanifestFE51F8AE": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B", + "Outputs.awscdkeksclustertestawscdkawseksKubectlProviderframeworkonEventC681B49AArn" + ] + }, + "Manifest": { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"aws-auth\",\"namespace\":\"kube-system\",\"labels\":{\"aws.cdk.eks/prune-c842be348c45337cd97b8759de76d5a68b4910d487\":\"\"}},\"data\":{\"mapRoles\":\"[{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "AdminRole38563C57", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"", + { + "Fn::GetAtt": [ + "AdminRole38563C57", + "Arn" + ] + }, + "\\\",\\\"groups\\\":[\\\"system:masters\\\"]},{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "ClusterNodegroupBottlerocketNG1NodeGroupRoleF0E6A2C6", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]},{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "ClusterNodegroupBottlerocketNG2NodeGroupRole8BD62EDB", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" + ] + ] + }, + "ClusterName": { + "Ref": "Cluster9EE0221C" + }, + "RoleArn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] + }, + "PruneLabel": "aws.cdk.eks/prune-c842be348c45337cd97b8759de76d5a68b4910d487", + "Overwrite": true + }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ClusterNodegroupBottlerocketNG1NodeGroupRoleF0E6A2C6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSWorkerNodePolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKS_CNI_Policy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + ] + } + ] + } + }, + "ClusterNodegroupBottlerocketNG1B78D1784": { + "Type": "AWS::EKS::Nodegroup", + "Properties": { + "ClusterName": { + "Ref": "Cluster9EE0221C" + }, + "NodeRole": { + "Fn::GetAtt": [ + "ClusterNodegroupBottlerocketNG1NodeGroupRoleF0E6A2C6", + "Arn" + ] + }, + "Subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ], + "AmiType": "BOTTLEROCKET_x86_64", + "ForceUpdateEnabled": true, + "ScalingConfig": { + "DesiredSize": 2, + "MaxSize": 2, + "MinSize": 1 + } + } + }, + "ClusterNodegroupBottlerocketNG2NodeGroupRole8BD62EDB": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSWorkerNodePolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKS_CNI_Policy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + ] + } + ] + } + }, + "ClusterNodegroupBottlerocketNG299226DAB": { + "Type": "AWS::EKS::Nodegroup", + "Properties": { + "ClusterName": { + "Ref": "Cluster9EE0221C" + }, + "NodeRole": { + "Fn::GetAtt": [ + "ClusterNodegroupBottlerocketNG2NodeGroupRole8BD62EDB", + "Arn" + ] + }, + "Subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ], + "AmiType": "BOTTLEROCKET_ARM_64", + "ForceUpdateEnabled": true, + "ScalingConfig": { + "DesiredSize": 2, + "MaxSize": 2, + "MinSize": 1 + } + } + }, + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParametersdcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9S3BucketA775E312" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersdcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9S3VersionKeyFDABEE9B" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersdcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9S3VersionKeyFDABEE9B" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetoawscdkeksclustertestAssetParameters26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665S3Bucket1771F046Ref": { + "Ref": "AssetParameters26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665S3Bucket1B280681" + }, + "referencetoawscdkeksclustertestAssetParameters26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665S3VersionKeyDA854AFERef": { + "Ref": "AssetParameters26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665S3VersionKeyB1E02791" + }, + "referencetoawscdkeksclustertestClusterCreationRole95F44854Arn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] + }, + "referencetoawscdkeksclustertestAssetParameters5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720S3BucketDA4E9DCDRef": { + "Ref": "AssetParameters5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720S3Bucket3B443230" + }, + "referencetoawscdkeksclustertestAssetParameters5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720S3VersionKey6F8004B6Ref": { + "Ref": "AssetParameters5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720S3VersionKeyAA4674FB" + }, + "referencetoawscdkeksclustertestAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3Bucket0815E7B5Ref": { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1" + }, + "referencetoawscdkeksclustertestAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKey657736ADRef": { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22S3Bucket0782C98E" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22S3VersionKey5E9D14CC" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22S3VersionKey5E9D14CC" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetoawscdkeksclustertestClusterD76DFF87Arn": { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "Arn" + ] + }, + "referencetoawscdkeksclustertestClusterCreationRole95F44854Arn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] + }, + "referencetoawscdkeksclustertestAssetParameters4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10S3Bucket3929FA93Ref": { + "Ref": "AssetParameters4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10S3BucketC6FAEEC9" + }, + "referencetoawscdkeksclustertestAssetParameters4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10S3VersionKey14530D6BRef": { + "Ref": "AssetParameters4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10S3VersionKeyA7EE7421" + }, + "referencetoawscdkeksclustertestVpcPrivateSubnet1Subnet32A4EC2ARef": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + "referencetoawscdkeksclustertestVpcPrivateSubnet2Subnet5CC53627Ref": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + "referencetoawscdkeksclustertestVpcPrivateSubnet3Subnet7F5D6918Ref": { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + }, + "referencetoawscdkeksclustertestClusterD76DFF87ClusterSecurityGroupId": { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "ClusterSecurityGroupId" + ] + }, + "referencetoawscdkeksclustertestAssetParameterse9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68S3BucketB4E9C142Ref": { + "Ref": "AssetParameterse9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68S3BucketAEADE8C7" + }, + "referencetoawscdkeksclustertestAssetParameterse9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68S3VersionKey1C7C1F5FRef": { + "Ref": "AssetParameterse9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68S3VersionKeyE415415F" + }, + "referencetoawscdkeksclustertestAssetParametersea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03eS3Bucket6ADB5CE5Ref": { + "Ref": "AssetParametersea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03eS3BucketD3288998" + }, + "referencetoawscdkeksclustertestAssetParametersea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03eS3VersionKey314C5B11Ref": { + "Ref": "AssetParametersea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03eS3VersionKeyB00C0565" + }, + "referencetoawscdkeksclustertestAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3Bucket0815E7B5Ref": { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1" + }, + "referencetoawscdkeksclustertestAssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKey657736ADRef": { + "Ref": "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F" + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Outputs": { + "ClusterConfigCommand43AAE40F": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks update-kubeconfig --name ", + { + "Ref": "Cluster9EE0221C" + }, + " --region test-region --role-arn ", + { + "Fn::GetAtt": [ + "AdminRole38563C57", + "Arn" + ] + } + ] + ] + } + }, + "ClusterGetTokenCommand06AE992E": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks get-token --cluster-name ", + { + "Ref": "Cluster9EE0221C" + }, + " --region test-region --role-arn ", + { + "Fn::GetAtt": [ + "AdminRole38563C57", + "Arn" + ] + } + ] + ] + } + } + }, + "Parameters": { + "AssetParameters26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665S3Bucket1B280681": { + "Type": "String", + "Description": "S3 bucket for asset \"26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665\"" + }, + "AssetParameters26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665S3VersionKeyB1E02791": { + "Type": "String", + "Description": "S3 key for asset version \"26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665\"" + }, + "AssetParameters26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665ArtifactHash9EA5AC29": { + "Type": "String", + "Description": "Artifact hash for asset \"26ac61b4195cccf80ff73f332788ad7ffaab36d81ce570340a583a8364901665\"" + }, + "AssetParameters5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720S3Bucket3B443230": { + "Type": "String", + "Description": "S3 bucket for asset \"5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720\"" + }, + "AssetParameters5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720S3VersionKeyAA4674FB": { + "Type": "String", + "Description": "S3 key for asset version \"5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720\"" + }, + "AssetParameters5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720ArtifactHash3D7A279D": { + "Type": "String", + "Description": "Artifact hash for asset \"5afea6e8e6c743a8d1766f21465e28d471e56bcb95c5970054b0514bc62a3720\"" + }, + "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1": { + "Type": "String", + "Description": "S3 bucket for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" + }, + "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3VersionKeyA495226F": { + "Type": "String", + "Description": "S3 key for asset version \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" + }, + "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1ArtifactHashA521A16F": { + "Type": "String", + "Description": "Artifact hash for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" + }, + "AssetParameters4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10S3BucketC6FAEEC9": { + "Type": "String", + "Description": "S3 bucket for asset \"4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10\"" + }, + "AssetParameters4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10S3VersionKeyA7EE7421": { + "Type": "String", + "Description": "S3 key for asset version \"4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10\"" + }, + "AssetParameters4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10ArtifactHash528547CD": { + "Type": "String", + "Description": "Artifact hash for asset \"4129bbca38164ecb28fee8e5b674f0d05e5957b4b8ed97d9c950527b5cc4ce10\"" + }, + "AssetParameterse9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68S3BucketAEADE8C7": { + "Type": "String", + "Description": "S3 bucket for asset \"e9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68\"" + }, + "AssetParameterse9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68S3VersionKeyE415415F": { + "Type": "String", + "Description": "S3 key for asset version \"e9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68\"" + }, + "AssetParameterse9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68ArtifactHashD9A515C3": { + "Type": "String", + "Description": "Artifact hash for asset \"e9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68\"" + }, + "AssetParametersea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03eS3BucketD3288998": { + "Type": "String", + "Description": "S3 bucket for asset \"ea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03e\"" + }, + "AssetParametersea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03eS3VersionKeyB00C0565": { + "Type": "String", + "Description": "S3 key for asset version \"ea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03e\"" + }, + "AssetParametersea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03eArtifactHash4654D012": { + "Type": "String", + "Description": "Artifact hash for asset \"ea17febe6d04c66048f3e8e060c71685c0cb53122abceff44842d27bc0d4a03e\"" + }, + "AssetParametersdcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9S3BucketA775E312": { + "Type": "String", + "Description": "S3 bucket for asset \"dcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9\"" + }, + "AssetParametersdcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9S3VersionKeyFDABEE9B": { + "Type": "String", + "Description": "S3 key for asset version \"dcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9\"" + }, + "AssetParametersdcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9ArtifactHashBC5BD0D7": { + "Type": "String", + "Description": "Artifact hash for asset \"dcdc759e2644fb3c4847d9a160ce99f0f40f137c825ae9cc094323ed4839bab9\"" + }, + "AssetParameters8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22S3Bucket0782C98E": { + "Type": "String", + "Description": "S3 bucket for asset \"8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22\"" + }, + "AssetParameters8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22S3VersionKey5E9D14CC": { + "Type": "String", + "Description": "S3 key for asset version \"8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22\"" + }, + "AssetParameters8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22ArtifactHash75F0D468": { + "Type": "String", + "Description": "Artifact hash for asset \"8a135d8a645edaff330758972da87b3dddc295ce07475e8d9ea8fad8c35dcb22\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-bottlerocket-ng.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-bottlerocket-ng.ts new file mode 100644 index 0000000000000..d27d92d984d4c --- /dev/null +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-bottlerocket-ng.ts @@ -0,0 +1,47 @@ +/// !cdk-integ pragma:ignore-assets +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; +import { App } from '@aws-cdk/core'; +import * as eks from '../lib'; +import { NodegroupAmiType } from '../lib'; +import { TestStack } from './util'; + + +class EksClusterStack extends TestStack { + + private cluster: eks.Cluster; + private vpc: ec2.IVpc; + + constructor(scope: App, id: string) { + super(scope, id); + + // allow all account users to assume this role in order to admin the cluster + const mastersRole = new iam.Role(this, 'AdminRole', { + assumedBy: new iam.AccountRootPrincipal(), + }); + + // just need one nat gateway to simplify the test + this.vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 3, natGateways: 1 }); + + // create the cluster with a default nodegroup capacity + this.cluster = new eks.Cluster(this, 'Cluster', { + vpc: this.vpc, + mastersRole, + defaultCapacity: 0, + version: eks.KubernetesVersion.V1_21, + }); + + this.cluster.addNodegroupCapacity('BottlerocketNG1', { + amiType: NodegroupAmiType.BOTTLEROCKET_X86_64, + }); + this.cluster.addNodegroupCapacity('BottlerocketNG2', { + amiType: NodegroupAmiType.BOTTLEROCKET_ARM_64, + }); + } +} + +const app = new App(); + +new EksClusterStack(app, 'aws-cdk-eks-cluster-test'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-eks/test/nodegroup.test.ts b/packages/@aws-cdk/aws-eks/test/nodegroup.test.ts index 47ef518f68032..0711bb5c0bb4c 100644 --- a/packages/@aws-cdk/aws-eks/test/nodegroup.test.ts +++ b/packages/@aws-cdk/aws-eks/test/nodegroup.test.ts @@ -1,7 +1,9 @@ import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as eks from '../lib'; +import { NodegroupAmiType } from '../lib'; import { testFixture } from './util'; /* eslint-disable max-len */ @@ -99,7 +101,7 @@ describe('node group', () => { }); - test('create nodegroup correctly', () => { + test('create a default nodegroup correctly', () => { // GIVEN const { stack, vpc } = testFixture(); @@ -139,6 +141,97 @@ describe('node group', () => { }); + }); + + test('create a x86_64 bottlerocket nodegroup correctly', () => { + // GIVEN + const { stack, vpc } = testFixture(); + + // WHEN + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); + new eks.Nodegroup(stack, 'Nodegroup', { + cluster, + amiType: NodegroupAmiType.BOTTLEROCKET_X86_64, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + ClusterName: { + Ref: 'Cluster9EE0221C', + }, + NodeRole: { + 'Fn::GetAtt': [ + 'NodegroupNodeGroupRole038A128B', + 'Arn', + ], + }, + Subnets: [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', + }, + { + Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', + }, + ], + AmiType: 'BOTTLEROCKET_x86_64', + ForceUpdateEnabled: true, + ScalingConfig: { + DesiredSize: 2, + MaxSize: 2, + MinSize: 1, + }, + }); + + + }); + test('create a ARM_64 bottlerocket nodegroup correctly', () => { + // GIVEN + const { stack, vpc } = testFixture(); + + // WHEN + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); + new eks.Nodegroup(stack, 'Nodegroup', { + cluster, + amiType: NodegroupAmiType.BOTTLEROCKET_ARM_64, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + ClusterName: { + Ref: 'Cluster9EE0221C', + }, + NodeRole: { + 'Fn::GetAtt': [ + 'NodegroupNodeGroupRole038A128B', + 'Arn', + ], + }, + Subnets: [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', + }, + { + Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', + }, + ], + AmiType: 'BOTTLEROCKET_ARM_64', + ForceUpdateEnabled: true, + ScalingConfig: { + DesiredSize: 2, + MaxSize: 2, + MinSize: 1, + }, + }); + + }); test('aws-auth will be updated', () => { // GIVEN @@ -250,7 +343,7 @@ describe('node group', () => { }); - test('create nodegroup with instanceType provided', () => { + test('create nodegroup with instanceTypes provided', () => { // GIVEN const { stack, vpc } = testFixture(); @@ -262,7 +355,7 @@ describe('node group', () => { }); new eks.Nodegroup(stack, 'Nodegroup', { cluster, - instanceType: new ec2.InstanceType('m5.large'), + instanceTypes: [new ec2.InstanceType('m5.large')], }); // THEN @@ -286,7 +379,7 @@ describe('node group', () => { }); new eks.Nodegroup(stack, 'Nodegroup', { cluster, - instanceType: new ec2.InstanceType('m5.large'), + instanceTypes: [new ec2.InstanceType('m5.large')], capacityType: eks.CapacityType.ON_DEMAND, }); @@ -362,7 +455,7 @@ describe('node group', () => { }); - test('throws when both instanceTypes and instanceType defined', () => { + testDeprecated('throws when both instanceTypes and instanceType defined', () => { // GIVEN const { stack, vpc } = testFixture(); diff --git a/packages/@aws-cdk/aws-eks/test/pinger/pinger.ts b/packages/@aws-cdk/aws-eks/test/pinger/pinger.ts index 1165da1ca90df..3702484a68339 100644 --- a/packages/@aws-cdk/aws-eks/test/pinger/pinger.ts +++ b/packages/@aws-cdk/aws-eks/test/pinger/pinger.ts @@ -12,6 +12,7 @@ export interface PingerProps { readonly url: string; readonly securityGroup?: ec2.SecurityGroup; readonly vpc?: ec2.IVpc; + readonly subnets?: ec2.ISubnet[]; } export class Pinger extends CoreConstruct { @@ -25,6 +26,7 @@ export class Pinger extends CoreConstruct { handler: 'index.handler', runtime: lambda.Runtime.PYTHON_3_6, vpc: props.vpc, + vpcSubnets: props.subnets ? { subnets: props.subnets } : undefined, securityGroups: props.securityGroup ? [props.securityGroup] : undefined, timeout: Duration.minutes(10), }); diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts b/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts index aa6c1c8b88ad0..457742ff5db70 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import { Connections, Peer, SubnetType, Vpc } from '@aws-cdk/aws-ec2'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Duration, Stack } from '@aws-cdk/core'; import { ILoadBalancerTarget, LoadBalancer, LoadBalancingProtocol } from '../lib'; @@ -177,7 +178,7 @@ describe('tests', () => { }); }); - test('does not fail when deprecated property sslCertificateId is used', () => { + testDeprecated('does not fail when deprecated property sslCertificateId is used', () => { // GIVEN const sslCertificateArn = 'arn:aws:acm:us-east-1:12345:test/12345'; const stack = new Stack(); @@ -231,7 +232,7 @@ describe('tests', () => { }); }); - test('throws error when both sslCertificateId and sslCertificateArn are used', () => { + testDeprecated('throws error when both sslCertificateId and sslCertificateArn are used', () => { // GIVEN const sslCertificateArn = 'arn:aws:acm:us-east-1:12345:test/12345'; const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts index e71ccbd5c74de..7530ad08f8cb1 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts @@ -258,7 +258,9 @@ export class ApplicationListenerRule extends CoreConstruct { this.configureAction(props.action); } - (props.targetGroups || []).forEach(this.addTargetGroup.bind(this)); + (props.targetGroups || []).forEach((group) => { + this.configureAction(ListenerAction.forward([group])); + }); if (props.fixedResponse) { this.addFixedResponse(props.fixedResponse); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts index 1a2e6c55ee815..2994a437b3929 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -293,12 +293,8 @@ export class ApplicationListener extends BaseListener implements IApplicationLis // TargetGroup.registerListener is called inside ApplicationListenerRule. new ApplicationListenerRule(this, id + 'Rule', { listener: this, - conditions: props.conditions, - hostHeader: props.hostHeader, - pathPattern: props.pathPattern, - pathPatterns: props.pathPatterns, priority: props.priority, - action: props.action, + ...props, }); } else { // New default target with these targetgroups @@ -325,12 +321,8 @@ export class ApplicationListener extends BaseListener implements IApplicationLis // TargetGroup.registerListener is called inside ApplicationListenerRule. new ApplicationListenerRule(this, id + 'Rule', { listener: this, - conditions: props.conditions, - hostHeader: props.hostHeader, - pathPattern: props.pathPattern, - pathPatterns: props.pathPatterns, priority: props.priority, - targetGroups: props.targetGroups, + ...props, }); } else { // New default target with these targetgroups @@ -359,27 +351,13 @@ export class ApplicationListener extends BaseListener implements IApplicationLis } const group = new ApplicationTargetGroup(this, id + 'Group', { - deregistrationDelay: props.deregistrationDelay, - healthCheck: props.healthCheck, - port: props.port, - protocol: props.protocol, - protocolVersion: props.protocolVersion, - slowStart: props.slowStart, - stickinessCookieDuration: props.stickinessCookieDuration, - stickinessCookieName: props.stickinessCookieName, - loadBalancingAlgorithmType: props.loadBalancingAlgorithmType, - targetGroupName: props.targetGroupName, - targets: props.targets, vpc: this.loadBalancer.vpc, + ...props, }); this.addTargetGroups(id, { - conditions: props.conditions, - hostHeader: props.hostHeader, - pathPattern: props.pathPattern, - pathPatterns: props.pathPatterns, - priority: props.priority, targetGroups: [group], + ...props, }); return group; @@ -607,10 +585,9 @@ abstract class ExternalApplicationListener extends Resource implements IApplicat * Add one or more certificates to this listener. */ public addCertificates(id: string, certificates: IListenerCertificate[]): void { - const arns = certificates.map(c => c.certificateArn); new ApplicationListenerCertificate(this, id, { listener: this, - certificateArns: arns, + certificates, }); } @@ -627,12 +604,8 @@ abstract class ExternalApplicationListener extends Resource implements IApplicat // New rule new ApplicationListenerRule(this, id, { listener: this, - conditions: props.conditions, - hostHeader: props.hostHeader, - pathPattern: props.pathPattern, - pathPatterns: props.pathPatterns, priority: props.priority, - targetGroups: props.targetGroups, + ...props, }); } else { throw new Error('Cannot add default Target Groups to imported ApplicationListener'); @@ -700,7 +673,7 @@ class LookedUpApplicationListener extends ExternalApplicationListener { }); for (const securityGroupId of props.securityGroupIds) { - const securityGroup = ec2.SecurityGroup.fromLookup(this, `SecurityGroup-${securityGroupId}`, securityGroupId); + const securityGroup = ec2.SecurityGroup.fromLookupById(this, `SecurityGroup-${securityGroupId}`, securityGroupId); this.connections.addSecurityGroup(securityGroup); } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts index c3cac3ae37eee..10291f2369849 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts @@ -148,7 +148,7 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic return new cloudwatch.Metric({ namespace: 'AWS/ApplicationELB', metricName, - dimensions: { LoadBalancer: this.loadBalancerFullName }, + dimensionsMap: { LoadBalancer: this.loadBalancerFullName }, ...props, }); } @@ -642,7 +642,7 @@ class LookedUpApplicationLoadBalancer extends Resource implements IApplicationLo this.connections = new ec2.Connections(); for (const securityGroupId of props.securityGroupIds) { - const securityGroup = ec2.SecurityGroup.fromLookup(this, `SecurityGroup-${securityGroupId}`, securityGroupId); + const securityGroup = ec2.SecurityGroup.fromLookupById(this, `SecurityGroup-${securityGroupId}`, securityGroupId); this.connections.addSecurityGroup(securityGroup); } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index 025d655aef181..487394019ed15 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -254,7 +254,7 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat return new cloudwatch.Metric({ namespace: 'AWS/ApplicationELB', metricName, - dimensions: { + dimensionsMap: { TargetGroup: this.targetGroupFullName, LoadBalancer: this.firstLoadBalancerFullName, }, diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts index f4ac146d8209d..35c56e21e29bc 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts @@ -213,7 +213,7 @@ export class NetworkTargetGroup extends TargetGroupBase implements INetworkTarge return new cloudwatch.Metric({ namespace: 'AWS/NetworkELB', metricName, - dimensions: { LoadBalancer: this.firstLoadBalancerFullName, TargetGroup: this.targetGroupFullName }, + dimensionsMap: { LoadBalancer: this.firstLoadBalancerFullName, TargetGroup: this.targetGroupFullName }, ...props, }).attachTo(this); } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index bcf51d4d81a78..85ae9d143f0e2 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -52,16 +52,18 @@ export interface ILoadBalancerV2 extends IResource { /** * The canonical hosted zone ID of this load balancer * + * Example value: `Z2P70J7EXAMPLE` + * * @attribute - * @example Z2P70J7EXAMPLE */ readonly loadBalancerCanonicalHostedZoneId: string; /** * The DNS name of this load balancer * + * Example value: `my-load-balancer-424835706.us-west-2.elb.amazonaws.com` + * * @attribute - * @example my-load-balancer-424835706.us-west-2.elb.amazonaws.com */ readonly loadBalancerDnsName: string; } @@ -141,40 +143,45 @@ export abstract class BaseLoadBalancer extends Resource { /** * The canonical hosted zone ID of this load balancer * + * Example value: `Z2P70J7EXAMPLE` + * * @attribute - * @example Z2P70J7EXAMPLE */ public readonly loadBalancerCanonicalHostedZoneId: string; /** * The DNS name of this load balancer * + * Example value: `my-load-balancer-424835706.us-west-2.elb.amazonaws.com` + * * @attribute - * @example my-load-balancer-424835706.us-west-2.elb.amazonaws.com */ public readonly loadBalancerDnsName: string; /** * The full name of this load balancer * + * Example value: `app/my-load-balancer/50dc6c495c0c9188` + * * @attribute - * @example app/my-load-balancer/50dc6c495c0c9188 */ public readonly loadBalancerFullName: string; /** * The name of this load balancer * + * Example value: `my-load-balancer` + * * @attribute - * @example my-load-balancer */ public readonly loadBalancerName: string; /** * The ARN of this load balancer * + * Example value: `arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-internal-load-balancer/50dc6c495c0c9188` + * * @attribute - * @example arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-internal-load-balancer/50dc6c495c0c9188 */ public readonly loadBalancerArn: string; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts index 29a9cb707556e..69d925bc933e9 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts @@ -186,7 +186,7 @@ export abstract class TargetGroupBase extends CoreConstruct implements ITargetGr * This identifier is emitted as a dimensions of the metrics of this target * group. * - * @example app/my-load-balancer/123456789 + * Example value: `app/my-load-balancer/123456789` */ public abstract readonly firstLoadBalancerFullName: string; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json index f7f5546a41b05..d2a9b352b4163 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/actions.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/actions.test.ts index e69f4d241bc04..b3a6397ba6eaa 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/actions.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/actions.test.ts @@ -155,7 +155,7 @@ describe('tests', () => { }); listener.addAction('Action2', { - hostHeader: 'example.com', + conditions: [elbv2.ListenerCondition.hostHeaders(['example.com'])], priority: 10, action: elbv2.ListenerAction.forward([group2]), }); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/listener.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/listener.test.ts index c47585d98a5f7..14f9bd51bee61 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/listener.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/listener.test.ts @@ -1,7 +1,9 @@ import { MatchStyle } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; +import * as acm from '@aws-cdk/aws-certificatemanager'; import { Metric } from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { describeDeprecated, testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; import * as elbv2 from '../../lib'; @@ -17,7 +19,7 @@ describe('tests', () => { // WHEN lb.addListener('Listener', { port: 443, - certificateArns: ['bla'], + certificates: [importedCertificate(stack)], defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], }); @@ -133,7 +135,7 @@ describe('tests', () => { defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], }); - listener.addCertificateArns('Arns', ['cert']); + listener.addCertificates('Certs', [importedCertificate(stack, 'cert')]); // THEN expect(stack).toHaveResource('AWS::ElasticLoadBalancingV2::Listener', { @@ -230,7 +232,7 @@ describe('tests', () => { }); listener.addTargetGroups('WithPath', { priority: 10, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], targetGroups: [group], }); @@ -248,7 +250,7 @@ describe('tests', () => { Conditions: [ { Field: 'path-pattern', - Values: ['/hello'], + PathPatternConfig: { Values: ['/hello'] }, }, ], Actions: [ @@ -260,7 +262,7 @@ describe('tests', () => { }); }); - test('Can implicitly create target groups with and without conditions', () => { + testDeprecated('Can implicitly create target groups with and without conditions', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -274,7 +276,7 @@ describe('tests', () => { }); listener.addTargets('WithPath', { priority: 10, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], port: 80, targets: [new elbv2.InstanceTarget('i-5678')], }); @@ -314,7 +316,7 @@ describe('tests', () => { }); }); - test('Add certificate to constructed listener', () => { + testDeprecated('Add certificate to constructed listener', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -322,7 +324,7 @@ describe('tests', () => { const listener = lb.addListener('Listener', { port: 443 }); // WHEN - listener.addCertificateArns('Arns', ['cert']); + listener.addCertificates('Certs', [importedCertificate(stack, 'cert')]); listener.addTargets('Targets', { port: 8080, targets: [new elbv2.IpTarget('1.2.3.4')] }); // THEN @@ -339,11 +341,11 @@ describe('tests', () => { const listener2 = elbv2.ApplicationListener.fromApplicationListenerAttributes(stack2, 'Listener', { listenerArn: 'listener-arn', defaultPort: 443, - securityGroupId: 'security-group-id', + securityGroup: ec2.SecurityGroup.fromSecurityGroupId(stack2, 'SG', 'security-group-id'), }); // WHEN - listener2.addCertificateArns('Arns', ['cert']); + listener2.addCertificates('Certs', [importedCertificate(stack2, 'cert')]); // THEN expect(stack2).toHaveResource('AWS::ElasticLoadBalancingV2::ListenerCertificate', { @@ -482,7 +484,7 @@ describe('tests', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); - const listener = lb.addListener('Listener', { port: 443, certificateArns: ['arn:someCert'] }); + const listener = lb.addListener('Listener', { port: 443, certificates: [importedCertificate(stack, 'arn:someCert')] }); // WHEN listener.addTargets('Group', { @@ -503,14 +505,14 @@ describe('tests', () => { const vpc = new ec2.Vpc(stack, 'VPC'); const listener = elbv2.ApplicationListener.fromApplicationListenerAttributes(stack, 'Listener', { listenerArn: 'ieks', - securityGroupId: 'sg-12345', + securityGroup: ec2.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'sg-12345'), }); const group = new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, port: 80 }); // WHEN listener.addTargetGroups('Gruuup', { priority: 30, - hostHeader: 'example.com', + conditions: [elbv2.ListenerCondition.hostHeaders(['example.com'])], targetGroups: [group], }); @@ -533,7 +535,7 @@ describe('tests', () => { const vpc = new ec2.Vpc(stack, 'VPC'); const listener = elbv2.ApplicationListener.fromApplicationListenerAttributes(stack, 'Listener', { listenerArn: 'ieks', - securityGroupId: 'sg-12345', + securityGroup: ec2.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'sg-12345'), }); const group = new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, port: 80 }); @@ -640,7 +642,7 @@ describe('tests', () => { new ResourceWithLBDependency(stack, 'SomeResource', group2); listener.addTargetGroups('SecondGroup', { - pathPattern: '/bla', + conditions: [elbv2.ListenerCondition.pathPatterns(['/bla'])], priority: 10, targetGroups: [group2], }); @@ -668,15 +670,16 @@ describe('tests', () => { }); // WHEN - listener.addFixedResponse('Default', { - contentType: elbv2.ContentType.TEXT_PLAIN, - messageBody: 'Not Found', - statusCode: '404', + listener.addAction('Default', { + action: elbv2.ListenerAction.fixedResponse(404, { + contentType: 'text/plain', + messageBody: 'Not Found', + }), }); - listener.addFixedResponse('Hello', { + listener.addAction('Hello', { + action: elbv2.ListenerAction.fixedResponse(503), + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], priority: 10, - pathPattern: '/hello', - statusCode: '503', }); // THEN @@ -705,7 +708,7 @@ describe('tests', () => { }); }); - test('Can add redirect responses', () => { + testDeprecated('Can add redirect responses', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); @@ -724,7 +727,7 @@ describe('tests', () => { }); listener.addRedirectResponse('Hello', { priority: 10, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], path: '/new/#{path}', statusCode: 'HTTP_302', }); @@ -824,7 +827,7 @@ describe('tests', () => { targetProtocol: elbv2.ApplicationProtocol.HTTP, targetPort: 8080, }); - listener.addCertificateArns('ListenerCertificateX', ['cert3']); + listener.addCertificates('ListenerCertificateX', [importedCertificate(stack, 'cert3')]); // THEN expect(stack).toHaveResource('AWS::ElasticLoadBalancingV2::Listener', { @@ -899,7 +902,7 @@ describe('tests', () => { }); }); - describe('Throws with bad fixed responses', () => { + describeDeprecated('Throws with bad fixed responses', () => { test('status code', () => { // GIVEN @@ -937,7 +940,7 @@ describe('tests', () => { }); }); - describe('Throws with bad redirect responses', () => { + describeDeprecated('Throws with bad redirect responses', () => { test('status code', () => { // GIVEN @@ -975,7 +978,7 @@ describe('tests', () => { }); }); - test('Throws when specifying both target groups and fixed response', () => { + test('Throws when specifying both target groups and an action', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); @@ -988,14 +991,12 @@ describe('tests', () => { // THEN expect(() => new elbv2.ApplicationListenerRule(stack, 'Rule', { + action: elbv2.ListenerAction.fixedResponse(500), listener, priority: 10, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], targetGroups: [new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, port: 80 })], - fixedResponse: { - statusCode: '500', - }, - })).toThrow(/'targetGroups,fixedResponse'.*/); + })).toThrow(/'action,targetGroups'.*/); }); test('Throws when specifying priority 0', () => { @@ -1011,12 +1012,10 @@ describe('tests', () => { // THEN expect(() => new elbv2.ApplicationListenerRule(stack, 'Rule', { + action: elbv2.ListenerAction.fixedResponse(500), listener, priority: 0, - pathPattern: '/hello', - fixedResponse: { - statusCode: '500', - }, + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], })).toThrowError('Priority must have value greater than or equal to 1'); }); @@ -1035,14 +1034,14 @@ describe('tests', () => { expect(() => new elbv2.ApplicationListenerRule(stack, 'Rule', { listener, priority: new cdk.CfnParameter(stack, 'PriorityParam', { type: 'Number' }).valueAsNumber, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], fixedResponse: { statusCode: '500', }, })).not.toThrowError('Priority must have value greater than or equal to 1'); }); - test('Throws when specifying both target groups and redirect response', () => { + testDeprecated('Throws when specifying both target groups and redirect response', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); @@ -1057,7 +1056,7 @@ describe('tests', () => { expect(() => new elbv2.ApplicationListenerRule(stack, 'Rule', { listener, priority: 10, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], targetGroups: [new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, port: 80 })], redirectResponse: { statusCode: 'HTTP_301', @@ -1067,7 +1066,7 @@ describe('tests', () => { expect(() => new elbv2.ApplicationListenerRule(stack, 'Rule2', { listener, priority: 10, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], targetGroups: [new elbv2.ApplicationTargetGroup(stack, 'TargetGroup2', { vpc, port: 80 })], fixedResponse: { statusCode: '500', @@ -1107,7 +1106,10 @@ describe('tests', () => { // WHEN lb.addListener('Listener', { port: 443, - certificateArns: ['cert1', 'cert2'], + certificates: [ + importedCertificate(stack, 'cert1'), + importedCertificate(stack, 'cert2'), + ], defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], }); @@ -1144,7 +1146,7 @@ describe('tests', () => { }); }); - test('Can add additional certificates via addCertificateArns to application listener', () => { + testDeprecated('Can add additional certificates via addCertificateArns to application listener', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -1182,13 +1184,13 @@ describe('tests', () => { // WHEN const listener = lb.addListener('Listener', { port: 443, - certificateArns: ['cert1', 'cert2'], + certificates: [importedCertificate(stack, 'cert1'), importedCertificate(stack, 'cert2')], defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], }); listener.addTargets('Target1', { priority: 10, - pathPatterns: ['/test/path/1', '/test/path/2'], + conditions: [elbv2.ListenerCondition.pathPatterns(['/test/path/1', '/test/path/2'])], }); // THEN @@ -1197,13 +1199,13 @@ describe('tests', () => { Conditions: [ { Field: 'path-pattern', - Values: ['/test/path/1', '/test/path/2'], + PathPatternConfig: { Values: ['/test/path/1', '/test/path/2'] }, }, ], }); }); - test('Cannot add pathPattern and pathPatterns to listener rule', () => { + testDeprecated('Cannot add pathPattern and pathPatterns to listener rule', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -1212,7 +1214,7 @@ describe('tests', () => { // WHEN const listener = lb.addListener('Listener', { port: 443, - certificateArns: ['cert1', 'cert2'], + certificates: [importedCertificate(stack, 'cert1'), importedCertificate(stack, 'cert2')], defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], }); @@ -1235,7 +1237,7 @@ describe('tests', () => { // WHEN const listener = lb.addListener('Listener', { port: 443, - certificateArns: ['cert1'], + certificates: [importedCertificate(stack, 'cert1')], defaultTargetGroups: [group2], }); listener.addTargetGroups('TargetGroup1', { @@ -1299,7 +1301,7 @@ describe('tests', () => { // WHEN const listener = lb.addListener('Listener', { port: 443, - certificateArns: ['cert1'], + certificates: [importedCertificate(stack, 'cert1')], defaultTargetGroups: [group3], }); listener.addTargetGroups('TargetGroup1', { @@ -1418,7 +1420,7 @@ describe('tests', () => { }); }); - test('Can exist together legacy style conditions and modern style conditions', () => { + testDeprecated('Can exist together legacy style conditions and modern style conditions', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -1429,7 +1431,7 @@ describe('tests', () => { // WHEN const listener = lb.addListener('Listener', { port: 443, - certificateArns: ['cert1'], + certificates: [importedCertificate(stack, 'cert1')], defaultTargetGroups: [group2], }); listener.addTargetGroups('TargetGroup1', { @@ -1472,14 +1474,14 @@ describe('tests', () => { const listener = elbv2.ApplicationListener.fromApplicationListenerAttributes(stack, 'Listener', { listenerArn: 'listener-arn', defaultPort: 443, - securityGroupId: 'security-group-id', + securityGroup: ec2.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'security-group-id'), }); // WHEN listener.addTargetGroups('OtherTG', { targetGroups: [group], priority: 1, - pathPatterns: ['/path1', '/path2'], + conditions: [elbv2.ListenerCondition.pathPatterns(['/path1', '/path2'])], }); // THEN @@ -1488,13 +1490,13 @@ describe('tests', () => { Conditions: [ { Field: 'path-pattern', - Values: ['/path1', '/path2'], + PathPatternConfig: { Values: ['/path1', '/path2'] }, }, ], }); }); - test('not allowed to combine action specifiers when instantiating a Rule directly', () => { + testDeprecated('not allowed to combine action specifiers when instantiating a Rule directly', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -1612,8 +1614,8 @@ describe('tests', () => { }); // WHEN - listener.addCertificateArns('certs', [ - 'arn:something', + listener.addCertificates('certs', [ + importedCertificate(stack, 'arn:something'), ]); // THEN @@ -1632,3 +1634,8 @@ class ResourceWithLBDependency extends cdk.CfnResource { this.node.addDependency(targetGroup.loadBalancerAttached); } } + +function importedCertificate(stack: cdk.Stack, + certificateArn = 'arn:aws:certificatemanager:123456789012:testregion:certificate/fd0b8392-3c0e-4704-81b6-8edf8612c852') { + return acm.Certificate.fromCertificateArn(stack, certificateArn, certificateArn); +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/security-group.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/security-group.test.ts index 93dd1c2d4ba33..03d17a207f16a 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/security-group.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/security-group.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as elbv2 from '../../lib'; import { FakeSelfRegisteringTarget } from '../helpers'; @@ -34,7 +35,7 @@ describe('tests', () => { fixture.listener.addTargetGroups('Rule', { priority: 10, - hostHeader: 'example.com', + conditions: [elbv2.ListenerCondition.hostHeaders(['example.com'])], targetGroups: [new elbv2.ApplicationTargetGroup(fixture.stack, 'TargetGroup2', { vpc: fixture.vpc, port: 8008, @@ -63,7 +64,7 @@ describe('tests', () => { }); fixture.listener.addTargetGroups('WithPath', { priority: 10, - pathPattern: '/hello', + conditions: [elbv2.ListenerCondition.pathPatterns(['/hello'])], targetGroups: [group], }); @@ -117,7 +118,7 @@ describe('tests', () => { listener: fixture.listener, targetGroups: [childGroup], priority: 100, - hostHeader: 'www.foo.com', + conditions: [elbv2.ListenerCondition.hostHeaders(['www.foo.com'])], }); // THEN @@ -164,16 +165,18 @@ describe('tests', () => { fixture.listener.addTargets('default', { port: 80 }); // WHEN + const securityGroup = ec2.SecurityGroup.fromSecurityGroupId(stack2, 'SecurityGroup', + fixture.listener.connections.securityGroups[0].securityGroupId, + { allowAllOutbound: false }); const listener2 = elbv2.ApplicationListener.fromApplicationListenerAttributes(stack2, 'YetAnotherListener', { defaultPort: 8008, - securityGroupId: fixture.listener.connections.securityGroups[0].securityGroupId, listenerArn: fixture.listener.listenerArn, - securityGroupAllowsAllOutbound: false, + securityGroup, }); listener2.addTargetGroups('Default', { // Must be a non-default target priority: 10, - hostHeader: 'example.com', + conditions: [elbv2.ListenerCondition.hostHeaders(['example.com'])], targetGroups: [group], }); @@ -181,7 +184,7 @@ describe('tests', () => { expectedImportedSGRules(stack2); }); - test('default port peering works on constructed listener', () => { + testDeprecated('default port peering works on constructed listener', () => { // GIVEN const fixture = new TestFixture(); fixture.listener.addTargets('Default', { port: 8080, targets: [new elbv2.InstanceTarget('i-12345')] }); @@ -206,11 +209,12 @@ describe('tests', () => { test('default port peering works on imported listener', () => { // GIVEN const stack2 = new cdk.Stack(); + const securityGroup = ec2.SecurityGroup.fromSecurityGroupId(stack2, 'SecurityGroup', 'imported-security-group-id'); // WHEN const listener2 = elbv2.ApplicationListener.fromApplicationListenerAttributes(stack2, 'YetAnotherListener', { listenerArn: 'listener-arn', - securityGroupId: 'imported-security-group-id', + securityGroup, defaultPort: 8080, }); listener2.connections.allowDefaultPortFromAnyIpv4('Open to the world'); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts index 313985e09d3ee..7eed0a4627011 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as elbv2 from '../../lib'; import { FakeSelfRegisteringTarget } from '../helpers'; @@ -32,7 +33,7 @@ describe('tests', () => { tg.addTarget(new FakeSelfRegisteringTarget(stack, 'Target', vpc)); }); - test('Cannot add direct target to imported TargetGroup', () => { + testDeprecated('Cannot add direct target to imported TargetGroup', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'Stack'); @@ -46,7 +47,7 @@ describe('tests', () => { }).toThrow(/Cannot add a non-self registering target to an imported TargetGroup/); }); - test('HealthCheck fields set if provided', () => { + testDeprecated('HealthCheck fields set if provided', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'Stack'); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/listener.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/listener.test.ts index e2666d3056c9f..a5b3d5aea1e5c 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/listener.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/listener.test.ts @@ -2,6 +2,7 @@ import { MatchStyle } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; import * as acm from '@aws-cdk/aws-certificatemanager'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; import * as elbv2 from '../../lib'; @@ -49,7 +50,7 @@ describe('tests', () => { }); }); - test('Can implicitly create target groups', () => { + testDeprecated('Can implicitly create target groups', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -81,7 +82,7 @@ describe('tests', () => { }); }); - test('implicitly created target group inherits protocol', () => { + testDeprecated('implicitly created target group inherits protocol', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -113,7 +114,7 @@ describe('tests', () => { }); }); - test('implicitly created target group but overrides inherited protocol', () => { + testDeprecated('implicitly created target group but overrides inherited protocol', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); diff --git a/packages/@aws-cdk/aws-elasticsearch/README.md b/packages/@aws-cdk/aws-elasticsearch/README.md index 3de87e175693c..6de168cf70f7b 100644 --- a/packages/@aws-cdk/aws-elasticsearch/README.md +++ b/packages/@aws-cdk/aws-elasticsearch/README.md @@ -29,21 +29,17 @@ Higher level constructs for Domain | ![Stable](https://img.shields.io/badge/stab Create a development cluster by simply specifying the version: ```ts -import * as es from '@aws-cdk/aws-elasticsearch'; - const devDomain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_1, + version: es.ElasticsearchVersion.V7_1, }); ``` To perform version upgrades without replacing the entire domain, specify the `enableVersionUpgrade` property. ```ts -import * as es from '@aws-cdk/aws-elasticsearch'; - const devDomain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_10, - enableVersionUpgrade: true // defaults to false + version: es.ElasticsearchVersion.V7_10, + enableVersionUpgrade: true, // defaults to false }); ``` @@ -51,22 +47,22 @@ Create a production grade cluster by also specifying things like capacity and az ```ts const prodDomain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_1, - capacity: { - masterNodes: 5, - dataNodes: 20 - }, - ebs: { - volumeSize: 20 - }, - zoneAwareness: { - availabilityZoneCount: 3 - }, - logging: { - slowSearchLogEnabled: true, - appLogEnabled: true, - slowIndexLogEnabled: true, - }, + version: es.ElasticsearchVersion.V7_1, + capacity: { + masterNodes: 5, + dataNodes: 20, + }, + ebs: { + volumeSize: 20, + }, + zoneAwareness: { + availabilityZoneCount: 3, + }, + logging: { + slowSearchLogEnabled: true, + appLogEnabled: true, + slowIndexLogEnabled: true, + }, }); ``` @@ -93,7 +89,7 @@ You can also create it using the CDK, **but note that only the first application ```ts const slr = new iam.CfnServiceLinkedRole(this, 'ElasticSLR', { - awsServiceName: 'es.amazonaws.com' + awsServiceName: 'es.amazonaws.com', }); ``` @@ -104,7 +100,7 @@ This method accepts a domain endpoint of an already existing domain: ```ts const domainEndpoint = 'https://my-domain-jcjotrt6f7otem4sqcwbch3c4u.us-east-1.es.amazonaws.com'; -const domain = Domain.fromDomainEndpoint(this, 'ImportedDomain', domainEndpoint); +const domain = es.Domain.fromDomainEndpoint(this, 'ImportedDomain', domainEndpoint); ``` ## Permissions @@ -114,13 +110,14 @@ const domain = Domain.fromDomainEndpoint(this, 'ImportedDomain', domainEndpoint) Helper methods also exist for managing access to the domain. ```ts -const lambda = new lambda.Function(this, 'Lambda', { /* ... */ }); +declare const fn: lambda.Function; +declare const domain: es.Domain; // Grant write access to the app-search index -domain.grantIndexWrite('app-search', lambda); +domain.grantIndexWrite('app-search', fn); // Grant read access to the 'app-search/_search' path -domain.grantPathRead('app-search/_search', lambda); +domain.grantPathRead('app-search/_search', fn); ``` ## Encryption @@ -129,15 +126,15 @@ The domain can also be created with encryption enabled: ```ts const domain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_4, - ebs: { - volumeSize: 100, - volumeType: EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, - }, - nodeToNodeEncryption: true, - encryptionAtRest: { - enabled: true, - }, + version: es.ElasticsearchVersion.V7_4, + ebs: { + volumeSize: 100, + volumeType: ec2.EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, + }, + nodeToNodeEncryption: true, + encryptionAtRest: { + enabled: true, + }, }); ``` @@ -177,6 +174,7 @@ which security groups will be attached to the domain. By default, CDK will selec Helper methods exist to access common domain metrics for example: ```ts +declare const domain: es.Domain; const freeStorageSpace = domain.metricFreeStorageSpace(); const masterSysMemoryUtilization = domain.metric('MasterSysMemoryUtilization'); ``` @@ -190,15 +188,15 @@ be supplied or dynamically created if not supplied. ```ts const domain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_1, - enforceHttps: true, - nodeToNodeEncryption: true, - encryptionAtRest: { - enabled: true, - }, - fineGrainedAccessControl: { - masterUserName: 'master-user', - }, + version: es.ElasticsearchVersion.V7_1, + enforceHttps: true, + nodeToNodeEncryption: true, + encryptionAtRest: { + enabled: true, + }, + fineGrainedAccessControl: { + masterUserName: 'master-user', + }, }); const masterUserPassword = domain.masterUserPassword; @@ -228,8 +226,8 @@ stored in the AWS Secrets Manager as secret. The secret has the prefix ```ts const domain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_1, - useUnsignedBasicAuth: true, + version: es.ElasticsearchVersion.V7_1, + useUnsignedBasicAuth: true, }); const masterUserPassword = domain.masterUserPassword; @@ -243,21 +241,21 @@ Audit logs can be enabled for a domain, but only when fine grained access contro ```ts const domain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_1, - enforceHttps: true, - nodeToNodeEncryption: true, - encryptionAtRest: { - enabled: true, - }, - fineGrainedAccessControl: { - masterUserName: 'master-user', - }, - logging: { - auditLogEnabled: true, - slowSearchLogEnabled: true, - appLogEnabled: true, - slowIndexLogEnabled: true, - }, + version: es.ElasticsearchVersion.V7_1, + enforceHttps: true, + nodeToNodeEncryption: true, + encryptionAtRest: { + enabled: true, + }, + fineGrainedAccessControl: { + masterUserName: 'master-user', + }, + logging: { + auditLogEnabled: true, + slowSearchLogEnabled: true, + appLogEnabled: true, + slowIndexLogEnabled: true, + }, }); ``` @@ -267,12 +265,12 @@ UltraWarm nodes can be enabled to provide a cost-effective way to store large am ```ts const domain = new es.Domain(this, 'Domain', { - version: es.ElasticsearchVersion.V7_10, - capacity: { - masterNodes: 2, - warmNodes: 2, - warmInstanceType: 'ultrawarm1.medium.elasticsearch', - }, + version: es.ElasticsearchVersion.V7_10, + capacity: { + masterNodes: 2, + warmNodes: 2, + warmInstanceType: 'ultrawarm1.medium.elasticsearch', + }, }); ``` @@ -281,11 +279,11 @@ const domain = new es.Domain(this, 'Domain', { Custom endpoints can be configured to reach the ES domain under a custom domain name. ```ts -new Domain(stack, 'Domain', { - version: ElasticsearchVersion.V7_7, - customEndpoint: { - domainName: 'search.example.com', - }, +new es.Domain(this, 'Domain', { + version: es.ElasticsearchVersion.V7_7, + customEndpoint: { + domainName: 'search.example.com', + }, }); ``` @@ -298,13 +296,13 @@ Additionally, an automatic CNAME-Record is created if a hosted zone is provided [Advanced options](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-createupdatedomains.html#es-createdomain-configure-advanced-options) can used to configure additional options. ```ts -new Domain(stack, 'Domain', { - version: ElasticsearchVersion.V7_7, - advancedOptions: { - 'rest.action.multi.allow_explicit_index': 'false', - 'indices.fielddata.cache.size': '25', - 'indices.query.bool.max_clause_count': '2048', - }, +new es.Domain(this, 'Domain', { + version: es.ElasticsearchVersion.V7_7, + advancedOptions: { + 'rest.action.multi.allow_explicit_index': 'false', + 'indices.fielddata.cache.size': '25', + 'indices.query.bool.max_clause_count': '2048', + }, }); ``` diff --git a/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts b/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts index e3caa77837910..14c41d3447395 100644 --- a/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts +++ b/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts @@ -952,7 +952,7 @@ abstract class DomainBase extends cdk.Resource implements IDomain { return new Metric({ namespace: 'AWS/ES', metricName, - dimensions: { + dimensionsMap: { DomainName: this.domainName, ClientId: this.stack.account, }, @@ -1212,7 +1212,8 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { */ public static fromDomainAttributes(scope: Construct, id: string, attrs: DomainAttributes): IDomain { const { domainArn, domainEndpoint } = attrs; - const domainName = cdk.Stack.of(scope).parseArn(domainArn).resourceName ?? extractNameFromEndpoint(domainEndpoint); + const domainName = cdk.Stack.of(scope).splitArn(domainArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName + ?? extractNameFromEndpoint(domainEndpoint); return new class extends DomainBase { public readonly domainArn = domainArn; diff --git a/packages/@aws-cdk/aws-elasticsearch/lib/elasticsearch-access-policy.ts b/packages/@aws-cdk/aws-elasticsearch/lib/elasticsearch-access-policy.ts index bb5a530211719..a79c716831b72 100644 --- a/packages/@aws-cdk/aws-elasticsearch/lib/elasticsearch-access-policy.ts +++ b/packages/@aws-cdk/aws-elasticsearch/lib/elasticsearch-access-policy.ts @@ -44,7 +44,7 @@ export class ElasticsearchAccessPolicy extends cr.AwsCustomResource { AccessPolicies: JSON.stringify(policyDocument.toJSON()), }, // this is needed to limit the response body, otherwise it exceeds the CFN 4k limit - outputPath: 'DomainConfig.ElasticsearchClusterConfig.AccessPolicies', + outputPaths: ['DomainConfig.ElasticsearchClusterConfig.AccessPolicies'], physicalResourceId: cr.PhysicalResourceId.of(`${props.domainName}AccessPolicy`), }, policy: cr.AwsCustomResourcePolicy.fromSdkCalls({ resources: [props.domainArn] }), diff --git a/packages/@aws-cdk/aws-elasticsearch/package.json b/packages/@aws-cdk/aws-elasticsearch/package.json index 18497a887276f..0ddd787d22acb 100644 --- a/packages/@aws-cdk/aws-elasticsearch/package.json +++ b/packages/@aws-cdk/aws-elasticsearch/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-elasticsearch/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-elasticsearch/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..d0c3bec94ce39 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticsearch/rosetta/default.ts-fixture @@ -0,0 +1,15 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { RemovalPolicy, Stack } from '@aws-cdk/core'; +import * as es from '@aws-cdk/aws-elasticsearch'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch-access-policy.test.ts b/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch-access-policy.test.ts index ebb83e123ed84..53d6afe3b2cb0 100644 --- a/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch-access-policy.test.ts +++ b/packages/@aws-cdk/aws-elasticsearch/test/elasticsearch-access-policy.test.ts @@ -40,7 +40,7 @@ test('minimal example renders correctly', () => { DomainName: 'TestDomain', AccessPolicies: '{"Statement":[{"Action":"es:ESHttp*","Effect":"Allow","Principal":{"AWS":"*"},"Resource":"test:arn"}],"Version":"2012-10-17"}', }, - outputPath: 'DomainConfig.ElasticsearchClusterConfig.AccessPolicies', + outputPaths: ['DomainConfig.ElasticsearchClusterConfig.AccessPolicies'], physicalResourceId: { id: 'TestDomainAccessPolicy' }, }), Update: JSON.stringify({ @@ -50,7 +50,7 @@ test('minimal example renders correctly', () => { DomainName: 'TestDomain', AccessPolicies: '{"Statement":[{"Action":"es:ESHttp*","Effect":"Allow","Principal":{"AWS":"*"},"Resource":"test:arn"}],"Version":"2012-10-17"}', }, - outputPath: 'DomainConfig.ElasticsearchClusterConfig.AccessPolicies', + outputPaths: ['DomainConfig.ElasticsearchClusterConfig.AccessPolicies'], physicalResourceId: { id: 'TestDomainAccessPolicy' }, }), }); diff --git a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.custom-kms-key.expected.json b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.custom-kms-key.expected.json index 8cbc696c94e14..636acfc906b05 100644 --- a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.custom-kms-key.expected.json +++ b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.custom-kms-key.expected.json @@ -252,7 +252,7 @@ { "Ref": "AWS::AccountId" }, - ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain66AC69E0" }, @@ -276,7 +276,7 @@ { "Ref": "AWS::AccountId" }, - ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain66AC69E0" }, diff --git a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.expected.json b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.expected.json index 3734722a35ca7..73c6624609fd4 100644 --- a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.expected.json +++ b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.expected.json @@ -219,7 +219,7 @@ { "Ref": "AWS::AccountId" }, - ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain19FCBCB91" }, @@ -243,7 +243,7 @@ { "Ref": "AWS::AccountId" }, - ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain19FCBCB91" }, @@ -565,7 +565,7 @@ { "Ref": "AWS::AccountId" }, - ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain2644FE48C" }, @@ -589,7 +589,7 @@ { "Ref": "AWS::AccountId" }, - ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + ":root\\\"},\\\"Resource\\\":\\\"*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain2644FE48C" }, diff --git a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.unsignedbasicauth.expected.json b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.unsignedbasicauth.expected.json index d74ea6423a7bd..2793a8beb231f 100644 --- a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.unsignedbasicauth.expected.json +++ b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch.unsignedbasicauth.expected.json @@ -117,7 +117,7 @@ "Arn" ] }, - "/*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + "/*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain66AC69E0" }, @@ -140,7 +140,7 @@ "Arn" ] }, - "/*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPath\":\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\",\"physicalResourceId\":{\"id\":\"", + "/*\\\"}],\\\"Version\\\":\\\"2012-10-17\\\"}\"},\"outputPaths\":[\"DomainConfig.ElasticsearchClusterConfig.AccessPolicies\"],\"physicalResourceId\":{\"id\":\"", { "Ref": "Domain66AC69E0" }, diff --git a/packages/@aws-cdk/aws-events-targets/lib/log-group.ts b/packages/@aws-cdk/aws-events-targets/lib/log-group.ts index 688cfc3773c13..18c11cad862db 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/log-group.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/log-group.ts @@ -2,6 +2,7 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; import * as cdk from '@aws-cdk/core'; +import { ArnFormat } from '@aws-cdk/core'; import { LogGroupResourcePolicy } from './log-group-resource-policy'; import { TargetBaseProps, bindBaseTargetConfig } from './util'; @@ -30,7 +31,7 @@ export class CloudWatchLogGroup implements events.IRuleTarget { */ public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig { // Use a custom resource to set the log group resource policy since it is not supported by CDK and cfn. - const resourcePolicyId = `EventsLogGroupPolicy${_rule.node.uniqueId}`; + const resourcePolicyId = `EventsLogGroupPolicy${cdk.Names.nodeUniqueId(_rule.node)}`; const logGroupStack = cdk.Stack.of(this.logGroup); @@ -50,7 +51,7 @@ export class CloudWatchLogGroup implements events.IRuleTarget { arn: logGroupStack.formatArn({ service: 'logs', resource: 'log-group', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, resourceName: this.logGroup.logGroupName, }), input: this.props.event, diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index e937e5c40ac4b..1184651b4e9a3 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -86,6 +93,7 @@ }, "dependencies": { "@aws-cdk/aws-apigateway": "0.0.0", + "@aws-cdk/aws-autoscaling": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codepipeline": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", @@ -108,6 +116,7 @@ "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { "@aws-cdk/aws-apigateway": "0.0.0", + "@aws-cdk/aws-autoscaling": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codepipeline": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts b/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts index 1b084a0ec35b1..eb17b98622369 100644 --- a/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts @@ -1,20 +1,31 @@ import '@aws-cdk/assert-internal/jest'; +import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as targets from '../../lib'; -test('Can use EC2 taskdef as EventRule target', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { +let stack: cdk.Stack; +let vpc: ec2.Vpc; +let cluster: ecs.Cluster; + +beforeEach(() => { + stack = new cdk.Stack(); + vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); + cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const autoScalingGroup = new autoscaling.AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + machineImage: ecs.EcsOptimizedImage.amazonLinux2(), instanceType: new ec2.InstanceType('t2.micro'), }); + const provider = new ecs.AsgCapacityProvider(stack, 'AsgCapacityProvider', { autoScalingGroup }); + cluster.addAsgCapacityProvider(provider); +}); +test('Can use EC2 taskdef as EventRule target', () => { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('henk'), @@ -61,13 +72,6 @@ test('Can use EC2 taskdef as EventRule target', () => { test('Throws error for lacking of taskRole ' + 'when importing from an EC2 task definition just from a task definition arn as EventRule target', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); - const taskDefinition = ecs.Ec2TaskDefinition.fromEc2TaskDefinitionArn(stack, 'TaskDef', 'importedTaskDefArn'); const rule = new events.Rule(stack, 'Rule', { @@ -91,13 +95,6 @@ test('Throws error for lacking of taskRole ' + test('Can import an EC2 task definition from task definition attributes as EventRule target', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addCapacity('DefaultAutoScalingGroup', { - instanceType: new ec2.InstanceType('t2.micro'), - }); - const taskDefinition = ecs.Ec2TaskDefinition.fromEc2TaskDefinitionAttributes(stack, 'TaskDef', { taskDefinitionArn: 'importedTaskDefArn', networkMode: ecs.NetworkMode.BRIDGE, @@ -146,10 +143,6 @@ test('Can import an EC2 task definition from task definition attributes as Event test('Throws error for lacking of taskRole ' + 'when importing from a Fargate task definition just from a task definition arn as EventRule target', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = ecs.FargateTaskDefinition.fromFargateTaskDefinitionArn(stack, 'TaskDef', 'ImportedTaskDefArn'); const rule = new events.Rule(stack, 'Rule', { @@ -173,10 +166,6 @@ test('Throws error for lacking of taskRole ' + test('Can import a Fargate task definition from task definition attributes as EventRule target', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = ecs.FargateTaskDefinition.fromFargateTaskDefinitionAttributes(stack, 'TaskDef', { taskDefinitionArn: 'importedTaskDefArn', networkMode: ecs.NetworkMode.AWS_VPC, @@ -225,10 +214,6 @@ test('Can import a Fargate task definition from task definition attributes as Ev test('Throws error for lacking of taskRole ' + 'when importing from a task definition just from a task definition arn as EventRule target', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = ecs.TaskDefinition.fromTaskDefinitionArn(stack, 'TaskDef', 'ImportedTaskDefArn'); const rule = new events.Rule(stack, 'Rule', { @@ -252,10 +237,6 @@ test('Throws error for lacking of taskRole ' + test('Can import a Task definition from task definition attributes as EventRule target', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = ecs.FargateTaskDefinition.fromFargateTaskDefinitionAttributes(stack, 'TaskDef', { taskDefinitionArn: 'importedTaskDefArn', networkMode: ecs.NetworkMode.AWS_VPC, @@ -303,10 +284,6 @@ test('Can import a Task definition from task definition attributes as EventRule test('Can use Fargate taskdef as EventRule target', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('henk'), @@ -329,7 +306,7 @@ test('Can use Fargate taskdef as EventRule target', () => { rule.addTarget(target); // THEN - expect(target.securityGroup).toBeDefined(); // Generated security groups should be accessible. + expect(target.securityGroups?.length).toBeGreaterThan(0); // Generated security groups should be accessible. expect(stack).toHaveResourceLike('AWS::Events::Rule', { Targets: [ { @@ -372,10 +349,6 @@ test('Can use Fargate taskdef as EventRule target', () => { test('Can use same fargate taskdef with multiple rules', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('henk'), @@ -404,10 +377,6 @@ test('Can use same fargate taskdef with multiple rules', () => { test('Can use same fargate taskdef multiple times in a rule', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('henk'), @@ -438,15 +407,14 @@ test('Can use same fargate taskdef multiple times in a rule', () => { test('Isolated subnet does not have AssignPublicIp=true', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { + vpc = new ec2.Vpc(stack, 'Vpc2', { maxAzs: 1, subnetConfiguration: [{ subnetType: ec2.SubnetType.ISOLATED, name: 'Isolated', }], }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster = new ecs.Cluster(stack, 'EcsCluster2', { vpc }); const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { @@ -473,7 +441,7 @@ test('Isolated subnet does not have AssignPublicIp=true', () => { expect(stack).toHaveResourceLike('AWS::Events::Rule', { Targets: [ { - Arn: { 'Fn::GetAtt': ['EcsCluster97242B84', 'Arn'] }, + Arn: { 'Fn::GetAtt': ['EcsCluster2F191ADEC', 'Arn'] }, EcsParameters: { TaskCount: 1, TaskDefinitionArn: { Ref: 'TaskDef54694570' }, @@ -482,7 +450,7 @@ test('Isolated subnet does not have AssignPublicIp=true', () => { AwsVpcConfiguration: { Subnets: [ { - Ref: 'VpcIsolatedSubnet1SubnetE48C5737', + Ref: 'Vpc2IsolatedSubnet1SubnetB1A200D6', }, ], AssignPublicIp: 'DISABLED', @@ -505,12 +473,8 @@ test('Isolated subnet does not have AssignPublicIp=true', () => { }); }); -test('throws an error if both securityGroup and securityGroups is specified', () => { +testDeprecated('throws an error if both securityGroup and securityGroups is specified', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('henk'), @@ -539,10 +503,6 @@ test('throws an error if both securityGroup and securityGroups is specified', () test('uses multiple security groups', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('henk'), @@ -600,9 +560,6 @@ test('uses multiple security groups', () => { test('uses existing IAM role', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); const role = new iam.Role(stack, 'CustomIamRole', { assumedBy: new iam.ServicePrincipal('events.amazonaws.com'), }); @@ -649,9 +606,6 @@ test('uses existing IAM role', () => { test('uses the specific fargate platform version', () => { // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); const platformVersion = ecs.FargatePlatformVersion.VERSION1_4; const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); diff --git a/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts b/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts index b1222b9e178f5..d23d7eb3feae0 100644 --- a/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts @@ -322,7 +322,7 @@ test('must display a warning when using a Dead Letter Queue from another account expect(stack1).not.toHaveResource('AWS::SQS::QueuePolicy'); let rule = stack1.node.children.find(child => child instanceof events.Rule); - expect(rule?.node.metadata[0].data).toMatch(/Cannot add a resource policy to your dead letter queue associated with rule .* because the queue is in a different account\. You must add the resource policy manually to the dead letter queue in account 222222222222\./); + expect(rule?.node.metadataEntry[0].data).toMatch(/Cannot add a resource policy to your dead letter queue associated with rule .* because the queue is in a different account\. You must add the resource policy manually to the dead letter queue in account 222222222222\./); }); diff --git a/packages/@aws-cdk/aws-events/lib/event-bus.ts b/packages/@aws-cdk/aws-events/lib/event-bus.ts index bdddbc9cceb61..3bf8768e54695 100644 --- a/packages/@aws-cdk/aws-events/lib/event-bus.ts +++ b/packages/@aws-cdk/aws-events/lib/event-bus.ts @@ -1,5 +1,5 @@ import * as iam from '@aws-cdk/aws-iam'; -import { IResource, Lazy, Names, Resource, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, IResource, Lazy, Names, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { Archive, BaseArchiveProps } from './archive'; import { CfnEventBus } from './events.generated'; @@ -169,7 +169,7 @@ export class EventBus extends EventBusBase { * @param eventBusArn ARN of imported event bus */ public static fromEventBusArn(scope: Construct, id: string, eventBusArn: string): IEventBus { - const parts = Stack.of(scope).parseArn(eventBusArn); + const parts = Stack.of(scope).splitArn(eventBusArn, ArnFormat.SLASH_RESOURCE_NAME); return new ImportedEventBus(scope, id, { eventBusArn: eventBusArn, @@ -335,7 +335,7 @@ class ImportedEventBus extends EventBusBase { public readonly eventBusPolicy: string; public readonly eventSourceName?: string; constructor(scope: Construct, id: string, attrs: EventBusAttributes) { - const arnParts = Stack.of(scope).parseArn(attrs.eventBusArn); + const arnParts = Stack.of(scope).splitArn(attrs.eventBusArn, ArnFormat.SLASH_RESOURCE_NAME); super(scope, id, { account: arnParts.account, region: arnParts.region, diff --git a/packages/@aws-cdk/aws-events/lib/rule.ts b/packages/@aws-cdk/aws-events/lib/rule.ts index 47f5d05201a53..19f84e8cc479c 100644 --- a/packages/@aws-cdk/aws-events/lib/rule.ts +++ b/packages/@aws-cdk/aws-events/lib/rule.ts @@ -1,5 +1,5 @@ import { IRole, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; -import { App, IResource, Lazy, Names, Resource, Stack, Token, PhysicalName } from '@aws-cdk/core'; +import { App, IResource, Lazy, Names, Resource, Stack, Token, PhysicalName, ArnFormat } from '@aws-cdk/core'; import { Node, Construct } from 'constructs'; import { IEventBus } from './event-bus'; import { EventPattern } from './event-pattern'; @@ -98,7 +98,7 @@ export class Rule extends Resource implements IRule { * @param eventRuleArn Event Rule ARN (i.e. arn:aws:events:::rule/MyScheduledRule). */ public static fromEventRuleArn(scope: Construct, id: string, eventRuleArn: string): IRule { - const parts = Stack.of(scope).parseArn(eventRuleArn); + const parts = Stack.of(scope).splitArn(eventRuleArn, ArnFormat.SLASH_RESOURCE_NAME); class Import extends Resource implements IRule { public ruleArn = eventRuleArn; diff --git a/packages/@aws-cdk/aws-events/package.json b/packages/@aws-cdk/aws-events/package.json index fb10cafd5c60b..7159bada50ee2 100644 --- a/packages/@aws-cdk/aws-events/package.json +++ b/packages/@aws-cdk/aws-events/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-events/test/event-bus.test.ts b/packages/@aws-cdk/aws-events/test/event-bus.test.ts index e50f0b24db771..f20aa3e81af74 100644 --- a/packages/@aws-cdk/aws-events/test/event-bus.test.ts +++ b/packages/@aws-cdk/aws-events/test/event-bus.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Aws, CfnResource, Stack, Arn, App, PhysicalName, CfnOutput } from '@aws-cdk/core'; import { EventBus } from '../lib'; @@ -287,7 +288,7 @@ describe('event bus', () => { }); - test('can grant PutEvents', () => { + testDeprecated('can grant PutEvents', () => { // GIVEN const stack = new Stack(); const role = new iam.Role(stack, 'Role', { diff --git a/packages/@aws-cdk/aws-glue/README.md b/packages/@aws-cdk/aws-glue/README.md index f5e200f0465e7..069b572b6cd71 100644 --- a/packages/@aws-cdk/aws-glue/README.md +++ b/packages/@aws-cdk/aws-glue/README.md @@ -42,7 +42,8 @@ These jobs run in an Apache Spark environment managed by AWS Glue. An ETL job processes data in batches using Apache Spark. ```ts -new glue.Job(stack, 'ScalaSparkEtlJob', { +declare const bucket: s3.Bucket; +new glue.Job(this, 'ScalaSparkEtlJob', { executable: glue.JobExecutable.scalaEtl({ glueVersion: glue.GlueVersion.V2_0, script: glue.Code.fromBucket(bucket, 'src/com/example/HelloWorld.scala'), @@ -58,7 +59,7 @@ new glue.Job(stack, 'ScalaSparkEtlJob', { A Streaming job is similar to an ETL job, except that it performs ETL on data streams. It uses the Apache Spark Structured Streaming framework. Some Spark job features are not available to streaming ETL jobs. ```ts -new glue.Job(stack, 'PythonSparkStreamingJob', { +new glue.Job(this, 'PythonSparkStreamingJob', { executable: glue.JobExecutable.pythonStreaming({ glueVersion: glue.GlueVersion.V2_0, pythonVersion: glue.PythonVersion.THREE, @@ -74,10 +75,11 @@ A Python shell job runs Python scripts as a shell and supports a Python version This can be used to schedule and run tasks that don't require an Apache Spark environment. ```ts -new glue.Job(stack, 'PythonShellJob', { +declare const bucket: s3.Bucket; +new glue.Job(this, 'PythonShellJob', { executable: glue.JobExecutable.pythonShell({ glueVersion: glue.GlueVersion.V1_0, - pythonVersion: PythonVersion.THREE, + pythonVersion: glue.PythonVersion.THREE, script: glue.Code.fromBucket(bucket, 'script.py'), }), description: 'an example Python Shell job', @@ -91,8 +93,10 @@ See [documentation](https://docs.aws.amazon.com/glue/latest/dg/add-job.html) for A `Connection` allows Glue jobs, crawlers and development endpoints to access certain types of data stores. For example, to create a network connection to connect to a data source within a VPC: ```ts -new glue.Connection(stack, 'MyConnection', { - connectionType: glue.ConnectionTypes.NETWORK, +declare const securityGroup: ec2.SecurityGroup; +declare const subnet: ec2.Subnet; +new glue.Connection(this, 'MyConnection', { + type: glue.ConnectionType.NETWORK, // The security groups granting AWS Glue inbound access to the data source within the VPC securityGroups: [securityGroup], // The VPC subnet which contains the data source @@ -109,7 +113,7 @@ See [Adding a Connection to Your Data Store](https://docs.aws.amazon.com/glue/la A `SecurityConfiguration` is a set of security properties that can be used by AWS Glue to encrypt data at rest. ```ts -new glue.SecurityConfiguration(stack, 'MySecurityConfiguration', { +new glue.SecurityConfiguration(this, 'MySecurityConfiguration', { securityConfigurationName: 'name', cloudWatchEncryption: { mode: glue.CloudWatchEncryptionMode.KMS, @@ -126,7 +130,8 @@ new glue.SecurityConfiguration(stack, 'MySecurityConfiguration', { By default, a shared KMS key is created for use with the encryption configurations that require one. You can also supply your own key for each encryption config, for example, for CloudWatch encryption: ```ts -new glue.SecurityConfiguration(stack, 'MySecurityConfiguration', { +declare const key: kms.Key; +new glue.SecurityConfiguration(this, 'MySecurityConfiguration', { securityConfigurationName: 'name', cloudWatchEncryption: { mode: glue.CloudWatchEncryptionMode.KMS, @@ -142,8 +147,8 @@ See [documentation](https://docs.aws.amazon.com/glue/latest/dg/encryption-securi A `Database` is a logical grouping of `Tables` in the Glue Catalog. ```ts -new glue.Database(stack, 'MyDatabase', { - databaseName: 'my_database' +new glue.Database(this, 'MyDatabase', { + databaseName: 'my_database', }); ``` @@ -152,7 +157,8 @@ new glue.Database(stack, 'MyDatabase', { A Glue table describes a table of data in S3: its structure (column names and types), location of data (S3 objects with a common prefix in a S3 bucket), and format for the files (Json, Avro, Parquet, etc.): ```ts -new glue.Table(stack, 'MyTable', { +declare const myDatabase: glue.Database; +new glue.Table(this, 'MyTable', { database: myDatabase, tableName: 'my_table', columns: [{ @@ -160,20 +166,29 @@ new glue.Table(stack, 'MyTable', { type: glue.Schema.STRING, }, { name: 'col2', - type: glue.Schema.array(Schema.STRING), + type: glue.Schema.array(glue.Schema.STRING), comment: 'col2 is an array of strings' // comment is optional }], - dataFormat: glue.DataFormat.JSON + dataFormat: glue.DataFormat.JSON, }); ``` By default, a S3 bucket will be created to store the table's data but you can manually pass the `bucket` and `s3Prefix`: ```ts -new glue.Table(stack, 'MyTable', { +declare const myBucket: s3.Bucket; +declare const myDatabase: glue.Database; +new glue.Table(this, 'MyTable', { bucket: myBucket, - s3Prefix: 'my-table/' - ... + s3Prefix: 'my-table/', + // ... + database: myDatabase, + tableName: 'my_table', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, }); ``` @@ -184,21 +199,22 @@ By default, an S3 bucket will be created to store the table's data and stored in To improve query performance, a table can specify `partitionKeys` on which data is stored and queried separately. For example, you might partition a table by `year` and `month` to optimize queries based on a time window: ```ts -new glue.Table(stack, 'MyTable', { +declare const myDatabase: glue.Database; +new glue.Table(this, 'MyTable', { database: myDatabase, tableName: 'my_table', columns: [{ name: 'col1', - type: glue.Schema.STRING + type: glue.Schema.STRING, }], partitionKeys: [{ name: 'year', - type: glue.Schema.SMALL_INT + type: glue.Schema.SMALL_INT, }, { name: 'month', - type: glue.Schema.SMALL_INT + type: glue.Schema.SMALL_INT, }], - dataFormat: glue.DataFormat.JSON + dataFormat: glue.DataFormat.JSON, }); ``` @@ -210,52 +226,98 @@ You can enable encryption on a Table's data: * [S3Managed](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html) - Server side encryption (`SSE-S3`) with an Amazon S3-managed key. ```ts -new glue.Table(stack, 'MyTable', { - encryption: glue.TableEncryption.S3_MANAGED - ... +declare const myDatabase: glue.Database; +new glue.Table(this, 'MyTable', { + encryption: glue.TableEncryption.S3_MANAGED, + // ... + database: myDatabase, + tableName: 'my_table', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, }); ``` * [Kms](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html) - Server-side encryption (`SSE-KMS`) with an AWS KMS Key managed by the account owner. ```ts +declare const myDatabase: glue.Database; // KMS key is created automatically -new glue.Table(stack, 'MyTable', { - encryption: glue.TableEncryption.KMS - ... +new glue.Table(this, 'MyTable', { + encryption: glue.TableEncryption.KMS, + // ... + database: myDatabase, + tableName: 'my_table', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, }); // with an explicit KMS key -new glue.Table(stack, 'MyTable', { +new glue.Table(this, 'MyTable', { encryption: glue.TableEncryption.KMS, - encryptionKey: new kms.Key(stack, 'MyKey') - ... + encryptionKey: new kms.Key(this, 'MyKey'), + // ... + database: myDatabase, + tableName: 'my_table', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, }); ``` * [KmsManaged](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html) - Server-side encryption (`SSE-KMS`), like `Kms`, except with an AWS KMS Key managed by the AWS Key Management Service. ```ts -new glue.Table(stack, 'MyTable', { - encryption: glue.TableEncryption.KMS_MANAGED - ... +declare const myDatabase: glue.Database; +new glue.Table(this, 'MyTable', { + encryption: glue.TableEncryption.KMS_MANAGED, + // ... + database: myDatabase, + tableName: 'my_table', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, }); ``` * [ClientSideKms](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingClientSideEncryption.html#client-side-encryption-kms-managed-master-key-intro) - Client-side encryption (`CSE-KMS`) with an AWS KMS Key managed by the account owner. ```ts +declare const myDatabase: glue.Database; // KMS key is created automatically -new glue.Table(stack, 'MyTable', { - encryption: glue.TableEncryption.CLIENT_SIDE_KMS - ... +new glue.Table(this, 'MyTable', { + encryption: glue.TableEncryption.CLIENT_SIDE_KMS, + // ... + database: myDatabase, + tableName: 'my_table', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, }); // with an explicit KMS key -new glue.Table(stack, 'MyTable', { +new glue.Table(this, 'MyTable', { encryption: glue.TableEncryption.CLIENT_SIDE_KMS, - encryptionKey: new kms.Key(stack, 'MyKey') - ... + encryptionKey: new kms.Key(this, 'MyKey'), + // ... + database: myDatabase, + tableName: 'my_table', + columns: [{ + name: 'col1', + type: glue.Schema.STRING, + }], + dataFormat: glue.DataFormat.JSON, }); ``` @@ -266,30 +328,35 @@ new glue.Table(stack, 'MyTable', { A table's schema is a collection of columns, each of which have a `name` and a `type`. Types are recursive structures, consisting of primitive and complex types: ```ts -new glue.Table(stack, 'MyTable', { +declare const myDatabase: glue.Database; +new glue.Table(this, 'MyTable', { columns: [{ name: 'primitive_column', - type: glue.Schema.STRING + type: glue.Schema.STRING, }, { name: 'array_column', type: glue.Schema.array(glue.Schema.INTEGER), - comment: 'array' + comment: 'array', }, { name: 'map_column', type: glue.Schema.map( glue.Schema.STRING, glue.Schema.TIMESTAMP), - comment: 'map' + comment: 'map', }, { name: 'struct_column', type: glue.Schema.struct([{ name: 'nested_column', type: glue.Schema.DATE, - comment: 'nested comment' + comment: 'nested comment', }]), - comment: "struct" + comment: "struct", }], - ... + // ... + database: myDatabase, + tableName: 'my_table', + dataFormat: glue.DataFormat.JSON, +}); ``` ### Primitives diff --git a/packages/@aws-cdk/aws-glue/lib/database.ts b/packages/@aws-cdk/aws-glue/lib/database.ts index 673b5748537c5..e93afa2a5fc3c 100644 --- a/packages/@aws-cdk/aws-glue/lib/database.ts +++ b/packages/@aws-cdk/aws-glue/lib/database.ts @@ -1,4 +1,4 @@ -import { IResource, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnDatabase } from './glue.generated'; @@ -53,7 +53,7 @@ export class Database extends Resource implements IDatabase { class Import extends Resource implements IDatabase { public databaseArn = databaseArn; - public databaseName = stack.parseArn(databaseArn).resourceName!; + public databaseName = stack.splitArn(databaseArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; public catalogArn = stack.formatArn({ service: 'glue', resource: 'catalog' }); public catalogId = stack.account; } diff --git a/packages/@aws-cdk/aws-glue/lib/job.ts b/packages/@aws-cdk/aws-glue/lib/job.ts index 0233783f94869..4d7f982990bdd 100644 --- a/packages/@aws-cdk/aws-glue/lib/job.ts +++ b/packages/@aws-cdk/aws-glue/lib/job.ts @@ -280,7 +280,7 @@ abstract class JobBase extends cdk.Resource implements IJob { return new cloudwatch.Metric({ metricName, namespace: 'Glue', - dimensions: { + dimensionsMap: { JobName: this.jobName, JobRunId: 'ALL', Type: type, @@ -782,7 +782,7 @@ function metricRule(rule: events.IRule, props?: cloudwatch.MetricOptions): cloud return new cloudwatch.Metric({ namespace: 'AWS/Events', metricName: 'TriggeredRules', - dimensions: { RuleName: rule.ruleName }, + dimensionsMap: { RuleName: rule.ruleName }, statistic: cloudwatch.Statistic.SUM, ...props, }).attachTo(rule); diff --git a/packages/@aws-cdk/aws-glue/lib/table.ts b/packages/@aws-cdk/aws-glue/lib/table.ts index 635fe67a68d35..0c790c5953ced 100644 --- a/packages/@aws-cdk/aws-glue/lib/table.ts +++ b/packages/@aws-cdk/aws-glue/lib/table.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; -import { Fn, IResource, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, Fn, IResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { DataFormat } from './data-format'; import { IDatabase } from './database'; @@ -151,7 +151,7 @@ export interface TableProps { export class Table extends Resource implements ITable { public static fromTableArn(scope: Construct, id: string, tableArn: string): ITable { - const tableName = Fn.select(1, Fn.split('/', Stack.of(scope).parseArn(tableArn).resourceName!)); + const tableName = Fn.select(1, Fn.split('/', Stack.of(scope).splitArn(tableArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!)); return Table.fromTableAttributes(scope, id, { tableArn, diff --git a/packages/@aws-cdk/aws-glue/package.json b/packages/@aws-cdk/aws-glue/package.json index 29c41f0bd02e6..b4325ec0a648b 100644 --- a/packages/@aws-cdk/aws-glue/package.json +++ b/packages/@aws-cdk/aws-glue/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-glue/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-glue/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..2054032733bfc --- /dev/null +++ b/packages/@aws-cdk/aws-glue/rosetta/default.ts-fixture @@ -0,0 +1,16 @@ +// Fixture with packages imported, but nothing else +import * as path from 'path'; +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as glue from '@aws-cdk/aws-glue'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as kms from '@aws-cdk/aws-kms'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-glue/test/code.test.ts b/packages/@aws-cdk/aws-glue/test/code.test.ts index 8049bc1b29c6a..2cc273f51d47d 100644 --- a/packages/@aws-cdk/aws-glue/test/code.test.ts +++ b/packages/@aws-cdk/aws-glue/test/code.test.ts @@ -99,7 +99,7 @@ describe('Code', () => { }), }); - expect(stack.node.metadata.find(m => m.type === 'aws:cdk:asset')).toBeDefined(); + expect(stack.node.metadataEntry.find(m => m.type === 'aws:cdk:asset')).toBeDefined(); Template.fromStack(stack).hasResourceProperties('AWS::Glue::Job', { Command: { ScriptLocation: { @@ -256,7 +256,7 @@ describe('Code', () => { ], }; - expect(stack.node.metadata.find(m => m.type === 'aws:cdk:asset')).toBeDefined(); + expect(stack.node.metadataEntry.find(m => m.type === 'aws:cdk:asset')).toBeDefined(); // Job1 and Job2 use reuse the asset Template.fromStack(stack).hasResourceProperties('AWS::Glue::Job', { Command: { diff --git a/packages/@aws-cdk/aws-glue/test/job.test.ts b/packages/@aws-cdk/aws-glue/test/job.test.ts index c338b4d09cb42..c9104daa92190 100644 --- a/packages/@aws-cdk/aws-glue/test/job.test.ts +++ b/packages/@aws-cdk/aws-glue/test/job.test.ts @@ -757,7 +757,7 @@ describe('Job', () => { testCase.invoke(job); expect(metric).toEqual(new cloudwatch.Metric({ - dimensions: { + dimensionsMap: { RuleName: (job.node.findChild(testCase.ruleId) as events.Rule).ruleName, }, metricName: 'TriggeredRules', @@ -814,7 +814,7 @@ describe('Job', () => { metricName, statistic: 'Sum', namespace: 'Glue', - dimensions: { + dimensionsMap: { JobName: job.jobName, JobRunId: 'ALL', Type: 'count', @@ -830,7 +830,7 @@ describe('Job', () => { metricName, statistic: 'Average', namespace: 'Glue', - dimensions: { + dimensionsMap: { JobName: job.jobName, JobRunId: 'ALL', Type: 'gauge', diff --git a/packages/@aws-cdk/aws-iam/lib/group.ts b/packages/@aws-cdk/aws-iam/lib/group.ts index eca266f6975dd..1fb4fac6b2fe4 100644 --- a/packages/@aws-cdk/aws-iam/lib/group.ts +++ b/packages/@aws-cdk/aws-iam/lib/group.ts @@ -1,4 +1,4 @@ -import { Lazy, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnGroup } from './iam.generated'; import { IIdentity } from './identity-base'; @@ -145,7 +145,7 @@ export class Group extends GroupBase { * @param groupArn the ARN of the group to import (e.g. `arn:aws:iam::account-id:group/group-name`) */ public static fromGroupArn(scope: Construct, id: string, groupArn: string): IGroup { - const arnComponents = Stack.of(scope).parseArn(groupArn); + const arnComponents = Stack.of(scope).splitArn(groupArn, ArnFormat.SLASH_RESOURCE_NAME); const groupName = arnComponents.resourceName!; class Import extends GroupBase { public groupName = groupName; diff --git a/packages/@aws-cdk/aws-iam/lib/managed-policy.ts b/packages/@aws-cdk/aws-iam/lib/managed-policy.ts index 3fd1a936d4ab7..f41ca17820c03 100644 --- a/packages/@aws-cdk/aws-iam/lib/managed-policy.ts +++ b/packages/@aws-cdk/aws-iam/lib/managed-policy.ts @@ -1,4 +1,4 @@ -import { IResolveContext, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResolveContext, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IGroup } from './group'; import { CfnManagedPolicy } from './iam.generated'; @@ -247,7 +247,7 @@ export class ManagedPolicy extends Resource implements IManagedPolicy { } // arn:aws:iam::123456789012:policy/teststack-CreateTestDBPolicy-16M23YE3CS700 - this.managedPolicyName = this.getResourceNameAttribute(Stack.of(this).parseArn(resource.ref, '/').resourceName!); + this.managedPolicyName = this.getResourceNameAttribute(Stack.of(this).splitArn(resource.ref, ArnFormat.SLASH_RESOURCE_NAME).resourceName!); this.managedPolicyArn = this.getResourceArnAttribute(resource.ref, { region: '', // IAM is global in each partition service: 'iam', diff --git a/packages/@aws-cdk/aws-iam/lib/role.ts b/packages/@aws-cdk/aws-iam/lib/role.ts index f142940b2b3ef..b103b21231e23 100644 --- a/packages/@aws-cdk/aws-iam/lib/role.ts +++ b/packages/@aws-cdk/aws-iam/lib/role.ts @@ -1,4 +1,4 @@ -import { Duration, Resource, Stack, Token, TokenComparison } from '@aws-cdk/core'; +import { ArnFormat, Duration, Resource, Stack, Token, TokenComparison } from '@aws-cdk/core'; import { Construct, Node } from 'constructs'; import { Grant } from './grant'; import { CfnRole } from './iam.generated'; @@ -185,7 +185,7 @@ export class Role extends Resource implements IRole { */ public static fromRoleArn(scope: Construct, id: string, roleArn: string, options: FromRoleArnOptions = {}): IRole { const scopeStack = Stack.of(scope); - const parsedArn = scopeStack.parseArn(roleArn); + const parsedArn = scopeStack.splitArn(roleArn, ArnFormat.SLASH_RESOURCE_NAME); const resourceName = parsedArn.resourceName!; const roleAccount = parsedArn.account; // service roles have an ARN like 'arn:aws:iam:::role/service-role/' diff --git a/packages/@aws-cdk/aws-iam/package.json b/packages/@aws-cdk/aws-iam/package.json index 7fda5ccefad3d..e448eaf64cec0 100644 --- a/packages/@aws-cdk/aws-iam/package.json +++ b/packages/@aws-cdk/aws-iam/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-iam/test/role.test.ts b/packages/@aws-cdk/aws-iam/test/role.test.ts index 1e64f8a9a9369..f251f8388a6d2 100644 --- a/packages/@aws-cdk/aws-iam/test/role.test.ts +++ b/packages/@aws-cdk/aws-iam/test/role.test.ts @@ -1,4 +1,5 @@ import '@aws-cdk/assert-internal/jest'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Duration, Stack, App } from '@aws-cdk/core'; import { AnyPrincipal, ArnPrincipal, CompositePrincipal, FederatedPrincipal, ManagedPolicy, PolicyStatement, Role, ServicePrincipal, User, Policy, PolicyDocument } from '../lib'; @@ -58,7 +59,7 @@ describe('IAM role', () => { }); }); - test('can supply externalId', () => { + testDeprecated('can supply externalId', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-iot-actions/NOTICE b/packages/@aws-cdk/aws-iot-actions/NOTICE index 5fc3826926b5b..39cd25bf899ae 100644 --- a/packages/@aws-cdk/aws-iot-actions/NOTICE +++ b/packages/@aws-cdk/aws-iot-actions/NOTICE @@ -1,2 +1,32 @@ AWS Cloud Development Kit (AWS CDK) Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +------------------------------------------------------------------------------- + +The AWS CDK includes the following third-party software/licensing: + +** case - https://www.npmjs.com/package/case +Copyright (c) 2013 Nathan Bubna + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +---------------- diff --git a/packages/@aws-cdk/aws-iot-actions/README.md b/packages/@aws-cdk/aws-iot-actions/README.md index b18182a80a9ad..e02f67cee0d45 100644 --- a/packages/@aws-cdk/aws-iot-actions/README.md +++ b/packages/@aws-cdk/aws-iot-actions/README.md @@ -22,6 +22,9 @@ supported AWS Services. Instances of these classes should be passed to Currently supported are: - Invoke a Lambda function +- Put objects to a S3 bucket +- Put logs to CloudWatch Logs +- Put records to Kinesis Data Firehose stream ## Invoke a Lambda function @@ -49,6 +52,59 @@ new iot.TopicRule(this, 'TopicRule', { }); ``` +## Put objects to a S3 bucket + +The code snippet below creates an AWS IoT Rule that put objects to a S3 bucket +when it is triggered. + +```ts +import * as iot from '@aws-cdk/aws-iot'; +import * as actions from '@aws-cdk/aws-iot-actions'; +import * as s3 from '@aws-cdk/aws-s3'; + +const bucket = new s3.Bucket(this, 'MyBucket'); + +new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + actions: [new actions.S3PutObjectAction(bucket)], +}); +``` + +The property `key` of `S3PutObjectAction` is given the value `${topic()}/${timestamp()}` by default. This `${topic()}` +and `${timestamp()}` is called Substitution templates. For more information see +[this documentation](https://docs.aws.amazon.com/iot/latest/developerguide/iot-substitution-templates.html). +In above sample, `${topic()}` is replaced by a given MQTT topic as `device/001/data`. And `${timestamp()}` is replaced +by the number of the current timestamp in milliseconds as `1636289461203`. So if the MQTT broker receives an MQTT topic +`device/001/data` on `2021-11-07T00:00:00.000Z`, the S3 bucket object will be put to `device/001/data/1636243200000`. + +You can also set specific `key` as following: + +```ts +new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323( + "SELECT topic(2) as device_id, year, month, day FROM 'device/+/data'", + ), + actions: [ + new actions.S3PutObjectAction(bucket, { + key: '${year}/${month}/${day}/${topic(2)}', + }), + ], +}); +``` + +If you wanna set access control to the S3 bucket object, you can specify `accessControl` as following: + +```ts +new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT * FROM 'device/+/data'"), + actions: [ + new actions.S3PutObjectAction(bucket, { + accessControl: s3.BucketAccessControl.PUBLIC_READ, + }), + ], +}); +``` + ## Put logs to CloudWatch Logs The code snippet below creates an AWS IoT Rule that put logs to CloudWatch Logs @@ -66,3 +122,32 @@ new iot.TopicRule(this, 'TopicRule', { actions: [new actions.CloudWatchLogsAction(logGroup)], }); ``` + + +## Put records to Kinesis Data Firehose stream + +The code snippet below creates an AWS IoT Rule that put records to Put records +to Kinesis Data Firehose stream when it is triggered. + +```ts +import * as iot from '@aws-cdk/aws-iot'; +import * as actions from '@aws-cdk/aws-iot-actions'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as firehose from '@aws-cdk/aws-kinesisfirehose'; +import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; + +const bucket = new s3.Bucket(this, 'MyBucket'); +const stream = new firehose.DeliveryStream(this, 'MyStream', { + destinations: [new destinations.S3Bucket(bucket)], +}); + +const topicRule = new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT * FROM 'device/+/data'"), + actions: [ + new actions.FirehoseStreamAction(stream, { + batchMode: true, + recordSeparator: actions.FirehoseStreamRecordSeparator.NEWLINE, + }), + ], +}); +``` diff --git a/packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts b/packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts index dda14de887774..fb8f2779f32e7 100644 --- a/packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts +++ b/packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts @@ -1,18 +1,13 @@ import * as iam from '@aws-cdk/aws-iam'; import * as iot from '@aws-cdk/aws-iot'; import * as logs from '@aws-cdk/aws-logs'; +import { CommonActionProps } from './common-action-props'; import { singletonActionRole } from './private/role'; /** * Configuration properties of an action for CloudWatch Logs. */ -export interface CloudWatchLogsActionProps { - /** - * The IAM role that allows access to the CloudWatch log group. - * - * @default a new role will be created - */ - readonly role?: iam.IRole; +export interface CloudWatchLogsActionProps extends CommonActionProps { } /** diff --git a/packages/@aws-cdk/aws-iot-actions/lib/common-action-props.ts b/packages/@aws-cdk/aws-iot-actions/lib/common-action-props.ts new file mode 100644 index 0000000000000..5a9b52d8b5f27 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/lib/common-action-props.ts @@ -0,0 +1,13 @@ +import * as iam from '@aws-cdk/aws-iam'; + +/** + * Common properties shared by Actions it access to AWS service. + */ +export interface CommonActionProps { + /** + * The IAM role that allows access to AWS service. + * + * @default a new role will be created + */ + readonly role?: iam.IRole; +} diff --git a/packages/@aws-cdk/aws-iot-actions/lib/firehose-stream-action.ts b/packages/@aws-cdk/aws-iot-actions/lib/firehose-stream-action.ts new file mode 100644 index 0000000000000..c694bef7cad38 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/lib/firehose-stream-action.ts @@ -0,0 +1,88 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as iot from '@aws-cdk/aws-iot'; +import * as firehose from '@aws-cdk/aws-kinesisfirehose'; +import { CommonActionProps } from './common-action-props'; +import { singletonActionRole } from './private/role'; + +/** + * Record Separator to be used to separate records. + */ +export enum FirehoseStreamRecordSeparator { + /** + * Separate by a new line + */ + NEWLINE = '\n', + + /** + * Separate by a tab + */ + TAB = '\t', + + /** + * Separate by a windows new line + */ + WINDOWS_NEWLINE = '\r\n', + + /** + * Separate by a commma + */ + COMMA = ',', +} + +/** + * Configuration properties of an action for the Kinesis Data Firehose stream. + */ +export interface FirehoseStreamActionProps extends CommonActionProps { + /** + * Whether to deliver the Kinesis Data Firehose stream as a batch by using `PutRecordBatch`. + * When batchMode is true and the rule's SQL statement evaluates to an Array, each Array + * element forms one record in the PutRecordBatch request. The resulting array can't have + * more than 500 records. + * + * @default false + */ + readonly batchMode?: boolean; + + /** + * A character separator that will be used to separate records written to the Kinesis Data Firehose stream. + * + * @default - none -- the stream does not use a separator + */ + readonly recordSeparator?: FirehoseStreamRecordSeparator; +} + + +/** + * The action to put the record from an MQTT message to the Kinesis Data Firehose stream. + */ +export class FirehoseStreamAction implements iot.IAction { + private readonly batchMode?: boolean; + private readonly recordSeparator?: string; + private readonly role?: iam.IRole; + + /** + * @param stream The Kinesis Data Firehose stream to which to put records. + * @param props Optional properties to not use default + */ + constructor(private readonly stream: firehose.IDeliveryStream, props: FirehoseStreamActionProps = {}) { + this.batchMode = props.batchMode; + this.recordSeparator = props.recordSeparator; + this.role = props.role; + } + + bind(rule: iot.ITopicRule): iot.ActionConfig { + const role = this.role ?? singletonActionRole(rule); + this.stream.grantPutRecords(role); + + return { + configuration: { + firehose: { + batchMode: this.batchMode, + deliveryStreamName: this.stream.deliveryStreamName, + roleArn: role.roleArn, + separator: this.recordSeparator, + }, + }, + }; + } +} diff --git a/packages/@aws-cdk/aws-iot-actions/lib/index.ts b/packages/@aws-cdk/aws-iot-actions/lib/index.ts index ef917fd0e2181..ce74a2ff2b685 100644 --- a/packages/@aws-cdk/aws-iot-actions/lib/index.ts +++ b/packages/@aws-cdk/aws-iot-actions/lib/index.ts @@ -1,2 +1,5 @@ export * from './cloudwatch-logs-action'; +export * from './common-action-props'; +export * from './firehose-stream-action'; export * from './lambda-function-action'; +export * from './s3-put-object-action'; diff --git a/packages/@aws-cdk/aws-iot-actions/lib/lambda-function-action.ts b/packages/@aws-cdk/aws-iot-actions/lib/lambda-function-action.ts index 8296e112e8be5..60cf056d6e5ba 100644 --- a/packages/@aws-cdk/aws-iot-actions/lib/lambda-function-action.ts +++ b/packages/@aws-cdk/aws-iot-actions/lib/lambda-function-action.ts @@ -1,6 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as iot from '@aws-cdk/aws-iot'; import * as lambda from '@aws-cdk/aws-lambda'; +import { Names } from '@aws-cdk/core'; /** * The action to invoke an AWS Lambda function, passing in an MQTT message. @@ -12,7 +13,7 @@ export class LambdaFunctionAction implements iot.IAction { constructor(private readonly func: lambda.IFunction) {} bind(topicRule: iot.ITopicRule): iot.ActionConfig { - this.func.addPermission('invokedByAwsIotRule', { + this.func.addPermission(`${Names.nodeUniqueId(topicRule.node)}:IotLambdaFunctionAction`, { action: 'lambda:InvokeFunction', principal: new iam.ServicePrincipal('iot.amazonaws.com'), sourceAccount: topicRule.env.account, diff --git a/packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts b/packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts new file mode 100644 index 0000000000000..f690bf813a922 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts @@ -0,0 +1,67 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as iot from '@aws-cdk/aws-iot'; +import * as s3 from '@aws-cdk/aws-s3'; +import { kebab as toKebabCase } from 'case'; +import { CommonActionProps } from './common-action-props'; +import { singletonActionRole } from './private/role'; + +/** + * Configuration properties of an action for s3. + */ +export interface S3PutObjectActionProps extends CommonActionProps { + /** + * The Amazon S3 canned ACL that controls access to the object identified by the object key. + * @see https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html#canned-acl + * + * @default None + */ + readonly accessControl?: s3.BucketAccessControl; + + /** + * The path to the file where the data is written. + * + * Supports substitution templates. + * @see https://docs.aws.amazon.com/iot/latest/developerguide/iot-substitution-templates.html + * + * @default '${topic()}/${timestamp()}' + */ + readonly key?: string; +} + +/** + * The action to write the data from an MQTT message to an Amazon S3 bucket. + */ +export class S3PutObjectAction implements iot.IAction { + private readonly accessControl?: string; + private readonly key?: string; + private readonly role?: iam.IRole; + + /** + * @param bucket The Amazon S3 bucket to which to write data. + * @param props Optional properties to not use default + */ + constructor(private readonly bucket: s3.IBucket, props: S3PutObjectActionProps = {}) { + this.accessControl = props.accessControl; + this.key = props.key; + this.role = props.role; + } + + bind(rule: iot.ITopicRule): iot.ActionConfig { + const role = this.role ?? singletonActionRole(rule); + role.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['s3:PutObject'], + resources: [this.bucket.arnForObjects('*')], + })); + + return { + configuration: { + s3: { + bucketName: this.bucket.bucketName, + cannedAcl: this.accessControl && toKebabCase(this.accessControl.toString()), + key: this.key ?? '${topic()}/${timestamp()}', + roleArn: role.roleArn, + }, + }, + }; + } +} diff --git a/packages/@aws-cdk/aws-iot-actions/package.json b/packages/@aws-cdk/aws-iot-actions/package.json index fb40db84577c3..b996897b7719d 100644 --- a/packages/@aws-cdk/aws-iot-actions/package.json +++ b/packages/@aws-cdk/aws-iot-actions/package.json @@ -71,6 +71,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assertions": "0.0.0", + "@aws-cdk/aws-kinesisfirehose-destinations": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", @@ -81,20 +82,28 @@ "dependencies": { "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-iot": "0.0.0", + "@aws-cdk/aws-kinesisfirehose": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", + "case": "1.6.3", "constructs": "^3.3.69" }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-iot": "0.0.0", + "@aws-cdk/aws-kinesisfirehose": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.3.69" }, + "bundledDependencies": [ + "case" + ], "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, diff --git a/packages/@aws-cdk/aws-iot-actions/test/cloudwatch-logs/cloudwatch-logs-action.test.ts b/packages/@aws-cdk/aws-iot-actions/test/cloudwatch/cloudwatch-logs-action.test.ts similarity index 65% rename from packages/@aws-cdk/aws-iot-actions/test/cloudwatch-logs/cloudwatch-logs-action.test.ts rename to packages/@aws-cdk/aws-iot-actions/test/cloudwatch/cloudwatch-logs-action.test.ts index 4e25f43367c31..4499cdd35d6f1 100644 --- a/packages/@aws-cdk/aws-iot-actions/test/cloudwatch-logs/cloudwatch-logs-action.test.ts +++ b/packages/@aws-cdk/aws-iot-actions/test/cloudwatch/cloudwatch-logs-action.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import { Template, Match } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as iot from '@aws-cdk/aws-iot'; import * as logs from '@aws-cdk/aws-logs'; @@ -11,7 +11,7 @@ test('Default cloudwatch logs action', () => { const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), }); - const logGroup = new logs.LogGroup(stack, 'MyLogGroup'); + const logGroup = logs.LogGroup.fromLogGroupArn(stack, 'my-log-group', 'arn:aws:logs:us-east-1:123456789012:log-group:my-log-group'); // WHEN topicRule.addAction( @@ -24,7 +24,7 @@ test('Default cloudwatch logs action', () => { Actions: [ { CloudwatchLogs: { - LogGroupName: { Ref: 'MyLogGroup5C0DAD85' }, + LogGroupName: 'my-log-group', RoleArn: { 'Fn::GetAtt': [ 'MyTopicRuleTopicRuleActionRoleCE2D05DA', @@ -58,16 +58,12 @@ test('Default cloudwatch logs action', () => { { Action: ['logs:CreateLogStream', 'logs:PutLogEvents'], Effect: 'Allow', - Resource: { - 'Fn::GetAtt': ['MyLogGroup5C0DAD85', 'Arn'], - }, + Resource: 'arn:aws:logs:us-east-1:123456789012:log-group:my-log-group:*', }, { Action: 'logs:DescribeLogStreams', Effect: 'Allow', - Resource: { - 'Fn::GetAtt': ['MyLogGroup5C0DAD85', 'Arn'], - }, + Resource: 'arn:aws:logs:us-east-1:123456789012:log-group:my-log-group:*', }, ], Version: '2012-10-17', @@ -85,7 +81,7 @@ test('can set role', () => { const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), }); - const logGroup = new logs.LogGroup(stack, 'MyLogGroup'); + const logGroup = logs.LogGroup.fromLogGroupArn(stack, 'my-log-group', 'arn:aws:logs:us-east-1:123456789012:log-group:my-log-group'); const role = iam.Role.fromRoleArn(stack, 'MyRole', 'arn:aws:iam::123456789012:role/ForTest'); // WHEN @@ -99,69 +95,25 @@ test('can set role', () => { Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { TopicRulePayload: { Actions: [ - { - CloudwatchLogs: { - LogGroupName: { Ref: 'MyLogGroup5C0DAD85' }, - RoleArn: 'arn:aws:iam::123456789012:role/ForTest', - }, - }, + Match.objectLike({ CloudwatchLogs: { RoleArn: 'arn:aws:iam::123456789012:role/ForTest' } }), ], }, }); -}); - -test('The specified role is added a policy needed for sending data to logs', () => { - // GIVEN - const stack = new cdk.Stack(); - const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { - sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), - }); - const logGroup = new logs.LogGroup(stack, 'MyLogGroup'); - const role = iam.Role.fromRoleArn(stack, 'MyRole', 'arn:aws:iam::123456789012:role/ForTest'); - // WHEN - topicRule.addAction( - new actions.CloudWatchLogsAction(logGroup, { - role, - }), - ); - - // THEN Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ - { - Action: ['logs:CreateLogStream', 'logs:PutLogEvents'], - Effect: 'Allow', - Resource: { - 'Fn::GetAtt': ['MyLogGroup5C0DAD85', 'Arn'], - }, - }, - { - Action: 'logs:DescribeLogStreams', - Effect: 'Allow', - Resource: { - 'Fn::GetAtt': ['MyLogGroup5C0DAD85', 'Arn'], - }, - }, - ], - Version: '2012-10-17', - }, PolicyName: 'MyRolePolicy64AB00A5', Roles: ['ForTest'], }); }); - test('When multiple actions are omitted role property, the actions use same one role', () => { - // GIVEN // GIVEN const stack = new cdk.Stack(); const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), }); - const logGroup1 = new logs.LogGroup(stack, 'MyLogGroup1'); - const logGroup2 = new logs.LogGroup(stack, 'MyLogGroup2'); + const logGroup1 = logs.LogGroup.fromLogGroupArn(stack, 'my-log-group1', 'arn:aws:logs:us-east-1:123456789012:log-group:my-log-group1'); + const logGroup2 = logs.LogGroup.fromLogGroupArn(stack, 'my-log-group2', 'arn:aws:logs:us-east-1:123456789012:log-group:my-log-group2'); // WHEN topicRule.addAction(new actions.CloudWatchLogsAction(logGroup1)); @@ -173,7 +125,7 @@ test('When multiple actions are omitted role property, the actions use same one Actions: [ { CloudwatchLogs: { - LogGroupName: { Ref: 'MyLogGroup14A6E382A' }, + LogGroupName: 'my-log-group1', RoleArn: { 'Fn::GetAtt': [ 'MyTopicRuleTopicRuleActionRoleCE2D05DA', @@ -184,7 +136,7 @@ test('When multiple actions are omitted role property, the actions use same one }, { CloudwatchLogs: { - LogGroupName: { Ref: 'MyLogGroup279D6359D' }, + LogGroupName: 'my-log-group2', RoleArn: { 'Fn::GetAtt': [ 'MyTopicRuleTopicRuleActionRoleCE2D05DA', diff --git a/packages/@aws-cdk/aws-iot-actions/test/cloudwatch-logs/integ.cloudwatch-logs-action.expected.json b/packages/@aws-cdk/aws-iot-actions/test/cloudwatch/integ.cloudwatch-logs-action.expected.json similarity index 100% rename from packages/@aws-cdk/aws-iot-actions/test/cloudwatch-logs/integ.cloudwatch-logs-action.expected.json rename to packages/@aws-cdk/aws-iot-actions/test/cloudwatch/integ.cloudwatch-logs-action.expected.json diff --git a/packages/@aws-cdk/aws-iot-actions/test/cloudwatch-logs/integ.cloudwatch-logs-action.ts b/packages/@aws-cdk/aws-iot-actions/test/cloudwatch/integ.cloudwatch-logs-action.ts similarity index 100% rename from packages/@aws-cdk/aws-iot-actions/test/cloudwatch-logs/integ.cloudwatch-logs-action.ts rename to packages/@aws-cdk/aws-iot-actions/test/cloudwatch/integ.cloudwatch-logs-action.ts diff --git a/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/firehose-stream-action.test.ts b/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/firehose-stream-action.test.ts new file mode 100644 index 0000000000000..2941cc1db270c --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/firehose-stream-action.test.ts @@ -0,0 +1,143 @@ +import { Template, Match } from '@aws-cdk/assertions'; +import * as iam from '@aws-cdk/aws-iam'; +import * as iot from '@aws-cdk/aws-iot'; +import * as firehose from '@aws-cdk/aws-kinesisfirehose'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + +test('Default firehose stream action', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const stream = firehose.DeliveryStream.fromDeliveryStreamArn(stack, 'MyStream', 'arn:aws:firehose:xx-west-1:111122223333:deliverystream/my-stream'); + + // WHEN + topicRule.addAction( + new actions.FirehoseStreamAction(stream), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + Firehose: { + DeliveryStreamName: 'my-stream', + RoleArn: { + 'Fn::GetAtt': ['MyTopicRuleTopicRuleActionRoleCE2D05DA', 'Arn'], + }, + }, + }, + ], + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'iot.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: ['firehose:PutRecord', 'firehose:PutRecordBatch'], + Effect: 'Allow', + Resource: 'arn:aws:firehose:xx-west-1:111122223333:deliverystream/my-stream', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyTopicRuleTopicRuleActionRoleDefaultPolicy54A701F7', + Roles: [ + { Ref: 'MyTopicRuleTopicRuleActionRoleCE2D05DA' }, + ], + }); +}); + +test('can set batchMode', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const stream = firehose.DeliveryStream.fromDeliveryStreamArn(stack, 'MyStream', 'arn:aws:firehose:xx-west-1:111122223333:deliverystream/my-stream'); + + // WHEN + topicRule.addAction( + new actions.FirehoseStreamAction(stream, { batchMode: true }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + Match.objectLike({ Firehose: { BatchMode: true } }), + ], + }, + }); +}); + +test('can set separotor', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const stream = firehose.DeliveryStream.fromDeliveryStreamArn(stack, 'MyStream', 'arn:aws:firehose:xx-west-1:111122223333:deliverystream/my-stream'); + + // WHEN + topicRule.addAction( + new actions.FirehoseStreamAction(stream, { recordSeparator: actions.FirehoseStreamRecordSeparator.NEWLINE }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + Match.objectLike({ Firehose: { Separator: '\n' } }), + ], + }, + }); +}); + +test('can set role', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const stream = firehose.DeliveryStream.fromDeliveryStreamArn(stack, 'MyStream', 'arn:aws:firehose:xx-west-1:111122223333:deliverystream/my-stream'); + const role = iam.Role.fromRoleArn(stack, 'MyRole', 'arn:aws:iam::123456789012:role/ForTest'); + + // WHEN + topicRule.addAction( + new actions.FirehoseStreamAction(stream, { role }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + Match.objectLike({ Firehose: { RoleArn: 'arn:aws:iam::123456789012:role/ForTest' } }), + ], + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'MyRolePolicy64AB00A5', + Roles: ['ForTest'], + }); +}); diff --git a/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/integ.firehose-stream-action.expected.json b/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/integ.firehose-stream-action.expected.json new file mode 100644 index 0000000000000..d1565669b5c04 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/integ.firehose-stream-action.expected.json @@ -0,0 +1,306 @@ +{ + "Resources": { + "TopicRule40A4EA44": { + "Type": "AWS::IoT::TopicRule", + "Properties": { + "TopicRulePayload": { + "Actions": [ + { + "Firehose": { + "BatchMode": true, + "DeliveryStreamName": { + "Ref": "MyStream5C050E93" + }, + "RoleArn": { + "Fn::GetAtt": [ + "TopicRuleTopicRuleActionRole246C4F77", + "Arn" + ] + }, + "Separator": "\n" + } + } + ], + "AwsIotSqlVersion": "2016-03-23", + "Sql": "SELECT * FROM 'device/+/data'" + } + } + }, + "TopicRuleTopicRuleActionRole246C4F77": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "iot.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TopicRuleTopicRuleActionRoleDefaultPolicy99ADD687": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "firehose:PutRecord", + "firehose:PutRecordBatch" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyStream5C050E93", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "TopicRuleTopicRuleActionRoleDefaultPolicy99ADD687", + "Roles": [ + { + "Ref": "TopicRuleTopicRuleActionRole246C4F77" + } + ] + } + }, + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "MyStreamServiceRole8C50608A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "firehose.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyStreamS3DestinationRole5E0BA960": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "firehose.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyStreamS3DestinationRoleDefaultPolicy401EF6F2": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyStreamLogGroupAB67AB09", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyStreamS3DestinationRoleDefaultPolicy401EF6F2", + "Roles": [ + { + "Ref": "MyStreamS3DestinationRole5E0BA960" + } + ] + } + }, + "MyStreamLogGroupAB67AB09": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731 + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "MyStreamLogGroupS3Destination423E82A8": { + "Type": "AWS::Logs::LogStream", + "Properties": { + "LogGroupName": { + "Ref": "MyStreamLogGroupAB67AB09" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "MyStream5C050E93": { + "Type": "AWS::KinesisFirehose::DeliveryStream", + "Properties": { + "DeliveryStreamType": "DirectPut", + "ExtendedS3DestinationConfiguration": { + "BucketARN": { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "CloudWatchLoggingOptions": { + "Enabled": true, + "LogGroupName": { + "Ref": "MyStreamLogGroupAB67AB09" + }, + "LogStreamName": { + "Ref": "MyStreamLogGroupS3Destination423E82A8" + } + }, + "RoleARN": { + "Fn::GetAtt": [ + "MyStreamS3DestinationRole5E0BA960", + "Arn" + ] + } + } + }, + "DependsOn": [ + "MyStreamS3DestinationRoleDefaultPolicy401EF6F2" + ] + } + }, + "Mappings": { + "awscdkawskinesisfirehoseCidrBlocks": { + "af-south-1": { + "FirehoseCidrBlock": "13.244.121.224/27" + }, + "ap-east-1": { + "FirehoseCidrBlock": "18.162.221.32/27" + }, + "ap-northeast-1": { + "FirehoseCidrBlock": "13.113.196.224/27" + }, + "ap-northeast-2": { + "FirehoseCidrBlock": "13.209.1.64/27" + }, + "ap-northeast-3": { + "FirehoseCidrBlock": "13.208.177.192/27" + }, + "ap-south-1": { + "FirehoseCidrBlock": "13.232.67.32/27" + }, + "ap-southeast-1": { + "FirehoseCidrBlock": "13.228.64.192/27" + }, + "ap-southeast-2": { + "FirehoseCidrBlock": "13.210.67.224/27" + }, + "ca-central-1": { + "FirehoseCidrBlock": "35.183.92.128/27" + }, + "cn-north-1": { + "FirehoseCidrBlock": "52.81.151.32/27" + }, + "cn-northwest-1": { + "FirehoseCidrBlock": "161.189.23.64/27" + }, + "eu-central-1": { + "FirehoseCidrBlock": "35.158.127.160/27" + }, + "eu-north-1": { + "FirehoseCidrBlock": "13.53.63.224/27" + }, + "eu-south-1": { + "FirehoseCidrBlock": "15.161.135.128/27" + }, + "eu-west-1": { + "FirehoseCidrBlock": "52.19.239.192/27" + }, + "eu-west-2": { + "FirehoseCidrBlock": "18.130.1.96/27" + }, + "eu-west-3": { + "FirehoseCidrBlock": "35.180.1.96/27" + }, + "me-south-1": { + "FirehoseCidrBlock": "15.185.91.0/27" + }, + "sa-east-1": { + "FirehoseCidrBlock": "18.228.1.128/27" + }, + "us-east-1": { + "FirehoseCidrBlock": "52.70.63.192/27" + }, + "us-east-2": { + "FirehoseCidrBlock": "13.58.135.96/27" + }, + "us-gov-east-1": { + "FirehoseCidrBlock": "18.253.138.96/27" + }, + "us-gov-west-1": { + "FirehoseCidrBlock": "52.61.204.160/27" + }, + "us-west-1": { + "FirehoseCidrBlock": "13.57.135.192/27" + }, + "us-west-2": { + "FirehoseCidrBlock": "52.89.255.224/27" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/integ.firehose-stream-action.ts b/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/integ.firehose-stream-action.ts new file mode 100644 index 0000000000000..9287f1294b4dd --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/kinesis-firehose/integ.firehose-stream-action.ts @@ -0,0 +1,38 @@ +/// !cdk-integ pragma:ignore-assets +import * as iot from '@aws-cdk/aws-iot'; +import * as firehose from '@aws-cdk/aws-kinesisfirehose'; +import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + + +const app = new cdk.App(); + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const topicRule = new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323( + "SELECT * FROM 'device/+/data'", + ), + }); + + const bucket = new s3.Bucket(this, 'MyBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + const stream = new firehose.DeliveryStream(this, 'MyStream', { + destinations: [new destinations.S3Bucket(bucket)], + }); + topicRule.addAction( + new actions.FirehoseStreamAction(stream, { + batchMode: true, + recordSeparator: actions.FirehoseStreamRecordSeparator.NEWLINE, + }), + ); + } +} + +new TestStack(app, 'test-stack'); +app.synth(); diff --git a/packages/@aws-cdk/aws-iot-actions/test/lambda/integ.lambda-function-action.expected.json b/packages/@aws-cdk/aws-iot-actions/test/lambda/integ.lambda-function-action.expected.json index 345ead052c921..4c619dff4cf84 100644 --- a/packages/@aws-cdk/aws-iot-actions/test/lambda/integ.lambda-function-action.expected.json +++ b/packages/@aws-cdk/aws-iot-actions/test/lambda/integ.lambda-function-action.expected.json @@ -50,7 +50,7 @@ "MyFunctionServiceRole3C357FF2" ] }, - "MyFunctioninvokedByAwsIotRule5581F304": { + "MyFunctionteststackTopicRule1CB8242FIotLambdaFunctionAction37A1A89F": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", diff --git a/packages/@aws-cdk/aws-iot-actions/test/lambda/lambda-function-action.test.ts b/packages/@aws-cdk/aws-iot-actions/test/lambda/lambda-function-action.test.ts index 76263f5fa5e5c..88974ae613d44 100644 --- a/packages/@aws-cdk/aws-iot-actions/test/lambda/lambda-function-action.test.ts +++ b/packages/@aws-cdk/aws-iot-actions/test/lambda/lambda-function-action.test.ts @@ -55,3 +55,27 @@ test('create a topic rule with lambda action and a lambda permission to be invok }, }); }); + +test('create two different permissions, when two topic rules have the same action', () => { + // GIVEN + const stack = new cdk.Stack(); + const func = new lambda.Function(stack, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + code: lambda.Code.fromInline('console.log("foo")'), + }); + const action = new actions.LambdaFunctionAction(func); + + // WHEN + new iot.TopicRule(stack, 'MyTopicRule1', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + actions: [action], + }); + new iot.TopicRule(stack, 'MyTopicRule2', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + actions: [action], + }); + + // THEN + Template.fromStack(stack).resourceCountIs('AWS::Lambda::Permission', 2); +}); diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-put-object-action.expected.json b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-put-object-action.expected.json new file mode 100644 index 0000000000000..4e530f04da2c1 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-put-object-action.expected.json @@ -0,0 +1,86 @@ +{ + "Resources": { + "TopicRule40A4EA44": { + "Type": "AWS::IoT::TopicRule", + "Properties": { + "TopicRulePayload": { + "Actions": [ + { + "S3": { + "BucketName": { + "Ref": "MyBucketF68F3FF0" + }, + "CannedAcl": "bucket-owner-full-control", + "Key": "${year}/${month}/${day}/${topic(2)}", + "RoleArn": { + "Fn::GetAtt": [ + "TopicRuleTopicRuleActionRole246C4F77", + "Arn" + ] + } + } + } + ], + "AwsIotSqlVersion": "2016-03-23", + "Sql": "SELECT topic(2) as device_id, year, month, day FROM 'device/+/data'" + } + } + }, + "TopicRuleTopicRuleActionRole246C4F77": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "iot.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TopicRuleTopicRuleActionRoleDefaultPolicy99ADD687": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:PutObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "TopicRuleTopicRuleActionRoleDefaultPolicy99ADD687", + "Roles": [ + { + "Ref": "TopicRuleTopicRuleActionRole246C4F77" + } + ] + } + }, + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-put-object-action.ts b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-put-object-action.ts new file mode 100644 index 0000000000000..9e100e0254eaf --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-put-object-action.ts @@ -0,0 +1,32 @@ +/// !cdk-integ pragma:ignore-assets +import * as iot from '@aws-cdk/aws-iot'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + +const app = new cdk.App(); + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const topicRule = new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323( + "SELECT topic(2) as device_id, year, month, day FROM 'device/+/data'", + ), + }); + + const bucket = new s3.Bucket(this, 'MyBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + topicRule.addAction( + new actions.S3PutObjectAction(bucket, { + key: '${year}/${month}/${day}/${topic(2)}', + accessControl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL, + }), + ); + } +} + +new TestStack(app, 'test-stack'); +app.synth(); diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3/s3-put-object-action.test.ts b/packages/@aws-cdk/aws-iot-actions/test/s3/s3-put-object-action.test.ts new file mode 100644 index 0000000000000..567bd59d05083 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/s3/s3-put-object-action.test.ts @@ -0,0 +1,148 @@ +import { Template, Match } from '@aws-cdk/assertions'; +import * as iam from '@aws-cdk/aws-iam'; +import * as iot from '@aws-cdk/aws-iot'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + +test('Default s3 action', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const bucket = s3.Bucket.fromBucketArn(stack, 'MyBucket', 'arn:aws:s3::123456789012:test-bucket'); + + // WHEN + topicRule.addAction( + new actions.S3PutObjectAction(bucket), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + S3: { + BucketName: 'test-bucket', + Key: '${topic()}/${timestamp()}', + RoleArn: { + 'Fn::GetAtt': ['MyTopicRuleTopicRuleActionRoleCE2D05DA', 'Arn'], + }, + }, + }, + ], + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'iot.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: 'arn:aws:s3::123456789012:test-bucket/*', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyTopicRuleTopicRuleActionRoleDefaultPolicy54A701F7', + Roles: [ + { Ref: 'MyTopicRuleTopicRuleActionRoleCE2D05DA' }, + ], + }); +}); + +test('can set key of bucket', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const bucket = s3.Bucket.fromBucketArn(stack, 'MyBucket', 'arn:aws:s3::123456789012:test-bucket'); + + // WHEN + topicRule.addAction( + new actions.S3PutObjectAction(bucket, { + key: 'test-key', + }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + Match.objectLike({ S3: { Key: 'test-key' } }), + ], + }, + }); +}); + +test('can set canned ACL and it convert to kebab case', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const bucket = s3.Bucket.fromBucketArn(stack, 'MyBucket', 'arn:aws:s3::123456789012:test-bucket'); + + // WHEN + topicRule.addAction( + new actions.S3PutObjectAction(bucket, { + accessControl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL, + }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + Match.objectLike({ S3: { CannedAcl: 'bucket-owner-full-control' } }), + ], + }, + }); +}); + +test('can set role', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const bucket = s3.Bucket.fromBucketArn(stack, 'MyBucket', 'arn:aws:s3::123456789012:test-bucket'); + const role = iam.Role.fromRoleArn(stack, 'MyRole', 'arn:aws:iam::123456789012:role/ForTest'); + + // WHEN + topicRule.addAction( + new actions.S3PutObjectAction(bucket, { role }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + Match.objectLike({ S3: { RoleArn: 'arn:aws:iam::123456789012:role/ForTest' } }), + ], + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyName: 'MyRolePolicy64AB00A5', + Roles: ['ForTest'], + }); +}); diff --git a/packages/@aws-cdk/aws-ivs/lib/channel.ts b/packages/@aws-cdk/aws-ivs/lib/channel.ts index 34d9228bc787c..0768847e4b668 100644 --- a/packages/@aws-cdk/aws-ivs/lib/channel.ts +++ b/packages/@aws-cdk/aws-ivs/lib/channel.ts @@ -117,7 +117,7 @@ export class Channel extends ChannelBase { */ public static fromChannelArn(scope: Construct, id: string, channelArn: string): IChannel { // This will throw an error if the arn cannot be parsed - let arnComponents = core.Arn.parse(channelArn); + let arnComponents = core.Arn.split(channelArn, core.ArnFormat.SLASH_RESOURCE_NAME); if (!core.Token.isUnresolved(arnComponents.service) && arnComponents.service !== 'ivs') { throw new Error(`Invalid service, expected 'ivs', got '${arnComponents.service}'`); diff --git a/packages/@aws-cdk/aws-kinesis/README.md b/packages/@aws-cdk/aws-kinesis/README.md index 1e7aaf89a5a88..75831cb52b1ed 100644 --- a/packages/@aws-cdk/aws-kinesis/README.md +++ b/packages/@aws-cdk/aws-kinesis/README.md @@ -34,8 +34,8 @@ Using the CDK, a new Kinesis stream can be created as part of the stack using th your own identifier to the stream. If not, CloudFormation will generate a name. ```ts -new Stream(this, "MyFirstStream", { - streamName: "my-awesome-stream" +new kinesis.Stream(this, 'MyFirstStream', { + streamName: 'my-awesome-stream', }); ``` @@ -44,10 +44,10 @@ to specify how long the data in the shards should remain accessible. Read more at [Creating and Managing Streams](https://docs.aws.amazon.com/streams/latest/dev/working-with-streams.html) ```ts -new Stream(this, "MyFirstStream", { - streamName: "my-awesome-stream", +new kinesis.Stream(this, 'MyFirstStream', { + streamName: 'my-awesome-stream', shardCount: 3, - retentionPeriod: Duration.hours(48) + retentionPeriod: Duration.hours(48), }); ``` @@ -59,28 +59,26 @@ server-side encryption using an AWS KMS key for a specified stream. Encryption is enabled by default on your stream with the master key owned by Kinesis Data Streams in regions where it is supported. ```ts -new Stream(this, 'MyEncryptedStream'); +new kinesis.Stream(this, 'MyEncryptedStream'); ``` You can enable encryption on your stream with a user-managed key by specifying the `encryption` property. A KMS key will be created for you and associated with the stream. ```ts -new Stream(this, "MyEncryptedStream", { - encryption: StreamEncryption.KMS +new kinesis.Stream(this, 'MyEncryptedStream', { + encryption: kinesis.StreamEncryption.KMS, }); ``` You can also supply your own external KMS key to use for stream encryption by specifying the `encryptionKey` property. ```ts -import * as kms from "@aws-cdk/aws-kms"; +const key = new kms.Key(this, 'MyKey'); -const key = new kms.Key(this, "MyKey"); - -new Stream(this, "MyEncryptedStream", { - encryption: StreamEncryption.KMS, - encryptionKey: key +new kinesis.Stream(this, 'MyEncryptedStream', { + encryption: kinesis.StreamEncryption.KMS, + encryptionKey: key, }); ``` @@ -91,32 +89,20 @@ Any Kinesis stream that has been created outside the stack can be imported into Streams can be imported by their ARN via the `Stream.fromStreamArn()` API ```ts -const stack = new Stack(app, "MyStack"); - -const importedStream = Stream.fromStreamArn( - stack, - "ImportedStream", - "arn:aws:kinesis:us-east-2:123456789012:stream/f3j09j2230j" +const importedStream = kinesis.Stream.fromStreamArn(this, 'ImportedStream', + 'arn:aws:kinesis:us-east-2:123456789012:stream/f3j09j2230j', ); ``` Encrypted Streams can also be imported by their attributes via the `Stream.fromStreamAttributes()` API ```ts -import { Key } from "@aws-cdk/aws-kms"; - -const stack = new Stack(app, "MyStack"); - -const importedStream = Stream.fromStreamAttributes( - stack, - "ImportedEncryptedStream", - { - streamArn: "arn:aws:kinesis:us-east-2:123456789012:stream/f3j09j2230j", - encryptionKey: kms.Key.fromKeyArn( - "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012" - ) - } -); +const importedStream = kinesis.Stream.fromStreamAttributes(this, 'ImportedEncryptedStream', { + streamArn: 'arn:aws:kinesis:us-east-2:123456789012:stream/f3j09j2230j', + encryptionKey: kms.Key.fromKeyArn(this, 'key', + 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012', + ), +}); ``` ### Permission Grants @@ -138,10 +124,10 @@ If the stream has an encryption key, read permissions will also be granted to th const lambdaRole = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), description: 'Example role...', -} +}); -const stream = new Stream(this, 'MyEncryptedStream', { - encryption: StreamEncryption.KMS +const stream = new kinesis.Stream(this, 'MyEncryptedStream', { + encryption: kinesis.StreamEncryption.KMS, }); // give lambda permissions to read stream @@ -165,10 +151,10 @@ If the stream has an encryption key, write permissions will also be granted to t const lambdaRole = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), description: 'Example role...', -} +}); -const stream = new Stream(this, 'MyEncryptedStream', { - encryption: StreamEncryption.KMS +const stream = new kinesis.Stream(this, 'MyEncryptedStream', { + encryption: kinesis.StreamEncryption.KMS, }); // give lambda permissions to write to stream @@ -186,9 +172,9 @@ The following write permissions are provided to a service principal by the `gran You can add any set of permissions to a stream by calling the `grant()` API. ```ts -const user = new iam.User(stack, 'MyUser'); +const user = new iam.User(this, 'MyUser'); -const stream = new Stream(stack, 'MyStream'); +const stream = new kinesis.Stream(this, 'MyStream'); // give my user permissions to list shards stream.grant(user, 'kinesis:ListShards'); @@ -199,7 +185,7 @@ stream.grant(user, 'kinesis:ListShards'); You can use common metrics from your stream to create alarms and/or dashboards. The `stream.metric('MetricName')` method creates a metric with the stream namespace and dimension. You can also use pre-define methods like `stream.metricGetRecordsSuccess()`. To find out more about Kinesis metrics check [Monitoring the Amazon Kinesis Data Streams Service with Amazon CloudWatch](https://docs.aws.amazon.com/streams/latest/dev/monitoring-with-cloudwatch.html). ```ts -const stream = new Stream(stack, 'MyStream'); +const stream = new kinesis.Stream(this, 'MyStream'); // Using base metric method passing the metric name stream.metric('GetRecords.Success'); @@ -210,4 +196,3 @@ stream.metricGetRecordsSuccess(); // using pre-defined and overriding the statistic stream.metricGetRecordsSuccess({ statistic: 'Maximum' }); ``` - diff --git a/packages/@aws-cdk/aws-kinesis/lib/kinesis-fixed-canned-metrics.ts b/packages/@aws-cdk/aws-kinesis/lib/kinesis-fixed-canned-metrics.ts index a10434d947bbf..53404627c39ae 100644 --- a/packages/@aws-cdk/aws-kinesis/lib/kinesis-fixed-canned-metrics.ts +++ b/packages/@aws-cdk/aws-kinesis/lib/kinesis-fixed-canned-metrics.ts @@ -13,7 +13,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'GetRecords.Bytes', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -21,7 +21,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'GetRecords.Success', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -29,7 +29,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'GetRecords.Records', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -37,7 +37,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'GetRecords.Latency', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -45,7 +45,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecord.Bytes', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -53,7 +53,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecord.Latency', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -70,7 +70,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecords.Latency', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -78,7 +78,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecords.Success', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -86,7 +86,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecords.TotalRecords', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -94,7 +94,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecords.SuccessfulRecords', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -102,7 +102,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecords.FailedRecords', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } @@ -110,7 +110,7 @@ export class KinesisMetrics { return { namespace: 'AWS/Kinesis', metricName: 'PutRecords.ThrottledRecords', - dimensions, + dimensionsMap: dimensions, statistic: 'Average', }; } diff --git a/packages/@aws-cdk/aws-kinesis/lib/stream.ts b/packages/@aws-cdk/aws-kinesis/lib/stream.ts index b2fed1eb10329..03d693e8dfa4e 100644 --- a/packages/@aws-cdk/aws-kinesis/lib/stream.ts +++ b/packages/@aws-cdk/aws-kinesis/lib/stream.ts @@ -1,7 +1,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; -import { Aws, CfnCondition, Duration, Fn, IResolvable, IResource, Resource, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, Aws, CfnCondition, Duration, Fn, IResolvable, IResource, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { KinesisMetrics } from './kinesis-fixed-canned-metrics'; import { CfnStream } from './kinesis.generated'; @@ -12,6 +12,8 @@ const READ_OPERATIONS = [ 'kinesis:GetShardIterator', 'kinesis:ListShards', 'kinesis:SubscribeToShard', + 'kinesis:DescribeStream', + 'kinesis:ListStreams', ]; const WRITE_OPERATIONS = [ @@ -392,7 +394,7 @@ abstract class StreamBase extends Resource implements IStream { return new cloudwatch.Metric({ namespace: 'AWS/Kinesis', metricName, - dimensions: { + dimensionsMap: { StreamName: this.streamName, }, ...props, @@ -725,7 +727,7 @@ export class Stream extends StreamBase { public static fromStreamAttributes(scope: Construct, id: string, attrs: StreamAttributes): IStream { class Import extends StreamBase { public readonly streamArn = attrs.streamArn; - public readonly streamName = Stack.of(scope).parseArn(attrs.streamArn).resourceName!; + public readonly streamName = Stack.of(scope).splitArn(attrs.streamArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; public readonly encryptionKey = attrs.encryptionKey; } diff --git a/packages/@aws-cdk/aws-kinesis/package.json b/packages/@aws-cdk/aws-kinesis/package.json index 23f1f8684a19c..1641e48f910f7 100644 --- a/packages/@aws-cdk/aws-kinesis/package.json +++ b/packages/@aws-cdk/aws-kinesis/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-kinesis/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-kinesis/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..742f48088c404 --- /dev/null +++ b/packages/@aws-cdk/aws-kinesis/rosetta/default.ts-fixture @@ -0,0 +1,14 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as kinesis from '@aws-cdk/aws-kinesis'; +import * as kms from '@aws-cdk/aws-kms'; +import * as iam from '@aws-cdk/aws-iam'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesis/test/integ.stream.expected.json b/packages/@aws-cdk/aws-kinesis/test/integ.stream.expected.json index 15055271413a2..41230acc599a2 100644 --- a/packages/@aws-cdk/aws-kinesis/test/integ.stream.expected.json +++ b/packages/@aws-cdk/aws-kinesis/test/integ.stream.expected.json @@ -44,6 +44,8 @@ "kinesis:GetShardIterator", "kinesis:ListShards", "kinesis:SubscribeToShard", + "kinesis:DescribeStream", + "kinesis:ListStreams", "kinesis:PutRecord", "kinesis:PutRecords" ], diff --git a/packages/@aws-cdk/aws-kinesis/test/stream.test.ts b/packages/@aws-cdk/aws-kinesis/test/stream.test.ts index 089261c6ebdae..7ab2c5eed849e 100644 --- a/packages/@aws-cdk/aws-kinesis/test/stream.test.ts +++ b/packages/@aws-cdk/aws-kinesis/test/stream.test.ts @@ -2,9 +2,9 @@ import '@aws-cdk/assert-internal/jest'; import { arrayWith } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; +import { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools'; import { App, Duration, Stack, CfnParameter } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import { Stream, StreamEncryption } from '../lib'; /* eslint-disable quote-props */ @@ -503,6 +503,8 @@ describe('Kinesis data streams', () => { 'kinesis:GetShardIterator', 'kinesis:ListShards', 'kinesis:SubscribeToShard', + 'kinesis:DescribeStream', + 'kinesis:ListStreams', ], Effect: 'Allow', Resource: { @@ -811,6 +813,8 @@ describe('Kinesis data streams', () => { 'kinesis:GetShardIterator', 'kinesis:ListShards', 'kinesis:SubscribeToShard', + 'kinesis:DescribeStream', + 'kinesis:ListStreams', 'kinesis:PutRecord', 'kinesis:PutRecords', ], @@ -884,6 +888,8 @@ describe('Kinesis data streams', () => { 'kinesis:GetShardIterator', 'kinesis:ListShards', 'kinesis:SubscribeToShard', + 'kinesis:DescribeStream', + 'kinesis:ListStreams', ], Effect: 'Allow', Resource: { @@ -1050,6 +1056,8 @@ describe('Kinesis data streams', () => { 'kinesis:GetShardIterator', 'kinesis:ListShards', 'kinesis:SubscribeToShard', + 'kinesis:DescribeStream', + 'kinesis:ListStreams', 'kinesis:PutRecord', 'kinesis:PutRecords', ], diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/README.md b/packages/@aws-cdk/aws-kinesisanalytics-flink/README.md index 830b59a2c7053..2882cc60afc54 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics-flink/README.md +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/README.md @@ -37,16 +37,17 @@ aws-kinesisanalytics-runtime library to [retrieve these properties](https://docs.aws.amazon.com/kinesisanalytics/latest/java/how-properties.html#how-properties-access). ```ts -import * as flink from '@aws-cdk/aws-kinesisanalytics-flink'; - +declare const bucket: s3.Bucket; const flinkApp = new flink.Application(this, 'Application', { - // ... propertyGroups: { FlinkApplicationProperties: { inputStreamName: 'my-input-kinesis-stream', outputStreamName: 'my-output-kinesis-stream', }, }, + // ... + runtime: flink.Runtime.FLINK_1_13, + code: flink.ApplicationCode.fromBucket(bucket, 'my-app.jar'), }); ``` @@ -55,14 +56,13 @@ when the Flink job starts. These include parameters for checkpointing, snapshotting, monitoring, and parallelism. ```ts -import * as logs from '@aws-cdk/aws-logs'; - +declare const bucket: s3.Bucket; const flinkApp = new flink.Application(this, 'Application', { code: flink.ApplicationCode.fromBucket(bucket, 'my-app.jar'), - runtime: file.Runtime.FLINK_1_13, + runtime: flink.Runtime.FLINK_1_13, checkpointingEnabled: true, // default is true - checkpointInterval: cdk.Duration.seconds(30), // default is 1 minute - minPauseBetweenCheckpoints: cdk.Duration.seconds(10), // default is 5 seconds + checkpointInterval: Duration.seconds(30), // default is 1 minute + minPauseBetweenCheckpoints: Duration.seconds(10), // default is 5 seconds logLevel: flink.LogLevel.ERROR, // default is INFO metricsLevel: flink.MetricsLevel.PARALLELISM, // default is APPLICATION autoScalingEnabled: false, // default is true diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application.ts b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application.ts index 5091d39c7f959..4a0f3bfc36138 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application.ts +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application.ts @@ -227,7 +227,7 @@ export class Application extends ApplicationBase { * applicationArn. */ public static fromApplicationArn(scope: Construct, id: string, applicationArn: string): IApplication { - const applicationName = core.Stack.of(scope).parseArn(applicationArn).resourceName; + const applicationName = core.Stack.of(scope).splitArn(applicationArn, core.ArnFormat.SLASH_RESOURCE_NAME).resourceName; if (!applicationName) { throw new Error(`applicationArn for fromApplicationArn (${applicationArn}) must include resource name`); } @@ -296,7 +296,7 @@ export class Application extends ApplicationBase { core.Stack.of(this).formatArn({ service: 'logs', resource: 'log-group', - sep: ':', + arnFormat: core.ArnFormat.COLON_RESOURCE_NAME, resourceName: '*', }), ], diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/package.json b/packages/@aws-cdk/aws-kinesisanalytics-flink/package.json index f7c50fa0af0a2..1f4a026686416 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics-flink/package.json +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-kinesisanalytics-flink/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..a9f46e29f793b --- /dev/null +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/rosetta/default.ts-fixture @@ -0,0 +1,14 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as flink from '@aws-cdk/aws-kinesisanalytics-flink'; +import * as logs from '@aws-cdk/aws-logs'; +import * as s3 from '@aws-cdk/aws-s3'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kinesisanalytics/package.json b/packages/@aws-cdk/aws-kinesisanalytics/package.json index f01223c6c4cbb..36a2b60ede671 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics/package.json +++ b/packages/@aws-cdk/aws-kinesisanalytics/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-kinesisfirehose-destinations/README.md b/packages/@aws-cdk/aws-kinesisfirehose-destinations/README.md index 03ef4657b3f78..3873a6a493052 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose-destinations/README.md +++ b/packages/@aws-cdk/aws-kinesisfirehose-destinations/README.md @@ -20,3 +20,7 @@ delivery stream. Destinations can be added by specifying the `destinations` prop defining a delivery stream. See [Amazon Kinesis Data Firehose module README](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-kinesisfirehose-readme.html) for usage examples. + +```ts nofixture +import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; +``` diff --git a/packages/@aws-cdk/aws-kinesisfirehose-destinations/package.json b/packages/@aws-cdk/aws-kinesisfirehose-destinations/package.json index a2ba48007718f..a872462d82cf8 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose-destinations/package.json +++ b/packages/@aws-cdk/aws-kinesisfirehose-destinations/package.json @@ -32,7 +32,7 @@ "metadata": { "jsii": { "rosetta": { - "strict": false + "strict": true } } } diff --git a/packages/@aws-cdk/aws-kinesisfirehose-destinations/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-kinesisfirehose-destinations/rosetta/default.ts-fixture index fe46e06908b34..f48bd7e013c59 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose-destinations/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-kinesisfirehose-destinations/rosetta/default.ts-fixture @@ -1,8 +1,9 @@ // Fixture with packages imported, but nothing else -import { Construct } from '@aws-cdk/core'; -import { S3Bucket } from '@aws-cdk/aws-kinesisfirehose-destinations'; +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; -class Fixture extends Construct { +class Fixture extends Stack { constructor(scope: Construct, id: string) { super(scope, id); diff --git a/packages/@aws-cdk/aws-kinesisfirehose/README.md b/packages/@aws-cdk/aws-kinesisfirehose/README.md index 936fcdc881a86..2db9f79e6ba30 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/README.md +++ b/packages/@aws-cdk/aws-kinesisfirehose/README.md @@ -44,11 +44,8 @@ In order to define a Delivery Stream, you must specify a destination. An S3 buck used as a destination. More supported destinations are covered [below](#destinations). ```ts -import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; -import * as s3 from '@aws-cdk/aws-s3'; - const bucket = new s3.Bucket(this, 'Bucket'); -new DeliveryStream(this, 'Delivery Stream', { +new firehose.DeliveryStream(this, 'Delivery Stream', { destinations: [new destinations.S3Bucket(bucket)], }); ``` @@ -74,11 +71,10 @@ A delivery stream can read directly from a Kinesis data stream as a consumer of stream. Configure this behaviour by providing a data stream in the `sourceStream` property when constructing a delivery stream: -```ts fixture=with-destination -import * as kinesis from '@aws-cdk/aws-kinesis'; - +```ts +declare const destination: firehose.IDestination; const sourceStream = new kinesis.Stream(this, 'Source Stream'); -new DeliveryStream(this, 'Delivery Stream', { +new firehose.DeliveryStream(this, 'Delivery Stream', { sourceStream: sourceStream, destinations: [destination], }); @@ -113,14 +109,10 @@ for the implementations of these destinations. Defining a delivery stream with an S3 bucket destination: ```ts -import * as s3 from '@aws-cdk/aws-s3'; -import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; - -const bucket = new s3.Bucket(this, 'Bucket'); - +declare const bucket: s3.Bucket; const s3Destination = new destinations.S3Bucket(bucket); -new DeliveryStream(this, 'Delivery Stream', { +new firehose.DeliveryStream(this, 'Delivery Stream', { destinations: [s3Destination], }); ``` @@ -129,7 +121,8 @@ The S3 destination also supports custom dynamic prefixes. `prefix` will be used successfully delivered to S3. `errorOutputPrefix` will be added to failed records before writing them to S3. -```ts fixture=with-bucket +```ts +declare const bucket: s3.Bucket; const s3Destination = new destinations.S3Bucket(bucket, { dataOutputPrefix: 'myFirehose/DeliveredYear=!{timestamp:yyyy}/anyMonth/rand=!{firehose:random-string}', errorOutputPrefix: 'myFirehoseFailures/!{firehose:error-output-type}/!{timestamp:yyyy}/anyMonth/!{timestamp:dd}', @@ -158,22 +151,22 @@ access, rotation, aliases, and deletion for these keys, and you are changed for use. See: [Customer master keys](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#master_keys) in the *KMS Developer Guide*. -```ts fixture=with-destination -import * as kms from '@aws-cdk/aws-kms'; +```ts +declare const destination: firehose.IDestination; // SSE with an AWS-owned CMK -new DeliveryStream(this, 'Delivery Stream AWS Owned', { - encryption: StreamEncryption.AWS_OWNED, +new firehose.DeliveryStream(this, 'Delivery Stream AWS Owned', { + encryption: firehose.StreamEncryption.AWS_OWNED, destinations: [destination], }); // SSE with an customer-managed CMK that is created automatically by the CDK -new DeliveryStream(this, 'Delivery Stream Implicit Customer Managed', { - encryption: StreamEncryption.CUSTOMER_MANAGED, +new firehose.DeliveryStream(this, 'Delivery Stream Implicit Customer Managed', { + encryption: firehose.StreamEncryption.CUSTOMER_MANAGED, destinations: [destination], }); // SSE with an customer-managed CMK that is explicitly specified -const key = new kms.Key(this, 'Key'); -new DeliveryStream(this, 'Delivery Stream Explicit Customer Managed', { +declare const key: kms.Key; +new firehose.DeliveryStream(this, 'Delivery Stream Explicit Customer Managed', { encryptionKey: key, destinations: [destination], }); @@ -196,28 +189,29 @@ and LogStream for your Delivery Stream. You can provide a specific log group to specify where the CDK will create the log streams where log events will be sent: -```ts fixture=with-bucket -import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; +```ts import * as logs from '@aws-cdk/aws-logs'; const logGroup = new logs.LogGroup(this, 'Log Group'); +declare const bucket: s3.Bucket; const destination = new destinations.S3Bucket(bucket, { logGroup: logGroup, }); -new DeliveryStream(this, 'Delivery Stream', { + +declare const destination: firehose.IDestination; +new firehose.DeliveryStream(this, 'Delivery Stream', { destinations: [destination], }); ``` Logging can also be disabled: -```ts fixture=with-bucket -import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; - +```ts +declare const bucket: s3.Bucket; const destination = new destinations.S3Bucket(bucket, { logging: false, }); -new DeliveryStream(this, 'Delivery Stream', { +new firehose.DeliveryStream(this, 'Delivery Stream', { destinations: [destination], }); ``` @@ -242,8 +236,9 @@ for a full list). CDK also provides a generic `metric` method that can be used t metric configurations for any metric provided by Kinesis Data Firehose; the configurations are pre-populated with the correct dimensions for the delivery stream. -```ts fixture=with-delivery-stream +```ts import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +declare const deliveryStream: firehose.DeliveryStream; // Alarm that triggers when the per-second average of incoming bytes exceeds 90% of the current service limit const incomingBytesPercentOfLimit = new cloudwatch.MathExpression({ @@ -253,6 +248,7 @@ const incomingBytesPercentOfLimit = new cloudwatch.MathExpression({ bytePerSecLimit: deliveryStream.metric('BytesPerSecondLimit'), }, }); + new cloudwatch.Alarm(this, 'Alarm', { metric: incomingBytesPercentOfLimit, threshold: 0.9, @@ -271,13 +267,14 @@ Hadoop-compatible Snappy, and ZIP, except for Redshift destinations, where Snapp (regardless of Hadoop-compatibility) and ZIP are not supported. By default, data is delivered to S3 without compression. -```ts fixture=with-bucket +```ts // Compress data delivered to S3 using Snappy +declare const bucket: s3.Bucket; const s3Destination = new destinations.S3Bucket(bucket, { - compression: Compression.SNAPPY, + compression: destinations.Compression.SNAPPY, }); -new DeliveryStream(this, 'Delivery Stream', { - destinations: [destination], +new firehose.DeliveryStream(this, 'Delivery Stream', { + destinations: [s3Destination], }); ``` @@ -290,15 +287,14 @@ threshold (the "buffer interval"), whichever happens first. You can configure th thresholds based on the capabilities of the destination and your use-case. By default, the buffer size is 5 MiB and the buffer interval is 5 minutes. -```ts fixture=with-bucket -import * as cdk from '@aws-cdk/core'; - +```ts // Increase the buffer interval and size to 10 minutes and 8 MiB, respectively +declare const bucket: s3.Bucket; const destination = new destinations.S3Bucket(bucket, { - bufferingInterval: cdk.Duration.minutes(10), - bufferingSize: cdk.Size.mebibytes(8), + bufferingInterval: Duration.minutes(10), + bufferingSize: Size.mebibytes(8), }); -new DeliveryStream(this, 'Delivery Stream', { +new firehose.DeliveryStream(this, 'Delivery Stream', { destinations: [destination], }); ``` @@ -315,14 +311,13 @@ in Amazon S3. You can choose to not encrypt the data or to encrypt with a key fr the list of AWS KMS keys that you own. For more information, see [Protecting Data Using Server-Side Encryption with AWS KMS–Managed Keys (SSE-KMS)](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html). Data is not encrypted by default. -```ts fixture=with-bucket -import * as cdk from '@aws-cdk/core'; -import * as kms from '@aws-cdk/aws-kms'; - +```ts +declare const bucket: s3.Bucket; +declare const key: kms.Key; const destination = new destinations.S3Bucket(bucket, { - encryptionKey: new kms.Key(this, 'MyKey'), + encryptionKey: key, }); -new DeliveryStream(this, 'Delivery Stream', { +new firehose.DeliveryStream(this, 'Delivery Stream', { destinations: [destination], }); ``` @@ -337,37 +332,35 @@ you can provide a bucket where data will be backed up. You can also provide a pr which your backed-up data will be placed within the bucket. By default, source data is not backed up to S3. -```ts fixture=with-bucket -import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; -import * as s3 from '@aws-cdk/aws-s3'; - +```ts // Enable backup of all source records (to an S3 bucket created by CDK). -new DeliveryStream(this, 'Delivery Stream Backup All', { +declare const bucket: s3.Bucket; +new firehose.DeliveryStream(this, 'Delivery Stream Backup All', { destinations: [ new destinations.S3Bucket(bucket, { s3Backup: { - mode: BackupMode.ALL, - } + mode: destinations.BackupMode.ALL, + }, }), ], }); // Explicitly provide an S3 bucket to which all source records will be backed up. -const backupBucket = new s3.Bucket(this, 'Bucket'); -new DeliveryStream(this, 'Delivery Stream Backup All Explicit Bucket', { +declare const backupBucket: s3.Bucket; +new firehose.DeliveryStream(this, 'Delivery Stream Backup All Explicit Bucket', { destinations: [ new destinations.S3Bucket(bucket, { s3Backup: { bucket: backupBucket, - } + }, }), ], }); // Explicitly provide an S3 prefix under which all source records will be backed up. -new DeliveryStream(this, 'Delivery Stream Backup All Explicit Prefix', { +new firehose.DeliveryStream(this, 'Delivery Stream Backup All Explicit Prefix', { destinations: [ new destinations.S3Bucket(bucket, { s3Backup: { - mode: BackupMode.ALL, + mode: destinations.BackupMode.ALL, dataOutputPrefix: 'mybackup', }, }), @@ -405,10 +398,7 @@ configuration (see: [Buffering](#buffering)). If the function invocation fails d network timeout or because of hitting an invocation limit, the invocation is retried 3 times by default, but can be configured using `retries` in the processor configuration. -```ts fixture=with-bucket -import * as cdk from '@aws-cdk/core'; -import * as lambda from '@aws-cdk/aws-lambda'; - +```ts // Provide a Lambda function that will transform records before delivery, with custom // buffering and retry configuration const lambdaFunction = new lambda.Function(this, 'Processor', { @@ -416,16 +406,17 @@ const lambdaFunction = new lambda.Function(this, 'Processor', { handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'process-records')), }); -const lambdaProcessor = new LambdaFunctionProcessor(lambdaFunction, { - bufferingInterval: cdk.Duration.minutes(5), - bufferingSize: cdk.Size.mebibytes(5), +const lambdaProcessor = new firehose.LambdaFunctionProcessor(lambdaFunction, { + bufferInterval: Duration.minutes(5), + bufferSize: Size.mebibytes(5), retries: 5, }); +declare const bucket: s3.Bucket; const s3Destination = new destinations.S3Bucket(bucket, { processor: lambdaProcessor, }); -new DeliveryStream(this, 'Delivery Stream', { - destinations: [destination], +new firehose.DeliveryStream(this, 'Delivery Stream', { + destinations: [s3Destination], }); ``` @@ -449,10 +440,7 @@ allow Kinesis Data Firehose to assume it) or delivery stream creation or data de will fail. Other required permissions to destination resources, encryption keys, etc., will be provided automatically. -```ts fixture=with-bucket -import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; -import * as iam from '@aws-cdk/aws-iam'; - +```ts // Create service roles for the delivery stream and destination. // These can be used for other purposes and granted access to different resources. // They must include the Kinesis Data Firehose service principal in their trust policies. @@ -465,8 +453,9 @@ const destinationRole = new iam.Role(this, 'Destination Role', { }); // Specify the roles created above when defining the destination and delivery stream. +declare const bucket: s3.Bucket; const destination = new destinations.S3Bucket(bucket, { role: destinationRole }); -new DeliveryStream(this, 'Delivery Stream', { +new firehose.DeliveryStream(this, 'Delivery Stream', { destinations: [destination], role: deliveryStreamRole, }); @@ -488,14 +477,13 @@ can be granted permissions to a delivery stream by calling: - `grant(principal, ...actions)` - grants the principal permission to a custom set of actions -```ts fixture=with-delivery-stream -import * as iam from '@aws-cdk/aws-iam'; - +```ts const lambdaRole = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), }); // Give the role permissions to write data to the delivery stream +declare const deliveryStream: firehose.DeliveryStream; deliveryStream.grantPutRecords(lambdaRole); ``` @@ -514,15 +502,14 @@ found in the `@aws-cdk/aws-kinesisfirehose-destinations` module, the CDK grants permissions automatically. However, custom or third-party destinations may require custom permissions. In this case, use the delivery stream as an `IGrantable`, as follows: -```ts fixture=with-delivery-stream -import * as lambda from '@aws-cdk/aws-lambda'; - +```ts const fn = new lambda.Function(this, 'Function', { code: lambda.Code.fromInline('exports.handler = (event) => {}'), runtime: lambda.Runtime.NODEJS_14_X, handler: 'index.handler', }); +declare const deliveryStream: firehose.DeliveryStream; fn.grantInvoke(deliveryStream); ``` diff --git a/packages/@aws-cdk/aws-kinesisfirehose/lib/delivery-stream.ts b/packages/@aws-cdk/aws-kinesisfirehose/lib/delivery-stream.ts index 7dfaed8eb384b..c9608904e9373 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/lib/delivery-stream.ts +++ b/packages/@aws-cdk/aws-kinesisfirehose/lib/delivery-stream.ts @@ -124,7 +124,7 @@ abstract class DeliveryStreamBase extends cdk.Resource implements IDeliveryStrea return new cloudwatch.Metric({ namespace: 'AWS/Firehose', metricName: metricName, - dimensions: { + dimensionsMap: { DeliveryStreamName: this.deliveryStreamName, }, ...props, @@ -358,13 +358,6 @@ export class DeliveryStream extends DeliveryStreamBase { roleArn: role.roleArn, } : undefined; const readStreamGrant = props.sourceStream?.grantRead(role); - /* - * Firehose still uses the deprecated DescribeStream API instead of the modern DescribeStreamSummary API. - * kinesis.IStream.grantRead does not provide DescribeStream permissions so we add it manually here. - */ - if (readStreamGrant && readStreamGrant.principalStatement) { - readStreamGrant.principalStatement.addActions('kinesis:DescribeStream'); - } const destinationConfig = props.destinations[0].bind(this, {}); diff --git a/packages/@aws-cdk/aws-kinesisfirehose/package.json b/packages/@aws-cdk/aws-kinesisfirehose/package.json index c0843bf1d6829..64c30be1210e3 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/package.json +++ b/packages/@aws-cdk/aws-kinesisfirehose/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-kinesisfirehose/rosetta/default.ts-fixture index 8a68efc25aa8e..9585eb9368d19 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-kinesisfirehose/rosetta/default.ts-fixture @@ -1,6 +1,14 @@ // Fixture with packages imported, but nothing else -import { Construct, Stack } from '@aws-cdk/core'; -import { DeliveryStream, DestinationBindOptions, DestinationConfig, IDestination } from '@aws-cdk/aws-kinesisfirehose'; +import { Construct } from 'constructs'; +import { Duration, Size, Stack } from '@aws-cdk/core'; +import * as firehose from '@aws-cdk/aws-kinesisfirehose'; +import * as kinesis from '@aws-cdk/aws-kinesis'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as destinations from '@aws-cdk/aws-kinesisfirehose-destinations'; +import * as kms from '@aws-cdk/aws-kms'; +import * as iam from '@aws-cdk/aws-iam'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as path from 'path'; class Fixture extends Stack { constructor(scope: Construct, id: string) { diff --git a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-bucket.ts-fixture b/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-bucket.ts-fixture deleted file mode 100644 index d0851cff49639..0000000000000 --- a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-bucket.ts-fixture +++ /dev/null @@ -1,13 +0,0 @@ -// Fixture with a bucket already created -import { Construct, Stack } from '@aws-cdk/core'; -import { DeliveryStream, DestinationBindOptions, DestinationConfig, IDestination } from '@aws-cdk/aws-kinesisfirehose'; -import * as s3 from '@aws-cdk/aws-s3'; -declare const bucket: s3.Bucket; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-delivery-stream.ts-fixture b/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-delivery-stream.ts-fixture deleted file mode 100644 index c7b75b20d2c1b..0000000000000 --- a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-delivery-stream.ts-fixture +++ /dev/null @@ -1,12 +0,0 @@ -// Fixture with a delivery stream already created -import { Construct, Stack } from '@aws-cdk/core'; -import { DeliveryStream, DestinationBindOptions, DestinationConfig, IDestination } from '@aws-cdk/aws-kinesisfirehose'; -declare const deliveryStream: DeliveryStream; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-destination.ts-fixture b/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-destination.ts-fixture deleted file mode 100644 index 37d78bf7a43d3..0000000000000 --- a/packages/@aws-cdk/aws-kinesisfirehose/rosetta/with-destination.ts-fixture +++ /dev/null @@ -1,12 +0,0 @@ -// Fixture with a destination already created -import { Construct, Stack } from '@aws-cdk/core'; -import { DeliveryStream, DestinationBindOptions, DestinationConfig, IDestination } from '@aws-cdk/aws-kinesisfirehose'; -declare const destination: IDestination; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/@aws-cdk/aws-kinesisfirehose/test/integ.delivery-stream.source-stream.expected.json b/packages/@aws-cdk/aws-kinesisfirehose/test/integ.delivery-stream.source-stream.expected.json index eb46541a1cdf2..896d0487a091c 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/test/integ.delivery-stream.source-stream.expected.json +++ b/packages/@aws-cdk/aws-kinesisfirehose/test/integ.delivery-stream.source-stream.expected.json @@ -119,7 +119,8 @@ "kinesis:GetShardIterator", "kinesis:ListShards", "kinesis:SubscribeToShard", - "kinesis:DescribeStream" + "kinesis:DescribeStream", + "kinesis:ListStreams" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-kms/lib/key.ts b/packages/@aws-cdk/aws-kms/lib/key.ts index 8c726958f500e..62f0ad7ddf0a0 100644 --- a/packages/@aws-cdk/aws-kms/lib/key.ts +++ b/packages/@aws-cdk/aws-kms/lib/key.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import { FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Duration, Token, ContextProvider, Arn } from '@aws-cdk/core'; +import { FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Duration, Token, ContextProvider, Arn, ArnFormat } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { IConstruct, Construct } from 'constructs'; import { Alias } from './alias'; @@ -479,7 +479,7 @@ export class Key extends KeyBase { } } - const keyResourceName = Stack.of(scope).parseArn(keyArn).resourceName; + const keyResourceName = Stack.of(scope).splitArn(keyArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName; if (!keyResourceName) { throw new Error(`KMS key ARN must be in the format 'arn:aws:kms:::key/', got: '${keyArn}'`); } diff --git a/packages/@aws-cdk/aws-kms/package.json b/packages/@aws-cdk/aws-kms/package.json index 66a47be7c03da..e38c467b3b4f7 100644 --- a/packages/@aws-cdk/aws-kms/package.json +++ b/packages/@aws-cdk/aws-kms/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-kms/test/key.test.ts b/packages/@aws-cdk/aws-kms/test/key.test.ts index fae1564223d45..5a42d030bf36a 100644 --- a/packages/@aws-cdk/aws-kms/test/key.test.ts +++ b/packages/@aws-cdk/aws-kms/test/key.test.ts @@ -1,9 +1,9 @@ import { arrayWith, countResources, expect as expectCdk, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; +import { describeDeprecated, testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import * as kms from '../lib'; const ADMIN_ACTIONS: string[] = [ @@ -450,10 +450,31 @@ testFutureBehavior('setting pendingWindow value to not in allowed range will thr .toThrow('\'pendingWindow\' value must between 7 and 30 days. Received: 6'); }); -testFutureBehavior('setting trustAccountIdentities to false will throw (when the defaultKeyPolicies feature flag is enabled)', flags, cdk.App, (app) => { - const stack = new cdk.Stack(app); - expect(() => new kms.Key(stack, 'MyKey', { trustAccountIdentities: false })) - .toThrow('`trustAccountIdentities` cannot be false if the @aws-cdk/aws-kms:defaultKeyPolicies feature flag is set'); +describeDeprecated('trustAccountIdentities is deprecated', () => { + testFutureBehavior('setting trustAccountIdentities to false will throw (when the defaultKeyPolicies feature flag is enabled)', flags, cdk.App, (app) => { + const stack = new cdk.Stack(app); + expect(() => new kms.Key(stack, 'MyKey', { trustAccountIdentities: false })) + .toThrow('`trustAccountIdentities` cannot be false if the @aws-cdk/aws-kms:defaultKeyPolicies feature flag is set'); + }); + + testLegacyBehavior('trustAccountIdentities changes key policy to allow IAM control', cdk.App, (app) => { + const stack = new cdk.Stack(app); + new kms.Key(stack, 'MyKey', { trustAccountIdentities: true }); + expect(stack).toHaveResourceLike('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { + AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::', { Ref: 'AWS::AccountId' }, ':root']] }, + }, + Resource: '*', + }, + ], + }, + }); + }); }); testFutureBehavior('addAlias creates an alias', flags, cdk.App, (app) => { @@ -989,25 +1010,6 @@ describe('when the defaultKeyPolicies feature flag is disabled', () => { }); }); - testLegacyBehavior('trustAccountIdentities changes key policy to allow IAM control', cdk.App, (app) => { - const stack = new cdk.Stack(app); - new kms.Key(stack, 'MyKey', { trustAccountIdentities: true }); - expect(stack).toHaveResourceLike('AWS::KMS::Key', { - KeyPolicy: { - Statement: [ - { - Action: 'kms:*', - Effect: 'Allow', - Principal: { - AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::', { Ref: 'AWS::AccountId' }, ':root']] }, - }, - Resource: '*', - }, - ], - }, - }); - }); - testLegacyBehavior('additional key admins can be specified (with imported/immutable principal)', cdk.App, (app) => { const stack = new cdk.Stack(app); const adminRole = iam.Role.fromRoleArn(stack, 'Admin', 'arn:aws:iam::123456789012:role/TrustedAdmin'); diff --git a/packages/@aws-cdk/aws-lambda-destinations/README.md b/packages/@aws-cdk/aws-lambda-destinations/README.md index 404b0b3157adb..675ca0b175757 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/README.md +++ b/packages/@aws-cdk/aws-lambda-destinations/README.md @@ -24,15 +24,17 @@ The following destinations are supported Example with a SNS topic for successful invocations: ```ts -import * as lambda from '@aws-cdk/aws-lambda'; -import * as destinations from '@aws-cdk/aws-lambda-destinations'; +// An sns topic for successful invocations of a lambda function import * as sns from '@aws-cdk/aws-sns'; const myTopic = new sns.Topic(this, 'Topic'); const myFn = new lambda.Function(this, 'Fn', { - // other props - onSuccess: new destinations.SnsDestination(myTopic) + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), + // sns topic for successful invocations + onSuccess: new destinations.SnsDestination(myTopic), }) ``` @@ -71,27 +73,27 @@ In case of failure, the record contains the reason and error object: ```json { - "version": "1.0", - "timestamp": "2019-11-24T21:52:47.333Z", - "requestContext": { - "requestId": "8ea123e4-1db7-4aca-ad10-d9ca1234c1fd", - "functionArn": "arn:aws:lambda:sa-east-1:123456678912:function:event-destinations:$LATEST", - "condition": "RetriesExhausted", - "approximateInvokeCount": 3 - }, - "requestPayload": { - "Success": false - }, - "responseContext": { - "statusCode": 200, - "executedVersion": "$LATEST", - "functionError": "Handled" - }, - "responsePayload": { - "errorMessage": "Failure from event, Success = false, I am failing!", - "errorType": "Error", - "stackTrace": [ "exports.handler (/var/task/index.js:18:18)" ] - } + "version": "1.0", + "timestamp": "2019-11-24T21:52:47.333Z", + "requestContext": { + "requestId": "8ea123e4-1db7-4aca-ad10-d9ca1234c1fd", + "functionArn": "arn:aws:lambda:sa-east-1:123456678912:function:event-destinations:$LATEST", + "condition": "RetriesExhausted", + "approximateInvokeCount": 3 + }, + "requestPayload": { + "Success": false + }, + "responseContext": { + "statusCode": 200, + "executedVersion": "$LATEST", + "functionError": "Handled" + }, + "responsePayload": { + "errorMessage": "Failure from event, Success = false, I am failing!", + "errorType": "Error", + "stackTrace": [ "exports.handler (/var/task/index.js:18:18)" ] + } } ``` @@ -112,18 +114,17 @@ The `responseOnly` option of `LambdaDestination` allows to auto-extract the resp invocation record: ```ts -import * as lambda from '@aws-cdk/aws-lambda'; -import * as destinations from '@aws-cdk/aws-lambda-destinations'; - -const destinationFn = new lambda.Function(this, 'Destination', { - // props -}); +// Auto-extract response payload with a lambda destination +declare const destinationFn: lambda.Function; const sourceFn = new lambda.Function(this, 'Source', { - // other props + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), + // auto-extract on success onSuccess: new destinations.LambdaDestination(destinationFn, { - responseOnly: true // auto-extract - }); + responseOnly: true, + }), }) ``` diff --git a/packages/@aws-cdk/aws-lambda-destinations/package.json b/packages/@aws-cdk/aws-lambda-destinations/package.json index 17ce33782126c..12108477a91de 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/package.json +++ b/packages/@aws-cdk/aws-lambda-destinations/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-lambda-destinations/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-lambda-destinations/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..fb7d525a027aa --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-destinations/rosetta/default.ts-fixture @@ -0,0 +1,14 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as destinations from '@aws-cdk/aws-lambda-destinations'; +import * as path from 'path'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-lambda-event-sources/README.md b/packages/@aws-cdk/aws-lambda-event-sources/README.md index e1ba637699efd..9abd6e59d9669 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/README.md +++ b/packages/@aws-cdk/aws-lambda-event-sources/README.md @@ -24,12 +24,14 @@ sources regardless of the underlying mechanism they use. The following code sets up a lambda function with an SQS queue event source - ```ts -const fn = new lambda.Function(this, 'MyFunction', { /* ... */ }); +import { SqsEventSource } from '@aws-cdk/aws-lambda-event-sources'; +declare const fn: lambda.Function; const queue = new sqs.Queue(this, 'MyQueue'); -const eventSource = fn.addEventSource(new SqsEventSource(queue)); +const eventSource = new SqsEventSource(queue); +fn.addEventSource(eventSource); -const eventSourceId = eventSource.eventSourceId; +const eventSourceId = eventSource.eventSourceMappingId; ``` The `eventSourceId` property contains the event source id. This will be a @@ -58,16 +60,15 @@ behavior: * __enabled__: If the SQS event source mapping should be enabled. The default is true. ```ts -import * as sqs from '@aws-cdk/aws-sqs'; import { SqsEventSource } from '@aws-cdk/aws-lambda-event-sources'; -import { Duration } from '@aws-cdk/core'; const queue = new sqs.Queue(this, 'MyQueue', { - visibilityTimeout: Duration.seconds(30) // default, - receiveMessageWaitTime: Duration.seconds(20) // default + visibilityTimeout: Duration.seconds(30), // default, + receiveMessageWaitTime: Duration.seconds(20), // default }); +declare const fn: lambda.Function; -lambda.addEventSource(new SqsEventSource(queue, { +fn.addEventSource(new SqsEventSource(queue, { batchSize: 10, // default maxBatchingWindow: Duration.minutes(5), })); @@ -88,11 +89,12 @@ Amazon S3 to publish and which Lambda function to invoke. import * as s3 from '@aws-cdk/aws-s3'; import { S3EventSource } from '@aws-cdk/aws-lambda-event-sources'; -const bucket = new s3.Bucket(...); +const bucket = new s3.Bucket(this, 'mybucket'); +declare const fn: lambda.Function; -lambda.addEventSource(new S3EventSource(bucket, { +fn.addEventSource(new S3EventSource(bucket, { events: [ s3.EventType.OBJECT_CREATED, s3.EventType.OBJECT_REMOVED ], - filters: [ { prefix: 'subdir/' } ] // optional + filters: [ { prefix: 'subdir/' } ], // optional })); ``` @@ -118,12 +120,13 @@ Accounts](https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html). import * as sns from '@aws-cdk/aws-sns'; import { SnsEventSource } from '@aws-cdk/aws-lambda-event-sources'; -const topic = new sns.Topic(...); +declare const topic: sns.Topic; const deadLetterQueue = new sqs.Queue(this, 'deadLetterQueue'); -lambda.addEventSource(new SnsEventSource(topic, { - filterPolicy: { ... }, - deadLetterQueue: deadLetterQueue +declare const fn: lambda.Function; +fn.addEventSource(new SnsEventSource(topic, { + filterPolicy: { }, + deadLetterQueue: deadLetterQueue, })); ``` @@ -157,24 +160,19 @@ and add it to your Lambda function. The following parameters will impact Amazon ```ts import * as dynamodb from '@aws-cdk/aws-dynamodb'; -import * as lambda from '@aws-cdk/aws-lambda'; -import * as sqs from '@aws-cdk/aws-sqs'; import { DynamoEventSource, SqsDlq } from '@aws-cdk/aws-lambda-event-sources'; -const table = new dynamodb.Table(..., { - partitionKey: ..., - stream: dynamodb.StreamViewType.NEW_IMAGE // make sure stream is configured -}); +declare const table: dynamodb.Table; const deadLetterQueue = new sqs.Queue(this, 'deadLetterQueue'); -const function = new lambda.Function(...); -function.addEventSource(new DynamoEventSource(table, { +declare const fn: lambda.Function; +fn.addEventSource(new DynamoEventSource(table, { startingPosition: lambda.StartingPosition.TRIM_HORIZON, batchSize: 5, bisectBatchOnError: true, onFailure: new SqsDlq(deadLetterQueue), - retryAttempts: 10 + retryAttempts: 10, })); ``` @@ -202,15 +200,15 @@ behavior: * __enabled__: If the DynamoDB Streams event source mapping should be enabled. The default is true. ```ts -import * as lambda from '@aws-cdk/aws-lambda'; import * as kinesis from '@aws-cdk/aws-kinesis'; import { KinesisEventSource } from '@aws-cdk/aws-lambda-event-sources'; const stream = new kinesis.Stream(this, 'MyStream'); +declare const myFunction: lambda.Function; myFunction.addEventSource(new KinesisEventSource(stream, { batchSize: 100, // default - startingPosition: lambda.StartingPosition.TRIM_HORIZON + startingPosition: lambda.StartingPosition.TRIM_HORIZON, })); ``` @@ -222,27 +220,26 @@ The following code sets up Amazon MSK as an event source for a lambda function. MSK cluster, as described in [Username/Password authentication](https://docs.aws.amazon.com/msk/latest/developerguide/msk-password.html). ```ts -import * as lambda from '@aws-cdk/aws-lambda'; -import * as msk from '@aws-cdk/aws-lambda'; -import { Secret } from '@aws-cdk/aws-secretmanager'; +import { Secret } from '@aws-cdk/aws-secretsmanager'; import { ManagedKafkaEventSource } from '@aws-cdk/aws-lambda-event-sources'; // Your MSK cluster arn -const cluster = 'arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-4'; +const clusterArn = 'arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-4'; // The Kafka topic you want to subscribe to -const topic = 'some-cool-topic' +const topic = 'some-cool-topic'; // The secret that allows access to your MSK cluster // You still have to make sure that it is associated with your cluster as described in the documentation const secret = new Secret(this, 'Secret', { secretName: 'AmazonMSK_KafkaSecret' }); +declare const myFunction: lambda.Function; myFunction.addEventSource(new ManagedKafkaEventSource({ clusterArn, topic: topic, secret: secret, batchSize: 100, // default - startingPosition: lambda.StartingPosition.TRIM_HORIZON + startingPosition: lambda.StartingPosition.TRIM_HORIZON, })); ``` @@ -250,25 +247,25 @@ The following code sets up a self managed Kafka cluster as an event source. User will need to be set up as described in [Managing access and permissions](https://docs.aws.amazon.com/lambda/latest/dg/smaa-permissions.html#smaa-permissions-add-secret). ```ts -import * as lambda from '@aws-cdk/aws-lambda'; -import { Secret } from '@aws-cdk/aws-secretmanager'; +import { Secret } from '@aws-cdk/aws-secretsmanager'; import { SelfManagedKafkaEventSource } from '@aws-cdk/aws-lambda-event-sources'; // The list of Kafka brokers -const bootstrapServers = ['kafka-broker:9092'] +const bootstrapServers = ['kafka-broker:9092']; // The Kafka topic you want to subscribe to -const topic = 'some-cool-topic' +const topic = 'some-cool-topic'; // The secret that allows access to your self hosted Kafka cluster -const secret = new Secret(this, 'Secret', { ... }); +declare const secret: Secret; +declare const myFunction: lambda.Function; myFunction.addEventSource(new SelfManagedKafkaEventSource({ bootstrapServers: bootstrapServers, topic: topic, secret: secret, batchSize: 100, // default - startingPosition: lambda.StartingPosition.TRIM_HORIZON + startingPosition: lambda.StartingPosition.TRIM_HORIZON, })); ``` diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts index 54e98a47bb55d..e31fac89100cb 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts @@ -3,7 +3,7 @@ import { ISecurityGroup, IVpc, SubnetSelection } from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { Stack } from '@aws-cdk/core'; +import { Stack, Names } from '@aws-cdk/core'; import { StreamEventSource, StreamEventSourceProps } from './stream'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main @@ -101,6 +101,7 @@ export interface SelfManagedKafkaEventSourceProps extends KafkaEventSourceProps export class ManagedKafkaEventSource extends StreamEventSource { // This is to work around JSII inheritance problems private innerProps: ManagedKafkaEventSourceProps; + private _eventSourceMappingId?: string = undefined; constructor(props: ManagedKafkaEventSourceProps) { super(props); @@ -108,8 +109,8 @@ export class ManagedKafkaEventSource extends StreamEventSource { } public bind(target: lambda.IFunction) { - target.addEventSourceMapping( - `KafkaEventSource:${this.innerProps.clusterArn}${this.innerProps.topic}`, + const eventSourceMapping = target.addEventSourceMapping( + `KafkaEventSource:${Names.nodeUniqueId(target.node)}${this.innerProps.topic}`, this.enrichMappingOptions({ eventSourceArn: this.innerProps.clusterArn, startingPosition: this.innerProps.startingPosition, @@ -118,6 +119,8 @@ export class ManagedKafkaEventSource extends StreamEventSource { }), ); + this._eventSourceMappingId = eventSourceMapping.eventSourceMappingId; + if (this.innerProps.secret !== undefined) { this.innerProps.secret.grantRead(target); } @@ -146,6 +149,16 @@ export class ManagedKafkaEventSource extends StreamEventSource { ? undefined : sourceAccessConfigurations; } + + /** + * The identifier for this EventSourceMapping + */ + public get eventSourceMappingId(): string { + if (!this._eventSourceMappingId) { + throw new Error('KafkaEventSource is not yet bound to an event source mapping'); + } + return this._eventSourceMappingId; + } } /** diff --git a/packages/@aws-cdk/aws-lambda-event-sources/package.json b/packages/@aws-cdk/aws-lambda-event-sources/package.json index 6f0295a38c625..3bd4375cc9d24 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/package.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-lambda-event-sources/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-lambda-event-sources/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..d2cdb69c90519 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-event-sources/rosetta/default.ts-fixture @@ -0,0 +1,13 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as sqs from '@aws-cdk/aws-sqs'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json index aafb84ca19c72..c1690f2f03aac 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json @@ -42,7 +42,9 @@ "kinesis:GetRecords", "kinesis:GetShardIterator", "kinesis:ListShards", - "kinesis:SubscribeToShard" + "kinesis:SubscribeToShard", + "kinesis:DescribeStream", + "kinesis:ListStreams" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.expected.json index 4d0a6c1a54707..616adaef6a86a 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesiswithdlq.expected.json @@ -56,7 +56,9 @@ "kinesis:GetRecords", "kinesis:GetShardIterator", "kinesis:ListShards", - "kinesis:SubscribeToShard" + "kinesis:SubscribeToShard", + "kinesis:DescribeStream", + "kinesis:ListStreams" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts index 804069373c114..1455931278129 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts @@ -488,5 +488,24 @@ describe('KafkaEventSource', () => { ]), }); }); + + test('ManagedKafkaEventSource name conforms to construct id rules', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const clusterArn = 'some-arn'; + const kafkaTopic = 'some-topic'; + + const mskEventMapping = new sources.ManagedKafkaEventSource( + { + clusterArn, + topic: kafkaTopic, + startingPosition: lambda.StartingPosition.TRIM_HORIZON, + }); + + // WHEN + fn.addEventSource(mskEventMapping); + expect(mskEventMapping.eventSourceMappingId).toBeDefined(); + }); }); }); diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/kinesis.test.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/kinesis.test.ts index 96701d6c83f7a..e77fec71e5079 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/kinesis.test.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/kinesis.test.ts @@ -30,6 +30,8 @@ describe('KinesisEventSource', () => { 'kinesis:GetShardIterator', 'kinesis:ListShards', 'kinesis:SubscribeToShard', + 'kinesis:DescribeStream', + 'kinesis:ListStreams', ], 'Effect': 'Allow', 'Resource': { diff --git a/packages/@aws-cdk/aws-lambda-go/README.md b/packages/@aws-cdk/aws-lambda-go/README.md index 16fcffee919ea..e4de2d1641113 100644 --- a/packages/@aws-cdk/aws-lambda-go/README.md +++ b/packages/@aws-cdk/aws-lambda-go/README.md @@ -29,7 +29,7 @@ Define a `GoFunction`: ```ts new lambda.GoFunction(this, 'handler', { - entry: 'app/cmd/api' + entry: 'app/cmd/api', }); ``` @@ -154,7 +154,7 @@ Use the `bundling.dockerImage` prop to use a custom bundling image: new lambda.GoFunction(this, 'handler', { entry: 'app/cmd/api', bundling: { - dockerImage: cdk.DockerImage.fromBuild('/path/to/Dockerfile'), + dockerImage: DockerImage.fromBuild('/path/to/Dockerfile'), }, }); ``` @@ -174,7 +174,9 @@ new lambda.GoFunction(this, 'handler', { It is possible to run additional commands by specifying the `commandHooks` prop: -```ts +```text +// This example only available in TypeScript +// Run additional commands on a GoFunction via `commandHooks` property new lambda.GoFunction(this, 'handler', { bundling: { commandHooks: { diff --git a/packages/@aws-cdk/aws-lambda-go/lib/types.ts b/packages/@aws-cdk/aws-lambda-go/lib/types.ts index bcd334809be33..28e058e1a685e 100644 --- a/packages/@aws-cdk/aws-lambda-go/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-go/lib/types.ts @@ -106,7 +106,7 @@ export interface BundlingOptions { * * Commands are chained with `&&`. * - * @example + * ```text * { * // Run tests prior to bundling * beforeBundling(inputDir: string, outputDir: string): string[] { @@ -114,6 +114,7 @@ export interface BundlingOptions { * } * // ... * } + * ``` */ export interface ICommandHooks { /** diff --git a/packages/@aws-cdk/aws-lambda-go/package.json b/packages/@aws-cdk/aws-lambda-go/package.json index 939988ea2410f..5df9aae607538 100644 --- a/packages/@aws-cdk/aws-lambda-go/package.json +++ b/packages/@aws-cdk/aws-lambda-go/package.json @@ -30,7 +30,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-lambda-go/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-lambda-go/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..96f3f6933780c --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-go/rosetta/default.ts-fixture @@ -0,0 +1,12 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { DockerImage, Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda-go'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-lambda-go/test/integ.function.expected.json b/packages/@aws-cdk/aws-lambda-go/test/integ.function.expected.json index 9ad2aa2afce94..fe40549eba6d3 100644 --- a/packages/@aws-cdk/aws-lambda-go/test/integ.function.expected.json +++ b/packages/@aws-cdk/aws-lambda-go/test/integ.function.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameterse04b349f3d0535498861679603e52edaa01d01ee442f4f665c16e22e5bc81820S3Bucket854EE9A9" + "Ref": "AssetParametersafe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1S3Bucket6ED74DF1" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse04b349f3d0535498861679603e52edaa01d01ee442f4f665c16e22e5bc81820S3VersionKey2AD7C6E5" + "Ref": "AssetParametersafe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1S3VersionKeyB0821FCE" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse04b349f3d0535498861679603e52edaa01d01ee442f4f665c16e22e5bc81820S3VersionKey2AD7C6E5" + "Ref": "AssetParametersafe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1S3VersionKeyB0821FCE" } ] } @@ -87,17 +87,17 @@ } }, "Parameters": { - "AssetParameterse04b349f3d0535498861679603e52edaa01d01ee442f4f665c16e22e5bc81820S3Bucket854EE9A9": { + "AssetParametersafe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1S3Bucket6ED74DF1": { "Type": "String", - "Description": "S3 bucket for asset \"e04b349f3d0535498861679603e52edaa01d01ee442f4f665c16e22e5bc81820\"" + "Description": "S3 bucket for asset \"afe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1\"" }, - "AssetParameterse04b349f3d0535498861679603e52edaa01d01ee442f4f665c16e22e5bc81820S3VersionKey2AD7C6E5": { + "AssetParametersafe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1S3VersionKeyB0821FCE": { "Type": "String", - "Description": "S3 key for asset version \"e04b349f3d0535498861679603e52edaa01d01ee442f4f665c16e22e5bc81820\"" + "Description": "S3 key for asset version \"afe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1\"" }, - "AssetParameterse04b349f3d0535498861679603e52edaa01d01ee442f4f665c16e22e5bc81820ArtifactHash245320AE": { + "AssetParametersafe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1ArtifactHash2DFD542A": { "Type": "String", - "Description": "Artifact hash for asset \"e04b349f3d0535498861679603e52edaa01d01ee442f4f665c16e22e5bc81820\"" + "Description": "Artifact hash for asset \"afe3256c3d565f40df78c0343322cb3d8b20d5dbc5e0ff560dd9ed4677e0adb1\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/cmd/api/main.go b/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/cmd/api/main.go index 3dc0f71439536..a704fea2def58 100644 --- a/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/cmd/api/main.go +++ b/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/cmd/api/main.go @@ -1,20 +1,12 @@ package main -import ( - "context" - "fmt" - - "github.com/aws/aws-lambda-go/lambda" -) - -type MyEvent struct { - Name string `json:"name"` -} - -func HandleRequest(ctx context.Context, name MyEvent) (string, error) { - return fmt.Sprintf("Hello %s!", name.Name), nil -} +// Intentionally empty. There were issues with 'gopkg.in', so: +// - we cannot depend on 'github.com/aws/aws-lambda-go' +// - since: it has a dependency on 'gopkg.in/yaml.v3' +// - therefore: we cannot type the handler properly here +// +// It doesn't matter that this isn't an actual Lambda handler, we +// just need the test build to succeed. func main() { - lambda.Start(HandleRequest) } diff --git a/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/go.mod b/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/go.mod index 24ac2f8c41afa..9f22f42e6b18c 100644 --- a/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/go.mod +++ b/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/go.mod @@ -2,9 +2,3 @@ module aws-lambda-golang go 1.16 -require ( - github.com/aws/aws-lambda-go v1.19.1 - github.com/kr/text v0.2.0 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect -) diff --git a/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/go.sum b/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/go.sum deleted file mode 100644 index 94f7ebf0f685e..0000000000000 --- a/packages/@aws-cdk/aws-lambda-go/test/lambda-handler-vendor/go.sum +++ /dev/null @@ -1,32 +0,0 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/aws/aws-lambda-go v1.19.1 h1:5iUHbIZ2sG6Yq/J1IN3sWm3+vAB1CWwhI21NffLNuNI= -github.com/aws/aws-lambda-go v1.19.1/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index ae6f6014fa9a1..81fb45b3b1f4a 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -173,7 +173,7 @@ new lambda.NodejsFunction(this, 'my-handler', { bundling: { minify: true, // minify code, defaults to false sourceMap: true, // include source map, defaults to false - sourceMapMode: SourceMapMode.INLINE, // defaults to SourceMapMode.DEFAULT + sourceMapMode: lambda.SourceMapMode.INLINE, // defaults to SourceMapMode.DEFAULT sourcesContent: false, // do not include original source into source map, defaults to true target: 'es2020', // target environment for the generated JavaScript code loader: { // Use the 'dataurl' loader for '.png' files @@ -184,13 +184,13 @@ new lambda.NodejsFunction(this, 'my-handler', { 'process.env.PRODUCTION': JSON.stringify(true), 'process.env.NUMBER': JSON.stringify(123), }, - logLevel: LogLevel.SILENT, // defaults to LogLevel.WARNING + logLevel: lambda.LogLevel.SILENT, // defaults to LogLevel.WARNING keepNames: true, // defaults to false tsconfig: 'custom-tsconfig.json', // use custom-tsconfig.json instead of default, metafile: true, // include meta file, defaults to false banner: '/* comments */', // requires esbuild >= 0.9.0, defaults to none footer: '/* comments */', // requires esbuild >= 0.9.0, defaults to none - charset: Charset.UTF8, // do not escape non-ASCII characters, defaults to Charset.ASCII + charset: lambda.Charset.UTF8, // do not escape non-ASCII characters, defaults to Charset.ASCII }, }); ``` @@ -199,18 +199,28 @@ new lambda.NodejsFunction(this, 'my-handler', { It is possible to run additional commands by specifying the `commandHooks` prop: -```ts +```text +// This example only available in TypeScript +// Run additional props via `commandHooks` new lambda.NodejsFunction(this, 'my-handler-with-commands', { bundling: { commandHooks: { - // Copy a file so that it will be included in the bundled asset + beforeBundling(inputDir: string, outputDir: string): string[] { + return [ + `echo hello > ${inputDir}/a.txt`, + `cp ${inputDir}/a.txt ${outputDir}`, + ]; + }, afterBundling(inputDir: string, outputDir: string): string[] { - return [`cp ${inputDir}/my-binary.node ${outputDir}`]; - } + return [`cp ${inputDir}/b.txt ${outputDir}/txt`]; + }, + beforeInstall() { + return []; + }, // ... - } + }, // ... - } + }, }); ``` @@ -262,9 +272,9 @@ Use `bundling.buildArgs` to pass build arguments when building the Docker bundli ```ts new lambda.NodejsFunction(this, 'my-handler', { bundling: { - buildArgs: { - HTTPS_PROXY: 'https://127.0.0.1:3001', - }, + buildArgs: { + HTTPS_PROXY: 'https://127.0.0.1:3001', + }, } }); ``` @@ -274,7 +284,7 @@ Use `bundling.dockerImage` to use a custom Docker bundling image: ```ts new lambda.NodejsFunction(this, 'my-handler', { bundling: { - dockerImage: cdk.DockerImage.fromBuild('/path/to/Dockerfile'), + dockerImage: DockerImage.fromBuild('/path/to/Dockerfile'), }, }); ``` diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts index b21d7a5fee5c2..e16e9db8120b6 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts @@ -52,7 +52,7 @@ export interface BundlingOptions { * * @see https://esbuild.github.io/api/#loader * - * @example { '.png': 'dataurl' } + * For example, `{ '.png': 'dataurl' }`. * * @default - use esbuild default loaders */ @@ -92,7 +92,7 @@ export interface BundlingOptions { * * This can be useful if you need to do multiple builds of the same code with different settings. * - * @example { 'tsconfig': 'path/custom.tsconfig.json' } + * For example, `{ 'tsconfig': 'path/custom.tsconfig.json' }`. * * @default - automatically discovered by `esbuild` */ @@ -103,19 +103,18 @@ export interface BundlingOptions { * * The metadata in this JSON file follows this schema (specified using TypeScript syntax): * - * ```typescript - * { - * outputs: { - * [path: string]: { - * bytes: number - * inputs: { - * [path: string]: { bytesInOutput: number } - * } - * imports: { path: string }[] - * exports: string[] - * } - * } + * ```text + * { + * outputs: { + * [path: string]: { + * bytes: number + * inputs: { + * [path: string]: { bytesInOutput: number } + * } + * imports: { path: string }[] + * exports: string[] * } + * } * } * ``` * This data can then be analyzed by other tools. For example, @@ -171,8 +170,9 @@ export interface BundlingOptions { /** * Replace global identifiers with constant expressions. * - * @example { 'process.env.DEBUG': 'true' } - * @example { 'process.env.API_KEY': JSON.stringify('xxx-xxxx-xxx') } + * For example, `{ 'process.env.DEBUG': 'true' }`. + * + * Another example, `{ 'process.env.API_KEY': JSON.stringify('xxx-xxxx-xxx') }`. * * @default - no replacements are made */ @@ -273,15 +273,14 @@ export interface BundlingOptions { * * Commands are chained with `&&`. * - * @example - * { - * // Copy a file from the input directory to the output directory - * // to include it in the bundled asset - * afterBundling(inputDir: string, outputDir: string): string[] { - * return [`cp ${inputDir}/my-binary.node ${outputDir}`]; - * } - * // ... + * The following example (specified in TypeScript) copies a file from the input + * directory to the output directory to include it in the bundled asset: + * + * ```text + * afterBundling(inputDir: string, outputDir: string): string[]{ + * return [`cp ${inputDir}/my-binary.node ${outputDir}`]; * } + * ``` */ export interface ICommandHooks { /** diff --git a/packages/@aws-cdk/aws-lambda-nodejs/package.json b/packages/@aws-cdk/aws-lambda-nodejs/package.json index f9d2895fab1fb..b0dfb32289dff 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/package.json +++ b/packages/@aws-cdk/aws-lambda-nodejs/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -71,7 +78,7 @@ "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.0.2", "delay": "5.0.0", - "esbuild": "^0.13.12" + "esbuild": "^0.13.14" }, "dependencies": { "@aws-cdk/aws-lambda": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda-nodejs/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-lambda-nodejs/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..d1ec43165548e --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/rosetta/default.ts-fixture @@ -0,0 +1,12 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { DockerImage, Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda-nodejs'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-lambda-python/README.md b/packages/@aws-cdk/aws-lambda-python/README.md index 4106b6210b871..f238a3f309410 100644 --- a/packages/@aws-cdk/aws-lambda-python/README.md +++ b/packages/@aws-cdk/aws-lambda-python/README.md @@ -25,14 +25,11 @@ To use this module, you will need to have Docker installed. Define a `PythonFunction`: ```ts -import * as lambda from "@aws-cdk/aws-lambda"; -import { PythonFunction } from "@aws-cdk/aws-lambda-python"; - -new PythonFunction(this, 'MyFunction', { +new lambda.PythonFunction(this, 'MyFunction', { entry: '/path/to/my/function', // required index: 'my_index.py', // optional, defaults to 'index.py' handler: 'my_exported_func', // optional, defaults to 'handler' - runtime: lambda.Runtime.PYTHON_3_6, // optional, defaults to lambda.Runtime.PYTHON_3_7 + runtime: Runtime.PYTHON_3_6, // optional, defaults to lambda.Runtime.PYTHON_3_7 }); ``` diff --git a/packages/@aws-cdk/aws-lambda-python/package.json b/packages/@aws-cdk/aws-lambda-python/package.json index ea33de04517ff..6cfec00d17893 100644 --- a/packages/@aws-cdk/aws-lambda-python/package.json +++ b/packages/@aws-cdk/aws-lambda-python/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-lambda-python/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-lambda-python/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..516396a167e8e --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-python/rosetta/default.ts-fixture @@ -0,0 +1,13 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import { Runtime } from '@aws-cdk/aws-lambda'; +import * as lambda from '@aws-cdk/aws-lambda-python'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/shared/requirements.txt b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/shared/requirements.txt index 10fdedeb59ab2..149a1792d9cdb 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/shared/requirements.txt +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler-project/shared/requirements.txt @@ -6,4 +6,4 @@ urllib3==1.25.11 # Requests used by this lambda requests==2.23.0 # Pillow 6.x so that python 2.7 and 3.x can both use this fixture -pillow==6.2.2 +pillow==8.3.2 diff --git a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler/requirements.txt b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler/requirements.txt index 10fdedeb59ab2..149a1792d9cdb 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/lambda-handler/requirements.txt +++ b/packages/@aws-cdk/aws-lambda-python/test/lambda-handler/requirements.txt @@ -6,4 +6,4 @@ urllib3==1.25.11 # Requests used by this lambda requests==2.23.0 # Pillow 6.x so that python 2.7 and 3.x can both use this fixture -pillow==6.2.2 +pillow==8.3.2 diff --git a/packages/@aws-cdk/aws-lambda/lib/alias.ts b/packages/@aws-cdk/aws-lambda/lib/alias.ts index 4287ffde73d6e..e497ad2e29071 100644 --- a/packages/@aws-cdk/aws-lambda/lib/alias.ts +++ b/packages/@aws-cdk/aws-lambda/lib/alias.ts @@ -1,6 +1,7 @@ import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; +import { ArnFormat } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { EventInvokeConfigOptions } from './event-invoke-config'; import { IFunction, QualifiedFunctionBase } from './function-base'; @@ -167,7 +168,7 @@ export class Alias extends QualifiedFunctionBase implements IAlias { service: 'lambda', resource: 'function', resourceName: `${this.lambda.functionName}:${this.physicalName}`, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); this.qualifier = extractQualifierFromArn(alias.ref); @@ -184,7 +185,7 @@ export class Alias extends QualifiedFunctionBase implements IAlias { // ARN parsing splits on `:`, so we can only get the function's name from the ARN as resourceName... // And we're parsing it out (instead of using the underlying function directly) in order to have use of it incur // an implicit dependency on the resource. - this.functionName = `${this.stack.parseArn(this.functionArn, ':').resourceName!}:${this.aliasName}`; + this.functionName = `${this.stack.splitArn(this.functionArn, ArnFormat.COLON_RESOURCE_NAME).resourceName!}:${this.aliasName}`; } public get grantPrincipal() { @@ -198,7 +199,7 @@ export class Alias extends QualifiedFunctionBase implements IAlias { public metric(metricName: string, props: cloudwatch.MetricOptions = {}): cloudwatch.Metric { // Metrics on Aliases need the "bare" function name, and the alias' ARN, this differs from the base behavior. return super.metric(metricName, { - dimensions: { + dimensionsMap: { FunctionName: this.lambda.functionName, // construct the name from the underlying lambda so that alarms on an alias // don't cause a circular dependency with CodeDeploy diff --git a/packages/@aws-cdk/aws-lambda/lib/code-signing-config.ts b/packages/@aws-cdk/aws-lambda/lib/code-signing-config.ts index 5e76436b75cca..9f9d017bc1ed7 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code-signing-config.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code-signing-config.ts @@ -1,5 +1,5 @@ import { ISigningProfile } from '@aws-cdk/aws-signer'; -import { IResource, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnCodeSigningConfig } from './lambda.generated'; @@ -79,7 +79,7 @@ export class CodeSigningConfig extends Resource implements ICodeSigningConfig { * @param codeSigningConfigArn The ARN of code signing config. */ public static fromCodeSigningConfigArn( scope: Construct, id: string, codeSigningConfigArn: string): ICodeSigningConfig { - const codeSigningProfileId = Stack.of(scope).parseArn(codeSigningConfigArn).resourceName; + const codeSigningProfileId = Stack.of(scope).splitArn(codeSigningConfigArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName; if (!codeSigningProfileId) { throw new Error(`Code signing config ARN must be in the format 'arn:aws:lambda:::code-signing-config:', got: '${codeSigningConfigArn}'`); } diff --git a/packages/@aws-cdk/aws-lambda/lib/code.ts b/packages/@aws-cdk/aws-lambda/lib/code.ts index 293c91f1485d9..f51e91de9bfb7 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code.ts @@ -517,28 +517,45 @@ export interface AssetImageCodeProps extends ecr_assets.DockerImageAssetOptions */ export class AssetImageCode extends Code { public readonly isInline: boolean = false; + private asset?: ecr_assets.DockerImageAsset; constructor(private readonly directory: string, private readonly props: AssetImageCodeProps) { super(); } public bind(scope: Construct): CodeConfig { - const asset = new ecr_assets.DockerImageAsset(scope, 'AssetImage', { - directory: this.directory, - ...this.props, - }); - - asset.repository.grantPull(new iam.ServicePrincipal('lambda.amazonaws.com')); + // If the same AssetImageCode is used multiple times, retain only the first instantiation. + if (!this.asset) { + this.asset = new ecr_assets.DockerImageAsset(scope, 'AssetImage', { + directory: this.directory, + ...this.props, + }); + this.asset.repository.grantPull(new iam.ServicePrincipal('lambda.amazonaws.com')); + } else if (cdk.Stack.of(this.asset) !== cdk.Stack.of(scope)) { + throw new Error(`Asset is already associated with another stack '${cdk.Stack.of(this.asset).stackName}'. ` + + 'Create a new Code instance for every stack.'); + } return { image: { - imageUri: asset.imageUri, + imageUri: this.asset.imageUri, entrypoint: this.props.entrypoint, cmd: this.props.cmd, workingDirectory: this.props.workingDirectory, }, }; } + + public bindToResource(resource: cdk.CfnResource, options: ResourceBindOptions = { }) { + if (!this.asset) { + throw new Error('bindToResource() must be called after bind()'); + } + + const resourceProperty = options.resourceProperty || 'Code.ImageUri'; + + // https://github.com/aws/aws-cdk/issues/14593 + this.asset.addResourceMetadata(resource, resourceProperty); + } } /** diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index 746cb23438c51..8a28f2e423657 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -1,7 +1,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { ConstructNode, IResource, Resource, Token } from '@aws-cdk/core'; +import { ArnFormat, ConstructNode, IResource, Resource, Token } from '@aws-cdk/core'; import { AliasOptions } from './alias'; import { EventInvokeConfig, EventInvokeConfigOptions } from './event-invoke-config'; import { IEventSource } from './event-source'; @@ -383,7 +383,7 @@ export abstract class FunctionBase extends Resource implements IFunction, ec2.IC if (Token.isUnresolved(this.stack.account) || Token.isUnresolved(this.functionArn)) { return false; } - return this.stack.parseArn(this.functionArn).account === this.stack.account; + return this.stack.splitArn(this.functionArn, ArnFormat.SLASH_RESOURCE_NAME).account === this.stack.account; } /** diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index fffd20decd8d9..c3936287a0990 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -5,7 +5,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as sqs from '@aws-cdk/aws-sqs'; -import { Annotations, CfnResource, Duration, Fn, Lazy, Names, Stack } from '@aws-cdk/core'; +import { Annotations, ArnFormat, CfnResource, Duration, Fn, Lazy, Names, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { Architecture } from './architecture'; import { Code, CodeConfig } from './code'; @@ -573,8 +573,13 @@ export class Function extends FunctionBase { */ public readonly deadLetterQueue?: sqs.IQueue; + /** + * The architecture of this Lambda Function (this is an optional attribute and defaults to X86_64). + */ + public readonly architecture?: Architecture; public readonly permissionsNode = this.node; + protected readonly canCreatePermissions = true; private readonly layers: ILayerVersion[] = []; @@ -716,11 +721,13 @@ export class Function extends FunctionBase { service: 'lambda', resource: 'function', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); this.runtime = props.runtime; + this.architecture = props.architecture; + if (props.layers) { if (props.runtime === Runtime.FROM_IMAGE) { throw new Error('Layers are not supported for container image functions'); diff --git a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts index 431b9bf6a71d9..c096730b1e8eb 100644 --- a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts @@ -1,11 +1,14 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; +import * as logs from '@aws-cdk/aws-logs'; import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { Function as LambdaFunction, FunctionProps } from './function'; +import { Function as LambdaFunction, FunctionProps, EnvironmentOptions } from './function'; import { FunctionBase } from './function-base'; import { Version } from './lambda-version'; +import { ILayerVersion } from './layers'; import { Permission } from './permission'; +import { Runtime } from './runtime'; /** * Properties for a newly created singleton Lambda @@ -47,6 +50,12 @@ export class SingletonFunction extends FunctionBase { public readonly functionArn: string; public readonly role?: iam.IRole; public readonly permissionsNode: cdk.ConstructNode; + + /** + * The runtime environment for the Lambda function. + */ + public readonly runtime: Runtime; + protected readonly canCreatePermissions: boolean; private lambdaFunction: LambdaFunction; @@ -59,6 +68,7 @@ export class SingletonFunction extends FunctionBase { this.functionArn = this.lambdaFunction.functionArn; this.functionName = this.lambdaFunction.functionName; this.role = this.lambdaFunction.role; + this.runtime = this.lambdaFunction.runtime; this.grantPrincipal = this.lambdaFunction.grantPrincipal; this.canCreatePermissions = true; // Doesn't matter, addPermission is overriden anyway @@ -78,6 +88,20 @@ export class SingletonFunction extends FunctionBase { return this.lambdaFunction.connections; } + /** + * The LogGroup where the Lambda function's logs are made available. + * + * If either `logRetention` is set or this property is called, a CloudFormation custom resource is added to the stack that + * pre-creates the log group as part of the stack deployment, if it already doesn't exist, and sets the correct log retention + * period (never expire, by default). + * + * Further, if the log group already exists and the `logRetention` is not set, the custom resource will reset the log retention + * to never expire even if it was configured with a different value. + */ + public get logGroup(): logs.ILogGroup { + return this.lambdaFunction.logGroup; + } + /** * Returns a `lambda.Version` which represents the current version of this * singleton Lambda function. A new version will be created every time the @@ -90,6 +114,28 @@ export class SingletonFunction extends FunctionBase { return this.lambdaFunction.currentVersion; } + /** + * Adds an environment variable to this Lambda function. + * If this is a ref to a Lambda function, this operation results in a no-op. + * @param key The environment variable key. + * @param value The environment variable's value. + * @param options Environment variable options. + */ + public addEnvironment(key: string, value: string, options?: EnvironmentOptions) { + return this.lambdaFunction.addEnvironment(key, value, options); + } + + /** + * Adds one or more Lambda Layers to this Lambda function. + * + * @param layers the layers to be added. + * + * @throws if there are already 5 layers on this function, or the layer is incompatible with this function's runtime. + */ + public addLayers(...layers: ILayerVersion[]) { + return this.lambdaFunction.addLayers(...layers); + } + public addPermission(name: string, permission: Permission) { return this.lambdaFunction.addPermission(name, permission); } diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index b966e65d20c98..93b724cdc1282 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -84,7 +91,7 @@ "@aws-cdk/pkglint": "0.0.0", "@types/aws-lambda": "^8.10.85", "@types/jest": "^27.0.2", - "@types/lodash": "^4.14.176", + "@types/lodash": "^4.14.177", "jest": "^27.3.1", "lodash": "^4.17.21" }, diff --git a/packages/@aws-cdk/aws-lambda/test/alias.test.ts b/packages/@aws-cdk/aws-lambda/test/alias.test.ts index bfe85e7acddd2..a470ace2a366a 100644 --- a/packages/@aws-cdk/aws-lambda/test/alias.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/alias.test.ts @@ -2,11 +2,12 @@ import '@aws-cdk/assert-internal/jest'; import { arrayWith, objectLike } from '@aws-cdk/assert-internal'; import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Lazy, Stack } from '@aws-cdk/core'; import * as lambda from '../lib'; describe('alias', () => { - test('version and aliases', () => { + testDeprecated('version and aliases', () => { const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('hello()'), @@ -53,7 +54,7 @@ describe('alias', () => { expect(stack).not.toHaveResource('AWS::Lambda::Version'); }); - test('can use newVersion to create a new Version', () => { + testDeprecated('can use newVersion to create a new Version', () => { const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('hello()'), @@ -78,7 +79,7 @@ describe('alias', () => { }); }); - test('can add additional versions to alias', () => { + testDeprecated('can add additional versions to alias', () => { const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { @@ -109,7 +110,7 @@ describe('alias', () => { }); }); - test('version and aliases with provisioned execution', () => { + testDeprecated('version and aliases with provisioned execution', () => { const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('hello()'), @@ -150,7 +151,7 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1'); + const version = fn.currentVersion; // WHEN: Individual weight too high expect(() => { @@ -181,7 +182,7 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1'); + const version = fn.currentVersion; const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', version }); // WHEN @@ -214,7 +215,7 @@ describe('alias', () => { }); }); - test('sanity checks provisionedConcurrentExecutions', () => { + testDeprecated('sanity checks provisionedConcurrentExecutions', () => { const stack = new Stack(); const pce = -1; @@ -228,7 +229,7 @@ describe('alias', () => { expect(() => { new lambda.Alias(stack, 'Alias1', { aliasName: 'prod', - version: fn.addVersion('1'), + version: fn.currentVersion, provisionedConcurrentExecutions: pce, }); }).toThrow(); @@ -259,7 +260,7 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1'); + const version = fn.currentVersion; const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', version }); // THEN @@ -276,7 +277,7 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1'); + const version = fn.currentVersion; const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', version }); // WHEN @@ -311,12 +312,11 @@ describe('alias', () => { handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1'); // WHEN new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, onSuccess: { bind: () => ({ destination: 'on-success-arn', @@ -358,10 +358,9 @@ describe('alias', () => { handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1'); const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, onSuccess: { bind: () => ({ destination: 'on-success-arn', @@ -392,7 +391,7 @@ describe('alias', () => { }); }); - test('can enable AutoScaling on aliases', () => { + testDeprecated('can enable AutoScaling on aliases', () => { // GIVEN const stack = new Stack(); const fn = new lambda.Function(stack, 'MyLambda', { @@ -401,11 +400,9 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1', undefined, 'testing'); - const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, }); // WHEN @@ -441,11 +438,9 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1', undefined, 'testing'); - const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, provisionedConcurrentExecutions: 10, }); @@ -488,11 +483,9 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1', undefined, 'testing'); - const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, provisionedConcurrentExecutions: 10, }); @@ -520,11 +513,9 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1', undefined, 'testing'); - const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, }); // WHEN @@ -543,11 +534,9 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1', undefined, 'testing'); - const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, }); // WHEN @@ -566,11 +555,9 @@ describe('alias', () => { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1', undefined, 'testing'); - const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', - version, + version: fn.currentVersion, }); // WHEN diff --git a/packages/@aws-cdk/aws-lambda/test/code.test.ts b/packages/@aws-cdk/aws-lambda/test/code.test.ts index 194ebda9aff37..40db469cc12d4 100644 --- a/packages/@aws-cdk/aws-lambda/test/code.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/code.test.ts @@ -2,9 +2,9 @@ import '@aws-cdk/assert-internal/jest'; import * as path from 'path'; import { ABSENT, ResourcePart } from '@aws-cdk/assert-internal'; import * as ecr from '@aws-cdk/aws-ecr'; +import { testFutureBehavior } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import * as lambda from '../lib'; /* eslint-disable dot-notation */ @@ -332,6 +332,110 @@ describe('code', () => { }, }); }); + + test('only one Asset object gets created even if multiple functions use the same AssetImageCode', () => { + // given + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'MyStack'); + const directoryAsset = lambda.Code.fromAssetImage(path.join(__dirname, 'docker-lambda-handler')); + + // when + new lambda.Function(stack, 'Fn1', { + code: directoryAsset, + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, + }); + + new lambda.Function(stack, 'Fn2', { + code: directoryAsset, + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, + }); + + // then + const assembly = app.synth(); + const synthesized = assembly.stacks[0]; + + // Func1 has an asset, Func2 does not + expect(synthesized.assets.length).toEqual(1); + }); + + test('adds code asset metadata', () => { + // given + const stack = new cdk.Stack(); + stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + + const dockerfilePath = 'Dockerfile'; + const dockerBuildTarget = 'stage'; + const dockerBuildArgs = { arg1: 'val1', arg2: 'val2' }; + + // when + new lambda.Function(stack, 'Fn', { + code: lambda.Code.fromAssetImage(path.join(__dirname, 'docker-lambda-handler'), { + file: dockerfilePath, + target: dockerBuildTarget, + buildArgs: dockerBuildArgs, + }), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, + }); + + // then + expect(stack).toHaveResource('AWS::Lambda::Function', { + Metadata: { + [cxapi.ASSET_RESOURCE_METADATA_PATH_KEY]: 'asset.650a009a909c30e767a843a84ff7812616447251d245e0ab65d9bfb37f413e32', + [cxapi.ASSET_RESOURCE_METADATA_DOCKERFILE_PATH_KEY]: dockerfilePath, + [cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_ARGS_KEY]: dockerBuildArgs, + [cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_TARGET_KEY]: dockerBuildTarget, + [cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY]: 'Code.ImageUri', + }, + }, ResourcePart.CompleteDefinition); + }); + + test('adds code asset metadata with default dockerfile path', () => { + // given + const stack = new cdk.Stack(); + stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + + // when + new lambda.Function(stack, 'Fn', { + code: lambda.Code.fromAssetImage(path.join(__dirname, 'docker-lambda-handler')), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, + }); + + // then + expect(stack).toHaveResource('AWS::Lambda::Function', { + Metadata: { + [cxapi.ASSET_RESOURCE_METADATA_PATH_KEY]: 'asset.a3cc4528c34874616814d9b3436ff0e5d01514c1d563ed8899657ca00982f308', + [cxapi.ASSET_RESOURCE_METADATA_DOCKERFILE_PATH_KEY]: 'Dockerfile', + [cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY]: 'Code.ImageUri', + }, + }, ResourcePart.CompleteDefinition); + }); + + test('fails if asset is bound with a second stack', () => { + // given + const app = new cdk.App(); + const asset = lambda.Code.fromAssetImage(path.join(__dirname, 'docker-lambda-handler')); + + // when + const stack1 = new cdk.Stack(app, 'Stack1'); + new lambda.Function(stack1, 'Fn', { + code: asset, + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, + }); + + const stack2 = new cdk.Stack(app, 'Stack2'); + + // then + expect(() => new lambda.Function(stack2, 'Fn', { + code: asset, + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, + })).toThrow(/already associated/); + }); }); describe('lambda.Code.fromDockerBuild', () => { diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index 66d18e663e7f9..965f41aed66f7 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -10,6 +10,7 @@ import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as signer from '@aws-cdk/aws-signer'; import * as sqs from '@aws-cdk/aws-sqs'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; import * as _ from 'lodash'; @@ -1382,7 +1383,7 @@ describe('function', () => { }); }); - test('add a version with event invoke config', () => { + testDeprecated('add a version with event invoke config', () => { // GIVEN const stack = new cdk.Stack(); const fn = new lambda.Function(stack, 'fn', { @@ -2174,7 +2175,7 @@ describe('function', () => { })).toThrow(/Layers are not supported for container image functions/); }); - test('specified architectures is recognized', () => { + testDeprecated('specified architectures is recognized', () => { const stack = new cdk.Stack(); new lambda.Function(stack, 'MyFunction', { code: lambda.Code.fromInline('foo'), @@ -2204,7 +2205,7 @@ describe('function', () => { }); }); - test('both architectures and architecture are not recognized', () => { + testDeprecated('both architectures and architecture are not recognized', () => { const stack = new cdk.Stack(); expect(() => new lambda.Function(stack, 'MyFunction', { code: lambda.Code.fromInline('foo'), @@ -2216,7 +2217,7 @@ describe('function', () => { })).toThrow(/architecture or architectures must be specified/); }); - test('Only one architecture allowed', () => { + testDeprecated('Only one architecture allowed', () => { const stack = new cdk.Stack(); expect(() => new lambda.Function(stack, 'MyFunction', { code: lambda.Code.fromInline('foo'), @@ -2226,7 +2227,16 @@ describe('function', () => { architectures: [lambda.Architecture.X86_64, lambda.Architecture.ARM_64], })).toThrow(/one architecture must be specified/); }); - + test('Architecture is properly readable from the function', () => { + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'MyFunction', { + code: lambda.Code.fromInline('foo'), + runtime: lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + architecture: lambda.Architecture.ARM_64, + }); + expect(fn.architecture?.name).toEqual('arm64'); + }); }); function newTestLambda(scope: constructs.Construct) { diff --git a/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.expected.json index dc1f0b431db23..1ee05ac72cef8 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.expected.json @@ -50,13 +50,12 @@ "MyLambdaServiceRole4539ECB6" ] }, - "MyLambdaVersion16CDE3C40": { + "MyLambdaCurrentVersionE7A382CC03fc10af301b823dc69dee9357b5caa0": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { "Ref": "MyLambdaCCE802FB" - }, - "Description": "integ-test" + } } }, "Alias325C5727": { @@ -67,7 +66,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaVersion16CDE3C40", + "MyLambdaCurrentVersionE7A382CC03fc10af301b823dc69dee9357b5caa0", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.ts b/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.ts index 949670ec636b6..e8d3411b072f3 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.ts @@ -17,7 +17,7 @@ class TestStack extends cdk.Stack { runtime: lambda.Runtime.NODEJS_10_X, }); - const version = fn.addVersion('1', undefined, 'integ-test'); + const version = fn.currentVersion; const alias = new lambda.Alias(this, 'Alias', { aliasName: 'prod', diff --git a/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json index 86a20d58e17c1..66f651fdbf280 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json @@ -72,13 +72,13 @@ ] } }, - "Handler": "index.main", "Role": { "Fn::GetAtt": [ "MyLambdaServiceRole4539ECB6", "Arn" ] }, + "Handler": "index.main", "Runtime": "python3.8" }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.lambda.expected.json index 65f9ebbeb5855..88e7b53442a15 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.expected.json @@ -72,7 +72,7 @@ "MyLambdaServiceRole4539ECB6" ] }, - "MyLambdaVersion16CDE3C40": { + "MyLambdaCurrentVersionE7A382CC306b64ef431b3e873cc6258340b63a78": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -88,7 +88,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaVersion16CDE3C40", + "MyLambdaCurrentVersionE7A382CC306b64ef431b3e873cc6258340b63a78", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.expected.json index ad788ff4425b6..15a57b7a0e598 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.expected.json @@ -72,7 +72,7 @@ "MyLambdaAliasPCEServiceRoleF7C9F212" ] }, - "MyLambdaAliasPCEVersion15F479C08": { + "MyLambdaAliasPCECurrentVersion072335D3974767ca5ab9a8786a5779ede8cb8cc5": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -88,7 +88,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaAliasPCEVersion15F479C08", + "MyLambdaAliasPCECurrentVersion072335D3974767ca5ab9a8786a5779ede8cb8cc5", "Version" ] }, @@ -180,7 +180,7 @@ "MyLambdaVersionPCEServiceRole2ACFB73E" ] }, - "MyLambdaVersionPCEVersion2C704112A": { + "MyLambdaVersionPCECurrentVersion27FC3932a1bc5d5d20600bf4225d17df43a36ea5": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -199,7 +199,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaVersionPCEVersion2C704112A", + "MyLambdaVersionPCECurrentVersion27FC3932a1bc5d5d20600bf4225d17df43a36ea5", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.ts b/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.ts index 0544d2535784c..4a100b4d6e462 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.ts @@ -23,7 +23,7 @@ fn.addToRolePolicy(new iam.PolicyStatement({ actions: ['*'], })); -const version = fn.addVersion('1'); +const version = fn.currentVersion; const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', @@ -40,6 +40,9 @@ const fnVersionPCE = new lambda.Function(stack, 'MyLambdaVersionPCE', { code: new lambda.InlineCode(lambdaCode.replace('#type#', 'Version')), handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, + currentVersionOptions: { + provisionedConcurrentExecutions: pce, + }, }); fnVersionPCE.addToRolePolicy(new iam.PolicyStatement({ @@ -47,7 +50,7 @@ fnVersionPCE.addToRolePolicy(new iam.PolicyStatement({ actions: ['*'], })); -const version2 = fnVersionPCE.addVersion('2', undefined, undefined, pce); +const version2 = fnVersionPCE.currentVersion; const alias2 = new lambda.Alias(stack, 'Alias2', { aliasName: 'prod', diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts b/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts index e2a97353c00e6..c6ca7302a1f91 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts @@ -17,7 +17,7 @@ fn.addToRolePolicy(new iam.PolicyStatement({ actions: ['*'], })); -const version = fn.addVersion('1'); +const version = fn.currentVersion; const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', diff --git a/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts b/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts index 3bf78253d5ed6..4200b7a18a6e5 100644 --- a/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts @@ -2,6 +2,7 @@ import '@aws-cdk/assert-internal/jest'; import { ResourcePart } from '@aws-cdk/assert-internal'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; +import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import * as lambda from '../lib'; @@ -109,6 +110,57 @@ describe('singleton lambda', () => { }, ResourcePart.CompleteDefinition); }); + test('Environment is added to Lambda, when .addEnvironment() is provided one key pair', () => { + // GIVEN + const stack = new cdk.Stack(); + const singleton = new lambda.SingletonFunction(stack, 'Singleton', { + uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', + code: new lambda.InlineCode('def hello(): pass'), + runtime: lambda.Runtime.PYTHON_2_7, + handler: 'index.hello', + timeout: cdk.Duration.minutes(5), + }); + + // WHEN + singleton.addEnvironment('KEY', 'value'); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Function', { + Environment: { + Variables: { + KEY: 'value', + }, + }, + }); + }); + + test('Layer is added to Lambda, when .addLayers() is provided a valid layer', () => { + // GIVEN + const stack = new cdk.Stack(); + const singleton = new lambda.SingletonFunction(stack, 'Singleton', { + uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', + code: new lambda.InlineCode('def hello(): pass'), + runtime: lambda.Runtime.PYTHON_2_7, + handler: 'index.hello', + timeout: cdk.Duration.minutes(5), + }); + const bucket = new s3.Bucket(stack, 'Bucket'); + const layer = new lambda.LayerVersion(stack, 'myLayer', { + code: new lambda.S3Code(bucket, 'ObjectKey'), + compatibleRuntimes: [lambda.Runtime.PYTHON_2_7], + }); + + // WHEN + singleton.addLayers(layer); + + // THEN + expect(stack).toHaveResource('AWS::Lambda::Function', { + Layers: [{ + Ref: 'myLayerBA1B098A', + }], + }); + }); + test('grantInvoke works correctly', () => { // GIVEN const stack = new cdk.Stack(); @@ -154,6 +206,41 @@ describe('singleton lambda', () => { .toThrow(/contains environment variables .* and is not compatible with Lambda@Edge/); }); + test('logGroup is correctly returned', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const singleton = new lambda.SingletonFunction(stack, 'Singleton', { + uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', + code: new lambda.InlineCode('def hello(): pass'), + runtime: lambda.Runtime.PYTHON_2_7, + handler: 'index.hello', + timeout: cdk.Duration.minutes(5), + }); + + // THEN + expect(singleton.logGroup.logGroupName).toBeDefined(); + expect(singleton.logGroup.logGroupArn).toBeDefined(); + }); + + test('runtime is correctly returned', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const singleton = new lambda.SingletonFunction(stack, 'Singleton', { + uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', + code: new lambda.InlineCode('def hello(): pass'), + runtime: lambda.Runtime.PYTHON_2_7, + handler: 'index.hello', + timeout: cdk.Duration.minutes(5), + }); + + // THEN + expect(singleton.runtime).toStrictEqual(lambda.Runtime.PYTHON_2_7); + }); + test('current version of a singleton function', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts b/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts index 2ecd2819becf5..409ffc5fa3a45 100644 --- a/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts @@ -1,5 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as lambda from '../lib'; @@ -41,7 +42,7 @@ describe('lambda + vpc', () => { }); }); - test('has securitygroup that is passed in props', () => { + testDeprecated('has securitygroup that is passed in props', () => { // WHEN new lambda.Function(stack, 'LambdaWithCustomSG', { code: new lambda.InlineCode('foo'), @@ -91,7 +92,7 @@ describe('lambda + vpc', () => { }); }); - test('fails if both of securityGroup and securityGroups are passed in props at once', () => { + testDeprecated('fails if both of securityGroup and securityGroups are passed in props at once', () => { // THEN expect(() => { new lambda.Function(stack, 'LambdaWithWrongProps', { diff --git a/packages/@aws-cdk/aws-logs/README.md b/packages/@aws-cdk/aws-logs/README.md index 631bc382365e0..804b5c56c4eb3 100644 --- a/packages/@aws-cdk/aws-logs/README.md +++ b/packages/@aws-cdk/aws-logs/README.md @@ -51,14 +51,11 @@ publish their log group to a specific region, such as AWS Chatbot creating a log ## Resource Policy CloudWatch Resource Policies allow other AWS services or IAM Principals to put log events into the log groups. -A resource policy is automatically created when `addToResourcePolicy` is called on the LogGroup for the first time. - -`ResourcePolicy` can also be created manually. +A resource policy is automatically created when `addToResourcePolicy` is called on the LogGroup for the first time: ```ts -const logGroup = new LogGroup(this, 'LogGroup'); -const resourcePolicy = new ResourcePolicy(this, 'ResourcePolicy'); -resourcePolicy.document.addStatements(new iam.PolicyStatement({ +const logGroup = new logs.LogGroup(this, 'LogGroup'); +logGroup.addToResourcePolicy(new iam.PolicyStatement({ actions: ['logs:CreateLogStream', 'logs:PutLogEvents'], principals: [new iam.ServicePrincipal('es.amazonaws.com')], resources: [logGroup.logGroupArn], @@ -68,22 +65,8 @@ resourcePolicy.document.addStatements(new iam.PolicyStatement({ Or more conveniently, write permissions to the log group can be granted as follows which gives same result as in the above example. ```ts -const logGroup = new LogGroup(this, 'LogGroup'); -logGroup.grantWrite(iam.ServicePrincipal('es.amazonaws.com')); -``` - -Optionally name and policy statements can also be passed on `ResourcePolicy` construction. - -```ts -const policyStatement = new new iam.PolicyStatement({ - resources: ["*"], - actions: ['logs:PutLogEvents'], - principals: [new iam.ArnPrincipal('arn:aws:iam::123456789012:user/user-name')], -}); -const resourcePolicy = new ResourcePolicy(this, 'ResourcePolicy', { - policyName: 'myResourcePolicy', - policyStatements: [policyStatement], -}); +const logGroup = new logs.LogGroup(this, 'LogGroup'); +logGroup.grantWrite(new iam.ServicePrincipal('es.amazonaws.com')); ``` ## Encrypting Log Groups diff --git a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts index 2d024ad8e88e1..7684ee84befd8 100644 --- a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts +++ b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts @@ -5,6 +5,10 @@ import { ILogGroup } from './log-group'; import { CfnDestination } from './logs.generated'; import { ILogSubscriptionDestination, LogSubscriptionDestinationConfig } from './subscription-filter'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { ArnFormat } from '@aws-cdk/core'; + // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order import { Construct as CoreConstruct } from '@aws-cdk/core'; @@ -88,7 +92,7 @@ export class CrossAccountDestination extends cdk.Resource implements ILogSubscri service: 'logs', resource: 'destination', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); this.destinationName = this.getResourceNameAttribute(this.resource.ref); } diff --git a/packages/@aws-cdk/aws-logs/lib/index.ts b/packages/@aws-cdk/aws-logs/lib/index.ts index 5054715ffe52b..416a9c9a9b257 100644 --- a/packages/@aws-cdk/aws-logs/lib/index.ts +++ b/packages/@aws-cdk/aws-logs/lib/index.ts @@ -5,6 +5,7 @@ export * from './metric-filter'; export * from './pattern'; export * from './subscription-filter'; export * from './log-retention'; +export * from './policy'; // AWS::Logs CloudFormation Resources: export * from './logs.generated'; diff --git a/packages/@aws-cdk/aws-logs/lib/log-group.ts b/packages/@aws-cdk/aws-logs/lib/log-group.ts index c701f4b5e4c9f..539c8ec035548 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-group.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-group.ts @@ -1,7 +1,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; -import { RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { LogStream } from './log-stream'; import { CfnLogGroup } from './logs.generated'; @@ -352,7 +352,7 @@ export class LogGroup extends LogGroupBase { class Import extends LogGroupBase { public readonly logGroupArn = `${baseLogGroupArn}:*`; - public readonly logGroupName = Stack.of(scope).parseArn(baseLogGroupArn, ':').resourceName!; + public readonly logGroupName = Stack.of(scope).splitArn(baseLogGroupArn, ArnFormat.COLON_RESOURCE_NAME).resourceName!; } return new Import(scope, id); @@ -369,7 +369,7 @@ export class LogGroup extends LogGroupBase { public readonly logGroupArn = Stack.of(scope).formatArn({ service: 'logs', resource: 'log-group', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, resourceName: baseLogGroupName + ':*', }); } @@ -412,7 +412,7 @@ export class LogGroup extends LogGroupBase { service: 'logs', resource: 'log-group', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); this.logGroupName = this.getResourceNameAttribute(resource.ref); } diff --git a/packages/@aws-cdk/aws-logs/lib/log-retention.ts b/packages/@aws-cdk/aws-logs/lib/log-retention.ts index fe4e3e3cff7af..5af8edaac96ed 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-retention.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-retention.ts @@ -5,6 +5,10 @@ import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import { RetentionDays } from './log-group'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { ArnFormat } from '@aws-cdk/core'; + // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order import { Construct as CoreConstruct } from '@aws-cdk/core'; @@ -107,7 +111,7 @@ export class LogRetention extends CoreConstruct { service: 'logs', resource: 'log-group', resourceName: `${logGroupName}:*`, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } @@ -171,6 +175,8 @@ class LogRetentionFunction extends CoreConstruct implements cdk.ITaggable { }); this.functionArn = resource.getAtt('Arn'); + asset.addResourceMetadata(resource, 'Code'); + // Function dependencies role.node.children.forEach((child) => { if (cdk.CfnResource.isCfnResource(child)) { diff --git a/packages/@aws-cdk/aws-logs/lib/policy.ts b/packages/@aws-cdk/aws-logs/lib/policy.ts index 974f517d48b25..de3af44f1ae2f 100644 --- a/packages/@aws-cdk/aws-logs/lib/policy.ts +++ b/packages/@aws-cdk/aws-logs/lib/policy.ts @@ -11,7 +11,7 @@ export interface ResourcePolicyProps { * Name of the log group resource policy * @default - Uses a unique id based on the construct path */ - readonly policyName?: string; + readonly resourcePolicyName?: string; /** * Initial statements to add to the resource policy @@ -31,15 +31,19 @@ export class ResourcePolicy extends Resource { public readonly document = new PolicyDocument(); constructor(scope: Construct, id: string, props?: ResourcePolicyProps) { - super(scope, id); - new CfnResourcePolicy(this, 'Resource', { + super(scope, id, { + physicalName: props?.resourcePolicyName, + }); + + new CfnResourcePolicy(this, 'ResourcePolicy', { policyName: Lazy.string({ - produce: () => props?.policyName ?? Names.uniqueId(this), + produce: () => props?.resourcePolicyName ?? Names.uniqueId(this), }), policyDocument: Lazy.string({ produce: () => JSON.stringify(this.document), }), }); + if (props?.policyStatements) { this.document.addStatements(...props.policyStatements); } diff --git a/packages/@aws-cdk/aws-logs/package.json b/packages/@aws-cdk/aws-logs/package.json index 91469ca459d3b..a865bde5c1dae 100644 --- a/packages/@aws-cdk/aws-logs/package.json +++ b/packages/@aws-cdk/aws-logs/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -83,7 +90,7 @@ "aws-sdk": "^2.848.0", "aws-sdk-mock": "^5.4.0", "jest": "^27.3.1", - "nock": "^13.1.4", + "nock": "^13.2.1", "sinon": "^9.2.4" }, "dependencies": { @@ -92,6 +99,7 @@ "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/cx-api": "0.0.0", "constructs": "^3.3.69" }, "homepage": "https://github.com/aws/aws-cdk", @@ -101,6 +109,7 @@ "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/cx-api": "0.0.0", "constructs": "^3.3.69" }, "engines": { diff --git a/packages/@aws-cdk/aws-logs/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-logs/rosetta/default.ts-fixture index 27c338ce30a32..6c34f60674f19 100644 --- a/packages/@aws-cdk/aws-logs/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-logs/rosetta/default.ts-fixture @@ -2,6 +2,7 @@ import { Construct } from 'constructs'; import { Stack } from '@aws-cdk/core'; import * as logs from '@aws-cdk/aws-logs'; +import * as iam from '@aws-cdk/aws-iam'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as lambda from '@aws-cdk/aws-lambda'; diff --git a/packages/@aws-cdk/aws-logs/test/log-retention.test.ts b/packages/@aws-cdk/aws-logs/test/log-retention.test.ts index 208031bc1744b..af746f956e675 100644 --- a/packages/@aws-cdk/aws-logs/test/log-retention.test.ts +++ b/packages/@aws-cdk/aws-logs/test/log-retention.test.ts @@ -1,7 +1,9 @@ +import * as path from 'path'; import '@aws-cdk/assert-internal/jest'; -import { ABSENT } from '@aws-cdk/assert-internal'; +import { ABSENT, ResourcePart } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; +import * as cxapi from '@aws-cdk/cx-api'; import { LogRetention, RetentionDays } from '../lib'; /* eslint-disable quote-props */ @@ -175,4 +177,28 @@ describe('log retention', () => { }); }); + + test('asset metadata added to log retention construct lambda function', () => { + // GIVEN + const stack = new cdk.Stack(); + stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + stack.node.setContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT, true); + + const assetLocation = path.join(__dirname, '../', '/lib', '/log-retention-provider'); + + // WHEN + new LogRetention(stack, 'MyLambda', { + logGroupName: 'group', + retention: RetentionDays.ONE_MONTH, + }); + + // Then + expect(stack).toHaveResource('AWS::Lambda::Function', { + Metadata: { + 'aws:asset:path': assetLocation, + 'aws:asset:property': 'Code', + }, + }, ResourcePart.CompleteDefinition); + + }); }); diff --git a/packages/@aws-cdk/aws-logs/test/policy.test.ts b/packages/@aws-cdk/aws-logs/test/policy.test.ts new file mode 100644 index 0000000000000..4b2684a9957b1 --- /dev/null +++ b/packages/@aws-cdk/aws-logs/test/policy.test.ts @@ -0,0 +1,52 @@ +import '@aws-cdk/assert-internal/jest'; +import { PolicyStatement, ServicePrincipal } from '@aws-cdk/aws-iam'; +import { Stack } from '@aws-cdk/core'; +import { LogGroup, ResourcePolicy } from '../lib'; + +describe('resource policy', () => { + test('ResourcePolicy is added to stack, when .addToResourcePolicy() is provided a valid Statement', () => { + // GIVEN + const stack = new Stack(); + const logGroup = new LogGroup(stack, 'LogGroup'); + + // WHEN + logGroup.addToResourcePolicy(new PolicyStatement({ + actions: ['logs:CreateLogStream'], + resources: ['*'], + })); + + // THEN + expect(stack).toHaveResource('AWS::Logs::ResourcePolicy', { + PolicyName: 'LogGroupPolicy643B329C', + PolicyDocument: JSON.stringify({ + Statement: [ + { + Action: 'logs:CreateLogStream', + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }), + }); + }); + + test('ResourcePolicy is added to stack, when created manually/directly', () => { + // GIVEN + const stack = new Stack(); + const logGroup = new LogGroup(stack, 'LogGroup'); + + // WHEN + const resourcePolicy = new ResourcePolicy(stack, 'ResourcePolicy'); + resourcePolicy.document.addStatements(new PolicyStatement({ + actions: ['logs:CreateLogStream', 'logs:PutLogEvents'], + principals: [new ServicePrincipal('es.amazonaws.com')], + resources: [logGroup.logGroupArn], + })); + + // THEN + expect(stack).toHaveResource('AWS::Logs::ResourcePolicy', { + PolicyName: 'ResourcePolicy', + }); + }); +}); diff --git a/packages/@aws-cdk/aws-opensearchservice/README.md b/packages/@aws-cdk/aws-opensearchservice/README.md index d0ddd81e7c0d4..79d2289dcfffb 100644 --- a/packages/@aws-cdk/aws-opensearchservice/README.md +++ b/packages/@aws-cdk/aws-opensearchservice/README.md @@ -31,21 +31,17 @@ See [Migrating to OpenSearch](https://docs.aws.amazon.com/cdk/api/latest/docs/aw Create a development cluster by simply specifying the version: ```ts -import * as opensearch from '@aws-cdk/aws-opensearchservice'; - const devDomain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, + version: opensearch.EngineVersion.OPENSEARCH_1_0, }); ``` To perform version upgrades without replacing the entire domain, specify the `enableVersionUpgrade` property. ```ts -import * as opensearch from '@aws-cdk/aws-opensearchservice'; - const devDomain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, - enableVersionUpgrade: true // defaults to false + version: opensearch.EngineVersion.OPENSEARCH_1_0, + enableVersionUpgrade: true, // defaults to false }); ``` @@ -53,22 +49,22 @@ Create a production grade cluster by also specifying things like capacity and az ```ts const prodDomain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, - capacity: { - masterNodes: 5, - dataNodes: 20 - }, - ebs: { - volumeSize: 20 - }, - zoneAwareness: { - availabilityZoneCount: 3 - }, - logging: { - slowSearchLogEnabled: true, - appLogEnabled: true, - slowIndexLogEnabled: true, - }, + version: opensearch.EngineVersion.OPENSEARCH_1_0, + capacity: { + masterNodes: 5, + dataNodes: 20, + }, + ebs: { + volumeSize: 20, + }, + zoneAwareness: { + availabilityZoneCount: 3, + }, + logging: { + slowSearchLogEnabled: true, + appLogEnabled: true, + slowIndexLogEnabled: true, + }, }); ``` @@ -95,7 +91,7 @@ You can also create it using the CDK, **but note that only the first application ```ts const slr = new iam.CfnServiceLinkedRole(this, 'Service Linked Role', { - awsServiceName: 'es.amazonaws.com' + awsServiceName: 'es.amazonaws.com', }); ``` @@ -106,7 +102,7 @@ This method accepts a domain endpoint of an already existing domain: ```ts const domainEndpoint = 'https://my-domain-jcjotrt6f7otem4sqcwbch3c4u.us-east-1.es.amazonaws.com'; -const domain = Domain.fromDomainEndpoint(this, 'ImportedDomain', domainEndpoint); +const domain = opensearch.Domain.fromDomainEndpoint(this, 'ImportedDomain', domainEndpoint); ``` ## Permissions @@ -116,13 +112,14 @@ const domain = Domain.fromDomainEndpoint(this, 'ImportedDomain', domainEndpoint) Helper methods also exist for managing access to the domain. ```ts -const lambda = new lambda.Function(this, 'Lambda', { /* ... */ }); +declare const fn: lambda.Function; +declare const domain: opensearch.Domain; // Grant write access to the app-search index -domain.grantIndexWrite('app-search', lambda); +domain.grantIndexWrite('app-search', fn); // Grant read access to the 'app-search/_search' path -domain.grantPathRead('app-search/_search', lambda); +domain.grantPathRead('app-search/_search', fn); ``` ## Encryption @@ -131,15 +128,15 @@ The domain can also be created with encryption enabled: ```ts const domain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, - ebs: { - volumeSize: 100, - volumeType: EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, - }, - nodeToNodeEncryption: true, - encryptionAtRest: { - enabled: true, - }, + version: opensearch.EngineVersion.OPENSEARCH_1_0, + ebs: { + volumeSize: 100, + volumeType: ec2.EbsDeviceVolumeType.GENERAL_PURPOSE_SSD, + }, + nodeToNodeEncryption: true, + encryptionAtRest: { + enabled: true, + }, }); ``` @@ -179,6 +176,7 @@ which security groups will be attached to the domain. By default, CDK will selec Helper methods exist to access common domain metrics for example: ```ts +declare const domain: opensearch.Domain; const freeStorageSpace = domain.metricFreeStorageSpace(); const masterSysMemoryUtilization = domain.metric('MasterSysMemoryUtilization'); ``` @@ -192,15 +190,15 @@ be supplied or dynamically created if not supplied. ```ts const domain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, - enforceHttps: true, - nodeToNodeEncryption: true, - encryptionAtRest: { - enabled: true, - }, - fineGrainedAccessControl: { - masterUserName: 'master-user', - }, + version: opensearch.EngineVersion.OPENSEARCH_1_0, + enforceHttps: true, + nodeToNodeEncryption: true, + encryptionAtRest: { + enabled: true, + }, + fineGrainedAccessControl: { + masterUserName: 'master-user', + }, }); const masterUserPassword = domain.masterUserPassword; @@ -230,8 +228,8 @@ stored in the AWS Secrets Manager as secret. The secret has the prefix ```ts const domain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, - useUnsignedBasicAuth: true, + version: opensearch.EngineVersion.OPENSEARCH_1_0, + useUnsignedBasicAuth: true, }); const masterUserPassword = domain.masterUserPassword; @@ -245,21 +243,21 @@ Audit logs can be enabled for a domain, but only when fine grained access contro ```ts const domain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, - enforceHttps: true, - nodeToNodeEncryption: true, - encryptionAtRest: { - enabled: true, - }, - fineGrainedAccessControl: { - masterUserName: 'master-user', - }, - logging: { - auditLogEnabled: true, - slowSearchLogEnabled: true, - appLogEnabled: true, - slowIndexLogEnabled: true, - }, + version: opensearch.EngineVersion.OPENSEARCH_1_0, + enforceHttps: true, + nodeToNodeEncryption: true, + encryptionAtRest: { + enabled: true, + }, + fineGrainedAccessControl: { + masterUserName: 'master-user', + }, + logging: { + auditLogEnabled: true, + slowSearchLogEnabled: true, + appLogEnabled: true, + slowIndexLogEnabled: true, + }, }); ``` @@ -269,12 +267,12 @@ UltraWarm nodes can be enabled to provide a cost-effective way to store large am ```ts const domain = new opensearch.Domain(this, 'Domain', { - version: opensearch.EngineVersion.OPENSEARCH_1_0, - capacity: { - masterNodes: 2, - warmNodes: 2, - warmInstanceType: 'ultrawarm1.medium.search', - }, + version: opensearch.EngineVersion.OPENSEARCH_1_0, + capacity: { + masterNodes: 2, + warmNodes: 2, + warmInstanceType: 'ultrawarm1.medium.search', + }, }); ``` @@ -283,11 +281,11 @@ const domain = new opensearch.Domain(this, 'Domain', { Custom endpoints can be configured to reach the domain under a custom domain name. ```ts -new Domain(stack, 'Domain', { - version: EngineVersion.OPENSEARCH_1_0, - customEndpoint: { - domainName: 'search.example.com', - }, +new opensearch.Domain(this, 'Domain', { + version: opensearch.EngineVersion.OPENSEARCH_1_0, + customEndpoint: { + domainName: 'search.example.com', + }, }); ``` @@ -300,12 +298,12 @@ Additionally, an automatic CNAME-Record is created if a hosted zone is provided [Advanced options](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/createupdatedomains.html#createdomain-configure-advanced-options) can used to configure additional options. ```ts -new Domain(stack, 'Domain', { - version: EngineVersion.OPENSEARCH_1_0, - advancedOptions: { - 'rest.action.multi.allow_explicit_index': 'false', - 'indices.fielddata.cache.size': '25', - 'indices.query.bool.max_clause_count': '2048', - }, +new opensearch.Domain(this, 'Domain', { + version: opensearch.EngineVersion.OPENSEARCH_1_0, + advancedOptions: { + 'rest.action.multi.allow_explicit_index': 'false', + 'indices.fielddata.cache.size': '25', + 'indices.query.bool.max_clause_count': '2048', + }, }); ``` diff --git a/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts b/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts index b058c08443f16..3d5eebb6f32d6 100644 --- a/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts +++ b/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts @@ -881,7 +881,7 @@ abstract class DomainBase extends cdk.Resource implements IDomain { return new Metric({ namespace: 'AWS/ES', metricName, - dimensions: { + dimensionsMap: { DomainName: this.domainName, ClientId: this.stack.account, }, @@ -1141,7 +1141,8 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { */ public static fromDomainAttributes(scope: Construct, id: string, attrs: DomainAttributes): IDomain { const { domainArn, domainEndpoint } = attrs; - const domainName = cdk.Stack.of(scope).parseArn(domainArn).resourceName ?? extractNameFromEndpoint(domainEndpoint); + const domainName = cdk.Stack.of(scope).splitArn(domainArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resourceName + ?? extractNameFromEndpoint(domainEndpoint); return new class extends DomainBase { public readonly domainArn = domainArn; diff --git a/packages/@aws-cdk/aws-opensearchservice/package.json b/packages/@aws-cdk/aws-opensearchservice/package.json index 266ba103ee0d6..24c01f69d0dd7 100644 --- a/packages/@aws-cdk/aws-opensearchservice/package.json +++ b/packages/@aws-cdk/aws-opensearchservice/package.json @@ -7,6 +7,13 @@ "jsii": { "outdir": "dist", "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, "targets": { "dotnet": { "namespace": "Amazon.CDK.AWS.OpenSearchService", diff --git a/packages/@aws-cdk/aws-opensearchservice/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-opensearchservice/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..625996f43fb30 --- /dev/null +++ b/packages/@aws-cdk/aws-opensearchservice/rosetta/default.ts-fixture @@ -0,0 +1,15 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { RemovalPolicy, Stack } from '@aws-cdk/core'; +import * as opensearch from '@aws-cdk/aws-opensearchservice'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index 9f350d3dd1f5b..acc7f61b6022b 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -129,6 +129,10 @@ new rds.DatabaseInstanceReadReplica(this, 'ReadReplica', { }); ``` +Automatic backups of read replica instances are only supported for MySQL and MariaDB. By default, +automatic backups are disabled for read replicas and can only be enabled (using `backupRetention`) +if also enabled on the source instance. + Creating a "production" Oracle database instance with option and parameter groups: [example of setting up a production oracle instance](test/integ.instance.lit.ts) diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts index cde235257ff8e..26a4743b7e598 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts @@ -336,6 +336,8 @@ export class AuroraMysqlEngineVersion { public static readonly VER_2_09_2 = AuroraMysqlEngineVersion.builtIn_5_7('2.09.2'); /** Version "5.7.mysql_aurora.2.10.0". */ public static readonly VER_2_10_0 = AuroraMysqlEngineVersion.builtIn_5_7('2.10.0'); + /** Version "5.7.mysql_aurora.2.10.1". */ + public static readonly VER_2_10_1 = AuroraMysqlEngineVersion.builtIn_5_7('2.10.1'); /** * Create a new AuroraMysqlEngineVersion with an arbitrary version. diff --git a/packages/@aws-cdk/aws-rds/lib/instance-engine.ts b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts index 05d045e56c8f3..81deab4cabbf4 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance-engine.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts @@ -100,6 +100,13 @@ export interface IInstanceEngine extends IEngine { /** The application used by this engine to perform rotation for a multi-user scenario. */ readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; + /** + * Whether this engine supports automatic backups of a read replica instance. + * + * @default false + */ + readonly supportsReadReplicaBackups?: boolean; + /** * Method called when the engine is used to create a new instance. */ @@ -123,6 +130,7 @@ abstract class InstanceEngineBase implements IInstanceEngine { public readonly singleUserRotationApplication: secretsmanager.SecretRotationApplication; public readonly multiUserRotationApplication: secretsmanager.SecretRotationApplication; public readonly engineFamily?: string; + public readonly supportsReadReplicaBackups?: boolean; private readonly features?: InstanceEngineFeatures; @@ -320,6 +328,8 @@ export interface MariaDbInstanceEngineProps { } class MariaDbInstanceEngine extends InstanceEngineBase { + public readonly supportsReadReplicaBackups = true; + constructor(version?: MariaDbEngineVersion) { super({ engineType: 'mariadb', @@ -533,6 +543,8 @@ export interface MySqlInstanceEngineProps { } class MySqlInstanceEngine extends InstanceEngineBase { + public readonly supportsReadReplicaBackups = true; + constructor(version?: MysqlEngineVersion) { super({ engineType: 'mysql', diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 3349d15fb5c97..d893720fed46e 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -5,7 +5,7 @@ import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { ArnComponents, Duration, FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Token, Tokenization } from '@aws-cdk/core'; +import { ArnComponents, ArnFormat, Duration, FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Token, Tokenization } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import { DatabaseSecret } from './database-secret'; @@ -190,7 +190,7 @@ export abstract class DatabaseInstanceBase extends Resource implements IDatabase const commonAnComponents: ArnComponents = { service: 'rds', resource: 'db', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }; const localArn = Stack.of(this).formatArn({ ...commonAnComponents, @@ -380,7 +380,7 @@ export interface DatabaseInstanceNewProps { * When creating a read replica, you must enable automatic backups on the source * database instance by setting the backup retention to a value other than zero. * - * @default Duration.days(1) + * @default - Duration.days(1) for source instances, disabled for read replicas */ readonly backupRetention?: Duration; @@ -1143,6 +1143,12 @@ export class DatabaseInstanceReadReplica extends DatabaseInstanceNew implements constructor(scope: Construct, id: string, props: DatabaseInstanceReadReplicaProps) { super(scope, id, props); + if (props.sourceDatabaseInstance.engine + && !props.sourceDatabaseInstance.engine.supportsReadReplicaBackups + && props.backupRetention) { + throw new Error(`Cannot set 'backupRetention', as engine '${engineDescription(props.sourceDatabaseInstance.engine)}' does not support automatic backups for read replicas`); + } + const instance = new CfnDBInstance(this, 'Resource', { ...this.newCfnProps, // this must be ARN, not ID, because of https://github.com/terraform-providers/terraform-provider-aws/issues/528#issuecomment-391169012 diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index 50577cf4dde29..75f8402b16d68 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -158,7 +158,6 @@ export interface CredentialsBaseOptions { /** * Options for creating Credentials from a username. - * @deprecated supporting API `fromUsername()` has been deprecated. See deprecation notice of the API. */ export interface CredentialsFromUsernameOptions extends CredentialsBaseOptions { /** @@ -202,10 +201,6 @@ export abstract class Credentials { /** * Creates Credentials for the given username, and optional password and key. * If no password is provided, one will be generated and stored in Secrets Manager. - * - * @deprecated use `fromGeneratedSecret()` or `fromPassword()` for new Clusters and Instances. - * Note that switching from `fromUsername()` to `fromGeneratedSecret()` or `fromPassword()` for already deployed - * Clusters or Instances will result in their replacement! */ public static fromUsername(username: string, options: CredentialsFromUsernameOptions = {}): Credentials { return { diff --git a/packages/@aws-cdk/aws-rds/lib/proxy.ts b/packages/@aws-cdk/aws-rds/lib/proxy.ts index fa50b891fca80..1fbb225b47086 100644 --- a/packages/@aws-cdk/aws-rds/lib/proxy.ts +++ b/packages/@aws-cdk/aws-rds/lib/proxy.ts @@ -346,12 +346,12 @@ abstract class DatabaseProxyBase extends cdk.Resource implements IDatabaseProxy throw new Error('For imported Database Proxies, the dbUser is required in grantConnect()'); } const scopeStack = cdk.Stack.of(this); - const proxyGeneratedId = scopeStack.parseArn(this.dbProxyArn, ':').resourceName; + const proxyGeneratedId = scopeStack.splitArn(this.dbProxyArn, cdk.ArnFormat.COLON_RESOURCE_NAME).resourceName; const userArn = scopeStack.formatArn({ service: 'rds-db', resource: 'dbuser', resourceName: `${proxyGeneratedId}/${dbUser}`, - sep: ':', + arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME, }); return iam.Grant.addToPrincipal({ grantee, diff --git a/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts b/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts index 8375948441f4c..0bebd4d2505c9 100644 --- a/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts @@ -2,7 +2,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { Resource, Duration, Token, Annotations, RemovalPolicy, IResource, Stack, Lazy, FeatureFlags } from '@aws-cdk/core'; +import { Resource, Duration, Token, Annotations, RemovalPolicy, IResource, Stack, Lazy, FeatureFlags, ArnFormat } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import { IClusterEngine } from './cluster-engine'; @@ -314,7 +314,7 @@ abstract class ServerlessClusterBase extends Resource implements IServerlessClus return Stack.of(this).formatArn({ service: 'rds', resource: 'cluster', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, resourceName: this.clusterIdentifier, }); } @@ -447,7 +447,7 @@ export class ServerlessCluster extends ServerlessClusterBase { engine: props.engine.engineType, engineVersion: props.engine.engineVersion?.fullVersion, engineMode: 'serverless', - enableHttpEndpoint: Lazy.anyValue({ produce: () => this.enableDataApi }), + enableHttpEndpoint: Lazy.any({ produce: () => this.enableDataApi }), kmsKeyId: props.storageEncryptionKey?.keyArn, masterUsername: credentials.username, masterUserPassword: credentials.password?.toString(), diff --git a/packages/@aws-cdk/aws-rds/package.json b/packages/@aws-cdk/aws-rds/package.json index b1bf50b500a40..a9c66bfb3f081 100644 --- a/packages/@aws-cdk/aws-rds/package.json +++ b/packages/@aws-cdk/aws-rds/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-rds/test/cluster.test.ts b/packages/@aws-cdk/aws-rds/test/cluster.test.ts index 35fe439c57857..b3dbdfc81d007 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-rds/test/cluster.test.ts @@ -5,9 +5,9 @@ import { ManagedPolicy, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; +import { testFutureBehavior } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import { AuroraEngineVersion, AuroraMysqlEngineVersion, AuroraPostgresEngineVersion, CfnDBCluster, Credentials, DatabaseCluster, DatabaseClusterEngine, DatabaseClusterFromSnapshot, ParameterGroup, PerformanceInsightRetention, SubnetGroup, DatabaseSecret, diff --git a/packages/@aws-cdk/aws-rds/test/database-secretmanager.test.ts b/packages/@aws-cdk/aws-rds/test/database-secretmanager.test.ts index cb8057cc464f5..135704a893f75 100644 --- a/packages/@aws-cdk/aws-rds/test/database-secretmanager.test.ts +++ b/packages/@aws-cdk/aws-rds/test/database-secretmanager.test.ts @@ -10,7 +10,7 @@ describe('database secret manager', () => { // GIVEN const stack = testStack(); const vpc = new ec2.Vpc(stack, 'VPC'); - const existingSecret = secretsmanager.Secret.fromSecretName(stack, 'DBSecret', 'myDBLoginInfo'); + const existingSecret = secretsmanager.Secret.fromSecretNameV2(stack, 'DBSecret', 'myDBLoginInfo'); // WHEN new ServerlessCluster(stack, 'ServerlessDatabase', { @@ -29,8 +29,30 @@ describe('database secret manager', () => { Ref: 'ServerlessDatabaseSubnets5643CD76', }, EngineMode: 'serverless', - MasterUsername: '{{resolve:secretsmanager:myDBLoginInfo:SecretString:username::}}', - MasterUserPassword: '{{resolve:secretsmanager:myDBLoginInfo:SecretString:password::}}', + MasterUsername: { + 'Fn::Join': [ + '', + [ + '{{resolve:secretsmanager:arn:', + { + Ref: 'AWS::Partition', + }, + ':secretsmanager:us-test-1:12345:secret:myDBLoginInfo:SecretString:username::}}', + ], + ], + }, + MasterUserPassword: { + 'Fn::Join': [ + '', + [ + '{{resolve:secretsmanager:arn:', + { + Ref: 'AWS::Partition', + }, + ':secretsmanager:us-test-1:12345:secret:myDBLoginInfo:SecretString:password::}}', + ], + ], + }, StorageEncrypted: true, VpcSecurityGroupIds: [ { diff --git a/packages/@aws-cdk/aws-rds/test/instance.test.ts b/packages/@aws-cdk/aws-rds/test/instance.test.ts index 9f94607eda8ad..e159a8971b204 100644 --- a/packages/@aws-cdk/aws-rds/test/instance.test.ts +++ b/packages/@aws-cdk/aws-rds/test/instance.test.ts @@ -7,9 +7,9 @@ import * as kms from '@aws-cdk/aws-kms'; import * as lambda from '@aws-cdk/aws-lambda'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; +import { testFutureBehavior } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import * as rds from '../lib'; let stack: cdk.Stack; @@ -258,8 +258,8 @@ describe('instance', () => { }), credentials: rds.Credentials.fromUsername('syscdk'), vpc, - vpcPlacement: { - subnetType: ec2.SubnetType.PRIVATE, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, }); @@ -305,7 +305,7 @@ describe('instance', () => { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_19 }), vpc, - credentials: rds.SnapshotCredentials.fromGeneratedPassword('admin', { + credentials: rds.SnapshotCredentials.fromGeneratedSecret('admin', { excludeCharacters: '"@/\\', }), }); @@ -313,7 +313,11 @@ describe('instance', () => { expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { MasterUsername: ABSENT, MasterUserPassword: { - 'Fn::Join': ['', ['{{resolve:secretsmanager:', { Ref: 'InstanceSecret478E0A47' }, ':SecretString:password::}}']], + 'Fn::Join': ['', [ + '{{resolve:secretsmanager:', + { Ref: 'InstanceSecretB6DFA6BE8ee0a797cad8a68dbeb85f8698cdb5bb' }, + ':SecretString:password::}}', + ]], }, }); expect(stack).toHaveResource('AWS::SecretsManager::Secret', { @@ -1579,6 +1583,26 @@ describe('instance', () => { }); }); + test('throws with backupRetention on a read replica if engine does not support it', () => { + // GIVEN + const instanceType = ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL); + const backupRetention = cdk.Duration.days(5); + const source = new rds.DatabaseInstance(stack, 'Source', { + engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_13 }), + backupRetention, + instanceType, + vpc, + }); + + expect(() => { + new rds.DatabaseInstanceReadReplica(stack, 'Replica', { + sourceDatabaseInstance: source, + backupRetention, + instanceType, + vpc, + }); + }).toThrow(/Cannot set 'backupRetention', as engine 'postgres-13' does not support automatic backups for read replicas/); + }); }); test.each([ diff --git a/packages/@aws-cdk/aws-rds/test/integ.read-replica.expected.json b/packages/@aws-cdk/aws-rds/test/integ.read-replica.expected.json new file mode 100644 index 0000000000000..d27f653c3e6ac --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/integ.read-replica.expected.json @@ -0,0 +1,502 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "cdk-rds-read-replica/Vpc" + } + ] + } + }, + "VpcisolatedSubnet1SubnetE62B1B9B": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/17", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "isolated" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Isolated" + }, + { + "Key": "Name", + "Value": "cdk-rds-read-replica/Vpc/isolatedSubnet1" + } + ] + } + }, + "VpcisolatedSubnet1RouteTableE442650B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "cdk-rds-read-replica/Vpc/isolatedSubnet1" + } + ] + } + }, + "VpcisolatedSubnet1RouteTableAssociationD259E31A": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcisolatedSubnet1RouteTableE442650B" + }, + "SubnetId": { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + } + } + }, + "VpcisolatedSubnet2Subnet39217055": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/17", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "isolated" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Isolated" + }, + { + "Key": "Name", + "Value": "cdk-rds-read-replica/Vpc/isolatedSubnet2" + } + ] + } + }, + "VpcisolatedSubnet2RouteTable334F9764": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "cdk-rds-read-replica/Vpc/isolatedSubnet2" + } + ] + } + }, + "VpcisolatedSubnet2RouteTableAssociation25A4716F": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcisolatedSubnet2RouteTable334F9764" + }, + "SubnetId": { + "Ref": "VpcisolatedSubnet2Subnet39217055" + } + } + }, + "PostgresSourceSubnetGroupBEEB1740": { + "Type": "AWS::RDS::DBSubnetGroup", + "Properties": { + "DBSubnetGroupDescription": "Subnet group for PostgresSource database", + "SubnetIds": [ + { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + }, + { + "Ref": "VpcisolatedSubnet2Subnet39217055" + } + ] + } + }, + "PostgresSourceSecurityGroup69289E68": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Security group for PostgresSource database", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "PostgresSourceSecret0A09A7AD": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "Description": { + "Fn::Join": [ + "", + [ + "Generated by the CDK for stack: ", + { + "Ref": "AWS::StackName" + } + ] + ] + }, + "GenerateSecretString": { + "ExcludeCharacters": " %+~`#$&*()|[]{}:;<>?!'/@\"\\", + "GenerateStringKey": "password", + "PasswordLength": 30, + "SecretStringTemplate": "{\"username\":\"postgres\"}" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "PostgresSourceSecretAttachmentE3C3B705": { + "Type": "AWS::SecretsManager::SecretTargetAttachment", + "Properties": { + "SecretId": { + "Ref": "PostgresSourceSecret0A09A7AD" + }, + "TargetId": { + "Ref": "PostgresSourceEB66BFC9" + }, + "TargetType": "AWS::RDS::DBInstance" + } + }, + "PostgresSourceEB66BFC9": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "DBInstanceClass": "db.t3.small", + "AllocatedStorage": "100", + "BackupRetentionPeriod": 5, + "CopyTagsToSnapshot": true, + "DBSubnetGroupName": { + "Ref": "PostgresSourceSubnetGroupBEEB1740" + }, + "Engine": "postgres", + "EngineVersion": "13", + "MasterUsername": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "PostgresSourceSecret0A09A7AD" + }, + ":SecretString:username::}}" + ] + ] + }, + "MasterUserPassword": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "PostgresSourceSecret0A09A7AD" + }, + ":SecretString:password::}}" + ] + ] + }, + "PubliclyAccessible": false, + "StorageType": "gp2", + "VPCSecurityGroups": [ + { + "Fn::GetAtt": [ + "PostgresSourceSecurityGroup69289E68", + "GroupId" + ] + } + ] + }, + "UpdateReplacePolicy": "Snapshot", + "DeletionPolicy": "Snapshot" + }, + "PostgresReplicaSubnetGroup301B59DA": { + "Type": "AWS::RDS::DBSubnetGroup", + "Properties": { + "DBSubnetGroupDescription": "Subnet group for PostgresReplica database", + "SubnetIds": [ + { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + }, + { + "Ref": "VpcisolatedSubnet2Subnet39217055" + } + ] + } + }, + "PostgresReplicaSecurityGroup5385C4C2": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Security group for PostgresReplica database", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "PostgresReplica23A3C738": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "DBInstanceClass": "db.t3.small", + "CopyTagsToSnapshot": true, + "DBSubnetGroupName": { + "Ref": "PostgresReplicaSubnetGroup301B59DA" + }, + "PubliclyAccessible": false, + "SourceDBInstanceIdentifier": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":rds:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":db:", + { + "Ref": "PostgresSourceEB66BFC9" + } + ] + ] + }, + "StorageType": "gp2", + "VPCSecurityGroups": [ + { + "Fn::GetAtt": [ + "PostgresReplicaSecurityGroup5385C4C2", + "GroupId" + ] + } + ] + }, + "UpdateReplacePolicy": "Snapshot", + "DeletionPolicy": "Snapshot" + }, + "MysqlSourceSubnetGroup213E979B": { + "Type": "AWS::RDS::DBSubnetGroup", + "Properties": { + "DBSubnetGroupDescription": "Subnet group for MysqlSource database", + "SubnetIds": [ + { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + }, + { + "Ref": "VpcisolatedSubnet2Subnet39217055" + } + ] + } + }, + "MysqlSourceSecurityGroupC691E169": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Security group for MysqlSource database", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "MysqlSourceSecretB727C3F2": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "Description": { + "Fn::Join": [ + "", + [ + "Generated by the CDK for stack: ", + { + "Ref": "AWS::StackName" + } + ] + ] + }, + "GenerateSecretString": { + "ExcludeCharacters": " %+~`#$&*()|[]{}:;<>?!'/@\"\\", + "GenerateStringKey": "password", + "PasswordLength": 30, + "SecretStringTemplate": "{\"username\":\"admin\"}" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "MysqlSourceSecretAttachment5E4EDF73": { + "Type": "AWS::SecretsManager::SecretTargetAttachment", + "Properties": { + "SecretId": { + "Ref": "MysqlSourceSecretB727C3F2" + }, + "TargetId": { + "Ref": "MysqlSource9A10350C" + }, + "TargetType": "AWS::RDS::DBInstance" + } + }, + "MysqlSource9A10350C": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "DBInstanceClass": "db.t3.small", + "AllocatedStorage": "100", + "BackupRetentionPeriod": 5, + "CopyTagsToSnapshot": true, + "DBSubnetGroupName": { + "Ref": "MysqlSourceSubnetGroup213E979B" + }, + "Engine": "mysql", + "EngineVersion": "8.0", + "MasterUsername": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "MysqlSourceSecretB727C3F2" + }, + ":SecretString:username::}}" + ] + ] + }, + "MasterUserPassword": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "MysqlSourceSecretB727C3F2" + }, + ":SecretString:password::}}" + ] + ] + }, + "PubliclyAccessible": false, + "StorageType": "gp2", + "VPCSecurityGroups": [ + { + "Fn::GetAtt": [ + "MysqlSourceSecurityGroupC691E169", + "GroupId" + ] + } + ] + }, + "UpdateReplacePolicy": "Snapshot", + "DeletionPolicy": "Snapshot" + }, + "MysqlReplicaSubnetGroup79E1F72A": { + "Type": "AWS::RDS::DBSubnetGroup", + "Properties": { + "DBSubnetGroupDescription": "Subnet group for MysqlReplica database", + "SubnetIds": [ + { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + }, + { + "Ref": "VpcisolatedSubnet2Subnet39217055" + } + ] + } + }, + "MysqlReplicaSecurityGroup169FAFAA": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Security group for MysqlReplica database", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "MysqlReplica87D29F78": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "DBInstanceClass": "db.t3.small", + "BackupRetentionPeriod": 3, + "CopyTagsToSnapshot": true, + "DBSubnetGroupName": { + "Ref": "MysqlReplicaSubnetGroup79E1F72A" + }, + "PubliclyAccessible": false, + "SourceDBInstanceIdentifier": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":rds:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":db:", + { + "Ref": "MysqlSource9A10350C" + } + ] + ] + }, + "StorageType": "gp2", + "VPCSecurityGroups": [ + { + "Fn::GetAtt": [ + "MysqlReplicaSecurityGroup169FAFAA", + "GroupId" + ] + } + ] + }, + "UpdateReplacePolicy": "Snapshot", + "DeletionPolicy": "Snapshot" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/integ.read-replica.ts b/packages/@aws-cdk/aws-rds/test/integ.read-replica.ts new file mode 100644 index 0000000000000..7ebbd176049b4 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/integ.read-replica.ts @@ -0,0 +1,59 @@ +import { InstanceClass, InstanceSize, InstanceType, SubnetSelection, SubnetType, Vpc } from '@aws-cdk/aws-ec2'; +import { App, Duration, Stack, StackProps } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as rds from '../lib'; + +class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const vpc = new Vpc(this, 'Vpc', { + maxAzs: 2, + subnetConfiguration: [ + { + name: 'isolated', + subnetType: SubnetType.PRIVATE_ISOLATED, + }, + ], + }); + + const instanceType = InstanceType.of(InstanceClass.T3, InstanceSize.SMALL); + + const vpcSubnets: SubnetSelection = { subnetType: SubnetType.PRIVATE_ISOLATED }; + + const postgresSource = new rds.DatabaseInstance(this, 'PostgresSource', { + engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_13 }), + backupRetention: Duration.days(5), + instanceType, + vpc, + vpcSubnets, + }); + + new rds.DatabaseInstanceReadReplica(this, 'PostgresReplica', { + sourceDatabaseInstance: postgresSource, + instanceType, + vpc, + vpcSubnets, + }); + + const mysqlSource = new rds.DatabaseInstance(this, 'MysqlSource', { + engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0 }), + backupRetention: Duration.days(5), + instanceType, + vpc, + vpcSubnets, + }); + + new rds.DatabaseInstanceReadReplica(this, 'MysqlReplica', { + sourceDatabaseInstance: mysqlSource, + backupRetention: Duration.days(3), + instanceType, + vpc, + vpcSubnets, + }); + } +} + +const app = new App(); +new TestStack(app, 'cdk-rds-read-replica'); +app.synth(); diff --git a/packages/@aws-cdk/aws-rds/test/option-group.test.ts b/packages/@aws-cdk/aws-rds/test/option-group.test.ts index 77a394a1fa6a7..a0ab91ac0dfdf 100644 --- a/packages/@aws-cdk/aws-rds/test/option-group.test.ts +++ b/packages/@aws-cdk/aws-rds/test/option-group.test.ts @@ -1,7 +1,7 @@ import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; -import { DatabaseInstanceEngine, OptionGroup, OracleEngineVersion, OracleLegacyEngineVersion } from '../lib'; +import { DatabaseInstanceEngine, OptionGroup, OracleEngineVersion } from '../lib'; describe('option group', () => { test('create an option group', () => { @@ -10,8 +10,8 @@ describe('option group', () => { // WHEN new OptionGroup(stack, 'Options', { - engine: DatabaseInstanceEngine.oracleSe1({ - version: OracleLegacyEngineVersion.VER_11_2, + engine: DatabaseInstanceEngine.oracleSe2({ + version: OracleEngineVersion.VER_12_1, }), configurations: [ { @@ -22,9 +22,8 @@ describe('option group', () => { // THEN expect(stack).toHaveResource('AWS::RDS::OptionGroup', { - EngineName: 'oracle-se1', - MajorEngineVersion: '11.2', - OptionGroupDescription: 'Option group for oracle-se1 11.2', + EngineName: 'oracle-se2', + MajorEngineVersion: '12.1', OptionConfigurations: [ { OptionName: 'XMLDB', @@ -42,8 +41,8 @@ describe('option group', () => { // WHEN const optionGroup = new OptionGroup(stack, 'Options', { - engine: DatabaseInstanceEngine.oracleSe({ - version: OracleLegacyEngineVersion.VER_11_2, + engine: DatabaseInstanceEngine.oracleSe2({ + version: OracleEngineVersion.VER_12_1, }), configurations: [ { @@ -57,9 +56,6 @@ describe('option group', () => { // THEN expect(stack).toHaveResource('AWS::RDS::OptionGroup', { - EngineName: 'oracle-se', - MajorEngineVersion: '11.2', - OptionGroupDescription: 'Option group for oracle-se 11.2', OptionConfigurations: [ { OptionName: 'OEM', @@ -103,8 +99,8 @@ describe('option group', () => { // WHEN const securityGroup = new ec2.SecurityGroup(stack, 'CustomSecurityGroup', { vpc }); new OptionGroup(stack, 'Options', { - engine: DatabaseInstanceEngine.oracleSe({ - version: OracleLegacyEngineVersion.VER_11_2, + engine: DatabaseInstanceEngine.oracleSe2({ + version: OracleEngineVersion.VER_12_1, }), configurations: [ { @@ -118,9 +114,6 @@ describe('option group', () => { // THEN expect(stack).toHaveResource('AWS::RDS::OptionGroup', { - EngineName: 'oracle-se', - MajorEngineVersion: '11.2', - OptionGroupDescription: 'Option group for oracle-se 11.2', OptionConfigurations: [ { OptionName: 'OEM', diff --git a/packages/@aws-cdk/aws-rds/test/proxy.test.ts b/packages/@aws-cdk/aws-rds/test/proxy.test.ts index 5e2a65f1f0352..9cd48e7686dd9 100644 --- a/packages/@aws-cdk/aws-rds/test/proxy.test.ts +++ b/packages/@aws-cdk/aws-rds/test/proxy.test.ts @@ -22,7 +22,9 @@ describe('proxy', () => { test('create a DB proxy from an instance', () => { // GIVEN const instance = new rds.DatabaseInstance(stack, 'Instance', { - engine: rds.DatabaseInstanceEngine.MYSQL, + engine: rds.DatabaseInstanceEngine.mysql({ + version: rds.MysqlEngineVersion.VER_5_7, + }), vpc, }); diff --git a/packages/@aws-cdk/aws-redshift/README.md b/packages/@aws-cdk/aws-redshift/README.md index 8ff734a6be255..7fd769670d808 100644 --- a/packages/@aws-cdk/aws-redshift/README.md +++ b/packages/@aws-cdk/aws-redshift/README.md @@ -167,6 +167,34 @@ new Table(this, 'Table', { }); ``` +The table can be configured to have distStyle attribute and a distKey column: + +```ts fixture=cluster +new Table(this, 'Table', { + tableColumns: [ + { name: 'col1', dataType: 'varchar(4)', distKey: true }, + { name: 'col2', dataType: 'float' }, + ], + cluster: cluster, + databaseName: 'databaseName', + distStyle: TableDistStyle.KEY, +}); +``` + +The table can also be configured to have sortStyle attribute and sortKey columns: + +```ts fixture=cluster +new Table(this, 'Table', { + tableColumns: [ + { name: 'col1', dataType: 'varchar(4)', sortKey: true }, + { name: 'col2', dataType: 'float', sortKey: true }, + ], + cluster: cluster, + databaseName: 'databaseName', + sortStyle: TableSortStyle.COMPOUND, +}); +``` + ### Granting Privileges You can give a user privileges to perform certain actions on a table by using the diff --git a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/privileges.ts b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/privileges.ts index 9f2064d0e5e5a..d95a9f9d97395 100644 --- a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/privileges.ts +++ b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/privileges.ts @@ -1,7 +1,9 @@ /* eslint-disable-next-line import/no-unresolved */ import * as AWSLambda from 'aws-lambda'; import { TablePrivilege, UserTablePrivilegesHandlerProps } from '../handler-props'; -import { ClusterProps, executeStatement, makePhysicalId } from './util'; +import { executeStatement } from './redshift-data'; +import { ClusterProps } from './types'; +import { makePhysicalId } from './util'; export async function handler(props: UserTablePrivilegesHandlerProps & ClusterProps, event: AWSLambda.CloudFormationCustomResourceEvent) { const username = props.username; diff --git a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/redshift-data.ts b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/redshift-data.ts new file mode 100644 index 0000000000000..45bf6d9810b98 --- /dev/null +++ b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/redshift-data.ts @@ -0,0 +1,34 @@ +/* eslint-disable-next-line import/no-extraneous-dependencies */ +import * as RedshiftData from 'aws-sdk/clients/redshiftdata'; +import { ClusterProps } from './types'; + +const redshiftData = new RedshiftData(); + +export async function executeStatement(statement: string, clusterProps: ClusterProps): Promise { + const executeStatementProps = { + ClusterIdentifier: clusterProps.clusterName, + Database: clusterProps.databaseName, + SecretArn: clusterProps.adminUserArn, + Sql: statement, + }; + const executedStatement = await redshiftData.executeStatement(executeStatementProps).promise(); + if (!executedStatement.Id) { + throw new Error('Service error: Statement execution did not return a statement ID'); + } + await waitForStatementComplete(executedStatement.Id); +} + +const waitTimeout = 100; +async function waitForStatementComplete(statementId: string): Promise { + await new Promise((resolve: (value: void) => void) => { + setTimeout(() => resolve(), waitTimeout); + }); + const statement = await redshiftData.describeStatement({ Id: statementId }).promise(); + if (statement.Status !== 'FINISHED' && statement.Status !== 'FAILED' && statement.Status !== 'ABORTED') { + return waitForStatementComplete(statementId); + } else if (statement.Status === 'FINISHED') { + return; + } else { + throw new Error(`Statement status was ${statement.Status}: ${statement.Error}`); + } +} diff --git a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/table.ts b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/table.ts index a2e2a4dc4bee9..0716477eb54fe 100644 --- a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/table.ts +++ b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/table.ts @@ -1,20 +1,21 @@ /* eslint-disable-next-line import/no-unresolved */ import * as AWSLambda from 'aws-lambda'; import { Column } from '../../table'; -import { TableHandlerProps } from '../handler-props'; -import { ClusterProps, executeStatement } from './util'; +import { executeStatement } from './redshift-data'; +import { ClusterProps, TableAndClusterProps, TableSortStyle } from './types'; +import { areColumnsEqual, getDistKeyColumn, getSortKeyColumns } from './util'; -export async function handler(props: TableHandlerProps & ClusterProps, event: AWSLambda.CloudFormationCustomResourceEvent) { +export async function handler(props: TableAndClusterProps, event: AWSLambda.CloudFormationCustomResourceEvent) { const tableNamePrefix = props.tableName.prefix; - const tableNameSuffix = props.tableName.generateSuffix ? `${event.RequestId.substring(0, 8)}` : ''; + const tableNameSuffix = props.tableName.generateSuffix === 'true' ? `${event.RequestId.substring(0, 8)}` : ''; const tableColumns = props.tableColumns; - const clusterProps = props; + const tableAndClusterProps = props; if (event.RequestType === 'Create') { - const tableName = await createTable(tableNamePrefix, tableNameSuffix, tableColumns, clusterProps); + const tableName = await createTable(tableNamePrefix, tableNameSuffix, tableColumns, tableAndClusterProps); return { PhysicalResourceId: tableName }; } else if (event.RequestType === 'Delete') { - await dropTable(event.PhysicalResourceId, clusterProps); + await dropTable(event.PhysicalResourceId, tableAndClusterProps); return; } else if (event.RequestType === 'Update') { const tableName = await updateTable( @@ -22,8 +23,8 @@ export async function handler(props: TableHandlerProps & ClusterProps, event: AW tableNamePrefix, tableNameSuffix, tableColumns, - clusterProps, - event.OldResourceProperties as TableHandlerProps & ClusterProps, + tableAndClusterProps, + event.OldResourceProperties as TableAndClusterProps, ); return { PhysicalResourceId: tableName }; } else { @@ -32,10 +33,33 @@ export async function handler(props: TableHandlerProps & ClusterProps, event: AW } } -async function createTable(tableNamePrefix: string, tableNameSuffix: string, tableColumns: Column[], clusterProps: ClusterProps): Promise { +async function createTable( + tableNamePrefix: string, + tableNameSuffix: string, + tableColumns: Column[], + tableAndClusterProps: TableAndClusterProps, +): Promise { const tableName = tableNamePrefix + tableNameSuffix; const tableColumnsString = tableColumns.map(column => `${column.name} ${column.dataType}`).join(); - await executeStatement(`CREATE TABLE ${tableName} (${tableColumnsString})`, clusterProps); + + let statement = `CREATE TABLE ${tableName} (${tableColumnsString})`; + + if (tableAndClusterProps.distStyle) { + statement += ` DISTSTYLE ${tableAndClusterProps.distStyle}`; + } + + const distKeyColumn = getDistKeyColumn(tableColumns); + if (distKeyColumn) { + statement += ` DISTKEY(${distKeyColumn.name})`; + } + + const sortKeyColumns = getSortKeyColumns(tableColumns); + if (sortKeyColumns.length > 0) { + const sortKeyColumnsString = getSortKeyColumnsString(sortKeyColumns); + statement += ` ${tableAndClusterProps.sortStyle} SORTKEY(${sortKeyColumnsString})`; + } + + await executeStatement(statement, tableAndClusterProps); return tableName; } @@ -48,28 +72,79 @@ async function updateTable( tableNamePrefix: string, tableNameSuffix: string, tableColumns: Column[], - clusterProps: ClusterProps, - oldResourceProperties: TableHandlerProps & ClusterProps, + tableAndClusterProps: TableAndClusterProps, + oldResourceProperties: TableAndClusterProps, ): Promise { + const alterationStatements: string[] = []; + const oldClusterProps = oldResourceProperties; - if (clusterProps.clusterName !== oldClusterProps.clusterName || clusterProps.databaseName !== oldClusterProps.databaseName) { - return createTable(tableNamePrefix, tableNameSuffix, tableColumns, clusterProps); + if (tableAndClusterProps.clusterName !== oldClusterProps.clusterName || tableAndClusterProps.databaseName !== oldClusterProps.databaseName) { + return createTable(tableNamePrefix, tableNameSuffix, tableColumns, tableAndClusterProps); } const oldTableNamePrefix = oldResourceProperties.tableName.prefix; if (tableNamePrefix !== oldTableNamePrefix) { - return createTable(tableNamePrefix, tableNameSuffix, tableColumns, clusterProps); + return createTable(tableNamePrefix, tableNameSuffix, tableColumns, tableAndClusterProps); } const oldTableColumns = oldResourceProperties.tableColumns; if (!oldTableColumns.every(oldColumn => tableColumns.some(column => column.name === oldColumn.name && column.dataType === oldColumn.dataType))) { - return createTable(tableNamePrefix, tableNameSuffix, tableColumns, clusterProps); + return createTable(tableNamePrefix, tableNameSuffix, tableColumns, tableAndClusterProps); } - const additions = tableColumns.filter(column => { + const columnAdditions = tableColumns.filter(column => { return !oldTableColumns.some(oldColumn => column.name === oldColumn.name && column.dataType === oldColumn.dataType); }).map(column => `ADD ${column.name} ${column.dataType}`); - await Promise.all(additions.map(addition => executeStatement(`ALTER TABLE ${tableName} ${addition}`, clusterProps))); + if (columnAdditions.length > 0) { + alterationStatements.push(...columnAdditions.map(addition => `ALTER TABLE ${tableName} ${addition}`)); + } + + const oldDistStyle = oldResourceProperties.distStyle; + if ((!oldDistStyle && tableAndClusterProps.distStyle) || + (oldDistStyle && !tableAndClusterProps.distStyle)) { + return createTable(tableNamePrefix, tableNameSuffix, tableColumns, tableAndClusterProps); + } else if (oldDistStyle !== tableAndClusterProps.distStyle) { + alterationStatements.push(`ALTER TABLE ${tableName} ALTER DISTSTYLE ${tableAndClusterProps.distStyle}`); + } + + const oldDistKey = getDistKeyColumn(oldTableColumns)?.name; + const newDistKey = getDistKeyColumn(tableColumns)?.name; + if ((!oldDistKey && newDistKey ) || (oldDistKey && !newDistKey)) { + return createTable(tableNamePrefix, tableNameSuffix, tableColumns, tableAndClusterProps); + } else if (oldDistKey !== newDistKey) { + alterationStatements.push(`ALTER TABLE ${tableName} ALTER DISTKEY ${newDistKey}`); + } + + const oldSortKeyColumns = getSortKeyColumns(oldTableColumns); + const newSortKeyColumns = getSortKeyColumns(tableColumns); + const oldSortStyle = oldResourceProperties.sortStyle; + const newSortStyle = tableAndClusterProps.sortStyle; + if ((oldSortStyle === newSortStyle && !areColumnsEqual(oldSortKeyColumns, newSortKeyColumns)) + || (oldSortStyle !== newSortStyle)) { + switch (newSortStyle) { + case TableSortStyle.INTERLEAVED: + // INTERLEAVED sort key addition requires replacement. + // https://docs.aws.amazon.com/redshift/latest/dg/r_ALTER_TABLE.html + return createTable(tableNamePrefix, tableNameSuffix, tableColumns, tableAndClusterProps); + + case TableSortStyle.COMPOUND: { + const sortKeyColumnsString = getSortKeyColumnsString(newSortKeyColumns); + alterationStatements.push(`ALTER TABLE ${tableName} ALTER ${newSortStyle} SORTKEY(${sortKeyColumnsString})`); + break; + } + + case TableSortStyle.AUTO: { + alterationStatements.push(`ALTER TABLE ${tableName} ALTER SORTKEY ${newSortStyle}`); + break; + } + } + } + + await Promise.all(alterationStatements.map(statement => executeStatement(statement, tableAndClusterProps))); return tableName; } + +function getSortKeyColumnsString(sortKeyColumns: Column[]) { + return sortKeyColumns.map(column => column.name).join(); +} diff --git a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/types.ts b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/types.ts new file mode 100644 index 0000000000000..6d80398b7f41b --- /dev/null +++ b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/types.ts @@ -0,0 +1,26 @@ +import { DatabaseQueryHandlerProps, TableHandlerProps } from '../handler-props'; + +export type ClusterProps = Omit; +export type TableAndClusterProps = TableHandlerProps & ClusterProps; + +/** + * The sort style of a table. + * This has been duplicated here to exporting private types. + */ +export enum TableSortStyle { + /** + * Amazon Redshift assigns an optimal sort key based on the table data. + */ + AUTO = 'AUTO', + + /** + * Specifies that the data is sorted using a compound key made up of all of the listed columns, + * in the order they are listed. + */ + COMPOUND = 'COMPOUND', + + /** + * Specifies that the data is sorted using an interleaved sort key. + */ + INTERLEAVED = 'INTERLEAVED', +} diff --git a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/user.ts b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/user.ts index 707af78714e43..ae19440230ae9 100644 --- a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/user.ts +++ b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/user.ts @@ -3,7 +3,9 @@ import * as AWSLambda from 'aws-lambda'; /* eslint-disable-next-line import/no-extraneous-dependencies */ import * as SecretsManager from 'aws-sdk/clients/secretsmanager'; import { UserHandlerProps } from '../handler-props'; -import { ClusterProps, executeStatement, makePhysicalId } from './util'; +import { executeStatement } from './redshift-data'; +import { ClusterProps } from './types'; +import { makePhysicalId } from './util'; const secretsManager = new SecretsManager(); diff --git a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/util.ts b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/util.ts index d834cd474f986..c6fe3709bd136 100644 --- a/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/util.ts +++ b/packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/util.ts @@ -1,40 +1,33 @@ -/* eslint-disable-next-line import/no-extraneous-dependencies */ -import * as RedshiftData from 'aws-sdk/clients/redshiftdata'; -import { DatabaseQueryHandlerProps } from '../handler-props'; +import { Column } from '../../table'; +import { ClusterProps } from './types'; -const redshiftData = new RedshiftData(); +export function makePhysicalId(resourceName: string, clusterProps: ClusterProps, requestId: string): string { + return `${clusterProps.clusterName}:${clusterProps.databaseName}:${resourceName}:${requestId}`; +} -export type ClusterProps = Omit; +export function getDistKeyColumn(columns: Column[]): Column | undefined { + // string comparison is required for custom resource since everything is passed as string + const distKeyColumns = columns.filter(column => column.distKey === true || (column.distKey as unknown as string) === 'true'); -export async function executeStatement(statement: string, clusterProps: ClusterProps): Promise { - const executeStatementProps = { - ClusterIdentifier: clusterProps.clusterName, - Database: clusterProps.databaseName, - SecretArn: clusterProps.adminUserArn, - Sql: statement, - }; - const executedStatement = await redshiftData.executeStatement(executeStatementProps).promise(); - if (!executedStatement.Id) { - throw new Error('Service error: Statement execution did not return a statement ID'); + if (distKeyColumns.length === 0) { + return undefined; + } else if (distKeyColumns.length > 1) { + throw new Error('Multiple dist key columns found'); } - await waitForStatementComplete(executedStatement.Id); + + return distKeyColumns[0]; } -const waitTimeout = 100; -async function waitForStatementComplete(statementId: string): Promise { - await new Promise((resolve: (value: void) => void) => { - setTimeout(() => resolve(), waitTimeout); - }); - const statement = await redshiftData.describeStatement({ Id: statementId }).promise(); - if (statement.Status !== 'FINISHED' && statement.Status !== 'FAILED' && statement.Status !== 'ABORTED') { - return waitForStatementComplete(statementId); - } else if (statement.Status === 'FINISHED') { - return; - } else { - throw new Error(`Statement status was ${statement.Status}: ${statement.Error}`); - } +export function getSortKeyColumns(columns: Column[]): Column[] { + // string comparison is required for custom resource since everything is passed as string + return columns.filter(column => column.sortKey === true || (column.sortKey as unknown as string) === 'true'); } -export function makePhysicalId(resourceName: string, clusterProps: ClusterProps, requestId: string): string { - return `${clusterProps.clusterName}:${clusterProps.databaseName}:${resourceName}:${requestId}`; +export function areColumnsEqual(columnsA: Column[], columnsB: Column[]): boolean { + if (columnsA.length !== columnsB.length) { + return false; + } + return columnsA.every(columnA => { + return columnsB.find(column => column.name === columnA.name && column.dataType === columnA.dataType); + }); } diff --git a/packages/@aws-cdk/aws-redshift/lib/private/handler-props.ts b/packages/@aws-cdk/aws-redshift/lib/private/handler-props.ts index b00cc667a2ced..97089078f00a2 100644 --- a/packages/@aws-cdk/aws-redshift/lib/private/handler-props.ts +++ b/packages/@aws-cdk/aws-redshift/lib/private/handler-props.ts @@ -1,4 +1,4 @@ -import { Column } from '../table'; +import { Column, TableDistStyle, TableSortStyle } from '../table'; export interface DatabaseQueryHandlerProps { readonly handler: string; @@ -15,9 +15,11 @@ export interface UserHandlerProps { export interface TableHandlerProps { readonly tableName: { readonly prefix: string; - readonly generateSuffix: boolean; + readonly generateSuffix: string; }; readonly tableColumns: Column[]; + readonly distStyle?: TableDistStyle; + readonly sortStyle: TableSortStyle; } export interface TablePrivilege { diff --git a/packages/@aws-cdk/aws-redshift/lib/table.ts b/packages/@aws-cdk/aws-redshift/lib/table.ts index 337abdedd00a1..c9bf7c4c46ac2 100644 --- a/packages/@aws-cdk/aws-redshift/lib/table.ts +++ b/packages/@aws-cdk/aws-redshift/lib/table.ts @@ -4,6 +4,7 @@ import { ICluster } from './cluster'; import { DatabaseOptions } from './database-options'; import { DatabaseQuery } from './private/database-query'; import { HandlerName } from './private/database-query-provider/handler-name'; +import { getDistKeyColumn, getSortKeyColumns } from './private/database-query-provider/util'; import { TableHandlerProps } from './private/handler-props'; import { IUser } from './user'; @@ -66,6 +67,20 @@ export interface Column { * The data type of the column. */ readonly dataType: string; + + /** + * Boolean value that indicates whether the column is to be configured as DISTKEY. + * + * @default - column is not DISTKEY + */ + readonly distKey?: boolean; + + /** + * Boolean value that indicates whether the column is to be configured as SORTKEY. + * + * @default - column is not a SORTKEY + */ + readonly sortKey?: boolean; } /** @@ -84,6 +99,20 @@ export interface TableProps extends DatabaseOptions { */ readonly tableColumns: Column[]; + /** + * The distribution style of the table. + * + * @default TableDistStyle.AUTO + */ + readonly distStyle?: TableDistStyle; + + /** + * The sort style of the table. + * + * @default TableSortStyle.AUTO if no sort key is specified, TableSortStyle.COMPOUND if a sort key is specified + */ + readonly sortStyle?: TableSortStyle; + /** * The policy to apply when this resource is removed from the application. * @@ -183,6 +212,14 @@ export class Table extends TableBase { constructor(scope: Construct, id: string, props: TableProps) { super(scope, id); + this.validateDistKeyColumns(props.tableColumns); + if (props.distStyle) { + this.validateDistStyle(props.distStyle, props.tableColumns); + } + if (props.sortStyle) { + this.validateSortStyle(props.sortStyle, props.tableColumns); + } + this.tableColumns = props.tableColumns; this.cluster = props.cluster; this.databaseName = props.databaseName; @@ -194,9 +231,11 @@ export class Table extends TableBase { properties: { tableName: { prefix: props.tableName ?? cdk.Names.uniqueId(this), - generateSuffix: !props.tableName, + generateSuffix: !props.tableName ? 'true' : 'false', }, tableColumns: this.tableColumns, + distStyle: props.distStyle, + sortStyle: props.sortStyle ?? this.getDefaultSortStyle(props.tableColumns), }, }); @@ -219,4 +258,83 @@ export class Table extends TableBase { public applyRemovalPolicy(policy: cdk.RemovalPolicy): void { this.resource.applyRemovalPolicy(policy); } + + private validateDistKeyColumns(columns: Column[]): void { + try { + getDistKeyColumn(columns); + } catch (err) { + throw new Error('Only one column can be configured as distKey.'); + } + } + + private validateDistStyle(distStyle: TableDistStyle, columns: Column[]): void { + const distKeyColumn = getDistKeyColumn(columns); + if (distKeyColumn && distStyle !== TableDistStyle.KEY) { + throw new Error(`Only 'TableDistStyle.KEY' can be configured when distKey is also configured. Found ${distStyle}`); + } + if (!distKeyColumn && distStyle === TableDistStyle.KEY) { + throw new Error('distStyle of "TableDistStyle.KEY" can only be configured when distKey is also configured.'); + } + } + + private validateSortStyle(sortStyle: TableSortStyle, columns: Column[]): void { + const sortKeyColumns = getSortKeyColumns(columns); + if (sortKeyColumns.length === 0 && sortStyle !== TableSortStyle.AUTO) { + throw new Error(`sortStyle of '${sortStyle}' can only be configured when sortKey is also configured.`); + } + if (sortKeyColumns.length > 0 && sortStyle === TableSortStyle.AUTO) { + throw new Error(`sortStyle of '${TableSortStyle.AUTO}' cannot be configured when sortKey is also configured.`); + } + } + + private getDefaultSortStyle(columns: Column[]): TableSortStyle { + const sortKeyColumns = getSortKeyColumns(columns); + return (sortKeyColumns.length === 0) ? TableSortStyle.AUTO : TableSortStyle.COMPOUND; + } +} + +/** + * The data distribution style of a table. + */ +export enum TableDistStyle { + /** + * Amazon Redshift assigns an optimal distribution style based on the table data + */ + AUTO = 'AUTO', + + /** + * The data in the table is spread evenly across the nodes in a cluster in a round-robin distribution. + */ + EVEN = 'EVEN', + + /** + * The data is distributed by the values in the DISTKEY column. + */ + KEY = 'KEY', + + /** + * A copy of the entire table is distributed to every node. + */ + ALL = 'ALL', +} + +/** + * The sort style of a table. + */ +export enum TableSortStyle { + /** + * Amazon Redshift assigns an optimal sort key based on the table data. + */ + AUTO = 'AUTO', + + /** + * Specifies that the data is sorted using a compound key made up of all of the listed columns, + * in the order they are listed. + */ + COMPOUND = 'COMPOUND', + + /** + * Specifies that the data is sorted using an interleaved sort key. + */ + INTERLEAVED = 'INTERLEAVED', } diff --git a/packages/@aws-cdk/aws-redshift/rosetta/cluster.ts-fixture b/packages/@aws-cdk/aws-redshift/rosetta/cluster.ts-fixture index 82d98ca3e381e..4c7ab6ccdb771 100644 --- a/packages/@aws-cdk/aws-redshift/rosetta/cluster.ts-fixture +++ b/packages/@aws-cdk/aws-redshift/rosetta/cluster.ts-fixture @@ -1,7 +1,7 @@ // Fixture with cluster already created import { Construct, SecretValue, Stack } from '@aws-cdk/core'; import { Vpc } from '@aws-cdk/aws-ec2'; -import { Cluster, Table, TableAction, User } from '@aws-cdk/aws-redshift'; +import { Cluster, Table, TableAction, TableDistStyle, TableSortStyle, User } from '@aws-cdk/aws-redshift'; class Fixture extends Stack { constructor(scope: Construct, id: string) { diff --git a/packages/@aws-cdk/aws-redshift/test/database-query-provider/table.test.ts b/packages/@aws-cdk/aws-redshift/test/database-query-provider/table.test.ts index 956efca1ab81f..7c5534d59a785 100644 --- a/packages/@aws-cdk/aws-redshift/test/database-query-provider/table.test.ts +++ b/packages/@aws-cdk/aws-redshift/test/database-query-provider/table.test.ts @@ -1,18 +1,30 @@ /* eslint-disable-next-line import/no-unresolved */ import type * as AWSLambda from 'aws-lambda'; +const mockExecuteStatement = jest.fn(() => ({ promise: jest.fn(() => ({ Id: 'statementId' })) })); +jest.mock('aws-sdk/clients/redshiftdata', () => class { + executeStatement = mockExecuteStatement; + describeStatement = () => ({ promise: jest.fn(() => ({ Status: 'FINISHED' })) }); +}); +import { Column, TableDistStyle, TableSortStyle } from '../../lib'; +import { handler as manageTable } from '../../lib/private/database-query-provider/table'; +import { TableAndClusterProps } from '../../lib/private/database-query-provider/types'; + +type ResourcePropertiesType = TableAndClusterProps & { ServiceToken: string }; + const tableNamePrefix = 'tableNamePrefix'; const tableColumns = [{ name: 'col1', dataType: 'varchar(1)' }]; const clusterName = 'clusterName'; const adminUserArn = 'adminUserArn'; const databaseName = 'databaseName'; const physicalResourceId = 'PhysicalResourceId'; -const resourceProperties = { +const resourceProperties: ResourcePropertiesType = { tableName: { prefix: tableNamePrefix, - generateSuffix: true, + generateSuffix: 'true', }, tableColumns, + sortStyle: TableSortStyle.AUTO, clusterName, adminUserArn, databaseName, @@ -30,13 +42,6 @@ const genericEvent: AWSLambda.CloudFormationCustomResourceEventCommon = { ResourceType: '', }; -const mockExecuteStatement = jest.fn(() => ({ promise: jest.fn(() => ({ Id: 'statementId' })) })); -jest.mock('aws-sdk/clients/redshiftdata', () => class { - executeStatement = mockExecuteStatement; - describeStatement = () => ({ promise: jest.fn(() => ({ Status: 'FINISHED' })) }); -}); -import { handler as manageTable } from '../../lib/private/database-query-provider/table'; - beforeEach(() => { jest.clearAllMocks(); }); @@ -64,7 +69,7 @@ describe('create', () => { ...resourceProperties, tableName: { ...resourceProperties.tableName, - generateSuffix: false, + generateSuffix: 'false', }, }; @@ -75,6 +80,60 @@ describe('create', () => { Sql: `CREATE TABLE ${tableNamePrefix} (col1 varchar(1))`, })); }); + + test('serializes distKey and distStyle in statement', async () => { + const event = baseEvent; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: [{ name: 'col1', dataType: 'varchar(1)', distKey: true }], + distStyle: TableDistStyle.KEY, + }; + + await manageTable(newResourceProperties, event); + + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1)) DISTSTYLE KEY DISTKEY(col1)`, + })); + }); + + test('serializes sortKeys and sortStyle in statement', async () => { + const event = baseEvent; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: [ + { name: 'col1', dataType: 'varchar(1)', sortKey: true }, + { name: 'col2', dataType: 'varchar(1)' }, + { name: 'col3', dataType: 'varchar(1)', sortKey: true }, + ], + sortStyle: TableSortStyle.COMPOUND, + }; + + await manageTable(newResourceProperties, event); + + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1),col2 varchar(1),col3 varchar(1)) COMPOUND SORTKEY(col1,col3)`, + })); + }); + + test('serializes distKey and sortKeys as string booleans', async () => { + const event = baseEvent; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: [ + { name: 'col1', dataType: 'varchar(4)', distKey: 'true' as unknown as boolean }, + { name: 'col2', dataType: 'float', sortKey: 'true' as unknown as boolean }, + { name: 'col3', dataType: 'float', sortKey: 'true' as unknown as boolean }, + ], + distStyle: TableDistStyle.KEY, + sortStyle: TableSortStyle.COMPOUND, + }; + + await manageTable(newResourceProperties, event); + + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(4),col2 float,col3 float) DISTSTYLE KEY DISTKEY(col1) COMPOUND SORTKEY(col2,col3)`, + })); + }); }); describe('delete', () => { @@ -199,4 +258,251 @@ describe('update', () => { Sql: `ALTER TABLE ${physicalResourceId} ADD ${newTableColumnName} ${newTableColumnDataType}`, })); }); + + describe('distStyle and distKey', () => { + test('replaces if distStyle is added', async () => { + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + distStyle: TableDistStyle.EVEN, + }; + + await expect(manageTable(newResourceProperties, event)).resolves.not.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1)) DISTSTYLE EVEN`, + })); + }); + + test('replaces if distStyle is removed', async () => { + const newEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + distStyle: TableDistStyle.EVEN, + }, + }; + const newResourceProperties = { + ...resourceProperties, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.not.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1))`, + })); + }); + + test('does not replace if distStyle is changed', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + distStyle: TableDistStyle.EVEN, + }, + }; + const newDistStyle = TableDistStyle.ALL; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + distStyle: newDistStyle, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `ALTER TABLE ${physicalResourceId} ALTER DISTSTYLE ${newDistStyle}`, + })); + }); + + test('replaces if distKey is added', async () => { + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: [{ name: 'col1', dataType: 'varchar(1)', distKey: true }], + }; + + await expect(manageTable(newResourceProperties, event)).resolves.not.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1)) DISTKEY(col1)`, + })); + }); + + test('replaces if distKey is removed', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + tableColumns: [{ name: 'col1', dataType: 'varchar(1)', distKey: true }], + }, + }; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.not.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1))`, + })); + }); + + test('does not replace if distKey is changed', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + tableColumns: [ + { name: 'col1', dataType: 'varchar(1)', distKey: true }, + { name: 'col2', dataType: 'varchar(1)' }, + ], + }, + }; + const newDistKey = 'col2'; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: [ + { name: 'col1', dataType: 'varchar(1)' }, + { name: 'col2', dataType: 'varchar(1)', distKey: true }, + ], + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `ALTER TABLE ${physicalResourceId} ALTER DISTKEY ${newDistKey}`, + })); + }); + }); + + describe('sortStyle and sortKeys', () => { + const oldTableColumnsWithSortKeys: Column[] = [ + { name: 'col1', dataType: 'varchar(1)', sortKey: true }, + { name: 'col2', dataType: 'varchar(1)' }, + ]; + const newTableColumnsWithSortKeys: Column[] = [ + { name: 'col1', dataType: 'varchar(1)' }, + { name: 'col2', dataType: 'varchar(1)', sortKey: true }, + ]; + + test('replaces when same sortStyle, different sortKey columns: INTERLEAVED', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.INTERLEAVED, + }, + }; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: newTableColumnsWithSortKeys, + sortStyle: TableSortStyle.INTERLEAVED, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.not.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1),col2 varchar(1)) INTERLEAVED SORTKEY(col2)`, + })); + }); + + test('replaces when differnt sortStyle: INTERLEAVED', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.AUTO, + }, + }; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.INTERLEAVED, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.not.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(1),col2 varchar(1)) INTERLEAVED SORTKEY(col1)`, + })); + }); + + test('does not replace when same sortStyle, different sortKey columns: COMPOUND', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.COMPOUND, + }, + }; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: newTableColumnsWithSortKeys, + sortStyle: TableSortStyle.COMPOUND, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `ALTER TABLE ${physicalResourceId} ALTER COMPOUND SORTKEY(col2)`, + })); + }); + + test('does not replace when differnt sortStyle: COMPOUND', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.AUTO, + }, + }; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.COMPOUND, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `ALTER TABLE ${physicalResourceId} ALTER COMPOUND SORTKEY(col1)`, + })); + }); + + test('does not replace when differnt sortStyle: AUTO', async () => { + const newEvent: AWSLambda.CloudFormationCustomResourceEvent = { + ...event, + OldResourceProperties: { + ...event.OldResourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.COMPOUND, + }, + }; + const newResourceProperties: ResourcePropertiesType = { + ...resourceProperties, + tableColumns: oldTableColumnsWithSortKeys, + sortStyle: TableSortStyle.AUTO, + }; + + await expect(manageTable(newResourceProperties, newEvent)).resolves.toMatchObject({ + PhysicalResourceId: physicalResourceId, + }); + expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({ + Sql: `ALTER TABLE ${physicalResourceId} ALTER SORTKEY AUTO`, + })); + }); + }); + }); diff --git a/packages/@aws-cdk/aws-redshift/test/integ.database.expected.json b/packages/@aws-cdk/aws-redshift/test/integ.database.expected.json index 4cfb1faea5118..6e909192a7f3d 100644 --- a/packages/@aws-cdk/aws-redshift/test/integ.database.expected.json +++ b/packages/@aws-cdk/aws-redshift/test/integ.database.expected.json @@ -1167,7 +1167,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4fS3Bucket3B967306" + "Ref": "AssetParameters85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066S3Bucket0B347C2E" }, "S3Key": { "Fn::Join": [ @@ -1180,7 +1180,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4fS3VersionKeyC171429B" + "Ref": "AssetParameters85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066S3VersionKey932D0479" } ] } @@ -1193,7 +1193,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4fS3VersionKeyC171429B" + "Ref": "AssetParameters85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066S3VersionKey932D0479" } ] } @@ -1369,35 +1369,44 @@ "databaseName": "my_db", "tableName": { "prefix": "awscdkredshiftclusterdatabaseTable24923533", - "generateSuffix": true + "generateSuffix": "true" }, "tableColumns": [ { "name": "col1", - "dataType": "varchar(4)" + "dataType": "varchar(4)", + "distKey": true }, { "name": "col2", - "dataType": "float" + "dataType": "float", + "sortKey": true + }, + { + "name": "col3", + "dataType": "float", + "sortKey": true } - ] + ], + "distStyle": "KEY", + "sortStyle": "INTERLEAVED" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" } }, "Parameters": { - "AssetParameters7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4fS3Bucket3B967306": { + "AssetParameters85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066S3Bucket0B347C2E": { "Type": "String", - "Description": "S3 bucket for asset \"7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4f\"" + "Description": "S3 bucket for asset \"85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066\"" }, - "AssetParameters7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4fS3VersionKeyC171429B": { + "AssetParameters85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066S3VersionKey932D0479": { "Type": "String", - "Description": "S3 key for asset version \"7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4f\"" + "Description": "S3 key for asset version \"85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066\"" }, - "AssetParameters7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4fArtifactHash0EE8CD3D": { + "AssetParameters85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066ArtifactHash78689978": { "Type": "String", - "Description": "Artifact hash for asset \"7eb6a250bd5ce32c07f08f536377d71e59ad43e16e25b9aa6e50f6fc20fdfc4f\"" + "Description": "Artifact hash for asset \"85597bcd6a07abd4673fe02c7e92e21df5859eee0d831e9db67f4d2e74d4d066\"" }, "AssetParametersdaeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1S3BucketDC4B98B1": { "Type": "String", @@ -1412,4 +1421,4 @@ "Description": "Artifact hash for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\"" } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-redshift/test/integ.database.ts b/packages/@aws-cdk/aws-redshift/test/integ.database.ts index 6e4893c0c0089..d5079b83f0c1b 100644 --- a/packages/@aws-cdk/aws-redshift/test/integ.database.ts +++ b/packages/@aws-cdk/aws-redshift/test/integ.database.ts @@ -39,7 +39,13 @@ const databaseOptions = { const user = new redshift.User(stack, 'User', databaseOptions); const table = new redshift.Table(stack, 'Table', { ...databaseOptions, - tableColumns: [{ name: 'col1', dataType: 'varchar(4)' }, { name: 'col2', dataType: 'float' }], + tableColumns: [ + { name: 'col1', dataType: 'varchar(4)', distKey: true }, + { name: 'col2', dataType: 'float', sortKey: true }, + { name: 'col3', dataType: 'float', sortKey: true }, + ], + distStyle: redshift.TableDistStyle.KEY, + sortStyle: redshift.TableSortStyle.INTERLEAVED, }); table.grant(user, redshift.TableAction.INSERT, redshift.TableAction.DELETE); diff --git a/packages/@aws-cdk/aws-redshift/test/table.test.ts b/packages/@aws-cdk/aws-redshift/test/table.test.ts index 97f66b57042f5..571a87fff5227 100644 --- a/packages/@aws-cdk/aws-redshift/test/table.test.ts +++ b/packages/@aws-cdk/aws-redshift/test/table.test.ts @@ -5,7 +5,10 @@ import * as redshift from '../lib'; describe('cluster table', () => { const tableName = 'tableName'; - const tableColumns = [{ name: 'col1', dataType: 'varchar(4)' }, { name: 'col2', dataType: 'float' }]; + const tableColumns: redshift.Column[] = [ + { name: 'col1', dataType: 'varchar(4)' }, + { name: 'col2', dataType: 'float' }, + ]; let stack: cdk.Stack; let vpc: ec2.Vpc; @@ -40,7 +43,7 @@ describe('cluster table', () => { Template.fromStack(stack).hasResourceProperties('Custom::RedshiftDatabaseQuery', { tableName: { prefix: 'Table', - generateSuffix: true, + generateSuffix: 'true', }, tableColumns, }); @@ -67,7 +70,7 @@ describe('cluster table', () => { Template.fromStack(stack).hasResourceProperties('Custom::RedshiftDatabaseQuery', { tableName: { prefix: tableName, - generateSuffix: false, + generateSuffix: 'false', }, }); }); @@ -135,4 +138,110 @@ describe('cluster table', () => { DeletionPolicy: 'Delete', }); }); + + describe('distKey and distStyle', () => { + it('throws if more than one distKeys are configured', () => { + const updatedTableColumns: redshift.Column[] = [ + ...tableColumns, + { name: 'col3', dataType: 'varchar(4)', distKey: true }, + { name: 'col4', dataType: 'float', distKey: true }, + ]; + + expect( + () => new redshift.Table(stack, 'Table', { + ...databaseOptions, + tableColumns: updatedTableColumns, + }), + ).toThrow(/Only one column can be configured as distKey./); + }); + + it('throws if distStyle other than KEY is configured with configured distKey column', () => { + const updatedTableColumns: redshift.Column[] = [ + ...tableColumns, + { name: 'col3', dataType: 'varchar(4)', distKey: true }, + ]; + + expect( + () => new redshift.Table(stack, 'Table', { + ...databaseOptions, + tableColumns: updatedTableColumns, + distStyle: redshift.TableDistStyle.EVEN, + }), + ).toThrow(`Only 'TableDistStyle.KEY' can be configured when distKey is also configured. Found ${redshift.TableDistStyle.EVEN}`); + }); + + it('throws if KEY distStyle is configired with no distKey column', () => { + expect( + () => new redshift.Table(stack, 'Table', { + ...databaseOptions, + tableColumns, + distStyle: redshift.TableDistStyle.KEY, + }), + ).toThrow('distStyle of "TableDistStyle.KEY" can only be configured when distKey is also configured.'); + }); + }); + + describe('sortKeys and sortStyle', () => { + it('configures default sortStyle based on sortKeys if no sortStyle is passed: AUTO', () => { + // GIVEN + const tableColumnsWithoutSortKey = tableColumns; + + // WHEN + new redshift.Table(stack, 'Table', { + ...databaseOptions, + tableColumns: tableColumnsWithoutSortKey, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('Custom::RedshiftDatabaseQuery', { + sortStyle: redshift.TableSortStyle.AUTO, + }); + }); + + it('configures default sortStyle based on sortKeys if no sortStyle is passed: COMPOUND', () => { + // GIVEN + const tableColumnsWithSortKey: redshift.Column[] = [ + ...tableColumns, + { name: 'col3', dataType: 'varchar(4)', sortKey: true }, + ]; + + // WHEN + new redshift.Table(stack, 'Table', { + ...databaseOptions, + tableColumns: tableColumnsWithSortKey, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('Custom::RedshiftDatabaseQuery', { + sortStyle: redshift.TableSortStyle.COMPOUND, + }); + }); + + it('throws if sortStlye other than AUTO is passed with no configured sortKeys', () => { + expect( + () => new redshift.Table(stack, 'Table', { + ...databaseOptions, + tableColumns, + sortStyle: redshift.TableSortStyle.COMPOUND, + }), + ).toThrow(`sortStyle of '${redshift.TableSortStyle.COMPOUND}' can only be configured when sortKey is also configured.`); + }); + + it('throws if sortStlye of AUTO is passed with some configured sortKeys', () => { + // GIVEN + const tableColumnsWithSortKey: redshift.Column[] = [ + ...tableColumns, + { name: 'col3', dataType: 'varchar(4)', sortKey: true }, + ]; + + // THEN + expect( + () => new redshift.Table(stack, 'Table', { + ...databaseOptions, + tableColumns: tableColumnsWithSortKey, + sortStyle: redshift.TableSortStyle.AUTO, + }), + ).toThrow(`sortStyle of '${redshift.TableSortStyle.AUTO}' cannot be configured when sortKey is also configured.`); + }); + }); }); diff --git a/packages/@aws-cdk/aws-route53-patterns/lib/website-redirect.ts b/packages/@aws-cdk/aws-route53-patterns/lib/website-redirect.ts index db63b7327a75d..06e53777cd277 100644 --- a/packages/@aws-cdk/aws-route53-patterns/lib/website-redirect.ts +++ b/packages/@aws-cdk/aws-route53-patterns/lib/website-redirect.ts @@ -4,7 +4,7 @@ import { CloudFrontWebDistribution, OriginProtocolPolicy, PriceClass, ViewerCert import { ARecord, AaaaRecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53'; import { CloudFrontTarget } from '@aws-cdk/aws-route53-targets'; import { BlockPublicAccess, Bucket, RedirectProtocol } from '@aws-cdk/aws-s3'; -import { RemovalPolicy, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, RemovalPolicy, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; // v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. @@ -62,7 +62,7 @@ export class HttpsRedirect extends CoreConstruct { const domainNames = props.recordNames ?? [props.zone.zoneName]; if (props.certificate) { - const certificateRegion = Stack.of(this).parseArn(props.certificate.certificateArn).region; + const certificateRegion = Stack.of(this).splitArn(props.certificate.certificateArn, ArnFormat.SLASH_RESOURCE_NAME).region; if (!Token.isUnresolved(certificateRegion) && certificateRegion !== 'us-east-1') { throw new Error(`The certificate must be in the us-east-1 region and the certificate you provided is in ${certificateRegion}.`); } diff --git a/packages/@aws-cdk/aws-route53-patterns/package.json b/packages/@aws-cdk/aws-route53-patterns/package.json index 7f3d2c396ede3..d8c12460322a6 100644 --- a/packages/@aws-cdk/aws-route53-patterns/package.json +++ b/packages/@aws-cdk/aws-route53-patterns/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-route53-targets/README.md b/packages/@aws-cdk/aws-route53-targets/README.md index 9d53630628ddd..73269abb6d6ed 100644 --- a/packages/@aws-cdk/aws-route53-targets/README.md +++ b/packages/@aws-cdk/aws-route53-targets/README.md @@ -58,7 +58,7 @@ This library contains Route53 Alias Record targets for: ```ts import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; - + declare const zone: route53.HostedZone; declare const lb: elbv2.ApplicationLoadBalancer; @@ -73,7 +73,7 @@ This library contains Route53 Alias Record targets for: ```ts import * as elb from '@aws-cdk/aws-elasticloadbalancing'; - + declare const zone: route53.HostedZone; declare const lb: elb.LoadBalancer; @@ -129,7 +129,7 @@ See [the Developer Guide](https://docs.aws.amazon.com/Route53/latest/DeveloperGu ```ts import * as s3 from '@aws-cdk/aws-s3'; - + const recordName = 'www'; const domainName = 'example.com'; @@ -176,11 +176,14 @@ See [the Developer Guide](https://docs.aws.amazon.com/Route53/latest/DeveloperGu **Important:** Only supports Elastic Beanstalk environments created after 2016 that have a regional endpoint. - ```ts - new route53.ARecord(this, 'AliasRecord', { - zone, - target: route53.RecordTarget.fromAlias(new alias.ElasticBeanstalkEnvironmentEndpointTarget(ebsEnvironmentUrl)), - }); - ``` +```ts +declare const zone: route53.HostedZone; +declare const ebsEnvironmentUrl: string; + +new route53.ARecord(this, 'AliasRecord', { + zone, + target: route53.RecordTarget.fromAlias(new targets.ElasticBeanstalkEnvironmentEndpointTarget(ebsEnvironmentUrl)), +}); +``` See the documentation of `@aws-cdk/aws-route53` for more information. diff --git a/packages/@aws-cdk/aws-route53-targets/package.json b/packages/@aws-cdk/aws-route53-targets/package.json index cd191bfc3173e..a65e4a0372abf 100644 --- a/packages/@aws-cdk/aws-route53-targets/package.json +++ b/packages/@aws-cdk/aws-route53-targets/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 35c3abefcc477..aabd22c95363b 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts b/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts index ccc93159714ff..cf0a5c7bc03af 100644 --- a/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts +++ b/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts @@ -25,7 +25,7 @@ test('simple use case', () => { // verify that metadata contains an "aws:cdk:asset" entry with // the correct information - const entry = stack.node.metadata.find(m => m.type === 'aws:cdk:asset'); + const entry = stack.node.metadataEntry.find(m => m.type === 'aws:cdk:asset'); expect(entry).toBeTruthy(); // verify that now the template contains parameters for this asset @@ -75,7 +75,7 @@ test('"file" assets', () => { const stack = new cdk.Stack(); const filePath = path.join(__dirname, 'file-asset.txt'); new Asset(stack, 'MyAsset', { path: filePath }); - const entry = stack.node.metadata.find(m => m.type === 'aws:cdk:asset'); + const entry = stack.node.metadataEntry.find(m => m.type === 'aws:cdk:asset'); expect(entry).toBeTruthy(); // synthesize first so "prepare" is called diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts index 7f799b2f9ff65..3713b99ae3159 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts @@ -4,9 +4,9 @@ import * as cloudfront from '@aws-cdk/aws-cloudfront'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; +import { testDeprecated, testFutureBehavior } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import * as s3deploy from '../lib'; /* eslint-disable max-len */ @@ -277,7 +277,13 @@ test('deploy from a local .zip file when efs is enabled', () => { }); }); -test('honors passed asset options', () => { +testDeprecated('honors passed asset options', () => { + // The 'exclude' property is deprecated and not deprecated in AssetOptions interface. + // The interface through a complex set of inheritance chain has a 'exclude' prop that is deprecated + // and another 'exclude' prop that is not deprecated. + // Using 'testDeprecated' block here since there's no way to work around this craziness. + // When the deprecated property is removed from source, this block can be dropped. + // GIVEN const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'Dest'); @@ -764,7 +770,6 @@ test('deploy without deleting missing files from destination', () => { }); test('deploy with excluded files from destination', () => { - // GIVEN const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'Dest'); diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 157aa31a3ed5f..ce8dafd5a69aa 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -105,9 +105,10 @@ export interface IBucket extends IResource { /** * The https URL of an S3 object. For example: - * @example https://s3.us-west-1.amazonaws.com/onlybucket - * @example https://s3.us-west-1.amazonaws.com/bucket/key - * @example https://s3.cn-north-1.amazonaws.com.cn/china-bucket/mykey + * + * - `https://s3.us-west-1.amazonaws.com/onlybucket` + * - `https://s3.us-west-1.amazonaws.com/bucket/key` + * - `https://s3.cn-north-1.amazonaws.com.cn/china-bucket/mykey` * @param key The S3 key of the object. If not specified, the URL of the * bucket is returned. * @returns an ObjectS3Url token @@ -117,10 +118,11 @@ export interface IBucket extends IResource { /** * The virtual hosted-style URL of an S3 object. Specify `regional: false` at * the options for non-regional URL. For example: - * @example https://only-bucket.s3.us-west-1.amazonaws.com - * @example https://bucket.s3.us-west-1.amazonaws.com/key - * @example https://bucket.s3.amazonaws.com/key - * @example https://china-bucket.s3.cn-north-1.amazonaws.com.cn/mykey + * + * - `https://only-bucket.s3.us-west-1.amazonaws.com` + * - `https://bucket.s3.us-west-1.amazonaws.com/key` + * - `https://bucket.s3.amazonaws.com/key` + * - `https://china-bucket.s3.cn-north-1.amazonaws.com.cn/mykey` * @param key The S3 key of the object. If not specified, the URL of the * bucket is returned. * @param options Options for generating URL. @@ -130,8 +132,8 @@ export interface IBucket extends IResource { /** * The S3 URL of an S3 object. For example: - * @example s3://onlybucket - * @example s3://bucket/key + * - `s3://onlybucket` + * - `s3://bucket/key` * @param key The S3 key of the object. If not specified, the S3 URL of the * bucket is returned. * @returns an ObjectS3Url token @@ -603,9 +605,11 @@ export abstract class BucketBase extends Resource implements IBucket { /** * The https URL of an S3 object. Specify `regional: false` at the options * for non-regional URLs. For example: - * @example https://s3.us-west-1.amazonaws.com/onlybucket - * @example https://s3.us-west-1.amazonaws.com/bucket/key - * @example https://s3.cn-north-1.amazonaws.com.cn/china-bucket/mykey + * + * - `https://s3.us-west-1.amazonaws.com/onlybucket` + * - `https://s3.us-west-1.amazonaws.com/bucket/key` + * - `https://s3.cn-north-1.amazonaws.com.cn/china-bucket/mykey` + * * @param key The S3 key of the object. If not specified, the URL of the * bucket is returned. * @returns an ObjectS3Url token @@ -622,10 +626,12 @@ export abstract class BucketBase extends Resource implements IBucket { /** * The virtual hosted-style URL of an S3 object. Specify `regional: false` at * the options for non-regional URL. For example: - * @example https://only-bucket.s3.us-west-1.amazonaws.com - * @example https://bucket.s3.us-west-1.amazonaws.com/key - * @example https://bucket.s3.amazonaws.com/key - * @example https://china-bucket.s3.cn-north-1.amazonaws.com.cn/mykey + * + * - `https://only-bucket.s3.us-west-1.amazonaws.com` + * - `https://bucket.s3.us-west-1.amazonaws.com/key` + * - `https://bucket.s3.amazonaws.com/key` + * - `https://china-bucket.s3.cn-north-1.amazonaws.com.cn/mykey` + * * @param key The S3 key of the object. If not specified, the URL of the * bucket is returned. * @param options Options for generating URL. @@ -642,8 +648,10 @@ export abstract class BucketBase extends Resource implements IBucket { /** * The S3 URL of an S3 object. For example: - * @example s3://onlybucket - * @example s3://bucket/key + * + * - `s3://onlybucket` + * - `s3://bucket/key` + * * @param key The S3 key of the object. If not specified, the S3 URL of the * bucket is returned. * @returns an ObjectS3Url token diff --git a/packages/@aws-cdk/aws-s3/lib/util.ts b/packages/@aws-cdk/aws-s3/lib/util.ts index 1c45ed899be4b..43bf816be6840 100644 --- a/packages/@aws-cdk/aws-s3/lib/util.ts +++ b/packages/@aws-cdk/aws-s3/lib/util.ts @@ -32,7 +32,7 @@ export function parseBucketName(construct: IConstruct, props: BucketAttributes): // extract bucket name from bucket arn if (props.bucketArn) { - return cdk.Stack.of(construct).parseArn(props.bucketArn).resource; + return cdk.Stack.of(construct).splitArn(props.bucketArn, cdk.ArnFormat.SLASH_RESOURCE_NAME).resource; } // no bucket name is okay since it's optional. diff --git a/packages/@aws-cdk/aws-s3/package.json b/packages/@aws-cdk/aws-s3/package.json index e3feff8d0fe6c..f8153d202545d 100644 --- a/packages/@aws-cdk/aws-s3/package.json +++ b/packages/@aws-cdk/aws-s3/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index 70144db7d2dbd..5537c72b2c0d9 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; -import { FeatureFlags, Fn, IResource, Lazy, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, FeatureFlags, Fn, IResource, Lazy, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { IConstruct, Construct } from 'constructs'; import { ResourcePolicy } from './policy'; @@ -373,7 +373,7 @@ export class Secret extends SecretBase { service: 'secretsmanager', resource: 'secret', resourceName: this.secretName + '*', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } }(scope, id); @@ -397,7 +397,7 @@ export class Secret extends SecretBase { service: 'secretsmanager', resource: 'secret', resourceName: secretName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } }(scope, id); @@ -475,7 +475,7 @@ export class Secret extends SecretBase { service: 'secretsmanager', resource: 'secret', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); this.encryptionKey = props.encryptionKey; @@ -604,8 +604,6 @@ export interface SecretAttachmentTargetProps { /** * Options to add a secret attachment to a secret. - * - * @deprecated use `secret.attach()` instead */ export interface AttachedSecretOptions { /** @@ -758,7 +756,7 @@ export interface SecretStringGenerator { /** Parses the secret name from the ARN. */ function parseSecretName(construct: IConstruct, secretArn: string) { - const resourceName = Stack.of(construct).parseArn(secretArn, ':').resourceName; + const resourceName = Stack.of(construct).splitArn(secretArn, ArnFormat.COLON_RESOURCE_NAME).resourceName; if (resourceName) { // Can't operate on the token to remove the SecretsManager suffix, so just return the full secret name if (Token.isUnresolved(resourceName)) { @@ -784,7 +782,7 @@ function parseSecretName(construct: IConstruct, secretArn: string) { * explicit between the Secret and wherever the secretName might be used (i.e., using Tokens). */ function parseSecretNameForOwnedSecret(construct: Construct, secretArn: string, secretName?: string) { - const resourceName = Stack.of(construct).parseArn(secretArn, ':').resourceName; + const resourceName = Stack.of(construct).splitArn(secretArn, ArnFormat.COLON_RESOURCE_NAME).resourceName; if (!resourceName) { throw new Error('invalid ARN format; no secret name provided'); } diff --git a/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts index 7f154c77b5f7f..e2ddfb9700350 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts @@ -3,8 +3,8 @@ import { expect as assertExpect, ResourcePart } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as lambda from '@aws-cdk/aws-lambda'; +import { testDeprecated, testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; -import { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import * as secretsmanager from '../lib'; let app: cdk.App; @@ -657,7 +657,7 @@ describe('secretName', () => { }); }); -test('import by secretArn', () => { +testDeprecated('import by secretArn', () => { // GIVEN const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; @@ -678,10 +678,12 @@ test('import by secretArn throws if ARN is malformed', () => { const arnWithoutResourceName = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret'; // WHEN - expect(() => secretsmanager.Secret.fromSecretArn(stack, 'Secret1', arnWithoutResourceName)).toThrow(/invalid ARN format/); + expect(() => secretsmanager.Secret.fromSecretAttributes(stack, 'Secret1', { + secretPartialArn: arnWithoutResourceName, + })).toThrow(/invalid ARN format/); }); -test('import by secretArn supports secret ARNs without suffixes', () => { +testDeprecated('import by secretArn supports secret ARNs without suffixes', () => { // GIVEN const arnWithoutSecretsManagerSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret'; @@ -693,7 +695,7 @@ test('import by secretArn supports secret ARNs without suffixes', () => { expect(secret.secretName).toBe('MySecret'); }); -test('import by secretArn does not strip suffixes unless the suffix length is six', () => { +testDeprecated('import by secretArn does not strip suffixes unless the suffix length is six', () => { // GIVEN const arnWith5CharacterSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:github-token'; const arnWith6CharacterSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:github-token-f3gDy9'; @@ -712,7 +714,7 @@ test('import by secretArn supports tokens for ARNs', () => { const secretA = new secretsmanager.Secret(stackA, 'SecretA'); // WHEN - const secretB = secretsmanager.Secret.fromSecretArn(stackB, 'SecretB', secretA.secretArn); + const secretB = secretsmanager.Secret.fromSecretCompleteArn(stackB, 'SecretB', secretA.secretArn); new cdk.CfnOutput(stackB, 'secretBSecretName', { value: secretB.secretName }); // THEN @@ -723,7 +725,7 @@ test('import by secretArn supports tokens for ARNs', () => { }); }); -test('import by secretArn guesses at complete or partial ARN', () => { +testDeprecated('import by secretArn guesses at complete or partial ARN', () => { // GIVEN const secretArnWithSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; const secretArnWithoutSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret'; @@ -864,7 +866,7 @@ describe('fromSecretAttributes', () => { // WHEN const secret = secretsmanager.Secret.fromSecretAttributes(stack, 'Secret', { - secretArn, encryptionKey, + secretCompleteArn: secretArn, encryptionKey, }); // THEN @@ -876,7 +878,7 @@ describe('fromSecretAttributes', () => { expect(stack.resolve(secret.secretValueFromJson('password'))).toBe(`{{resolve:secretsmanager:${secretArn}:SecretString:password::}}`); }); - test('throws if secretArn and either secretCompleteArn or secretPartialArn are provided', () => { + testDeprecated('throws if secretArn and either secretCompleteArn or secretPartialArn are provided', () => { const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; const error = /cannot use `secretArn` with `secretCompleteArn` or `secretPartialArn`/; @@ -922,7 +924,7 @@ describe('fromSecretAttributes', () => { }); }); -test('import by secret name', () => { +testDeprecated('import by secret name', () => { // GIVEN const secretName = 'MySecret'; @@ -937,7 +939,7 @@ test('import by secret name', () => { expect(stack.resolve(secret.secretValueFromJson('password'))).toBe(`{{resolve:secretsmanager:${secretName}:SecretString:password::}}`); }); -test('import by secret name with grants', () => { +testDeprecated('import by secret name with grants', () => { // GIVEN const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); const secret = secretsmanager.Secret.fromSecretName(stack, 'Secret', 'MySecret'); @@ -1135,7 +1137,7 @@ test('equivalence of SecretValue and Secret.fromSecretAttributes', () => { const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; // WHEN - const imported = secretsmanager.Secret.fromSecretAttributes(stack, 'Imported', { secretArn: secretArn }).secretValueFromJson('password'); + const imported = secretsmanager.Secret.fromSecretAttributes(stack, 'Imported', { secretCompleteArn: secretArn }).secretValueFromJson('password'); const value = cdk.SecretValue.secretsManager(secretArn, { jsonField: 'password' }); // THEN diff --git a/packages/@aws-cdk/aws-securityhub/README.md b/packages/@aws-cdk/aws-securityhub/README.md index 831f2af57d18b..c4b1bebcf6e6c 100644 --- a/packages/@aws-cdk/aws-securityhub/README.md +++ b/packages/@aws-cdk/aws-securityhub/README.md @@ -15,6 +15,6 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. -```ts +```ts nofixture import * as securityhub from '@aws-cdk/aws-securityhub'; ``` diff --git a/packages/@aws-cdk/aws-securityhub/package.json b/packages/@aws-cdk/aws-securityhub/package.json index b1fef1fe83a49..bb36e474a4b74 100644 --- a/packages/@aws-cdk/aws-securityhub/package.json +++ b/packages/@aws-cdk/aws-securityhub/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-servicecatalog/README.md b/packages/@aws-cdk/aws-servicecatalog/README.md index 2d7694d3e84b6..bbc82e13e2d7b 100644 --- a/packages/@aws-cdk/aws-servicecatalog/README.md +++ b/packages/@aws-cdk/aws-servicecatalog/README.md @@ -307,8 +307,36 @@ const launchRole = new iam.Role(this, 'LaunchRole', { portfolio.setLaunchRole(product, launchRole); ``` +You can also set the launch role using just the name of a role which is locally deployed in end user accounts. +This is useful for when roles and users are separately managed outside of the CDK. +The given role must exist in both the account that creates the launch role constraint, +as well as in any end user accounts that wish to provision a product with the launch role. + +You can do this by passing in the role with an explicitly set name: + +```ts fixture=portfolio-product +import * as iam from '@aws-cdk/aws-iam'; + +const launchRole = new iam.Role(this, 'LaunchRole', { + roleName: 'MyRole', + assumedBy: new iam.ServicePrincipal('servicecatalog.amazonaws.com'), +}); + +portfolio.setLocalLaunchRole(product, launchRole); +``` + +Or you can simply pass in a role name and CDK will create a role with that name that trusts service catalog in the account: + +```ts fixture=portfolio-product +import * as iam from '@aws-cdk/aws-iam'; + +const roleName = 'MyRole'; + +const launchRole: iam.IRole = portfolio.setLocalLaunchRoleName(product, roleName); +``` + See [Launch Constraint](https://docs.aws.amazon.com/servicecatalog/latest/adminguide/constraints-launch.html) documentation -to understand permissions roles need. +to understand the permissions roles need. ### Deploy with StackSets diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts b/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts index 3056a48e19777..36d267d022519 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts @@ -112,6 +112,9 @@ export interface IPortfolio extends cdk.IResource { /** * Force users to assume a certain role when launching a product. + * This sets the launch role using the role arn which is tied to the account this role exists in. + * This is useful if you will be provisioning products from the account where this role exists. + * If you intend to share the portfolio across accounts, use a local launch role. * * @param product A service catalog product. * @param launchRole The IAM role a user must assume when provisioning the product. @@ -120,7 +123,30 @@ export interface IPortfolio extends cdk.IResource { setLaunchRole(product: IProduct, launchRole: iam.IRole, options?: CommonConstraintOptions): void; /** - * Configure deployment options using AWS Cloudformaiton StackSets + * Force users to assume a certain role when launching a product. + * The role will be referenced by name in the local account instead of a static role arn. + * A role with this name will automatically be created and assumable by Service Catalog in this account. + * This is useful when sharing the portfolio with multiple accounts. + * + * @param product A service catalog product. + * @param launchRoleName The name of the IAM role a user must assume when provisioning the product. A role with this name must exist in the account where the portolio is created and the accounts it is shared with. + * @param options options for the constraint. + */ + setLocalLaunchRoleName(product: IProduct, launchRoleName: string, options?: CommonConstraintOptions): iam.IRole; + + /** + * Force users to assume a certain role when launching a product. + * The role name will be referenced by in the local account and must be set explicitly. + * This is useful when sharing the portfolio with multiple accounts. + * + * @param product A service catalog product. + * @param launchRole The IAM role a user must assume when provisioning the product. A role with this name must exist in the account where the portolio is created and the accounts it is shared with. The role name must be set explicitly. + * @param options options for the constraint. + */ + setLocalLaunchRole(product: IProduct, launchRole: iam.IRole, options?: CommonConstraintOptions): void; + + /** + * Configure deployment options using AWS Cloudformation StackSets * * @param product A service catalog product. * @param options Configuration options for the constraint. @@ -179,6 +205,20 @@ abstract class PortfolioBase extends cdk.Resource implements IPortfolio { AssociationManager.setLaunchRole(this, product, launchRole, options); } + public setLocalLaunchRoleName(product: IProduct, launchRoleName: string, options: CommonConstraintOptions = {}): iam.IRole { + const launchRole: iam.IRole = new iam.Role(this, `LaunchRole${launchRoleName}`, { + roleName: launchRoleName, + assumedBy: new iam.ServicePrincipal('servicecatalog.amazonaws.com'), + }); + AssociationManager.setLocalLaunchRoleName(this, product, launchRole.roleName, options); + return launchRole; + } + + public setLocalLaunchRole(product: IProduct, launchRole: iam.IRole, options: CommonConstraintOptions = {}): void { + InputValidator.validateRoleNameSetForLocalLaunchRole(launchRole); + AssociationManager.setLocalLaunchRoleName(this, product, launchRole.roleName, options); + } + public deployWithStackSets(product: IProduct, options: StackSetsConstraintOptions) { AssociationManager.deployWithStackSets(this, product, options); } diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts b/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts index bf5a68a8e70d3..b92fb2483ad54 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts @@ -100,27 +100,15 @@ export class AssociationManager { } public static setLaunchRole(portfolio: IPortfolio, product: IProduct, launchRole: iam.IRole, options: CommonConstraintOptions): void { - const association = this.associateProductWithPortfolio(portfolio, product, options); - // Check if a stackset deployment constraint has already been configured. - if (portfolio.node.tryFindChild(this.stackSetConstraintLogicalId(association.associationKey))) { - throw new Error(`Cannot set launch role when a StackSet rule is already defined for association ${this.prettyPrintAssociation(portfolio, product)}`); - } - - const constructId = this.launchRoleConstraintLogicalId(association.associationKey); - if (!portfolio.node.tryFindChild(constructId)) { - const constraint = new CfnLaunchRoleConstraint(portfolio as unknown as cdk.Resource, constructId, { - acceptLanguage: options.messageLanguage, - description: options.description, - portfolioId: portfolio.portfolioId, - productId: product.productId, - roleArn: launchRole.roleArn, - }); + this.setLaunchRoleConstraint(portfolio, product, options, { + roleArn: launchRole.roleArn, + }); + } - // Add dependsOn to force proper order in deployment. - constraint.addDependsOn(association.cfnPortfolioProductAssociation); - } else { - throw new Error(`Cannot set multiple launch roles for association ${this.prettyPrintAssociation(portfolio, product)}`); - } + public static setLocalLaunchRoleName(portfolio: IPortfolio, product: IProduct, launchRoleName: string, options: CommonConstraintOptions): void { + this.setLaunchRoleConstraint(portfolio, product, options, { + localRoleName: launchRoleName, + }); } public static deployWithStackSets(portfolio: IPortfolio, product: IProduct, options: StackSetsConstraintOptions) { @@ -179,6 +167,34 @@ export class AssociationManager { }; } + private static setLaunchRoleConstraint( + portfolio: IPortfolio, product: IProduct, options: CommonConstraintOptions, + roleOptions: LaunchRoleConstraintRoleOptions, + ): void { + const association = this.associateProductWithPortfolio(portfolio, product, options); + // Check if a stackset deployment constraint has already been configured. + if (portfolio.node.tryFindChild(this.stackSetConstraintLogicalId(association.associationKey))) { + throw new Error(`Cannot set launch role when a StackSet rule is already defined for association ${this.prettyPrintAssociation(portfolio, product)}`); + } + + const constructId = this.launchRoleConstraintLogicalId(association.associationKey); + if (!portfolio.node.tryFindChild(constructId)) { + const constraint = new CfnLaunchRoleConstraint(portfolio as unknown as cdk.Resource, constructId, { + acceptLanguage: options.messageLanguage, + description: options.description, + portfolioId: portfolio.portfolioId, + productId: product.productId, + roleArn: roleOptions.roleArn, + localRoleName: roleOptions.localRoleName, + }); + + // Add dependsOn to force proper order in deployment. + constraint.addDependsOn(association.cfnPortfolioProductAssociation); + } else { + throw new Error(`Cannot set multiple launch roles for association ${this.prettyPrintAssociation(portfolio, product)}`); + } + } + private static stackSetConstraintLogicalId(associationKey: string): string { return `StackSetConstraint${associationKey}`; } @@ -213,3 +229,14 @@ export class AssociationManager { }; } +interface LaunchRoleArnOption { + readonly roleArn: string, + readonly localRoleName?: never, +} + +interface LaunchRoleNameOption { + readonly localRoleName: string, + readonly roleArn?: never, +} + +type LaunchRoleConstraintRoleOptions = LaunchRoleArnOption | LaunchRoleNameOption; diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/private/validation.ts b/packages/@aws-cdk/aws-servicecatalog/lib/private/validation.ts index 3beaa42552eff..cd70006fbe373 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/private/validation.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/private/validation.ts @@ -1,3 +1,4 @@ +import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; /** @@ -36,6 +37,17 @@ export class InputValidator { this.validateRegex(resourceName, inputName, /^[\w\d.%+\-]+@[a-z\d.\-]+\.[a-z]{2,4}$/i, inputString); } + /** + * Validates that a role being used as a local launch role has the role name set + */ + public static validateRoleNameSetForLocalLaunchRole(role: iam.IRole): void { + if (role.node.defaultChild) { + if (cdk.Token.isUnresolved((role.node.defaultChild as iam.CfnRole).roleName)) { + throw new Error(`Role ${role.node.id} used for Local Launch Role must have roleName explicitly set`); + } + } + } + private static truncateString(string: string, maxLength: number): string { if (string.length > maxLength) { return string.substring(0, maxLength) + '[truncated]'; diff --git a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts index 8f9a27a96a940..43a283c157f80 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts @@ -568,6 +568,7 @@ describe('portfolio associations and product constraints', () => { assumedBy: new iam.AccountRootPrincipal(), }); launchRole = new iam.Role(stack, 'LaunchRole', { + roleName: 'LaunchRole', assumedBy: new iam.ServicePrincipal('servicecatalog.amazonaws.com'), }); }), @@ -591,6 +592,59 @@ describe('portfolio associations and product constraints', () => { }); }), + test('set a launch role constraint using local role name', () => { + portfolio.addProduct(product); + + portfolio.setLocalLaunchRoleName(product, 'LocalLaunchRole', { + description: 'set launch role description', + messageLanguage: servicecatalog.MessageLanguage.EN, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ServiceCatalog::LaunchRoleConstraint', { + PortfolioId: { Ref: 'MyPortfolio59CCA9C9' }, + ProductId: { Ref: 'MyProduct49A3C587' }, + Description: 'set launch role description', + AcceptLanguage: 'en', + LocalRoleName: { Ref: 'MyPortfolioLaunchRoleLocalLaunchRoleB2E6E22A' }, + }); + }), + + test('set a launch role constraint using local role', () => { + portfolio.addProduct(product); + + portfolio.setLocalLaunchRole(product, launchRole, { + description: 'set launch role description', + messageLanguage: servicecatalog.MessageLanguage.EN, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ServiceCatalog::LaunchRoleConstraint', { + PortfolioId: { Ref: 'MyPortfolio59CCA9C9' }, + ProductId: { Ref: 'MyProduct49A3C587' }, + Description: 'set launch role description', + AcceptLanguage: 'en', + LocalRoleName: { Ref: 'LaunchRole2CFB2E44' }, + }); + }), + + test('set a launch role constraint using imported local role', () => { + portfolio.addProduct(product); + + const importedLaunchRole = iam.Role.fromRoleArn(portfolio.stack, 'ImportedLaunchRole', 'arn:aws:iam::123456789012:role/ImportedLaunchRole'); + + portfolio.setLocalLaunchRole(product, importedLaunchRole, { + description: 'set launch role description', + messageLanguage: servicecatalog.MessageLanguage.EN, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ServiceCatalog::LaunchRoleConstraint', { + PortfolioId: { Ref: 'MyPortfolio59CCA9C9' }, + ProductId: { Ref: 'MyProduct49A3C587' }, + Description: 'set launch role description', + AcceptLanguage: 'en', + LocalRoleName: 'ImportedLaunchRole', + }); + }), + test('set launch role constraint still adds without explicit association', () => { portfolio.setLaunchRole(product, launchRole); @@ -606,7 +660,57 @@ describe('portfolio associations and product constraints', () => { expect(() => { portfolio.setLaunchRole(product, otherLaunchRole); - }).toThrowError(/Cannot set multiple launch roles for association/); + }).toThrow(/Cannot set multiple launch roles for association/); + }), + + test('local launch role must have roleName explicitly set', () => { + const otherLaunchRole = new iam.Role(stack, 'otherLaunchRole', { + assumedBy: new iam.ServicePrincipal('servicecatalog.amazonaws.com'), + }); + + expect(() => { + portfolio.setLocalLaunchRole(product, otherLaunchRole); + }).toThrow(/Role otherLaunchRole used for Local Launch Role must have roleName explicitly set/); + }), + + test('fails to add multiple set launch roles - local launch role first', () => { + portfolio.setLocalLaunchRoleName(product, 'LaunchRole'); + + expect(() => { + portfolio.setLaunchRole(product, launchRole); + }).toThrow(/Cannot set multiple launch roles for association/); + }), + + test('fails to add multiple set local launch roles - local launch role first', () => { + portfolio.setLocalLaunchRoleName(product, 'LaunchRole'); + + expect(() => { + portfolio.setLocalLaunchRole(product, launchRole); + }).toThrow(/Cannot set multiple launch roles for association/); + }), + + test('fails to add multiple set local launch roles - local launch role name first', () => { + portfolio.setLocalLaunchRole(product, launchRole); + + expect(() => { + portfolio.setLocalLaunchRoleName(product, 'LaunchRole'); + }).toThrow(/Cannot set multiple launch roles for association/); + }), + + test('fails to add multiple set launch roles - local launch role second', () => { + portfolio.setLaunchRole(product, launchRole); + + expect(() => { + portfolio.setLocalLaunchRole(product, launchRole); + }).toThrow(/Cannot set multiple launch roles for association/); + }), + + test('fails to add multiple set launch roles - local launch role second', () => { + portfolio.setLaunchRole(product, launchRole); + + expect(() => { + portfolio.setLocalLaunchRoleName(product, 'LaunchRole'); + }).toThrow(/Cannot set multiple launch roles for association/); }), test('fails to set launch role if stackset rule is already defined', () => { diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts index aa7581653d5ba..99e2bdb919280 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; -import { Names, Stack } from '@aws-cdk/core'; +import { ArnFormat, Names, Stack, Token } from '@aws-cdk/core'; import { SubscriptionProps } from './subscription'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main @@ -36,6 +36,12 @@ export class LambdaSubscription implements sns.ITopicSubscription { principal: new iam.ServicePrincipal('sns.amazonaws.com'), }); + // if the topic and function are created in different stacks + // then we need to make sure the topic is created first + if (topic instanceof sns.Topic && topic.stack !== this.fn.stack) { + this.fn.stack.addDependency(topic.stack); + } + return { subscriberScope: this.fn, subscriberId: topic.node.id, @@ -50,8 +56,16 @@ export class LambdaSubscription implements sns.ITopicSubscription { private regionFromArn(topic: sns.ITopic): string | undefined { // no need to specify `region` for topics defined within the same stack. if (topic instanceof sns.Topic) { + if (topic.stack !== this.fn.stack) { + // only if we know the region, will not work for + // env agnostic stacks + if (!Token.isUnresolved(topic.stack.region) && + (topic.stack.region !== this.fn.stack.region)) { + return topic.stack.region; + } + } return undefined; } - return Stack.of(topic).parseArn(topic.topicArn).region; + return Stack.of(topic).splitArn(topic.topicArn, ArnFormat.SLASH_RESOURCE_NAME).region; } } diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts index 6cf89ebc53c60..10c218dd3ef38 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; import * as sqs from '@aws-cdk/aws-sqs'; -import { Names, Stack } from '@aws-cdk/core'; +import { ArnFormat, Names, Stack, Token } from '@aws-cdk/core'; import { SubscriptionProps } from './subscription'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main @@ -61,6 +61,12 @@ export class SqsSubscription implements sns.ITopicSubscription { })); } + // if the topic and queue are created in different stacks + // then we need to make sure the topic is created first + if (topic instanceof sns.Topic && topic.stack !== this.queue.stack) { + this.queue.stack.addDependency(topic.stack); + } + return { subscriberScope: this.queue, subscriberId: Names.nodeUniqueId(topic.node), @@ -76,8 +82,16 @@ export class SqsSubscription implements sns.ITopicSubscription { private regionFromArn(topic: sns.ITopic): string | undefined { // no need to specify `region` for topics defined within the same stack if (topic instanceof sns.Topic) { + if (topic.stack !== this.queue.stack) { + // only if we know the region, will not work for + // env agnostic stacks + if (!Token.isUnresolved(topic.stack.region) && + (topic.stack.region !== this.queue.stack.region)) { + return topic.stack.region; + } + } return undefined; } - return Stack.of(topic).parseArn(topic.topicArn).region; + return Stack.of(topic).splitArn(topic.topicArn, ArnFormat.SLASH_RESOURCE_NAME).region; } } diff --git a/packages/@aws-cdk/aws-sns-subscriptions/package.json b/packages/@aws-cdk/aws-sns-subscriptions/package.json index ba1cfe852a8aa..6f8a4f5a2b9cc 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/package.json +++ b/packages/@aws-cdk/aws-sns-subscriptions/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.expected.json b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.expected.json new file mode 100644 index 0000000000000..de5216b565954 --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.expected.json @@ -0,0 +1,116 @@ +[ + { + "Resources": { + "MyTopic86869434": { + "Type": "AWS::SNS::Topic", + "Properties": { + "TopicName": "topicstackopicstackmytopicc43e67afb24f28bb94f9" + } + } + } + }, + { + "Resources": { + "EchoServiceRoleBE28060B": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "Echo11F3FB29": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function handler(event, _context, callback) {\n /* eslint-disable no-console */\n console.log('====================================================');\n console.log(JSON.stringify(event, undefined, 2));\n console.log('====================================================');\n return callback(undefined, event);\n}" + }, + "Role": { + "Fn::GetAtt": [ + "EchoServiceRoleBE28060B", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs10.x" + }, + "DependsOn": [ + "EchoServiceRoleBE28060B" + ] + }, + "EchoAllowInvokeTopicStackMyTopicC43E67AF32CF6EFA": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Echo11F3FB29", + "Arn" + ] + }, + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sns:us-east-1:12345678:topicstackopicstackmytopicc43e67afb24f28bb94f9" + ] + ] + } + } + }, + "EchoMyTopic4CB8819E": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "lambda", + "TopicArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sns:us-east-1:12345678:topicstackopicstackmytopicc43e67afb24f28bb94f9" + ] + ] + }, + "Endpoint": { + "Fn::GetAtt": [ + "Echo11F3FB29", + "Arn" + ] + }, + "Region": "us-east-1" + } + } + } + } +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.ts new file mode 100644 index 0000000000000..cfec9592e3dba --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda-cross-region.ts @@ -0,0 +1,35 @@ +import * as lambda from '@aws-cdk/aws-lambda'; +import * as sns from '@aws-cdk/aws-sns'; +import * as cdk from '@aws-cdk/core'; +import * as subs from '../lib'; + +/// !cdk-integ * +const app = new cdk.App(); + +const topicStack = new cdk.Stack(app, 'TopicStack', { + env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: 'us-east-1' }, +}); +const topic = new sns.Topic(topicStack, 'MyTopic', { + topicName: cdk.PhysicalName.GENERATE_IF_NEEDED, +}); + +const functionStack = new cdk.Stack(app, 'FunctionStack', { + env: { region: 'us-east-2' }, +}); +const fction = new lambda.Function(functionStack, 'Echo', { + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromInline(`exports.handler = ${handler.toString()}`), +}); + +topic.addSubscription(new subs.LambdaSubscription(fction)); + +app.synth(); + +function handler(event: any, _context: any, callback: any) { + /* eslint-disable no-console */ + console.log('===================================================='); + console.log(JSON.stringify(event, undefined, 2)); + console.log('===================================================='); + return callback(undefined, event); +} diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.expected.json b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.expected.json new file mode 100644 index 0000000000000..5bbffb5e31628 --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.expected.json @@ -0,0 +1,90 @@ +[ + { + "Resources": { + "MyTopic86869434": { + "Type": "AWS::SNS::Topic", + "Properties": { + "TopicName": "topicstackopicstackmytopicc43e67afb24f28bb94f9" + } + } + } + }, + { + "Resources": { + "MyQueueE6CA6235": { + "Type": "AWS::SQS::Queue", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "MyQueuePolicy6BBEDDAC": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sns:us-east-1:12345678:topicstackopicstackmytopicc43e67afb24f28bb94f9" + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "sns.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "MyQueueE6CA6235", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "MyQueueE6CA6235" + } + ] + } + }, + "MyQueueTopicStackMyTopicC43E67AFC8DC8B4A": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "sqs", + "TopicArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":sns:us-east-1:12345678:topicstackopicstackmytopicc43e67afb24f28bb94f9" + ] + ] + }, + "Endpoint": { + "Fn::GetAtt": [ + "MyQueueE6CA6235", + "Arn" + ] + }, + "Region": "us-east-1" + } + } + } + } +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.ts new file mode 100644 index 0000000000000..ca53a70194e03 --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs-cross-region.lit.ts @@ -0,0 +1,25 @@ +import * as sns from '@aws-cdk/aws-sns'; +import * as sqs from '@aws-cdk/aws-sqs'; +import * as cdk from '@aws-cdk/core'; +import * as subs from '../lib'; + +/// !cdk-integ * +const app = new cdk.App(); + +/// !show +const topicStack = new cdk.Stack(app, 'TopicStack', { + env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: 'us-east-1' }, +}); +const topic = new sns.Topic(topicStack, 'MyTopic', { + topicName: cdk.PhysicalName.GENERATE_IF_NEEDED, +}); + +const queueStack = new cdk.Stack(app, 'QueueStack', { + env: { region: 'us-east-2' }, +}); +const queue = new sqs.Queue(queueStack, 'MyQueue'); + +topic.addSubscription(new subs.SqsSubscription(queue)); +/// !hide + +app.synth(); diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts index 8be564b5a9188..671937a3ed01e 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts @@ -3,7 +3,7 @@ import * as kms from '@aws-cdk/aws-kms'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; import * as sqs from '@aws-cdk/aws-sqs'; -import { CfnParameter, Duration, RemovalPolicy, Stack, Token } from '@aws-cdk/core'; +import { App, CfnParameter, Duration, RemovalPolicy, Stack, Token } from '@aws-cdk/core'; import * as subs from '../lib'; /* eslint-disable quote-props */ @@ -308,6 +308,455 @@ test('queue subscription', () => { }); }); +test('queue subscription cross region', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', { + env: { + account: '11111111111', + region: 'us-east-1', + }, + }); + const queueStack = new Stack(app, 'QueueStack', { + env: { + account: '11111111111', + region: 'us-east-2', + }, + }); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + + const queue = new sqs.Queue(queueStack, 'MyQueue'); + + topic1.addSubscription(new subs.SqsSubscription(queue)); + + expect(topicStack).toMatchTemplate({ + 'Resources': { + 'TopicBFC7AF6E': { + 'Type': 'AWS::SNS::Topic', + 'Properties': { + 'DisplayName': 'displayName', + 'TopicName': 'topicName', + }, + }, + }, + }); + + expect(queueStack).toMatchTemplate({ + 'Resources': { + 'MyQueueE6CA6235': { + 'Type': 'AWS::SQS::Queue', + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + 'MyQueuePolicy6BBEDDAC': { + 'Type': 'AWS::SQS::QueuePolicy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': 'sqs:SendMessage', + 'Condition': { + 'ArnEquals': { + 'aws:SourceArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + }, + }, + 'Effect': 'Allow', + 'Principal': { + 'Service': 'sns.amazonaws.com', + }, + 'Resource': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'Queues': [ + { + 'Ref': 'MyQueueE6CA6235', + }, + ], + }, + }, + 'MyQueueTopicStackTopicFBF76EB349BDFA94': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'sqs', + 'TopicArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + 'Region': 'us-east-1', + }, + }, + }, + }); +}); + +test('queue subscription cross region, env agnostic', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', {}); + const queueStack = new Stack(app, 'QueueStack', {}); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + + const queue = new sqs.Queue(queueStack, 'MyQueue'); + + topic1.addSubscription(new subs.SqsSubscription(queue)); + + expect(topicStack).toMatchTemplate({ + 'Resources': { + 'TopicBFC7AF6E': { + 'Type': 'AWS::SNS::Topic', + 'Properties': { + 'DisplayName': 'displayName', + 'TopicName': 'topicName', + }, + }, + }, + 'Outputs': { + 'ExportsOutputRefTopicBFC7AF6ECB4A357A': { + 'Value': { + 'Ref': 'TopicBFC7AF6E', + }, + 'Export': { + 'Name': 'TopicStack:ExportsOutputRefTopicBFC7AF6ECB4A357A', + }, + }, + }, + }); + + expect(queueStack).toMatchTemplate({ + 'Resources': { + 'MyQueueE6CA6235': { + 'Type': 'AWS::SQS::Queue', + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + 'MyQueuePolicy6BBEDDAC': { + 'Type': 'AWS::SQS::QueuePolicy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': 'sqs:SendMessage', + 'Condition': { + 'ArnEquals': { + 'aws:SourceArn': { + 'Fn::ImportValue': 'TopicStack:ExportsOutputRefTopicBFC7AF6ECB4A357A', + }, + }, + }, + 'Effect': 'Allow', + 'Principal': { + 'Service': 'sns.amazonaws.com', + }, + 'Resource': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'Queues': [ + { + 'Ref': 'MyQueueE6CA6235', + }, + ], + }, + }, + 'MyQueueTopicStackTopicFBF76EB349BDFA94': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'sqs', + 'TopicArn': { + 'Fn::ImportValue': 'TopicStack:ExportsOutputRefTopicBFC7AF6ECB4A357A', + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + }, + }, + }); +}); + +test('queue subscription cross region, topic env agnostic', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', {}); + const queueStack = new Stack(app, 'QueueStack', { + env: { + account: '11111111111', + region: 'us-east-1', + }, + }); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + + const queue = new sqs.Queue(queueStack, 'MyQueue'); + + topic1.addSubscription(new subs.SqsSubscription(queue)); + + expect(topicStack).toMatchTemplate({ + 'Resources': { + 'TopicBFC7AF6E': { + 'Type': 'AWS::SNS::Topic', + 'Properties': { + 'DisplayName': 'displayName', + 'TopicName': 'topicName', + }, + }, + }, + }); + + expect(queueStack).toMatchTemplate({ + 'Resources': { + 'MyQueueE6CA6235': { + 'Type': 'AWS::SQS::Queue', + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + 'MyQueuePolicy6BBEDDAC': { + 'Type': 'AWS::SQS::QueuePolicy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': 'sqs:SendMessage', + 'Condition': { + 'ArnEquals': { + 'aws:SourceArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:', + { + 'Ref': 'AWS::Region', + }, + ':', + { + 'Ref': 'AWS::AccountId', + }, + ':topicName', + ], + ], + }, + }, + }, + 'Effect': 'Allow', + 'Principal': { + 'Service': 'sns.amazonaws.com', + }, + 'Resource': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'Queues': [ + { + 'Ref': 'MyQueueE6CA6235', + }, + ], + }, + }, + 'MyQueueTopicStackTopicFBF76EB349BDFA94': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'sqs', + 'TopicArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:', + { + 'Ref': 'AWS::Region', + }, + ':', + { + 'Ref': 'AWS::AccountId', + }, + ':topicName', + ], + ], + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + }, + }, + }); +}); + +test('queue subscription cross region, queue env agnostic', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', { + env: { + account: '11111111111', + region: 'us-east-1', + }, + }); + const queueStack = new Stack(app, 'QueueStack', {}); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + + const queue = new sqs.Queue(queueStack, 'MyQueue'); + + topic1.addSubscription(new subs.SqsSubscription(queue)); + + expect(topicStack).toMatchTemplate({ + 'Resources': { + 'TopicBFC7AF6E': { + 'Type': 'AWS::SNS::Topic', + 'Properties': { + 'DisplayName': 'displayName', + 'TopicName': 'topicName', + }, + }, + }, + }); + + expect(queueStack).toMatchTemplate({ + 'Resources': { + 'MyQueueE6CA6235': { + 'Type': 'AWS::SQS::Queue', + 'UpdateReplacePolicy': 'Delete', + 'DeletionPolicy': 'Delete', + }, + 'MyQueuePolicy6BBEDDAC': { + 'Type': 'AWS::SQS::QueuePolicy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': 'sqs:SendMessage', + 'Condition': { + 'ArnEquals': { + 'aws:SourceArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + }, + }, + 'Effect': 'Allow', + 'Principal': { + 'Service': 'sns.amazonaws.com', + }, + 'Resource': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'Queues': [ + { + 'Ref': 'MyQueueE6CA6235', + }, + ], + }, + }, + 'MyQueueTopicStackTopicFBF76EB349BDFA94': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'sqs', + 'TopicArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyQueueE6CA6235', + 'Arn', + ], + }, + 'Region': 'us-east-1', + }, + }, + }, + }); +}); test('queue subscription with user provided dlq', () => { const queue = new sqs.Queue(stack, 'MyQueue'); const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { @@ -712,6 +1161,243 @@ test('lambda subscription', () => { }); }); +test('lambda subscription, cross region env agnostic', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', {}); + const lambdaStack = new Stack(app, 'LambdaStack', {}); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + const fction = new lambda.Function(lambdaStack, 'MyFunc', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = function(e, c, cb) { return cb() }'), + }); + + topic1.addSubscription(new subs.LambdaSubscription(fction)); + + expect(lambdaStack).toMatchTemplate({ + 'Resources': { + 'MyFuncServiceRole54065130': { + 'Type': 'AWS::IAM::Role', + 'Properties': { + 'AssumeRolePolicyDocument': { + 'Statement': [ + { + 'Action': 'sts:AssumeRole', + 'Effect': 'Allow', + 'Principal': { + 'Service': 'lambda.amazonaws.com', + }, + }, + ], + 'Version': '2012-10-17', + }, + 'ManagedPolicyArns': [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + ], + ], + }, + ], + }, + }, + 'MyFunc8A243A2C': { + 'Type': 'AWS::Lambda::Function', + 'Properties': { + 'Code': { + 'ZipFile': 'exports.handler = function(e, c, cb) { return cb() }', + }, + 'Role': { + 'Fn::GetAtt': [ + 'MyFuncServiceRole54065130', + 'Arn', + ], + }, + 'Handler': 'index.handler', + 'Runtime': 'nodejs10.x', + }, + 'DependsOn': [ + 'MyFuncServiceRole54065130', + ], + }, + 'MyFuncAllowInvokeTopicStackTopicFBF76EB3D4A699EF': { + 'Type': 'AWS::Lambda::Permission', + 'Properties': { + 'Action': 'lambda:InvokeFunction', + 'FunctionName': { + 'Fn::GetAtt': [ + 'MyFunc8A243A2C', + 'Arn', + ], + }, + 'Principal': 'sns.amazonaws.com', + 'SourceArn': { + 'Fn::ImportValue': 'TopicStack:ExportsOutputRefTopicBFC7AF6ECB4A357A', + }, + }, + }, + 'MyFuncTopic3B7C24C5': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'lambda', + 'TopicArn': { + 'Fn::ImportValue': 'TopicStack:ExportsOutputRefTopicBFC7AF6ECB4A357A', + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyFunc8A243A2C', + 'Arn', + ], + }, + }, + }, + }, + }); +}); + +test('lambda subscription, cross region', () => { + const app = new App(); + const topicStack = new Stack(app, 'TopicStack', { + env: { + account: '11111111111', + region: 'us-east-1', + }, + }); + const lambdaStack = new Stack(app, 'LambdaStack', { + env: { + account: '11111111111', + region: 'us-east-2', + }, + }); + + const topic1 = new sns.Topic(topicStack, 'Topic', { + topicName: 'topicName', + displayName: 'displayName', + }); + const fction = new lambda.Function(lambdaStack, 'MyFunc', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = function(e, c, cb) { return cb() }'), + }); + + topic1.addSubscription(new subs.LambdaSubscription(fction)); + + expect(lambdaStack).toMatchTemplate({ + 'Resources': { + 'MyFuncServiceRole54065130': { + 'Type': 'AWS::IAM::Role', + 'Properties': { + 'AssumeRolePolicyDocument': { + 'Statement': [ + { + 'Action': 'sts:AssumeRole', + 'Effect': 'Allow', + 'Principal': { + 'Service': 'lambda.amazonaws.com', + }, + }, + ], + 'Version': '2012-10-17', + }, + 'ManagedPolicyArns': [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', + ], + ], + }, + ], + }, + }, + 'MyFunc8A243A2C': { + 'Type': 'AWS::Lambda::Function', + 'Properties': { + 'Code': { + 'ZipFile': 'exports.handler = function(e, c, cb) { return cb() }', + }, + 'Role': { + 'Fn::GetAtt': [ + 'MyFuncServiceRole54065130', + 'Arn', + ], + }, + 'Handler': 'index.handler', + 'Runtime': 'nodejs10.x', + }, + 'DependsOn': [ + 'MyFuncServiceRole54065130', + ], + }, + 'MyFuncAllowInvokeTopicStackTopicFBF76EB3D4A699EF': { + 'Type': 'AWS::Lambda::Permission', + 'Properties': { + 'Action': 'lambda:InvokeFunction', + 'FunctionName': { + 'Fn::GetAtt': [ + 'MyFunc8A243A2C', + 'Arn', + ], + }, + 'Principal': 'sns.amazonaws.com', + 'SourceArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + }, + }, + 'MyFuncTopic3B7C24C5': { + 'Type': 'AWS::SNS::Subscription', + 'Properties': { + 'Protocol': 'lambda', + 'TopicArn': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':sns:us-east-1:11111111111:topicName', + ], + ], + }, + 'Endpoint': { + 'Fn::GetAtt': [ + 'MyFunc8A243A2C', + 'Arn', + ], + }, + 'Region': 'us-east-1', + }, + }, + }, + }); +}); + test('email subscription', () => { topic.addSubscription(new subs.EmailSubscription('foo@bar.com')); diff --git a/packages/@aws-cdk/aws-sns/lib/topic.ts b/packages/@aws-cdk/aws-sns/lib/topic.ts index f4bbfc10cb2ca..aa78dcfee6b80 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic.ts @@ -1,5 +1,5 @@ import { IKey } from '@aws-cdk/aws-kms'; -import { Stack } from '@aws-cdk/core'; +import { ArnFormat, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnTopic } from './sns.generated'; import { ITopic, TopicBase } from './topic-base'; @@ -63,7 +63,7 @@ export class Topic extends TopicBase { public static fromTopicArn(scope: Construct, id: string, topicArn: string): ITopic { class Import extends TopicBase { public readonly topicArn = topicArn; - public readonly topicName = Stack.of(scope).parseArn(topicArn).resource; + public readonly topicName = Stack.of(scope).splitArn(topicArn, ArnFormat.NO_RESOURCE_NAME).resource; protected autoCreatePolicy: boolean = false; } diff --git a/packages/@aws-cdk/aws-sns/package.json b/packages/@aws-cdk/aws-sns/package.json index 2250bcb72ba1b..b5f4039c63744 100644 --- a/packages/@aws-cdk/aws-sns/package.json +++ b/packages/@aws-cdk/aws-sns/package.json @@ -31,7 +31,14 @@ "excludeTypescript": [ "examples" ], - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index 874d8caf10d72..af74b209b3931 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-ssm/lib/util.ts b/packages/@aws-cdk/aws-ssm/lib/util.ts index d023ef902282d..c79a909628629 100644 --- a/packages/@aws-cdk/aws-ssm/lib/util.ts +++ b/packages/@aws-cdk/aws-ssm/lib/util.ts @@ -1,4 +1,4 @@ -import { Stack, Token } from '@aws-cdk/core'; +import { ArnFormat, Stack, Token } from '@aws-cdk/core'; import { IConstruct } from 'constructs'; export const AUTOGEN_MARKER = '$$autogen$$'; @@ -22,12 +22,19 @@ export function arnForParameterName(scope: IConstruct, parameterName: string, op throw new Error(`Parameter names must be fully qualified (if they include "/" they must also begin with a "/"): ${nameToValidate}`); } - return Stack.of(scope).formatArn({ - service: 'ssm', - resource: 'parameter', - sep: isSimpleName() ? '/' : '', - resourceName: parameterName, - }); + if (isSimpleName()) { + return Stack.of(scope).formatArn({ + service: 'ssm', + resource: 'parameter', + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, + resourceName: parameterName, + }); + } else { + return Stack.of(scope).formatArn({ + service: 'ssm', + resource: `parameter${parameterName}`, + }); + } /** * Determines the ARN separator for this parameter: if we have a concrete diff --git a/packages/@aws-cdk/aws-ssm/package.json b/packages/@aws-cdk/aws-ssm/package.json index 55ad40f1dfd04..cdf17bfb69c1c 100644 --- a/packages/@aws-cdk/aws-ssm/package.json +++ b/packages/@aws-cdk/aws-ssm/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-ssmcontacts/README.md b/packages/@aws-cdk/aws-ssmcontacts/README.md index 6418d35f765af..cab7c329a4bab 100644 --- a/packages/@aws-cdk/aws-ssmcontacts/README.md +++ b/packages/@aws-cdk/aws-ssmcontacts/README.md @@ -16,5 +16,5 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. ```ts -import ssmcontacts = require('@aws-cdk/aws-ssmcontacts'); +import * as ssmcontacts from '@aws-cdk/aws-ssmcontacts'; ``` diff --git a/packages/@aws-cdk/aws-ssmcontacts/package.json b/packages/@aws-cdk/aws-ssmcontacts/package.json index 7a5181ba58adb..603c082e46845 100644 --- a/packages/@aws-cdk/aws-ssmcontacts/package.json +++ b/packages/@aws-cdk/aws-ssmcontacts/package.json @@ -7,6 +7,13 @@ "jsii": { "outdir": "dist", "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, "targets": { "dotnet": { "namespace": "Amazon.CDK.AWS.SSMContacts", diff --git a/packages/monocdk/rosetta/basic.ts-fixture b/packages/@aws-cdk/aws-ssmcontacts/rosetta/default.ts-fixture similarity index 80% rename from packages/monocdk/rosetta/basic.ts-fixture rename to packages/@aws-cdk/aws-ssmcontacts/rosetta/default.ts-fixture index 0cc6d1104d521..e8deb6060d76d 100644 --- a/packages/monocdk/rosetta/basic.ts-fixture +++ b/packages/@aws-cdk/aws-ssmcontacts/rosetta/default.ts-fixture @@ -1,6 +1,6 @@ // Fixture with packages imported, but nothing else import { Construct } from 'constructs'; -import { Stack, Duration } from '@aws-cdk/core'; +import { Stack } from '@aws-cdk/core'; class Fixture extends Stack { constructor(scope: Construct, id: string) { @@ -9,4 +9,3 @@ class Fixture extends Stack { /// here } } - diff --git a/packages/@aws-cdk/aws-ssmincidents/README.md b/packages/@aws-cdk/aws-ssmincidents/README.md index 169151903df0d..411c652fc7d8c 100644 --- a/packages/@aws-cdk/aws-ssmincidents/README.md +++ b/packages/@aws-cdk/aws-ssmincidents/README.md @@ -16,5 +16,5 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. ```ts -import ssmincidents = require('@aws-cdk/aws-ssmincidents'); +import * as ssmincidents from '@aws-cdk/aws-ssmincidents'; ``` diff --git a/packages/@aws-cdk/aws-ssmincidents/package.json b/packages/@aws-cdk/aws-ssmincidents/package.json index 5cc2d1d7061c4..dfbe2be486b87 100644 --- a/packages/@aws-cdk/aws-ssmincidents/package.json +++ b/packages/@aws-cdk/aws-ssmincidents/package.json @@ -7,6 +7,13 @@ "jsii": { "outdir": "dist", "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, "targets": { "dotnet": { "namespace": "Amazon.CDK.AWS.SSMIncidents", diff --git a/packages/@aws-cdk/aws-ssmincidents/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-ssmincidents/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e8deb6060d76d --- /dev/null +++ b/packages/@aws-cdk/aws-ssmincidents/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-sso/README.md b/packages/@aws-cdk/aws-sso/README.md index 8da757ee4994c..d1f2b8988da89 100644 --- a/packages/@aws-cdk/aws-sso/README.md +++ b/packages/@aws-cdk/aws-sso/README.md @@ -16,5 +16,5 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. ```ts -import sso = require('@aws-cdk/aws-sso'); +import * as sso from '@aws-cdk/aws-sso'; ``` diff --git a/packages/@aws-cdk/aws-sso/lib/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-sso/lib/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e8deb6060d76d --- /dev/null +++ b/packages/@aws-cdk/aws-sso/lib/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-sso/package.json b/packages/@aws-cdk/aws-sso/package.json index 3146e96ec3d6d..38e6621ed0fa0 100644 --- a/packages/@aws-cdk/aws-sso/package.json +++ b/packages/@aws-cdk/aws-sso/package.json @@ -7,6 +7,13 @@ "jsii": { "outdir": "dist", "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, "targets": { "dotnet": { "namespace": "Amazon.CDK.AWS.SSO", diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md index f4721f21fda00..2f5d463067a35 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md @@ -700,20 +700,6 @@ new tasks.EmrCreateCluster(this, 'Create Cluster', { }); ``` -If you want to use an [auto-termination policy](https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-auto-termination-policy.html), -you can specify the `autoTerminationPolicy` property. Set the `idleTimeout` as a `Duration` between 60 seconds and 7 days. -`autoTerminationPolicy` requires the EMR release label to be 5.30.0 or above. - -```ts -new tasks.EmrCreateCluster(this, 'Create Cluster', { - instances: {}, - name: 'ClusterName', - autoTerminationPolicy: { - idleTimeout: Duration.seconds(120), - }, -}); -``` - ### Termination Protection Locks a cluster (job flow) so the EC2 instances in the cluster cannot be diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-http-api.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-http-api.ts index 876626ea05fd3..67def4d6a4699 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-http-api.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/apigateway/call-http-api.ts @@ -60,7 +60,7 @@ export class CallApiGatewayHttpApiEndpoint extends CallApiGatewayEndpointBase { return this.props.apiStack.formatArn({ service: 'execute-api', resource: apiId, - sep: '/', + arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, resourceName: `${stageName}/${method}${apiPath}`, }); } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-execution.ts index 6a9eaab73eb4b..23e4079b77921 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-execution.ts @@ -10,7 +10,7 @@ export interface AthenaGetQueryExecutionProps extends sfn.TaskStateBaseProps { /** * Query that will be retrieved * - * @example 'adfsaf-23trf23-f23rt23' + * Example value: `adfsaf-23trf23-f23rt23` */ readonly queryExecutionId: string; } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-results.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-results.ts index 07ec38efa97e1..700b43fc2a599 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-results.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-results.ts @@ -10,8 +10,8 @@ export interface AthenaGetQueryResultsProps extends sfn.TaskStateBaseProps { /** * Query that will be retrieved * - * @example 'adfsaf-23trf23-f23rt23' - */ + * Example value: `adfsaf-23trf23-f23rt23` + */ readonly queryExecutionId: string; /** diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts index f23bd66fc5591..ba3f68b95abb1 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts @@ -212,8 +212,9 @@ export interface ResultConfiguration { /** * S3 path of query results * + * Example value: `s3://query-results-bucket/folder/` + * * @default - Query Result Location set in Athena settings for this workgroup - * @example s3://query-results-bucket/folder/ */ readonly outputLocation?: s3.Location; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/shared-types.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/shared-types.ts index c07d1e8fc5e12..bed1b16a86f70 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/shared-types.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/dynamodb/shared-types.ts @@ -119,10 +119,10 @@ export class DynamoProjectionExpression { export class DynamoAttributeValue { /** * Sets an attribute of type String. For example: "S": "Hello" - * Strings may be literal values or as JsonPath. + * Strings may be literal values or as JsonPath. Example values: * - * @example `DynamoAttributeValue.fromString('someValue') - * @example `DynamoAttributeValue.fromString(JsonPath.stringAt('$.bar')) + * - `DynamoAttributeValue.fromString('someValue')` + * - `DynamoAttributeValue.fromString(JsonPath.stringAt('$.bar'))` */ public static fromString(value: string) { return new DynamoAttributeValue({ S: value }); @@ -219,6 +219,16 @@ export class DynamoAttributeValue { return new DynamoAttributeValue({ L: value.map((val) => val.toObject()) }); } + /** + * Sets an attribute of type List. For example: "L": [ {"S": "Cookies"} , {"S": "Coffee"}, {"S", "Veggies"}] + * + * @param value Json path that specifies state input to be used + */ + public static listFromJsonPath(value: string) { + validateJsonPath(value); + return new DynamoAttributeValue({ L: value }); + } + /** * Sets an attribute of type Null. For example: "NULL": true */ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts index 496decc60c851..26dbc84a00e56 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts @@ -356,14 +356,22 @@ export class EcsRunTask extends sfn.TaskStateBase implements ec2.IConnectable { * After - arn:aws:ecs:us-west-2:123456789012:task-definition/hello_world */ private getTaskDefinitionFamilyArn(): string { - const arnComponents = cdk.Stack.of(this).parseArn(this.props.taskDefinition.taskDefinitionArn); + const arnComponents = cdk.Stack.of(this).splitArn(this.props.taskDefinition.taskDefinitionArn, cdk.ArnFormat.SLASH_RESOURCE_NAME); let { resourceName } = arnComponents; if (resourceName) { resourceName = resourceName.split(':')[0]; } - return cdk.Stack.of(this).formatArn({ ...arnComponents, resourceName }); + return cdk.Stack.of(this).formatArn({ + partition: arnComponents.partition, + service: arnComponents.service, + account: arnComponents.account, + region: arnComponents.region, + resource: arnComponents.resource, + arnFormat: arnComponents.arnFormat, + resourceName, + }); } private taskExecutionRoles(): iam.IRole[] { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-create-cluster.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-create-cluster.ts index eed78656efec0..4cac7c7180bde 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 @@ -5,7 +5,6 @@ import { Construct } from 'constructs'; import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; import { ApplicationConfigPropertyToJson, - AutoTerminationPolicyPropertyToJson, BootstrapActionConfigToJson, ConfigurationPropertyToJson, InstancesConfigPropertyToJson, @@ -68,15 +67,6 @@ export interface EmrCreateClusterProps extends sfn.TaskStateBaseProps { */ readonly autoScalingRole?: iam.IRole; - /** - * An auto-termination policy for an Amazon EMR cluster. An auto-termination policy defines the amount of - * idle time in seconds after which a cluster automatically terminates. The value must be between - * 60 seconds and 7 days. - * - * @default - None - */ - readonly autoTerminationPolicy?: EmrCreateCluster.AutoTerminationPolicyProperty; - /** * A list of bootstrap actions to run before Hadoop starts on the cluster nodes. * @@ -279,7 +269,6 @@ export class EmrCreateCluster extends sfn.TaskStateBase { AdditionalInfo: cdk.stringToCloudFormation(this.props.additionalInfo), Applications: cdk.listMapper(ApplicationConfigPropertyToJson)(this.props.applications), AutoScalingRole: cdk.stringToCloudFormation(this._autoScalingRole?.roleName), - AutoTerminationPolicy: this.props.autoTerminationPolicy ? AutoTerminationPolicyPropertyToJson(this.props.autoTerminationPolicy) : undefined, BootstrapActions: cdk.listMapper(BootstrapActionConfigToJson)(this.props.bootstrapActions), Configurations: cdk.listMapper(ConfigurationPropertyToJson)(this.props.configurations), CustomAmiId: cdk.stringToCloudFormation(this.props.customAmiId), @@ -1400,20 +1389,6 @@ export namespace EmrCreateCluster { readonly version?: string; } - /** - * Auto-termination policy for the EMR cluster. - * - * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_AutoTerminationPolicy.html - * - */ - export interface AutoTerminationPolicyProperty { - - /** - * Specifies the amount of idle time after which the cluster automatically terminates. - */ - readonly idleTimeout: cdk.Duration; - } - /** * Configuration of the script to run during a bootstrap action. * diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/private/cluster-utils.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/private/cluster-utils.ts index fd05d71370584..c8ae8a50a360c 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/private/cluster-utils.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/private/cluster-utils.ts @@ -4,6 +4,8 @@ import { EmrModifyInstanceGroupByName } from '../emr-modify-instance-group-by-na /** * Render the KerberosAttributesProperty as JSON + * + * @param property */ export function KerberosAttributesPropertyToJson(property: EmrCreateCluster.KerberosAttributesProperty) { return { @@ -17,6 +19,8 @@ export function KerberosAttributesPropertyToJson(property: EmrCreateCluster.Kerb /** * Render the InstancesConfigProperty to JSON + * + * @param property */ export function InstancesConfigPropertyToJson(property: EmrCreateCluster.InstancesConfigProperty) { return { @@ -42,6 +46,8 @@ export function InstancesConfigPropertyToJson(property: EmrCreateCluster.Instanc /** * Render the ApplicationConfigProperty as JSON + * + * @param property */ export function ApplicationConfigPropertyToJson(property: EmrCreateCluster.ApplicationConfigProperty) { return { @@ -52,17 +58,10 @@ export function ApplicationConfigPropertyToJson(property: EmrCreateCluster.Appli }; } -/** - * Render the AutoTerminationPolicyProperty as JSON - */ -export function AutoTerminationPolicyPropertyToJson(property: EmrCreateCluster.AutoTerminationPolicyProperty) { - return { - IdleTimeout: cdk.numberToCloudFormation(property.idleTimeout.toSeconds()), - }; -} - /** * Render the ConfigurationProperty as JSON + * + * @param property */ export function ConfigurationPropertyToJson(property: EmrCreateCluster.ConfigurationProperty) { return { @@ -74,6 +73,8 @@ export function ConfigurationPropertyToJson(property: EmrCreateCluster.Configura /** * Render the EbsBlockDeviceConfigProperty as JSON + * + * @param property */ export function EbsBlockDeviceConfigPropertyToJson(property: EmrCreateCluster.EbsBlockDeviceConfigProperty) { return { @@ -88,6 +89,8 @@ export function EbsBlockDeviceConfigPropertyToJson(property: EmrCreateCluster.Eb /** * Render the EbsConfigurationProperty to JSON + * + * @param property */ export function EbsConfigurationPropertyToJson(property: EmrCreateCluster.EbsConfigurationProperty) { return { @@ -114,6 +117,8 @@ export function InstanceTypeConfigPropertyToJson(property: EmrCreateCluster.Inst /** * Render the InstanceFleetProvisioningSpecificationsProperty to JSON + * + * @param property */ export function InstanceFleetProvisioningSpecificationsPropertyToJson(property: EmrCreateCluster.InstanceFleetProvisioningSpecificationsProperty) { return { @@ -128,6 +133,8 @@ export function InstanceFleetProvisioningSpecificationsPropertyToJson(property: /** * Render the InstanceFleetConfigProperty as JSON + * + * @param property */ export function InstanceFleetConfigPropertyToJson(property: EmrCreateCluster.InstanceFleetConfigProperty) { return { @@ -145,6 +152,8 @@ export function InstanceFleetConfigPropertyToJson(property: EmrCreateCluster.Ins /** * Render the MetricDimensionProperty as JSON + * + * @param property */ export function MetricDimensionPropertyToJson(property: EmrCreateCluster.MetricDimensionProperty) { return { @@ -155,6 +164,8 @@ export function MetricDimensionPropertyToJson(property: EmrCreateCluster.MetricD /** * Render the ScalingTriggerProperty to JSON + * + * @param property */ export function ScalingTriggerPropertyToJson(property: EmrCreateCluster.ScalingTriggerProperty) { return { @@ -174,6 +185,8 @@ export function ScalingTriggerPropertyToJson(property: EmrCreateCluster.ScalingT /** * Render the ScalingActionProperty to JSON + * + * @param property */ export function ScalingActionPropertyToJson(property: EmrCreateCluster.ScalingActionProperty) { return { @@ -188,6 +201,8 @@ export function ScalingActionPropertyToJson(property: EmrCreateCluster.ScalingAc /** * Render the ScalingRuleProperty to JSON + * + * @param property */ export function ScalingRulePropertyToJson(property: EmrCreateCluster.ScalingRuleProperty) { return { @@ -200,6 +215,8 @@ export function ScalingRulePropertyToJson(property: EmrCreateCluster.ScalingRule /** * Render the AutoScalingPolicyProperty to JSON + * + * @param property */ export function AutoScalingPolicyPropertyToJson(property: EmrCreateCluster.AutoScalingPolicyProperty) { return { @@ -213,6 +230,8 @@ export function AutoScalingPolicyPropertyToJson(property: EmrCreateCluster.AutoS /** * Render the InstanceGroupConfigProperty to JSON + * + * @param property */ export function InstanceGroupConfigPropertyToJson(property: EmrCreateCluster.InstanceGroupConfigProperty) { return { @@ -231,6 +250,8 @@ export function InstanceGroupConfigPropertyToJson(property: EmrCreateCluster.Ins /** * Render the PlacementTypeProperty to JSON + * + * @param property */ export function PlacementTypePropertyToJson(property: EmrCreateCluster.PlacementTypeProperty) { return { @@ -241,6 +262,8 @@ export function PlacementTypePropertyToJson(property: EmrCreateCluster.Placement /** * Render the BootstrapActionProperty as JSON + * + * @param property */ export function BootstrapActionConfigToJson(property: EmrCreateCluster.BootstrapActionConfigProperty) { return { @@ -254,6 +277,8 @@ export function BootstrapActionConfigToJson(property: EmrCreateCluster.Bootstrap /** * Render the InstanceGroupModifyConfigProperty to JSON + * + * @param property */ export function InstanceGroupModifyConfigPropertyToJson(property: EmrModifyInstanceGroupByName.InstanceGroupModifyConfigProperty) { return { @@ -266,6 +291,8 @@ export function InstanceGroupModifyConfigPropertyToJson(property: EmrModifyInsta /** * Render the ShrinkPolicyProperty to JSON + * + * @param property */ function ShrinkPolicyPropertyToJson(property: EmrModifyInstanceGroupByName.ShrinkPolicyProperty) { return { @@ -276,6 +303,8 @@ function ShrinkPolicyPropertyToJson(property: EmrModifyInstanceGroupByName.Shrin /** * Render the InstanceResizePolicyProperty to JSON + * + * @param property */ function InstanceResizePolicyPropertyToJson(property: EmrModifyInstanceGroupByName.InstanceResizePolicyProperty) { return { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts index 7fb010119373b..5b90ce066c70d 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts @@ -12,7 +12,7 @@ export interface EvaluateExpressionProps extends sfn.TaskStateBaseProps { /** * The expression to evaluate. The expression may contain state paths. * - * @example '$.a + $.b' + * Example value: `'$.a + $.b'` */ readonly expression: string; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eventbridge/put-events.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eventbridge/put-events.ts index 457bdfa6d0011..670b7f01cb1df 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eventbridge/put-events.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eventbridge/put-events.ts @@ -16,9 +16,10 @@ export interface EventBridgePutEventsEntry { * * Can either be provided as an object or as a JSON-serialized string * @example - * sfn.TaskInput.fromText('{"instance-id": "i-1234567890abcdef0", "state": "terminated"}') - * sfn.TaskInput.fromObject({ Message: 'Hello from Step Functions' }) - * sfn.TaskInput.fromJsonPathAt('$.EventDetail') + * + * sfn.TaskInput.fromText('{"instance-id": "i-1234567890abcdef0", "state": "terminated"}'); + * sfn.TaskInput.fromObject({ Message: 'Hello from Step Functions' }); + * sfn.TaskInput.fromJsonPathAt('$.EventDetail'); */ readonly detail: sfn.TaskInput; @@ -40,7 +41,8 @@ export interface EventBridgePutEventsEntry { /** * The service or application that caused this event to be generated * - * @example 'com.example.service' + * Example value: `com.example.service` + * * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events.html */ readonly source: string; @@ -108,7 +110,7 @@ export class EventBridgePutEvents extends sfn.TaskStateBase { return cdk.Stack.of(this).formatArn({ resource: 'event-bus', resourceName: 'default', - sep: '/', + arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, service: 'events', }); } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/start-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/start-execution.ts index 0572459f1e4a3..d7d5ebdbda79a 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/start-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/start-execution.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; -import { Stack } from '@aws-cdk/core'; +import { ArnFormat, Stack } from '@aws-cdk/core'; import { getResourceArn } from './resource-arn-suffix'; /** @@ -103,8 +103,8 @@ export class StartExecution implements sfn.IStepFunctionsTask { resources: [stack.formatArn({ service: 'states', resource: 'execution', - sep: ':', - resourceName: `${stack.parseArn(this.stateMachine.stateMachineArn, ':').resourceName}*`, + arnFormat: ArnFormat.COLON_RESOURCE_NAME, + resourceName: `${stack.splitArn(this.stateMachine.stateMachineArn, ArnFormat.COLON_RESOURCE_NAME).resourceName}*`, })], })); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts index 638392e636b09..0916dbb720913 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/start-execution.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; -import { Stack } from '@aws-cdk/core'; +import { ArnFormat, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; @@ -133,8 +133,8 @@ export class StepFunctionsStartExecution extends sfn.TaskStateBase { stack.formatArn({ service: 'states', resource: 'execution', - sep: ':', - resourceName: `${stack.parseArn(this.props.stateMachine.stateMachineArn, ':').resourceName}*`, + arnFormat: ArnFormat.COLON_RESOURCE_NAME, + resourceName: `${stack.splitArn(this.props.stateMachine.stateMachineArn, ArnFormat.COLON_RESOURCE_NAME).resourceName}*`, }), ], }), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json index e520ee7473000..f1e8f3d215ed4 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -71,9 +78,10 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert-internal": "0.0.0", + "@aws-cdk/assertions": "0.0.0", "@aws-cdk/aws-apigatewayv2": "0.0.0", "@aws-cdk/aws-apigatewayv2-integrations": "0.0.0", + "@aws-cdk/aws-autoscaling": "0.0.0", "@aws-cdk/aws-batch": "0.0.0", "@aws-cdk/aws-databrew": "0.0.0", "@aws-cdk/aws-glue": "0.0.0", diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/aws-sdk/call-aws-service.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/aws-sdk/call-aws-service.test.ts index 89720879f7d9b..d3b9e3e0d42ba 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/aws-sdk/call-aws-service.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/aws-sdk/call-aws-service.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -48,7 +48,7 @@ test('CallAwsService task', () => { }, }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -94,7 +94,7 @@ test('with custom IAM action', () => { Parameters: {}, }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/run-batch-job.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/run-batch-job.test.ts index 1d290e8bba8eb..9b3e4ab8667af 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/run-batch-job.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/run-batch-job.test.ts @@ -3,6 +3,7 @@ import * as batch from '@aws-cdk/aws-batch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -34,266 +35,268 @@ beforeEach(() => { }); }); -test('Task with only the required parameters', () => { +describeDeprecated('RunBatchJob', () => { + test('Task with only the required parameters', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', - jobQueueArn: batchJobQueue.jobQueueArn, - }), - }); - - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::batch:submitJob.sync', - ], - ], - }, - End: true, - Parameters: { - JobDefinition: { Ref: 'JobDefinition24FFE3ED' }, - JobName: 'JobName', - JobQueue: { Ref: 'JobQueueEE3AD499' }, - }, - }); -}); - -test('Task with all the parameters', () => { - // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', - jobQueueArn: batchJobQueue.jobQueueArn, - arraySize: 15, - containerOverrides: { - command: ['sudo', 'rm'], - environment: { key: 'value' }, - instanceType: new ec2.InstanceType('MULTI'), - memory: 1024, - gpuCount: 1, - vcpus: 10, - }, - dependsOn: [{ jobId: '1234', type: 'some_type' }], - payload: { - foo: sfn.JsonPath.stringAt('$.bar'), - }, - attempts: 3, - timeout: cdk.Duration.seconds(60), - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }), - }); - - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::batch:submitJob', - ], - ], - }, - End: true, - Parameters: { - JobDefinition: { Ref: 'JobDefinition24FFE3ED' }, - JobName: 'JobName', - JobQueue: { Ref: 'JobQueueEE3AD499' }, - ArrayProperties: { Size: 15 }, - ContainerOverrides: { - Command: ['sudo', 'rm'], - Environment: [{ Name: 'key', Value: 'value' }], - InstanceType: 'MULTI', - Memory: 1024, - ResourceRequirements: [{ Type: 'GPU', Value: '1' }], - Vcpus: 10, - }, - DependsOn: [{ JobId: '1234', Type: 'some_type' }], - Parameters: { 'foo.$': '$.bar' }, - RetryStrategy: { Attempts: 3 }, - Timeout: { AttemptDurationSeconds: 60 }, - }, - }); -}); - -test('supports tokens', () => { - // WHEN - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobQueueArn: batchJobQueue.jobQueueArn, - jobName: sfn.JsonPath.stringAt('$.jobName'), - arraySize: sfn.JsonPath.numberAt('$.arraySize'), - timeout: cdk.Duration.seconds(sfn.JsonPath.numberAt('$.timeout')), - attempts: sfn.JsonPath.numberAt('$.attempts'), - }), - }); - - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::batch:submitJob.sync', - ], - ], - }, - End: true, - Parameters: { - 'JobDefinition': { Ref: 'JobDefinition24FFE3ED' }, - 'JobName.$': '$.jobName', - 'JobQueue': { Ref: 'JobQueueEE3AD499' }, - 'ArrayProperties': { - 'Size.$': '$.arraySize', - }, - 'RetryStrategy': { - 'Attempts.$': '$.attempts', - }, - 'Timeout': { - 'AttemptDurationSeconds.$': '$.timeout', - }, - }, - }); -}); - -test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { - expect(() => { - new sfn.Task(stack, 'Task', { + const task = new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', jobQueueArn: batchJobQueue.jobQueueArn, - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, }), }); - }).toThrow( - /Invalid Service Integration Pattern: WAIT_FOR_TASK_TOKEN is not supported to call RunBatchJob./i, - ); -}); -test('Task throws if environment in containerOverrides contain env with name starting with AWS_BATCH', () => { - expect(() => { - new sfn.Task(stack, 'Task', { + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::batch:submitJob.sync', + ], + ], + }, + End: true, + Parameters: { + JobDefinition: { Ref: 'JobDefinition24FFE3ED' }, + JobName: 'JobName', + JobQueue: { Ref: 'JobQueueEE3AD499' }, + }, + }); + }); + + test('Task with all the parameters', () => { + // WHEN + const task = new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', jobQueueArn: batchJobQueue.jobQueueArn, + arraySize: 15, containerOverrides: { - environment: { AWS_BATCH_MY_NAME: 'MY_VALUE' }, + command: ['sudo', 'rm'], + environment: { key: 'value' }, + instanceType: new ec2.InstanceType('MULTI'), + memory: 1024, + gpuCount: 1, + vcpus: 10, + }, + dependsOn: [{ jobId: '1234', type: 'some_type' }], + payload: { + foo: sfn.JsonPath.stringAt('$.bar'), }, + attempts: 3, + timeout: cdk.Duration.seconds(60), + integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, }), }); - }).toThrow( - /Invalid environment variable name: AWS_BATCH_MY_NAME. Environment variable names starting with 'AWS_BATCH' are reserved./i, - ); -}); -test('Task throws if arraySize is out of limits 2-10000', () => { - expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', - jobQueueArn: batchJobQueue.jobQueueArn, - arraySize: 1, - }), + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::batch:submitJob', + ], + ], + }, + End: true, + Parameters: { + JobDefinition: { Ref: 'JobDefinition24FFE3ED' }, + JobName: 'JobName', + JobQueue: { Ref: 'JobQueueEE3AD499' }, + ArrayProperties: { Size: 15 }, + ContainerOverrides: { + Command: ['sudo', 'rm'], + Environment: [{ Name: 'key', Value: 'value' }], + InstanceType: 'MULTI', + Memory: 1024, + ResourceRequirements: [{ Type: 'GPU', Value: '1' }], + Vcpus: 10, + }, + DependsOn: [{ JobId: '1234', Type: 'some_type' }], + Parameters: { 'foo.$': '$.bar' }, + RetryStrategy: { Attempts: 3 }, + Timeout: { AttemptDurationSeconds: 60 }, + }, }); - }).toThrow( - /arraySize must be between 2 and 10,000/, - ); + }); - expect(() => { - new sfn.Task(stack, 'Task', { + test('supports tokens', () => { + // WHEN + const task = new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', jobQueueArn: batchJobQueue.jobQueueArn, - arraySize: 10001, + jobName: sfn.JsonPath.stringAt('$.jobName'), + arraySize: sfn.JsonPath.numberAt('$.arraySize'), + timeout: cdk.Duration.seconds(sfn.JsonPath.numberAt('$.timeout')), + attempts: sfn.JsonPath.numberAt('$.attempts'), }), }); - }).toThrow( - /arraySize must be between 2 and 10,000/, - ); -}); -test('Task throws if dependencies exceeds 20', () => { - expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', - jobQueueArn: batchJobQueue.jobQueueArn, - dependsOn: [...Array(21).keys()].map(i => ({ - jobId: `${i}`, - type: `some_type-${i}`, - })), - }), + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::batch:submitJob.sync', + ], + ], + }, + End: true, + Parameters: { + 'JobDefinition': { Ref: 'JobDefinition24FFE3ED' }, + 'JobName.$': '$.jobName', + 'JobQueue': { Ref: 'JobQueueEE3AD499' }, + 'ArrayProperties': { + 'Size.$': '$.arraySize', + }, + 'RetryStrategy': { + 'Attempts.$': '$.attempts', + }, + 'Timeout': { + 'AttemptDurationSeconds.$': '$.timeout', + }, + }, }); - }).toThrow( - /dependencies must be 20 or less/, - ); -}); + }); -test('Task throws if attempts is out of limits 1-10', () => { - expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', - jobQueueArn: batchJobQueue.jobQueueArn, - attempts: 0, - }), - }); - }).toThrow( - /attempts must be between 1 and 10/, - ); + test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + }), + }); + }).toThrow( + /Invalid Service Integration Pattern: WAIT_FOR_TASK_TOKEN is not supported to call RunBatchJob./i, + ); + }); - expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', - jobQueueArn: batchJobQueue.jobQueueArn, - attempts: 11, - }), - }); - }).toThrow( - /attempts must be between 1 and 10/, - ); -}); + test('Task throws if environment in containerOverrides contain env with name starting with AWS_BATCH', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + containerOverrides: { + environment: { AWS_BATCH_MY_NAME: 'MY_VALUE' }, + }, + }), + }); + }).toThrow( + /Invalid environment variable name: AWS_BATCH_MY_NAME. Environment variable names starting with 'AWS_BATCH' are reserved./i, + ); + }); -test('Task throws if timeout is less than 60 sec', () => { - expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.RunBatchJob({ - jobDefinitionArn: batchJobDefinition.jobDefinitionArn, - jobName: 'JobName', - jobQueueArn: batchJobQueue.jobQueueArn, - timeout: cdk.Duration.seconds(59), - }), - }); - }).toThrow( - /timeout must be greater than 60 seconds/, - ); -}); + test('Task throws if arraySize is out of limits 2-10000', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + arraySize: 1, + }), + }); + }).toThrow( + /arraySize must be between 2 and 10,000/, + ); + + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + arraySize: 10001, + }), + }); + }).toThrow( + /arraySize must be between 2 and 10,000/, + ); + }); + + test('Task throws if dependencies exceeds 20', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + dependsOn: [...Array(21).keys()].map(i => ({ + jobId: `${i}`, + type: `some_type-${i}`, + })), + }), + }); + }).toThrow( + /dependencies must be 20 or less/, + ); + }); + + test('Task throws if attempts is out of limits 1-10', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + attempts: 0, + }), + }); + }).toThrow( + /attempts must be between 1 and 10/, + ); + + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + attempts: 11, + }), + }); + }).toThrow( + /attempts must be between 1 and 10/, + ); + }); + + test('Task throws if timeout is less than 60 sec', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunBatchJob({ + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobName: 'JobName', + jobQueueArn: batchJobQueue.jobQueueArn, + timeout: cdk.Duration.seconds(59), + }), + }); + }).toThrow( + /timeout must be greater than 60 seconds/, + ); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.expected.json index 094221d1ba6bb..a8b7510287767 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.expected.json @@ -193,7 +193,7 @@ { "Ref": "AWS::Partition" }, - ":states:::dynamodb:putItem\",\"Parameters\":{\"Item\":{\"MessageId\":{\"S\":\"1234\"},\"Text\":{\"S.$\":\"$.bar\"},\"TotalCount\":{\"N\":\"18\"},\"Activated\":{\"BOOL.$\":\"$.foo\"}},\"TableName\":\"", + ":states:::dynamodb:putItem\",\"Parameters\":{\"Item\":{\"MessageId\":{\"S\":\"1234\"},\"Text\":{\"S.$\":\"$.bar\"},\"TotalCount\":{\"N\":\"18\"},\"Activated\":{\"BOOL.$\":\"$.foo\"},\"List\":{\"L.$\":\"$.list\"}},\"TableName\":\"", { "Ref": "Messages804FA4EB" }, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.ts index e78bba4f6721e..18be323c22db3 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/integ.call-dynamodb.ts @@ -37,6 +37,7 @@ class CallDynamoDBStack extends cdk.Stack { Text: tasks.DynamoAttributeValue.fromString(sfn.JsonPath.stringAt('$.bar')), TotalCount: tasks.DynamoAttributeValue.fromNumber(firstNumber), Activated: tasks.DynamoAttributeValue.booleanFromJsonPath(sfn.JsonPath.stringAt('$.foo')), + List: tasks.DynamoAttributeValue.listFromJsonPath(sfn.JsonPath.stringAt('$.list')), }, table, }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/shared-types.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/shared-types.test.ts index f0810de3e3949..a882fb9f99b2b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/shared-types.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/dynamodb/shared-types.test.ts @@ -222,6 +222,22 @@ describe('DynamoAttributeValue', () => { }); }); + test('from list with json path', () => { + // GIVEN + const m = '$.path'; + // WHEN + const attribute = tasks.DynamoAttributeValue.listFromJsonPath( + sfn.JsonPath.stringAt(m), + ); + + // THEN + expect(sfn.FieldUtils.renderObject(attribute)).toEqual({ + attributeValue: { + 'L.$': m, + }, + }); + }); + test('from null', () => { // WHEN const attribute = tasks.DynamoAttributeValue.fromNull(true); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/ecs-tasks.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/ecs-tasks.test.ts index 74344f8139758..adc18b68f9e13 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/ecs-tasks.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/ecs-tasks.test.ts @@ -1,7 +1,9 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; +import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -14,381 +16,420 @@ beforeEach(() => { stack = new Stack(); vpc = new ec2.Vpc(stack, 'Vpc'); cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('Capacity', { - instanceType: new ec2.InstanceType('t3.medium'), - }); -}); - -test('Cannot create a Fargate task with a fargate-incompatible task definition', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { - memoryMiB: '512', - cpu: '256', - compatibility: ecs.Compatibility.EC2, - }); - taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromRegistry('foo/bar'), - memoryLimitMiB: 256, - }); - - expect(() => new tasks.RunEcsFargateTask({ cluster, taskDefinition })).toThrowError(/not configured for compatibility with Fargate/); + cluster.addAsgCapacityProvider(new ecs.AsgCapacityProvider(stack, 'Capacity', { + autoScalingGroup: new autoscaling.AutoScalingGroup(stack, 'ASG', { + vpc, + instanceType: new ec2.InstanceType('t3.medium'), + machineImage: ec2.MachineImage.latestAmazonLinux(), + }), + })); }); -test('Cannot create a Fargate task without a default container', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { - memoryMiB: '512', - cpu: '256', - compatibility: ecs.Compatibility.FARGATE, - }); - expect(() => new tasks.RunEcsFargateTask({ cluster, taskDefinition })).toThrowError(/must have at least one essential container/); -}); +describeDeprecated('ecs-tasks', () => { + test('Cannot create a Fargate task with a fargate-incompatible task definition', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + memoryMiB: '512', + cpu: '256', + compatibility: ecs.Compatibility.EC2, + }); + taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + memoryLimitMiB: 256, + }); -test('Running a Fargate Task', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { - memoryMiB: '512', - cpu: '256', - compatibility: ecs.Compatibility.FARGATE, - }); - const containerDefinition = taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromRegistry('foo/bar'), - memoryLimitMiB: 256, + expect(() => new tasks.RunEcsFargateTask({ cluster, taskDefinition })).toThrowError(/not configured for compatibility with Fargate/); }); - // WHEN - const runTask = new sfn.Task(stack, 'RunFargate', { - task: new tasks.RunEcsFargateTask({ - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - cluster, - taskDefinition, - containerOverrides: [ - { - containerDefinition, - environment: [ - { name: 'SOME_KEY', value: sfn.JsonPath.stringAt('$.SomeKey') }, - ], - }, - ], - }), + test('Cannot create a Fargate task without a default container', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + memoryMiB: '512', + cpu: '256', + compatibility: ecs.Compatibility.FARGATE, + }); + expect(() => new tasks.RunEcsFargateTask({ cluster, taskDefinition })).toThrowError(/must have at least one essential container/); }); - new sfn.StateMachine(stack, 'SM', { - definition: runTask, - }); + test('Running a Fargate Task', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + memoryMiB: '512', + cpu: '256', + compatibility: ecs.Compatibility.FARGATE, + }); + const containerDefinition = taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + memoryLimitMiB: 256, + }); - // THEN - expect(stack.resolve(runTask.toStateJson())).toEqual({ - End: true, - Parameters: { - Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, - LaunchType: 'FARGATE', - NetworkConfiguration: { - AwsvpcConfiguration: { - SecurityGroups: [{ 'Fn::GetAtt': ['RunFargateSecurityGroup709740F2', 'GroupId'] }], - Subnets: [{ Ref: 'VpcPrivateSubnet1Subnet536B997A' }, { Ref: 'VpcPrivateSubnet2Subnet3788AAA1' }], - }, - }, - TaskDefinition: { Ref: 'TD49C78F36' }, - Overrides: { - ContainerOverrides: [ + // WHEN + const runTask = new sfn.Task(stack, 'RunFargate', { + task: new tasks.RunEcsFargateTask({ + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + cluster, + taskDefinition, + containerOverrides: [ { - Environment: [ - { - 'Name': 'SOME_KEY', - 'Value.$': '$.SomeKey', - }, + containerDefinition, + environment: [ + { name: 'SOME_KEY', value: sfn.JsonPath.stringAt('$.SomeKey') }, ], - Name: 'TheContainer', }, ], + }), + }); + + new sfn.StateMachine(stack, 'SM', { + definition: runTask, + }); + + // THEN + expect(stack.resolve(runTask.toStateJson())).toEqual({ + End: true, + Parameters: { + Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, + LaunchType: 'FARGATE', + NetworkConfiguration: { + AwsvpcConfiguration: { + SecurityGroups: [{ 'Fn::GetAtt': ['RunFargateSecurityGroup709740F2', 'GroupId'] }], + Subnets: [{ Ref: 'VpcPrivateSubnet1Subnet536B997A' }, { Ref: 'VpcPrivateSubnet2Subnet3788AAA1' }], + }, + }, + TaskDefinition: { Ref: 'TD49C78F36' }, + Overrides: { + ContainerOverrides: [ + { + Environment: [ + { + 'Name': 'SOME_KEY', + 'Value.$': '$.SomeKey', + }, + ], + Name: 'TheContainer', + }, + ], + }, }, - }, - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::ecs:runTask.sync', + ], + ], + }, + Type: 'Task', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'ecs:RunTask', + Effect: 'Allow', + Resource: { Ref: 'TD49C78F36' }, + }, + { + Action: ['ecs:StopTask', 'ecs:DescribeTasks'], + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'iam:PassRole', + Effect: 'Allow', + Resource: [{ 'Fn::GetAtt': ['TDTaskRoleC497AFFC', 'Arn'] }], + }, { - Ref: 'AWS::Partition', + Action: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':events:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':rule/StepFunctionsGetEventsForECSTaskRule', + ], + ], + }, }, - ':states:::ecs:runTask.sync', ], - ], - }, - Type: 'Task', + }, + }); }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ - { - Action: 'ecs:RunTask', - Effect: 'Allow', - Resource: { Ref: 'TD49C78F36' }, - }, - { - Action: ['ecs:StopTask', 'ecs:DescribeTasks'], - Effect: 'Allow', - Resource: '*', - }, - { - Action: 'iam:PassRole', - Effect: 'Allow', - Resource: [{ 'Fn::GetAtt': ['TDTaskRoleC497AFFC', 'Arn'] }], - }, - { - Action: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'], - Effect: 'Allow', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { Ref: 'AWS::Partition' }, - ':events:', - { Ref: 'AWS::Region' }, - ':', - { Ref: 'AWS::AccountId' }, - ':rule/StepFunctionsGetEventsForECSTaskRule', - ], + test('Running an EC2 Task with bridge network', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + compatibility: ecs.Compatibility.EC2, + }); + const containerDefinition = taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + memoryLimitMiB: 256, + }); + + // WHEN + const runTask = new sfn.Task(stack, 'Run', { + task: new tasks.RunEcsEc2Task({ + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + cluster, + taskDefinition, + containerOverrides: [ + { + containerDefinition, + environment: [ + { name: 'SOME_KEY', value: sfn.JsonPath.stringAt('$.SomeKey') }, ], }, - }, - ], - }, - }); -}); + ], + }), + }); -test('Running an EC2 Task with bridge network', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { - compatibility: ecs.Compatibility.EC2, - }); - const containerDefinition = taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromRegistry('foo/bar'), - memoryLimitMiB: 256, - }); + new sfn.StateMachine(stack, 'SM', { + definition: runTask, + }); - // WHEN - const runTask = new sfn.Task(stack, 'Run', { - task: new tasks.RunEcsEc2Task({ - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - cluster, - taskDefinition, - containerOverrides: [ - { - containerDefinition, - environment: [ - { name: 'SOME_KEY', value: sfn.JsonPath.stringAt('$.SomeKey') }, + // THEN + expect(stack.resolve(runTask.toStateJson())).toEqual({ + End: true, + Parameters: { + Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, + LaunchType: 'EC2', + TaskDefinition: { Ref: 'TD49C78F36' }, + Overrides: { + ContainerOverrides: [ + { + Environment: [ + { + 'Name': 'SOME_KEY', + 'Value.$': '$.SomeKey', + }, + ], + Name: 'TheContainer', + }, ], }, - ], - }), - }); - - new sfn.StateMachine(stack, 'SM', { - definition: runTask, - }); + }, + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::ecs:runTask.sync', + ], + ], + }, + Type: 'Task', + }); - // THEN - expect(stack.resolve(runTask.toStateJson())).toEqual({ - End: true, - Parameters: { - Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, - LaunchType: 'EC2', - TaskDefinition: { Ref: 'TD49C78F36' }, - Overrides: { - ContainerOverrides: [ + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ { - Environment: [ - { - 'Name': 'SOME_KEY', - 'Value.$': '$.SomeKey', - }, - ], - Name: 'TheContainer', + Action: 'ecs:RunTask', + Effect: 'Allow', + Resource: { Ref: 'TD49C78F36' }, }, - ], - }, - }, - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', { - Ref: 'AWS::Partition', + Action: ['ecs:StopTask', 'ecs:DescribeTasks'], + Effect: 'Allow', + Resource: '*', }, - ':states:::ecs:runTask.sync', - ], - ], - }, - Type: 'Task', - }); - - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ - { - Action: 'ecs:RunTask', - Effect: 'Allow', - Resource: { Ref: 'TD49C78F36' }, - }, - { - Action: ['ecs:StopTask', 'ecs:DescribeTasks'], - Effect: 'Allow', - Resource: '*', - }, - { - Action: 'iam:PassRole', - Effect: 'Allow', - Resource: [{ 'Fn::GetAtt': ['TDTaskRoleC497AFFC', 'Arn'] }], - }, - { - Action: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'], - Effect: 'Allow', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { Ref: 'AWS::Partition' }, - ':events:', - { Ref: 'AWS::Region' }, - ':', - { Ref: 'AWS::AccountId' }, - ':rule/StepFunctionsGetEventsForECSTaskRule', + { + Action: 'iam:PassRole', + Effect: 'Allow', + Resource: [{ 'Fn::GetAtt': ['TDTaskRoleC497AFFC', 'Arn'] }], + }, + { + Action: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':events:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':rule/StepFunctionsGetEventsForECSTaskRule', + ], ], - ], + }, }, - }, - ], - }, + ], + }, + }); }); -}); -test('Running an EC2 Task with placement strategies', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { - compatibility: ecs.Compatibility.EC2, - }); - taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromRegistry('foo/bar'), - memoryLimitMiB: 256, - }); + test('Running an EC2 Task with placement strategies', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + compatibility: ecs.Compatibility.EC2, + }); + taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + memoryLimitMiB: 256, + }); - const ec2Task = new tasks.RunEcsEc2Task({ - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - cluster, - taskDefinition, - placementStrategies: [ecs.PlacementStrategy.spreadAcrossInstances(), ecs.PlacementStrategy.packedByCpu(), ecs.PlacementStrategy.randomly()], - placementConstraints: [ecs.PlacementConstraint.memberOf('blieptuut')], - }); + const ec2Task = new tasks.RunEcsEc2Task({ + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + cluster, + taskDefinition, + placementStrategies: [ecs.PlacementStrategy.spreadAcrossInstances(), ecs.PlacementStrategy.packedByCpu(), ecs.PlacementStrategy.randomly()], + placementConstraints: [ecs.PlacementConstraint.memberOf('blieptuut')], + }); - // WHEN - const runTask = new sfn.Task(stack, 'Run', { task: ec2Task }); + // WHEN + const runTask = new sfn.Task(stack, 'Run', { task: ec2Task }); - new sfn.StateMachine(stack, 'SM', { - definition: runTask, - }); + new sfn.StateMachine(stack, 'SM', { + definition: runTask, + }); - // THEN - expect(stack.resolve(runTask.toStateJson())).toEqual({ - End: true, - Parameters: { - Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, - LaunchType: 'EC2', - TaskDefinition: { Ref: 'TD49C78F36' }, - PlacementConstraints: [{ Type: 'memberOf', Expression: 'blieptuut' }], - PlacementStrategy: [{ Field: 'instanceId', Type: 'spread' }, { Field: 'cpu', Type: 'binpack' }, { Type: 'random' }], - }, - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::ecs:runTask.sync', + // THEN + expect(stack.resolve(runTask.toStateJson())).toEqual({ + End: true, + Parameters: { + Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, + LaunchType: 'EC2', + TaskDefinition: { Ref: 'TD49C78F36' }, + PlacementConstraints: [{ Type: 'memberOf', Expression: 'blieptuut' }], + PlacementStrategy: [{ Field: 'instanceId', Type: 'spread' }, { Field: 'cpu', Type: 'binpack' }, { Type: 'random' }], + }, + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::ecs:runTask.sync', + ], ], - ], - }, - Type: 'Task', + }, + Type: 'Task', + }); }); -}); -test('Running an EC2 Task with overridden number values', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { - compatibility: ecs.Compatibility.EC2, - }); - const containerDefinition = taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromRegistry('foo/bar'), - memoryLimitMiB: 256, - }); + test('Running an EC2 Task with overridden number values', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + compatibility: ecs.Compatibility.EC2, + }); + const containerDefinition = taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + memoryLimitMiB: 256, + }); - const ec2Task = new tasks.RunEcsEc2Task({ - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - cluster, - taskDefinition, - containerOverrides: [ - { - containerDefinition, - command: sfn.JsonPath.listAt('$.TheCommand'), - cpu: 5, - memoryLimit: sfn.JsonPath.numberAt('$.MemoryLimit'), - }, - ], - }); + const ec2Task = new tasks.RunEcsEc2Task({ + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + cluster, + taskDefinition, + containerOverrides: [ + { + containerDefinition, + command: sfn.JsonPath.listAt('$.TheCommand'), + cpu: 5, + memoryLimit: sfn.JsonPath.numberAt('$.MemoryLimit'), + }, + ], + }); - // WHEN - const runTask = new sfn.Task(stack, 'Run', { task: ec2Task }); + // WHEN + const runTask = new sfn.Task(stack, 'Run', { task: ec2Task }); - // THEN - expect(stack.resolve(runTask.toStateJson())).toEqual({ - End: true, - Parameters: { - Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, - LaunchType: 'EC2', - TaskDefinition: { Ref: 'TD49C78F36' }, - Overrides: { - ContainerOverrides: [ - { - 'Command.$': '$.TheCommand', - 'Cpu': 5, - 'Memory.$': '$.MemoryLimit', - 'Name': 'TheContainer', - }, + // THEN + expect(stack.resolve(runTask.toStateJson())).toEqual({ + End: true, + Parameters: { + Cluster: { 'Fn::GetAtt': ['ClusterEB0386A7', 'Arn'] }, + LaunchType: 'EC2', + TaskDefinition: { Ref: 'TD49C78F36' }, + Overrides: { + ContainerOverrides: [ + { + 'Command.$': '$.TheCommand', + 'Cpu': 5, + 'Memory.$': '$.MemoryLimit', + 'Name': 'TheContainer', + }, + ], + }, + }, + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::ecs:runTask.sync', + ], ], }, - }, - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', + Type: 'Task', + }); + }); + + test('Cannot create a task with WAIT_FOR_TASK_TOKEN if no TaskToken provided', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TaskDefinition', { + compatibility: ecs.Compatibility.EC2, + }); + + const containerDefinition = taskDefinition.addContainer('ContainerDefinition', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + }); + + expect(() => + new tasks.RunEcsEc2Task({ + cluster, + containerOverrides: [ { - Ref: 'AWS::Partition', + containerDefinition, + environment: [ + { + name: 'Foo', + value: 'Bar', + }, + ], }, - ':states:::ecs:runTask.sync', ], - ], - }, - Type: 'Task', + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + taskDefinition, + }), + ).toThrowError(/Task Token is required in at least one `containerOverrides.environment`/); }); -}); -test('Cannot create a task with WAIT_FOR_TASK_TOKEN if no TaskToken provided', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TaskDefinition', { - compatibility: ecs.Compatibility.EC2, - }); + test('Running a task with WAIT_FOR_TASK_TOKEN and task token in environment', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TaskDefinition', { + compatibility: ecs.Compatibility.EC2, + }); - const containerDefinition = taskDefinition.addContainer('ContainerDefinition', { - image: ecs.ContainerImage.fromRegistry('foo/bar'), - }); + const primaryContainerDef = taskDefinition.addContainer('PrimaryContainerDef', { + image: ecs.ContainerImage.fromRegistry('foo/primary'), + essential: true, + }); + + const sidecarContainerDef = taskDefinition.addContainer('SideCarContainerDef', { + image: ecs.ContainerImage.fromRegistry('foo/sidecar'), + essential: false, + }); - expect(() => - new tasks.RunEcsEc2Task({ + expect(() => new tasks.RunEcsEc2Task({ cluster, containerOverrides: [ { - containerDefinition, + containerDefinition: primaryContainerDef, environment: [ { name: 'Foo', @@ -396,51 +437,18 @@ test('Cannot create a task with WAIT_FOR_TASK_TOKEN if no TaskToken provided', ( }, ], }, + { + containerDefinition: sidecarContainerDef, + environment: [ + { + name: 'TaskToken.$', + value: sfn.JsonPath.taskToken, + }, + ], + }, ], integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, taskDefinition, - }), - ).toThrowError(/Task Token is required in at least one `containerOverrides.environment`/); -}); - -test('Running a task with WAIT_FOR_TASK_TOKEN and task token in environment', () => { - const taskDefinition = new ecs.TaskDefinition(stack, 'TaskDefinition', { - compatibility: ecs.Compatibility.EC2, - }); - - const primaryContainerDef = taskDefinition.addContainer('PrimaryContainerDef', { - image: ecs.ContainerImage.fromRegistry('foo/primary'), - essential: true, + })).not.toThrow(); }); - - const sidecarContainerDef = taskDefinition.addContainer('SideCarContainerDef', { - image: ecs.ContainerImage.fromRegistry('foo/sidecar'), - essential: false, - }); - - expect(() => new tasks.RunEcsEc2Task({ - cluster, - containerOverrides: [ - { - containerDefinition: primaryContainerDef, - environment: [ - { - name: 'Foo', - value: 'Bar', - }, - ], - }, - { - containerDefinition: sidecarContainerDef, - environment: [ - { - name: 'TaskToken.$', - value: sfn.JsonPath.taskToken, - }, - ], - }, - ], - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - taskDefinition, - })).not.toThrow(); }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/run-tasks.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/run-tasks.test.ts index 8e93cdd6d0aab..a1ff441ec9654 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/run-tasks.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs/run-tasks.test.ts @@ -1,4 +1,5 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; +import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as sfn from '@aws-cdk/aws-stepfunctions'; @@ -16,9 +17,13 @@ beforeEach(() => { stack = new Stack(); vpc = new ec2.Vpc(stack, 'Vpc'); cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('Capacity', { - instanceType: new ec2.InstanceType('t3.medium'), - }); + cluster.addAsgCapacityProvider(new ecs.AsgCapacityProvider(stack, 'Capacity', { + autoScalingGroup: new autoscaling.AutoScalingGroup(stack, 'ASG', { + vpc, + instanceType: new ec2.InstanceType('t3.medium'), + machineImage: ec2.MachineImage.latestAmazonLinux(), + }), + })); }); test('Cannot create a Fargate task with a fargate-incompatible task definition', () => { @@ -196,7 +201,7 @@ test('Running a Fargate Task', () => { Type: 'Task', }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -318,7 +323,7 @@ test('Running an EC2 Task with bridge network', () => { Type: 'Task', }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-add-step.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-add-step.test.ts index 56e970d049b51..4f2c2f9c2b2d6 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-add-step.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-add-step.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -305,7 +305,7 @@ test('task policies are generated', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [{ Action: [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-cancel-step.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-cancel-step.test.ts index bd9bcce3bef10..16cfac3d07327 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-cancel-step.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-cancel-step.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -51,7 +51,7 @@ test('task policies are generated', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-create-cluster.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-create-cluster.test.ts index 74342d66efb53..3d846ec1d06d2 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-create-cluster.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-create-cluster.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; @@ -605,7 +605,7 @@ test('Create Cluster without Roles', () => { }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [ @@ -620,7 +620,7 @@ test('Create Cluster without Roles', () => { // The stack renders the ec2.amazonaws.com Service principal id with a // Join to the URLSuffix - expect(stack).toHaveResourceLike('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [ @@ -646,7 +646,7 @@ test('Create Cluster without Roles', () => { }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [ @@ -864,58 +864,6 @@ test('Create Cluster with InstanceFleet with allocation strategy=capacity-optimi }); }); -test('Create Cluster with AutoTerminationPolicy', () => { - // WHEN - const task = new EmrCreateCluster(stack, 'Task', { - instances: {}, - clusterRole, - name: 'Cluster', - serviceRole, - autoScalingRole, - autoTerminationPolicy: { - idleTimeout: cdk.Duration.seconds(120), - }, - integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE, - }); - - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::elasticmapreduce:createCluster', - ], - ], - }, - End: true, - Parameters: { - Name: 'Cluster', - Instances: { - KeepJobFlowAliveWhenNoSteps: true, - }, - VisibleToAllUsers: true, - JobFlowRole: { - Ref: 'ClusterRoleD9CA7471', - }, - ServiceRole: { - Ref: 'ServiceRole4288B192', - }, - AutoScalingRole: { - Ref: 'AutoScalingRole015ADA0A', - }, - AutoTerminationPolicy: { - IdleTimeout: 120, - }, - }, - }); -}); - test('Create Cluster with InstanceFleet', () => { // WHEN const task = new EmrCreateCluster(stack, 'Task', { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-fleet-by-name.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-fleet-by-name.test.ts index 85bb737641440..33fc2a2f14f81 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-fleet-by-name.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-fleet-by-name.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -59,7 +59,7 @@ test('task policies are generated', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-group-by-name.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-group-by-name.test.ts index f2188455cd24b..4a44d0d64cd29 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-group-by-name.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-modify-instance-group-by-name.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -95,7 +95,7 @@ test('task policies are generated', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-set-cluster-termination-protection.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-set-cluster-termination-protection.test.ts index 9d4f1523378d3..8eb439d65e6f7 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-set-cluster-termination-protection.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-set-cluster-termination-protection.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -51,7 +51,7 @@ test('task policies are generated', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-terminate-cluster.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-terminate-cluster.test.ts index a7c2e795f7386..0b3391f24835b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-terminate-cluster.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/emr/emr-terminate-cluster.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -50,7 +50,7 @@ test('task policies are generated', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts index f56ce0d3bce70..95e27f0e71812 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/evaluate-expression.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import { Stack } from '@aws-cdk/core'; import * as tasks from '../lib'; @@ -18,7 +18,7 @@ test('Eval with Node.js', () => { }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { DefinitionString: { 'Fn::Join': [ '', @@ -33,7 +33,7 @@ test('Eval with Node.js', () => { }, }); - expect(stack).toHaveResource('AWS::Lambda::Function', { + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { Runtime: 'nodejs14.x', }); }); @@ -47,7 +47,7 @@ test('expression does not contain paths', () => { definition: task, }); - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { DefinitionString: { 'Fn::Join': [ '', @@ -72,7 +72,7 @@ test('with dash and underscore in path', () => { definition: task, }); - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { DefinitionString: { 'Fn::Join': [ '', diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/eventbridge/put-events.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/eventbridge/put-events.test.ts index be513fee1b0da..1b1c3f6ebe669 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/eventbridge/put-events.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/eventbridge/put-events.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as events from '@aws-cdk/aws-events'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; @@ -238,7 +238,7 @@ describe('Put Events', () => { new sfn.StateMachine(stack, 'State Machine', { definition: task }); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/run-glue-job-task.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/run-glue-job-task.test.ts index 47dd07fc83d9e..68baa9ef20804 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/run-glue-job-task.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/run-glue-job-task.test.ts @@ -1,5 +1,6 @@ -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Duration, Stack } from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -9,133 +10,135 @@ beforeEach(() => { stack = new Stack(); }); -test('Invoke glue job with just job ARN', () => { - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunGlueJobTask(jobName), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); +describeDeprecated('RunGlueJobTask', () => { + test('Invoke glue job with just job ARN', () => { + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunGlueJobTask(jobName), + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::glue:startJobRun', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::glue:startJobRun', + ], ], - ], - }, - End: true, - Parameters: { - JobName: jobName, - }, + }, + End: true, + Parameters: { + JobName: jobName, + }, + }); }); -}); -test('Invoke glue job with full properties', () => { - const jobArguments = { - key: 'value', - }; - const timeoutMinutes = 1440; - const timeout = Duration.minutes(timeoutMinutes); - const securityConfiguration = 'securityConfiguration'; - const notifyDelayAfterMinutes = 10; - const notifyDelayAfter = Duration.minutes(notifyDelayAfterMinutes); - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunGlueJobTask(jobName, { - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - arguments: jobArguments, - timeout, - securityConfiguration, - notifyDelayAfter, - }), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); + test('Invoke glue job with full properties', () => { + const jobArguments = { + key: 'value', + }; + const timeoutMinutes = 1440; + const timeout = Duration.minutes(timeoutMinutes); + const securityConfiguration = 'securityConfiguration'; + const notifyDelayAfterMinutes = 10; + const notifyDelayAfter = Duration.minutes(notifyDelayAfterMinutes); + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunGlueJobTask(jobName, { + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + arguments: jobArguments, + timeout, + securityConfiguration, + notifyDelayAfter, + }), + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::glue:startJobRun.sync', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::glue:startJobRun.sync', + ], ], - ], - }, - End: true, - Parameters: { - JobName: jobName, - Arguments: jobArguments, - Timeout: timeoutMinutes, - SecurityConfiguration: securityConfiguration, - NotificationProperty: { - NotifyDelayAfter: notifyDelayAfterMinutes, }, - }, - }); -}); - -test('permitted role actions limited to start job run if service integration pattern is FIRE_AND_FORGET', () => { - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunGlueJobTask(jobName, { - integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, - }), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); - - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [{ - Action: 'glue:StartJobRun', - }], - }, + End: true, + Parameters: { + JobName: jobName, + Arguments: jobArguments, + Timeout: timeoutMinutes, + SecurityConfiguration: securityConfiguration, + NotificationProperty: { + NotifyDelayAfter: notifyDelayAfterMinutes, + }, + }, + }); }); -}); -test('permitted role actions include start, get, and stop job run if service integration pattern is SYNC', () => { - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunGlueJobTask(jobName, { - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - }), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); + test('permitted role actions limited to start job run if service integration pattern is FIRE_AND_FORGET', () => { + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunGlueJobTask(jobName, { + integrationPattern: sfn.ServiceIntegrationPattern.FIRE_AND_FORGET, + }), + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [{ - Action: [ - 'glue:StartJobRun', - 'glue:GetJobRun', - 'glue:GetJobRuns', - 'glue:BatchStopJobRun', - ], - }], - }, + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [Match.objectLike({ + Action: 'glue:StartJobRun', + })], + }, + }); }); -}); -test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { - expect(() => { - new sfn.Task(stack, 'Task', { + test('permitted role actions include start, get, and stop job run if service integration pattern is SYNC', () => { + const task = new sfn.Task(stack, 'Task', { task: new tasks.RunGlueJobTask(jobName, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, }), }); - }).toThrow(/Invalid Service Integration Pattern: WAIT_FOR_TASK_TOKEN is not supported to call Glue./i); -}); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [Match.objectLike({ + Action: [ + 'glue:StartJobRun', + 'glue:GetJobRun', + 'glue:GetJobRuns', + 'glue:BatchStopJobRun', + ], + })], + }, + }); + }); + + test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunGlueJobTask(jobName, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + }), + }); + }).toThrow(/Invalid Service Integration Pattern: WAIT_FOR_TASK_TOKEN is not supported to call Glue./i); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/start-job-run.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/start-job-run.test.ts index b7a920a1abe9e..269a92774a0a1 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/start-job-run.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/start-job-run.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import { Duration, Stack } from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -129,11 +129,11 @@ test('permitted role actions limited to start job run if service integration pat definition: task, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: [{ + Statement: [Match.objectLike({ Action: 'glue:StartJobRun', - }], + })], }, }); }); @@ -148,16 +148,16 @@ test('permitted role actions include start, get, and stop job run if service int definition: task, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: [{ + Statement: [Match.objectLike({ Action: [ 'glue:StartJobRun', 'glue:GetJobRun', 'glue:GetJobRuns', 'glue:BatchStopJobRun', ], - }], + })], }, }); }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts index 926593e2e9d9c..6a39717ae11d7 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts @@ -1,64 +1,67 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import * as tasks from '../lib'; -test('Activity can be used in a Task', () => { +describeDeprecated('InvokeActivity', () => { + test('Activity can be used in a Task', () => { // GIVEN - const stack = new Stack(); + const stack = new Stack(); - // WHEN - const activity = new sfn.Activity(stack, 'Activity'); - const task = new sfn.Task(stack, 'Task', { task: new tasks.InvokeActivity(activity) }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); + // WHEN + const activity = new sfn.Activity(stack, 'Activity'); + const task = new sfn.Task(stack, 'Task', { task: new tasks.InvokeActivity(activity) }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { - DefinitionString: { - 'Fn::Join': ['', [ - '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', - { Ref: 'Activity04690B0A' }, - '"}}}', - ]], - }, + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { + DefinitionString: { + 'Fn::Join': ['', [ + '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', + { Ref: 'Activity04690B0A' }, + '"}}}', + ]], + }, + }); }); -}); -test('Activity Task metrics and Activity metrics are the same', () => { + test('Activity Task metrics and Activity metrics are the same', () => { // GIVEN - const stack = new Stack(); - const activity = new sfn.Activity(stack, 'Activity'); - const task = new sfn.Task(stack, 'Invoke', { task: new tasks.InvokeActivity(activity) }); + const stack = new Stack(); + const activity = new sfn.Activity(stack, 'Activity'); + const task = new sfn.Task(stack, 'Invoke', { task: new tasks.InvokeActivity(activity) }); - // WHEN - const activityMetrics = [ - activity.metricFailed(), - activity.metricHeartbeatTimedOut(), - activity.metricRunTime(), - activity.metricScheduled(), - activity.metricScheduleTime(), - activity.metricStarted(), - activity.metricSucceeded(), - activity.metricTime(), - activity.metricTimedOut(), - ]; + // WHEN + const activityMetrics = [ + activity.metricFailed(), + activity.metricHeartbeatTimedOut(), + activity.metricRunTime(), + activity.metricScheduled(), + activity.metricScheduleTime(), + activity.metricStarted(), + activity.metricSucceeded(), + activity.metricTime(), + activity.metricTimedOut(), + ]; - const taskMetrics = [ - task.metricFailed(), - task.metricHeartbeatTimedOut(), - task.metricRunTime(), - task.metricScheduled(), - task.metricScheduleTime(), - task.metricStarted(), - task.metricSucceeded(), - task.metricTime(), - task.metricTimedOut(), - ]; + const taskMetrics = [ + task.metricFailed(), + task.metricHeartbeatTimedOut(), + task.metricRunTime(), + task.metricScheduled(), + task.metricScheduleTime(), + task.metricStarted(), + task.metricSucceeded(), + task.metricTime(), + task.metricTimedOut(), + ]; - // THEN - for (let i = 0; i < activityMetrics.length; i++) { - expect(activityMetrics[i]).toEqual(taskMetrics[i]); - } -}); + // THEN + for (let i = 0; i < activityMetrics.length; i++) { + expect(activityMetrics[i]).toEqual(taskMetrics[i]); + } + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts index 04f86319088ce..940b45bd7486b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke-function.test.ts @@ -1,6 +1,7 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -15,43 +16,45 @@ beforeEach(() => { }); }); -test('Invoke lambda with function ARN', () => { +describeDeprecated('InvokeFunction', () => { + test('Invoke lambda with function ARN', () => { // WHEN - const task = new sfn.Task(stack, 'Task', { task: new tasks.InvokeFunction(fn) }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); + const task = new sfn.Task(stack, 'Task', { task: new tasks.InvokeFunction(fn) }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { - DefinitionString: { - 'Fn::Join': ['', [ - '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', - { 'Fn::GetAtt': ['Fn9270CBC0', 'Arn'] }, - '"}}}', - ]], - }, + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { + DefinitionString: { + 'Fn::Join': ['', [ + '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', + { 'Fn::GetAtt': ['Fn9270CBC0', 'Arn'] }, + '"}}}', + ]], + }, + }); }); -}); -test('Lambda function payload ends up in Parameters', () => { - new sfn.StateMachine(stack, 'SM', { - definition: new sfn.Task(stack, 'Task', { - task: new tasks.InvokeFunction(fn, { - payload: { - foo: sfn.JsonPath.stringAt('$.bar'), - }, + test('Lambda function payload ends up in Parameters', () => { + new sfn.StateMachine(stack, 'SM', { + definition: new sfn.Task(stack, 'Task', { + task: new tasks.InvokeFunction(fn, { + payload: { + foo: sfn.JsonPath.stringAt('$.bar'), + }, + }), }), - }), - }); + }); - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { - DefinitionString: { - 'Fn::Join': ['', [ - '{"StartAt":"Task","States":{"Task":{"End":true,"Parameters":{"foo.$":"$.bar"},"Type":"Task","Resource":"', - { 'Fn::GetAtt': ['Fn9270CBC0', 'Arn'] }, - '"}}}', - ]], - }, + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { + DefinitionString: { + 'Fn::Join': ['', [ + '{"StartAt":"Task","States":{"Task":{"End":true,"Parameters":{"foo.$":"$.bar"},"Type":"Task","Resource":"', + { 'Fn::GetAtt': ['Fn9270CBC0', 'Arn'] }, + '"}}}', + ]], + }, + }); }); -}); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts index 4accba8d46e22..cbbd0092706ab 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/invoke.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import { Stack } from '@aws-cdk/core'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts index d52099e90906e..7aa2ec16dccac 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts @@ -1,6 +1,6 @@ -import '@aws-cdk/assert-internal/jest'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -15,171 +15,173 @@ beforeEach(() => { }); }); -test('Invoke lambda with default magic ARN', () => { - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunLambdaTask(fn, { - payload: sfn.TaskInput.fromObject({ - foo: 'bar', +describeDeprecated('run lambda task', () => { + test('Invoke lambda with default magic ARN', () => { + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunLambdaTask(fn, { + payload: sfn.TaskInput.fromObject({ + foo: 'bar', + }), + invocationType: tasks.InvocationType.REQUEST_RESPONSE, + clientContext: 'eyJoZWxsbyI6IndvcmxkIn0=', + qualifier: '1', }), - invocationType: tasks.InvocationType.REQUEST_RESPONSE, - clientContext: 'eyJoZWxsbyI6IndvcmxkIn0=', - qualifier: '1', - }), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::lambda:invoke', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::lambda:invoke', + ], ], - ], - }, - End: true, - Parameters: { - FunctionName: { - Ref: 'Fn9270CBC0', }, - Payload: { - foo: 'bar', + End: true, + Parameters: { + FunctionName: { + Ref: 'Fn9270CBC0', + }, + Payload: { + foo: 'bar', + }, + InvocationType: 'RequestResponse', + ClientContext: 'eyJoZWxsbyI6IndvcmxkIn0=', + Qualifier: '1', }, - InvocationType: 'RequestResponse', - ClientContext: 'eyJoZWxsbyI6IndvcmxkIn0=', - Qualifier: '1', - }, + }); }); -}); -test('Lambda function can be used in a Task with Task Token', () => { - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunLambdaTask(fn, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - payload: sfn.TaskInput.fromObject({ - token: sfn.JsonPath.taskToken, + test('Lambda function can be used in a Task with Task Token', () => { + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunLambdaTask(fn, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + payload: sfn.TaskInput.fromObject({ + token: sfn.JsonPath.taskToken, + }), }), - }), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::lambda:invoke.waitForTaskToken', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::lambda:invoke.waitForTaskToken', + ], ], - ], - }, - End: true, - Parameters: { - FunctionName: { - Ref: 'Fn9270CBC0', }, - Payload: { - 'token.$': '$$.Task.Token', + End: true, + Parameters: { + FunctionName: { + Ref: 'Fn9270CBC0', + }, + Payload: { + 'token.$': '$$.Task.Token', + }, }, - }, + }); }); -}); -test('Lambda function is invoked with the state input as payload by default', () => { - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunLambdaTask(fn), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); + test('Lambda function is invoked with the state input as payload by default', () => { + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunLambdaTask(fn), + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::lambda:invoke', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::lambda:invoke', + ], ], - ], - }, - End: true, - Parameters: { - 'FunctionName': { - Ref: 'Fn9270CBC0', }, - 'Payload.$': '$', - }, - }); -}); - -test('Lambda function can be provided with the state input as the payload', () => { - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunLambdaTask(fn, { - payload: sfn.TaskInput.fromJsonPathAt('$'), - }), - }); - new sfn.StateMachine(stack, 'SM', { - definition: task, - }); - - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::lambda:invoke', - ], - ], - }, - End: true, - Parameters: { - 'FunctionName': { - Ref: 'Fn9270CBC0', + End: true, + Parameters: { + 'FunctionName': { + Ref: 'Fn9270CBC0', + }, + 'Payload.$': '$', }, - 'Payload.$': '$', - }, + }); }); -}); -test('Task throws if WAIT_FOR_TASK_TOKEN is supplied but task token is not included in payLoad', () => { - expect(() => { - new sfn.Task(stack, 'Task', { + test('Lambda function can be provided with the state input as the payload', () => { + const task = new sfn.Task(stack, 'Task', { task: new tasks.RunLambdaTask(fn, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + payload: sfn.TaskInput.fromJsonPathAt('$'), }), }); - }).toThrow(/Task Token is missing in payload/i); -}); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); -test('Task throws if SYNC is supplied as service integration pattern', () => { - expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.RunLambdaTask(fn, { - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - }), + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::lambda:invoke', + ], + ], + }, + End: true, + Parameters: { + 'FunctionName': { + Ref: 'Fn9270CBC0', + }, + 'Payload.$': '$', + }, }); - }).toThrow(/Invalid Service Integration Pattern: SYNC is not supported to call Lambda./i); -}); + }); + + test('Task throws if WAIT_FOR_TASK_TOKEN is supplied but task token is not included in payLoad', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunLambdaTask(fn, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + }), + }); + }).toThrow(/Task Token is missing in payload/i); + }); + + test('Task throws if SYNC is supplied as service integration pattern', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunLambdaTask(fn, { + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + }), + }); + }).toThrow(/Invalid Service Integration Pattern: SYNC is not supported to call Lambda./i); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint-config.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint-config.test.ts index 1c61906207399..39c9f9a02278b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint-config.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint-config.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import * as sfn from '@aws-cdk/aws-stepfunctions'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint.test.ts index ee543a7fb251f..a48703d51df61 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-endpoint.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-model.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-model.test.ts index cea5b585f83f7..69c941b385579 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-model.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-model.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts index e16d20f94cfeb..7e7a5eff26348 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-transform-job.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-transform-job.test.ts index 16f499e53708c..a3fdf27ba7dbb 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-transform-job.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-transform-job.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/update-endpoint.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/update-endpoint.test.ts index ee89086763aa6..90a58850e64a5 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/update-endpoint.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/update-endpoint.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish-to-topic.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish-to-topic.test.ts index 47bd200c97daa..59638c06d52d1 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish-to-topic.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sns/publish-to-topic.test.ts @@ -1,146 +1,149 @@ import * as sns from '@aws-cdk/aws-sns'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; -test('Publish literal message to SNS topic', () => { +describeDeprecated('PublishToTopic', () => { + test('Publish literal message to SNS topic', () => { // GIVEN - const stack = new cdk.Stack(); - const topic = new sns.Topic(stack, 'Topic'); - - // WHEN - const pub = new sfn.Task(stack, 'Publish', { - task: new tasks.PublishToTopic(topic, { - message: sfn.TaskInput.fromText('Publish this message'), - }), - }); - - // THEN - expect(stack.resolve(pub.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sns:publish', - ], - ], - }, - End: true, - Parameters: { - TopicArn: { Ref: 'TopicBFC7AF6E' }, - Message: 'Publish this message', - }, - }); -}); - -test('Publish JSON to SNS topic with task token', () => { - // GIVEN - const stack = new cdk.Stack(); - const topic = new sns.Topic(stack, 'Topic'); + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); - // WHEN - const pub = new sfn.Task(stack, 'Publish', { - task: new tasks.PublishToTopic(topic, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - message: sfn.TaskInput.fromObject({ - Input: 'Publish this message', - Token: sfn.JsonPath.taskToken, + // WHEN + const pub = new sfn.Task(stack, 'Publish', { + task: new tasks.PublishToTopic(topic, { + message: sfn.TaskInput.fromText('Publish this message'), }), - }), - }); + }); - // THEN - expect(stack.resolve(pub.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sns:publish.waitForTaskToken', + // THEN + expect(stack.resolve(pub.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sns:publish', + ], ], - ], - }, - End: true, - Parameters: { - TopicArn: { Ref: 'TopicBFC7AF6E' }, - Message: { - 'Input': 'Publish this message', - 'Token.$': '$$.Task.Token', }, - }, + End: true, + Parameters: { + TopicArn: { Ref: 'TopicBFC7AF6E' }, + Message: 'Publish this message', + }, + }); }); -}); -test('Task throws if WAIT_FOR_TASK_TOKEN is supplied but task token is not included in message', () => { - expect(() => { - // GIVEN + test('Publish JSON to SNS topic with task token', () => { + // GIVEN const stack = new cdk.Stack(); const topic = new sns.Topic(stack, 'Topic'); + // WHEN - new sfn.Task(stack, 'Publish', { + const pub = new sfn.Task(stack, 'Publish', { task: new tasks.PublishToTopic(topic, { integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - message: sfn.TaskInput.fromText('Publish this message'), + message: sfn.TaskInput.fromObject({ + Input: 'Publish this message', + Token: sfn.JsonPath.taskToken, + }), }), }); - // THEN - }).toThrow(/Task Token is missing in message/i); -}); -test('Publish to topic with ARN from payload', () => { - // GIVEN - const stack = new cdk.Stack(); - const topic = sns.Topic.fromTopicArn(stack, 'Topic', sfn.JsonPath.stringAt('$.topicArn')); - - // WHEN - const pub = new sfn.Task(stack, 'Publish', { - task: new tasks.PublishToTopic(topic, { - message: sfn.TaskInput.fromText('Publish this message'), - }), + // THEN + expect(stack.resolve(pub.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sns:publish.waitForTaskToken', + ], + ], + }, + End: true, + Parameters: { + TopicArn: { Ref: 'TopicBFC7AF6E' }, + Message: { + 'Input': 'Publish this message', + 'Token.$': '$$.Task.Token', + }, + }, + }); }); - // THEN - expect(stack.resolve(pub.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sns:publish', - ], - ], - }, - End: true, - Parameters: { - 'TopicArn.$': '$.topicArn', - 'Message': 'Publish this message', - }, + test('Task throws if WAIT_FOR_TASK_TOKEN is supplied but task token is not included in message', () => { + expect(() => { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + // WHEN + new sfn.Task(stack, 'Publish', { + task: new tasks.PublishToTopic(topic, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + message: sfn.TaskInput.fromText('Publish this message'), + }), + }); + // THEN + }).toThrow(/Task Token is missing in message/i); }); -}); -test('Task throws if SYNC is supplied as service integration pattern', () => { - expect(() => { + test('Publish to topic with ARN from payload', () => { + // GIVEN const stack = new cdk.Stack(); - const topic = new sns.Topic(stack, 'Topic'); + const topic = sns.Topic.fromTopicArn(stack, 'Topic', sfn.JsonPath.stringAt('$.topicArn')); - new sfn.Task(stack, 'Publish', { + // WHEN + const pub = new sfn.Task(stack, 'Publish', { task: new tasks.PublishToTopic(topic, { - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, message: sfn.TaskInput.fromText('Publish this message'), }), }); - }).toThrow(/Invalid Service Integration Pattern: SYNC is not supported to call SNS./i); -}); + + // THEN + expect(stack.resolve(pub.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sns:publish', + ], + ], + }, + End: true, + Parameters: { + 'TopicArn.$': '$.topicArn', + 'Message': 'Publish this message', + }, + }); + }); + + test('Task throws if SYNC is supplied as service integration pattern', () => { + expect(() => { + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + + new sfn.Task(stack, 'Publish', { + task: new tasks.PublishToTopic(topic, { + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + message: sfn.TaskInput.fromText('Publish this message'), + }), + }); + }).toThrow(/Invalid Service Integration Pattern: SYNC is not supported to call SNS./i); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-to-queue.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-to-queue.test.ts index 9e168d68705a6..abf2fdd365268 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-to-queue.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sqs/send-to-queue.test.ts @@ -1,5 +1,6 @@ import * as sqs from '@aws-cdk/aws-sqs'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as tasks from '../../lib'; @@ -12,200 +13,202 @@ beforeEach(() => { queue = new sqs.Queue(stack, 'Queue'); }); -test('Send message to queue', () => { +describeDeprecated('SendToQueue', () => { + test('Send message to queue', () => { // WHEN - const task = new sfn.Task(stack, 'Send', { - task: new tasks.SendToQueue(queue, { - messageBody: sfn.TaskInput.fromText('Send this message'), - messageDeduplicationId: sfn.JsonPath.stringAt('$.deduping'), - }), - }); + const task = new sfn.Task(stack, 'Send', { + task: new tasks.SendToQueue(queue, { + messageBody: sfn.TaskInput.fromText('Send this message'), + messageDeduplicationId: sfn.JsonPath.stringAt('$.deduping'), + }), + }); - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sqs:sendMessage', + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sqs:sendMessage', + ], ], - ], - }, - End: true, - Parameters: { - 'QueueUrl': { Ref: 'Queue4A7E3555' }, - 'MessageBody': 'Send this message', - 'MessageDeduplicationId.$': '$.deduping', - }, + }, + End: true, + Parameters: { + 'QueueUrl': { Ref: 'Queue4A7E3555' }, + 'MessageBody': 'Send this message', + 'MessageDeduplicationId.$': '$.deduping', + }, + }); }); -}); -test('Send message to SQS queue with task token', () => { + test('Send message to SQS queue with task token', () => { // WHEN - const task = new sfn.Task(stack, 'Send', { - task: new tasks.SendToQueue(queue, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - messageBody: sfn.TaskInput.fromObject({ - Input: 'Send this message', - Token: sfn.JsonPath.taskToken, + const task = new sfn.Task(stack, 'Send', { + task: new tasks.SendToQueue(queue, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + messageBody: sfn.TaskInput.fromObject({ + Input: 'Send this message', + Token: sfn.JsonPath.taskToken, + }), }), - }), - }); + }); - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sqs:sendMessage.waitForTaskToken', + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sqs:sendMessage.waitForTaskToken', + ], ], - ], - }, - End: true, - Parameters: { - QueueUrl: { Ref: 'Queue4A7E3555' }, - MessageBody: { - 'Input': 'Send this message', - 'Token.$': '$$.Task.Token', }, - }, + End: true, + Parameters: { + QueueUrl: { Ref: 'Queue4A7E3555' }, + MessageBody: { + 'Input': 'Send this message', + 'Token.$': '$$.Task.Token', + }, + }, + }); }); -}); -test('Task throws if WAIT_FOR_TASK_TOKEN is supplied but task token is not included in messageBody', () => { - expect(() => { + test('Task throws if WAIT_FOR_TASK_TOKEN is supplied but task token is not included in messageBody', () => { + expect(() => { // WHEN - new sfn.Task(stack, 'Send', { - task: new tasks.SendToQueue(queue, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - messageBody: sfn.TaskInput.fromText('Send this message'), - }), - }); + new sfn.Task(stack, 'Send', { + task: new tasks.SendToQueue(queue, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + messageBody: sfn.TaskInput.fromText('Send this message'), + }), + }); // THEN - }).toThrow(/Task Token is missing in messageBody/i); -}); + }).toThrow(/Task Token is missing in messageBody/i); + }); -test('Message body can come from state', () => { + test('Message body can come from state', () => { // WHEN - const task = new sfn.Task(stack, 'Send', { - task: new tasks.SendToQueue(queue, { - messageBody: sfn.TaskInput.fromJsonPathAt('$.theMessage'), - }), - }); + const task = new sfn.Task(stack, 'Send', { + task: new tasks.SendToQueue(queue, { + messageBody: sfn.TaskInput.fromJsonPathAt('$.theMessage'), + }), + }); - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sqs:sendMessage', + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sqs:sendMessage', + ], ], - ], - }, - End: true, - Parameters: { - 'QueueUrl': { Ref: 'Queue4A7E3555' }, - 'MessageBody.$': '$.theMessage', - }, + }, + End: true, + Parameters: { + 'QueueUrl': { Ref: 'Queue4A7E3555' }, + 'MessageBody.$': '$.theMessage', + }, + }); }); -}); -test('Message body can be an object', () => { + test('Message body can be an object', () => { // WHEN - const task = new sfn.Task(stack, 'Send', { - task: new tasks.SendToQueue(queue, { - messageBody: sfn.TaskInput.fromObject({ - literal: 'literal', - SomeInput: sfn.JsonPath.stringAt('$.theMessage'), + const task = new sfn.Task(stack, 'Send', { + task: new tasks.SendToQueue(queue, { + messageBody: sfn.TaskInput.fromObject({ + literal: 'literal', + SomeInput: sfn.JsonPath.stringAt('$.theMessage'), + }), }), - }), - }); + }); - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sqs:sendMessage', + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sqs:sendMessage', + ], ], - ], - }, - End: true, - Parameters: { - QueueUrl: { Ref: 'Queue4A7E3555' }, - MessageBody: { - 'literal': 'literal', - 'SomeInput.$': '$.theMessage', }, - }, + End: true, + Parameters: { + QueueUrl: { Ref: 'Queue4A7E3555' }, + MessageBody: { + 'literal': 'literal', + 'SomeInput.$': '$.theMessage', + }, + }, + }); }); -}); -test('Message body object can contain references', () => { + test('Message body object can contain references', () => { // WHEN - const task = new sfn.Task(stack, 'Send', { - task: new tasks.SendToQueue(queue, { - messageBody: sfn.TaskInput.fromObject({ - queueArn: queue.queueArn, + const task = new sfn.Task(stack, 'Send', { + task: new tasks.SendToQueue(queue, { + messageBody: sfn.TaskInput.fromObject({ + queueArn: queue.queueArn, + }), }), - }), - }); + }); - // THEN - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::sqs:sendMessage', + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::sqs:sendMessage', + ], ], - ], - }, - End: true, - Parameters: { - QueueUrl: { Ref: 'Queue4A7E3555' }, - MessageBody: { - queueArn: { 'Fn::GetAtt': ['Queue4A7E3555', 'Arn'] }, }, - }, + End: true, + Parameters: { + QueueUrl: { Ref: 'Queue4A7E3555' }, + MessageBody: { + queueArn: { 'Fn::GetAtt': ['Queue4A7E3555', 'Arn'] }, + }, + }, + }); }); -}); -test('Task throws if SYNC is supplied as service integration pattern', () => { - expect(() => { - new sfn.Task(stack, 'Send', { - task: new tasks.SendToQueue(queue, { - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - messageBody: sfn.TaskInput.fromText('Send this message'), - }), - }); - }).toThrow(/Invalid Service Integration Pattern: SYNC is not supported to call SQS./i); -}); + test('Task throws if SYNC is supplied as service integration pattern', () => { + expect(() => { + new sfn.Task(stack, 'Send', { + task: new tasks.SendToQueue(queue, { + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + messageBody: sfn.TaskInput.fromText('Send this message'), + }), + }); + }).toThrow(/Invalid Service Integration Pattern: SYNC is not supported to call SQS./i); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/start-execution.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/start-execution.test.ts index a1d5b582a62ba..f921a57e5b740 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/start-execution.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/start-execution.test.ts @@ -1,5 +1,6 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import * as tasks from '../lib'; @@ -12,216 +13,218 @@ beforeEach(() => { }); }); -test('Execute State Machine - Default - Fire and Forget', () => { - const task = new sfn.Task(stack, 'ChildTask', { - task: new tasks.StartExecution(child, { - input: { - foo: 'bar', - }, - name: 'myExecutionName', - }), - }); +describeDeprecated('StartExecution', () => { + test('Execute State Machine - Default - Fire and Forget', () => { + const task = new sfn.Task(stack, 'ChildTask', { + task: new tasks.StartExecution(child, { + input: { + foo: 'bar', + }, + name: 'myExecutionName', + }), + }); - new sfn.StateMachine(stack, 'ParentStateMachine', { - definition: task, - }); + new sfn.StateMachine(stack, 'ParentStateMachine', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::states:startExecution', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::states:startExecution', + ], ], - ], - }, - End: true, - Parameters: { - Input: { - foo: 'bar', }, - Name: 'myExecutionName', - StateMachineArn: { - Ref: 'ChildStateMachine9133117F', + End: true, + Parameters: { + Input: { + foo: 'bar', + }, + Name: 'myExecutionName', + StateMachineArn: { + Ref: 'ChildStateMachine9133117F', + }, }, - }, + }); }); -}); -test('Execute State Machine - Sync', () => { - const task = new sfn.Task(stack, 'ChildTask', { - task: new tasks.StartExecution(child, { - integrationPattern: sfn.ServiceIntegrationPattern.SYNC, - }), - }); + test('Execute State Machine - Sync', () => { + const task = new sfn.Task(stack, 'ChildTask', { + task: new tasks.StartExecution(child, { + integrationPattern: sfn.ServiceIntegrationPattern.SYNC, + }), + }); - new sfn.StateMachine(stack, 'ParentStateMachine', { - definition: task, - }); + new sfn.StateMachine(stack, 'ParentStateMachine', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::states:startExecution.sync', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::states:startExecution.sync', + ], ], - ], - }, - End: true, - Parameters: { - StateMachineArn: { - Ref: 'ChildStateMachine9133117F', }, - }, - }); + End: true, + Parameters: { + StateMachineArn: { + Ref: 'ChildStateMachine9133117F', + }, + }, + }); - expect(stack).toHaveResource('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ - { - Action: 'states:StartExecution', - Effect: 'Allow', - Resource: { - Ref: 'ChildStateMachine9133117F', + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'states:StartExecution', + Effect: 'Allow', + Resource: { + Ref: 'ChildStateMachine9133117F', + }, }, - }, - { - Action: [ - 'states:DescribeExecution', - 'states:StopExecution', - ], - Effect: 'Allow', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:', - { - Ref: 'AWS::Region', - }, - ':', - { - Ref: 'AWS::AccountId', - }, - ':execution:', - { - 'Fn::Select': [ - 6, - { - 'Fn::Split': [ - ':', - { - Ref: 'ChildStateMachine9133117F', - }, - ], - }, - ], - }, - '*', - ], + { + Action: [ + 'states:DescribeExecution', + 'states:StopExecution', ], - }, - }, - { - Action: [ - 'events:PutTargets', - 'events:PutRule', - 'events:DescribeRule', - ], - Effect: 'Allow', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':events:', - { - Ref: 'AWS::Region', - }, - ':', - { - Ref: 'AWS::AccountId', - }, - ':rule/StepFunctionsGetEventsForStepFunctionsExecutionRule', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':execution:', + { + 'Fn::Select': [ + 6, + { + 'Fn::Split': [ + ':', + { + Ref: 'ChildStateMachine9133117F', + }, + ], + }, + ], + }, + '*', + ], ], + }, + }, + { + Action: [ + 'events:PutTargets', + 'events:PutRule', + 'events:DescribeRule', ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':events:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':rule/StepFunctionsGetEventsForStepFunctionsExecutionRule', + ], + ], + }, }, + ], + Version: '2012-10-17', + }, + Roles: [ + { + Ref: 'ParentStateMachineRoleE902D002', }, ], - Version: '2012-10-17', - }, - Roles: [ - { - Ref: 'ParentStateMachineRoleE902D002', - }, - ], + }); }); -}); -test('Execute State Machine - Wait For Task Token', () => { - const task = new sfn.Task(stack, 'ChildTask', { - task: new tasks.StartExecution(child, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - input: { - token: sfn.JsonPath.taskToken, - }, - }), - }); + test('Execute State Machine - Wait For Task Token', () => { + const task = new sfn.Task(stack, 'ChildTask', { + task: new tasks.StartExecution(child, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + input: { + token: sfn.JsonPath.taskToken, + }, + }), + }); - new sfn.StateMachine(stack, 'ParentStateMachine', { - definition: task, - }); + new sfn.StateMachine(stack, 'ParentStateMachine', { + definition: task, + }); - expect(stack.resolve(task.toStateJson())).toEqual({ - Type: 'Task', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':states:::states:startExecution.waitForTaskToken', + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::states:startExecution.waitForTaskToken', + ], ], - ], - }, - End: true, - Parameters: { - Input: { - 'token.$': '$$.Task.Token', }, - StateMachineArn: { - Ref: 'ChildStateMachine9133117F', + End: true, + Parameters: { + Input: { + 'token.$': '$$.Task.Token', + }, + StateMachineArn: { + Ref: 'ChildStateMachine9133117F', + }, }, - }, + }); }); -}); -test('Execute State Machine - Wait For Task Token - Missing Task Token', () => { - expect(() => { - new sfn.Task(stack, 'ChildTask', { - task: new tasks.StartExecution(child, { - integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - }), - }); - }).toThrow('Task Token is missing in input (pass JsonPath.taskToken somewhere in input'); -}); + test('Execute State Machine - Wait For Task Token - Missing Task Token', () => { + expect(() => { + new sfn.Task(stack, 'ChildTask', { + task: new tasks.StartExecution(child, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + }), + }); + }).toThrow('Task Token is missing in input (pass JsonPath.taskToken somewhere in input'); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.test.ts similarity index 92% rename from packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.ts rename to packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.test.ts index fb7cc2302130b..dfcf84183155a 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import { Stack } from '@aws-cdk/core'; import { StepFunctionsInvokeActivity } from '../../lib/stepfunctions/invoke-activity'; @@ -15,7 +15,7 @@ test('Activity can be used in a Task', () => { }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { DefinitionString: { 'Fn::Join': ['', [ '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/start-execution.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/start-execution.test.ts index 33a70c17265b6..48e03fca934b9 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/start-execution.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/start-execution.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import { Stack } from '@aws-cdk/core'; import { StepFunctionsStartExecution } from '../../lib/stepfunctions/start-execution'; @@ -85,7 +85,7 @@ test('Execute State Machine - Run Job', () => { }, }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts index 3f474c93d7a1d..a226142e11959 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts @@ -1,6 +1,6 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; -import { IResource, Lazy, Names, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, IResource, Lazy, Names, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { StatesMetrics } from './stepfunctions-canned-metrics.generated'; import { CfnActivity } from './stepfunctions.generated'; @@ -28,7 +28,7 @@ export class Activity extends Resource implements IActivity { class Imported extends Resource implements IActivity { public get activityArn() { return activityArn; } public get activityName() { - return Stack.of(this).parseArn(activityArn, ':').resourceName || ''; + return Stack.of(this).splitArn(activityArn, ArnFormat.COLON_RESOURCE_NAME).resourceName || ''; } } @@ -43,7 +43,7 @@ export class Activity extends Resource implements IActivity { service: 'states', resource: 'activity', resourceName: activityName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, })); } @@ -71,7 +71,7 @@ export class Activity extends Resource implements IActivity { service: 'states', resource: 'activity', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); this.activityName = this.getResourceNameAttribute(resource.attrName); } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts index d7e7a73efa3f0..a75a5f1843992 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts @@ -1,7 +1,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; -import { Arn, Duration, IResource, Resource, Stack, Token } from '@aws-cdk/core'; +import { Arn, ArnFormat, Duration, IResource, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { StateGraph } from './state-graph'; import { StatesMetrics } from './stepfunctions-canned-metrics.generated'; @@ -244,7 +244,7 @@ abstract class StateMachineBase extends Resource implements IStateMachine { return new cloudwatch.Metric({ namespace: 'AWS/States', metricName, - dimensions: { StateMachineArn: this.stateMachineArn }, + dimensionsMap: { StateMachineArn: this.stateMachineArn }, statistic: 'sum', ...props, }).attachTo(this); @@ -333,8 +333,8 @@ abstract class StateMachineBase extends Resource implements IStateMachine { return Stack.of(this).formatArn({ resource: 'execution', service: 'states', - resourceName: Arn.parse(this.stateMachineArn, ':').resourceName, - sep: ':', + resourceName: Arn.split(this.stateMachineArn, ArnFormat.COLON_RESOURCE_NAME).resourceName, + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } @@ -412,7 +412,7 @@ export class StateMachine extends StateMachineBase { service: 'states', resource: 'stateMachine', resourceName: this.physicalName, - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, }); } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-transition-metrics.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-transition-metrics.ts index 953a73bb33192..c3a9a12e2c111 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-transition-metrics.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-transition-metrics.ts @@ -15,7 +15,7 @@ export class StateTransitionMetric { return new cloudwatch.Metric({ namespace: 'AWS/States', metricName, - dimensions: { ServiceMetric: 'StateTransition' }, + dimensionsMap: { ServiceMetric: 'StateTransition' }, ...props, }); } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts index 5743c0171d188..ae0859a35c69b 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task-base.ts @@ -168,7 +168,7 @@ export abstract class TaskStateBase extends State implements INextable { return new cloudwatch.Metric({ namespace: 'AWS/States', metricName, - dimensions: this.taskMetrics?.metricDimensions, + dimensionsMap: this.taskMetrics?.metricDimensions, statistic: 'sum', ...props, }).attachTo(this); diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts index 8386b3ce334b1..f0fd18d22c090 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts @@ -18,21 +18,21 @@ export class WaitTime { /** * Wait until the given ISO8601 timestamp * - * @example 2016-03-14T01:59:00Z + * Example value: `2016-03-14T01:59:00Z` */ public static timestamp(timestamp: string) { return new WaitTime({ Timestamp: timestamp }); } /** * Wait for a number of seconds stored in the state object. * - * @example $.waitSeconds + * Example value: `$.waitSeconds` */ public static secondsPath(path: string) { return new WaitTime({ SecondsPath: path }); } /** * Wait until a timestamp found in the state object. * - * @example $.waitTimestamp + * Example value: `$.waitTimestamp` */ public static timestampPath(path: string) { return new WaitTime({ TimestampPath: path }); } diff --git a/packages/@aws-cdk/aws-stepfunctions/package.json b/packages/@aws-cdk/aws-stepfunctions/package.json index 4d596d16b697f..40cea293bc09f 100644 --- a/packages/@aws-cdk/aws-stepfunctions/package.json +++ b/packages/@aws-cdk/aws-stepfunctions/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -72,7 +79,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert-internal": "0.0.0", + "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-stepfunctions/test/activity.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/activity.test.ts index f2ca02a571ffc..ec89fbfe3a964 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/activity.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/activity.test.ts @@ -1,5 +1,4 @@ -import { arrayWith, objectLike } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as stepfunctions from '../lib'; @@ -13,7 +12,7 @@ describe('Activity', () => { new stepfunctions.Activity(stack, 'Activity'); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::Activity', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::Activity', { Name: 'Activity', }); }); @@ -58,15 +57,15 @@ describe('Activity', () => { activity.grant(role, 'states:SendTaskSuccess'); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith(objectLike({ + Statement: Match.arrayWith([Match.objectLike({ Action: 'states:SendTaskSuccess', Effect: 'Allow', Resource: { Ref: 'Activity04690B0A', }, - })), + })]), }, }); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/condition.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/condition.test.ts index 6689cfcb64530..2af4fa09e66ab 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/condition.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/condition.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as stepfunctions from '../lib'; describe('Condition Variables', () => { diff --git a/packages/@aws-cdk/aws-stepfunctions/test/custom-state.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/custom-state.test.ts index c03b716eb6248..9dc1e278097a8 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/custom-state.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/custom-state.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as sfn from '../lib'; import { render } from './private/render-util'; diff --git a/packages/@aws-cdk/aws-stepfunctions/test/fail.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/fail.test.ts index a8e94b156466c..98c3295d0a68a 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/fail.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/fail.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as stepfunctions from '../lib'; diff --git a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts index 85549614a0538..0e6e3e9c25175 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import { FieldUtils, JsonPath, TaskInput } from '../lib'; describe('Fields', () => { diff --git a/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts index e5e379f578800..7840d2207f9a3 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/map.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as stepfunctions from '../lib'; diff --git a/packages/@aws-cdk/aws-stepfunctions/test/parallel.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/parallel.test.ts index b89a567b22826..9ca3bd8876781 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/parallel.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/parallel.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as stepfunctions from '../lib'; diff --git a/packages/@aws-cdk/aws-stepfunctions/test/pass.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/pass.test.ts index f3cc78bb68455..75779bd62f381 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/pass.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/pass.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import { Result } from '../lib'; describe('Pass State', () => { diff --git a/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts index 528b79b59dd58..78448372842b9 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts @@ -1,12 +1,13 @@ -import { arrayWith, objectLike, ResourcePart } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; import * as stepfunctions from '../lib'; describe('State Machine Resources', () => { - test('Tasks can add permissions to the execution role', () => { + testDeprecated('Tasks can add permissions to the execution role', () => { // GIVEN const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { @@ -27,7 +28,7 @@ describe('State Machine Resources', () => { }); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Version: '2012-10-17', Statement: [ @@ -44,18 +45,13 @@ describe('State Machine Resources', () => { test('Tasks hidden inside a Parallel state are also included', () => { // GIVEN const stack = new cdk.Stack(); - const task = new stepfunctions.Task(stack, 'Task', { - task: { - bind: () => ({ - resourceArn: 'resource', - policyStatements: [ - new iam.PolicyStatement({ - actions: ['resource:Everything'], - resources: ['resource'], - }), - ], + const task = new FakeTask(stack, 'Task', { + policies: [ + new iam.PolicyStatement({ + actions: ['resource:Everything'], + resources: ['resource'], }), - }, + ], }); const para = new stepfunctions.Parallel(stack, 'Para'); @@ -67,7 +63,7 @@ describe('State Machine Resources', () => { }); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Version: '2012-10-17', Statement: [ @@ -81,7 +77,7 @@ describe('State Machine Resources', () => { }); }), - test('Task should render InputPath / Parameters / OutputPath correctly', () => { + testDeprecated('Task should render InputPath / Parameters / OutputPath correctly', () => { // GIVEN const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { @@ -128,7 +124,7 @@ describe('State Machine Resources', () => { }); }), - test('Task combines taskobject parameters with direct parameters', () => { + testDeprecated('Task combines taskobject parameters with direct parameters', () => { // GIVEN const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { @@ -174,11 +170,7 @@ describe('State Machine Resources', () => { test('Created state machine can grant start execution to a role', () => { // GIVEN const stack = new cdk.Stack(); - const task = new stepfunctions.Task(stack, 'Task', { - task: { - bind: () => ({ resourceArn: 'resource' }), - }, - }); + const task = new FakeTask(stack, 'Task'); const stateMachine = new stepfunctions.StateMachine(stack, 'StateMachine', { definition: task, }); @@ -190,15 +182,15 @@ describe('State Machine Resources', () => { stateMachine.grantStartExecution(role); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith(objectLike({ + Statement: Match.arrayWith([Match.objectLike({ Action: 'states:StartExecution', Effect: 'Allow', Resource: { Ref: 'StateMachine2E01A3A5', }, - })), + })]), }, }); @@ -207,11 +199,7 @@ describe('State Machine Resources', () => { test('Created state machine can grant read access to a role', () => { // GIVEN const stack = new cdk.Stack(); - const task = new stepfunctions.Task(stack, 'Task', { - task: { - bind: () => ({ resourceArn: 'resource' }), - }, - }); + const task = new FakeTask(stack, 'Task'); const stateMachine = new stepfunctions.StateMachine(stack, 'StateMachine', { definition: task, }); @@ -223,7 +211,7 @@ describe('State Machine Resources', () => { stateMachine.grantRead(role); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -297,11 +285,7 @@ describe('State Machine Resources', () => { test('Created state machine can grant task response actions to the state machine', () => { // GIVEN const stack = new cdk.Stack(); - const task = new stepfunctions.Task(stack, 'Task', { - task: { - bind: () => ({ resourceArn: 'resource' }), - }, - }); + const task = new FakeTask(stack, 'Task'); const stateMachine = new stepfunctions.StateMachine(stack, 'StateMachine', { definition: task, }); @@ -313,7 +297,7 @@ describe('State Machine Resources', () => { stateMachine.grantTaskResponse(role); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -335,11 +319,7 @@ describe('State Machine Resources', () => { test('Created state machine can grant actions to the executions', () => { // GIVEN const stack = new cdk.Stack(); - const task = new stepfunctions.Task(stack, 'Task', { - task: { - bind: () => ({ resourceArn: 'resource' }), - }, - }); + const task = new FakeTask(stack, 'Task'); const stateMachine = new stepfunctions.StateMachine(stack, 'StateMachine', { definition: task, }); @@ -351,7 +331,7 @@ describe('State Machine Resources', () => { stateMachine.grantExecution(role, 'states:GetExecutionHistory'); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -400,11 +380,7 @@ describe('State Machine Resources', () => { test('Created state machine can grant actions to a role', () => { // GIVEN const stack = new cdk.Stack(); - const task = new stepfunctions.Task(stack, 'Task', { - task: { - bind: () => ({ resourceArn: 'resource' }), - }, - }); + const task = new FakeTask(stack, 'Task'); const stateMachine = new stepfunctions.StateMachine(stack, 'StateMachine', { definition: task, }); @@ -416,7 +392,7 @@ describe('State Machine Resources', () => { stateMachine.grant(role, 'states:ListExecution'); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -445,7 +421,7 @@ describe('State Machine Resources', () => { stateMachine.grantStartExecution(role); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -478,7 +454,7 @@ describe('State Machine Resources', () => { stateMachine.grantRead(role); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -545,7 +521,7 @@ describe('State Machine Resources', () => { stateMachine.grantTaskResponse(role); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -575,7 +551,7 @@ describe('State Machine Resources', () => { stateMachine.grant(role, 'states:ListExecution'); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -675,30 +651,48 @@ describe('State Machine Resources', () => { test('State machines must depend on their roles', () => { // GIVEN const stack = new cdk.Stack(); - const task = new stepfunctions.Task(stack, 'Task', { - task: { - bind: () => ({ - resourceArn: 'resource', - policyStatements: [ - new iam.PolicyStatement({ - resources: ['resource'], - actions: ['lambda:InvokeFunction'], - }), - ], + const task = new FakeTask(stack, 'Task', { + policies: [ + new iam.PolicyStatement({ + resources: ['resource'], + actions: ['lambda:InvokeFunction'], }), - }, + ], }); new stepfunctions.StateMachine(stack, 'StateMachine', { definition: task, }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResource('AWS::StepFunctions::StateMachine', { DependsOn: [ 'StateMachineRoleDefaultPolicyDF1E6607', 'StateMachineRoleB840431D', ], - }, ResourcePart.CompleteDefinition); + }); }); }); + +interface FakeTaskProps extends stepfunctions.TaskStateBaseProps { + readonly policies?: iam.PolicyStatement[]; +} + +class FakeTask extends stepfunctions.TaskStateBase { + protected readonly taskMetrics?: stepfunctions.TaskMetricsConfig; + protected readonly taskPolicies?: iam.PolicyStatement[]; + + constructor(scope: Construct, id: string, props: FakeTaskProps = {}) { + super(scope, id, props); + this.taskPolicies = props.policies; + } + + protected _renderTask(): any { + return { + Resource: 'my-resource', + Parameters: stepfunctions.FieldUtils.renderObject({ + MyParameter: 'myParameter', + }), + }; + } +} diff --git a/packages/@aws-cdk/aws-stepfunctions/test/state-machine.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/state-machine.test.ts index 6db3dd6b476bd..f60c0b756e66a 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/state-machine.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/state-machine.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; @@ -16,7 +16,7 @@ describe('State Machine', () => { }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { StateMachineName: 'MyStateMachine', DefinitionString: '{"StartAt":"Pass","States":{"Pass":{"Type":"Pass","End":true}}}', }); @@ -34,7 +34,7 @@ describe('State Machine', () => { }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { StateMachineName: 'MyStateMachine', StateMachineType: 'STANDARD', DefinitionString: '{"StartAt":"Pass","States":{"Pass":{"Type":"Pass","End":true}}}', @@ -54,7 +54,7 @@ describe('State Machine', () => { }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { StateMachineName: 'MyStateMachine', StateMachineType: 'EXPRESS', DefinitionString: '{"StartAt":"Pass","States":{"Pass":{"Type":"Pass","End":true}}}', @@ -138,7 +138,7 @@ describe('State Machine', () => { }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { DefinitionString: '{"StartAt":"Pass","States":{"Pass":{"Type":"Pass","End":true}}}', LoggingConfiguration: { Destinations: [{ @@ -153,7 +153,7 @@ describe('State Machine', () => { }, }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [{ Action: [ @@ -191,14 +191,14 @@ describe('State Machine', () => { }); // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', { DefinitionString: '{"StartAt":"Pass","States":{"Pass":{"Type":"Pass","End":true}}}', TracingConfiguration: { Enabled: true, }, }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [{ Action: [ @@ -233,7 +233,7 @@ describe('State Machine', () => { bucket.grantRead(sm); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions/test/states-language.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/states-language.test.ts index 993b6a6d27351..1ddddaae31cab 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/states-language.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/states-language.test.ts @@ -1,6 +1,7 @@ -import '@aws-cdk/assert-internal/jest'; +import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; +import { Construct } from 'constructs'; import * as stepfunctions from '../lib'; describe('States Language', () => { @@ -363,7 +364,7 @@ describe('States Language', () => { test('States can have error branches', () => { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); + const task1 = new FakeTask(stack, 'Task1'); const failure = new stepfunctions.Fail(stack, 'Failed', { error: 'DidNotWork', cause: 'We got stuck' }); // WHEN @@ -393,7 +394,7 @@ describe('States Language', () => { test('Retries and errors with a result path', () => { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); + const task1 = new FakeTask(stack, 'Task1'); const failure = new stepfunctions.Fail(stack, 'Failed', { error: 'DidNotWork', cause: 'We got stuck' }); // WHEN @@ -423,8 +424,8 @@ describe('States Language', () => { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); - const task2 = new stepfunctions.Task(stack, 'Task2', { task: new FakeTask() }); + const task1 = new FakeTask(stack, 'Task1'); + const task2 = new FakeTask(stack, 'Task2'); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -467,8 +468,8 @@ describe('States Language', () => { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); - const task2 = new stepfunctions.Task(stack, 'Task2', { task: new FakeTask() }); + const task1 = new FakeTask(stack, 'Task1'); + const task2 = new FakeTask(stack, 'Task2'); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -496,9 +497,9 @@ describe('States Language', () => { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); - const task2 = new stepfunctions.Task(stack, 'Task2', { task: new FakeTask() }); - const task3 = new stepfunctions.Task(stack, 'Task3', { task: new FakeTask() }); + const task1 = new FakeTask(stack, 'Task1'); + const task2 = new FakeTask(stack, 'Task2'); + const task3 = new FakeTask(stack, 'Task3'); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -523,8 +524,8 @@ describe('States Language', () => { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); - const task2 = new stepfunctions.Task(stack, 'Task2', { task: new FakeTask() }); + const task1 = new FakeTask(stack, 'Task1'); + const task2 = new FakeTask(stack, 'Task2'); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -537,8 +538,8 @@ describe('States Language', () => { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); - const task2 = new stepfunctions.Task(stack, 'Task2', { task: new FakeTask() }); + const task1 = new FakeTask(stack, 'Task1'); + const task2 = new FakeTask(stack, 'Task2'); const failure = new stepfunctions.Fail(stack, 'Failed', { error: 'DidNotWork', cause: 'We got stuck' }); // WHEN @@ -709,12 +710,12 @@ class SimpleChain extends stepfunctions.StateMachineFragment { public readonly startState: stepfunctions.State; public readonly endStates: stepfunctions.INextable[]; - private readonly task2: stepfunctions.Task; + private readonly task2: stepfunctions.TaskStateBase; constructor(scope: constructs.Construct, id: string) { super(scope, id); - const task1 = new stepfunctions.Task(this, 'Task1', { task: new FakeTask() }); - this.task2 = new stepfunctions.Task(this, 'Task2', { task: new FakeTask() }); + const task1 = new FakeTask(this, 'Task1'); + this.task2 = new FakeTask(this, 'Task2'); task1.next(this.task2); @@ -732,10 +733,22 @@ function render(sm: stepfunctions.IChainable) { return new cdk.Stack().resolve(new stepfunctions.StateGraph(sm.startState, 'Test Graph').toGraphJson()); } -class FakeTask implements stepfunctions.IStepFunctionsTask { - public bind(_task: stepfunctions.Task): stepfunctions.StepFunctionsTaskConfig { +interface FakeTaskProps extends stepfunctions.TaskStateBaseProps { + readonly policies?: iam.PolicyStatement[]; +} + +class FakeTask extends stepfunctions.TaskStateBase { + protected readonly taskMetrics?: stepfunctions.TaskMetricsConfig; + protected readonly taskPolicies?: iam.PolicyStatement[]; + + constructor(scope: Construct, id: string, props: FakeTaskProps = {}) { + super(scope, id, props); + this.taskPolicies = props.policies; + } + + protected _renderTask(): any { return { - resourceArn: 'resource', + Resource: 'resource', }; } } diff --git a/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts index a57c489134efd..a616ca0061e11 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/task-base.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import { Metric } from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; diff --git a/packages/@aws-cdk/aws-stepfunctions/test/task.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/task.test.ts index 06dc0282c9c05..ba4c2a79e660e 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/task.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/task.test.ts @@ -1,8 +1,9 @@ import { Metric } from '@aws-cdk/aws-cloudwatch'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as sfn from '../lib'; -describe('Task state', () => { +describeDeprecated('Task state', () => { let stack: cdk.Stack; let task: sfn.Task; diff --git a/packages/@aws-cdk/aws-stepfunctions/test/wait.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/wait.test.ts index 350ff400a4d86..42671449be619 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/wait.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/wait.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import { Pass, Wait, WaitTime } from '../lib'; import { render } from './private/render-util'; diff --git a/packages/@aws-cdk/aws-synthetics/package.json b/packages/@aws-cdk/aws-synthetics/package.json index 76e2fa4520b1e..745f97fc21261 100644 --- a/packages/@aws-cdk/aws-synthetics/package.json +++ b/packages/@aws-cdk/aws-synthetics/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-timestream/README.md b/packages/@aws-cdk/aws-timestream/README.md index 80120fe64f2ca..c5b4cb3de1e85 100644 --- a/packages/@aws-cdk/aws-timestream/README.md +++ b/packages/@aws-cdk/aws-timestream/README.md @@ -16,5 +16,5 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. ```ts -import timestream = require('@aws-cdk/aws-timestream'); +import * as timestream from '@aws-cdk/aws-timestream'; ``` diff --git a/packages/@aws-cdk/aws-timestream/package.json b/packages/@aws-cdk/aws-timestream/package.json index 96208e6d94b0b..9aa39709df395 100644 --- a/packages/@aws-cdk/aws-timestream/package.json +++ b/packages/@aws-cdk/aws-timestream/package.json @@ -7,6 +7,13 @@ "jsii": { "outdir": "dist", "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, "targets": { "dotnet": { "namespace": "Amazon.CDK.AWS.Timestream", diff --git a/packages/@aws-cdk/aws-timestream/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-timestream/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e8deb6060d76d --- /dev/null +++ b/packages/@aws-cdk/aws-timestream/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-transfer/package.json b/packages/@aws-cdk/aws-transfer/package.json index db27c864db2b6..cc1d96d9fb22e 100644 --- a/packages/@aws-cdk/aws-transfer/package.json +++ b/packages/@aws-cdk/aws-transfer/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-transfer/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-transfer/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e8deb6060d76d --- /dev/null +++ b/packages/@aws-cdk/aws-transfer/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-wafv2/package.json b/packages/@aws-cdk/aws-wafv2/package.json index 6fe2bfe3c21f0..4c689f62396f0 100644 --- a/packages/@aws-cdk/aws-wafv2/package.json +++ b/packages/@aws-cdk/aws-wafv2/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/aws-wafv2/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-wafv2/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e8deb6060d76d --- /dev/null +++ b/packages/@aws-cdk/aws-wafv2/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-wisdom/README.md b/packages/@aws-cdk/aws-wisdom/README.md index d942b3358d363..218dae4f51735 100644 --- a/packages/@aws-cdk/aws-wisdom/README.md +++ b/packages/@aws-cdk/aws-wisdom/README.md @@ -16,5 +16,5 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. ```ts -import aws-wisdom = require('@aws-cdk/aws-wisdom'); +import * as wisdom from '@aws-cdk/aws-wisdom'; ``` diff --git a/packages/@aws-cdk/aws-wisdom/package.json b/packages/@aws-cdk/aws-wisdom/package.json index a0bf376211e4c..aa12d6e4aa992 100644 --- a/packages/@aws-cdk/aws-wisdom/package.json +++ b/packages/@aws-cdk/aws-wisdom/package.json @@ -7,6 +7,13 @@ "jsii": { "outdir": "dist", "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, "targets": { "dotnet": { "namespace": "Amazon.CDK.AWS.Wisdom", diff --git a/packages/@aws-cdk/aws-wisdom/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-wisdom/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e8deb6060d76d --- /dev/null +++ b/packages/@aws-cdk/aws-wisdom/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-xray/README.md b/packages/@aws-cdk/aws-xray/README.md index 77a0090e7f659..c543893c88228 100644 --- a/packages/@aws-cdk/aws-xray/README.md +++ b/packages/@aws-cdk/aws-xray/README.md @@ -16,5 +16,5 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. ```ts -import xray = require('@aws-cdk/aws-xray'); +import * as xray from '@aws-cdk/aws-xray'; ``` diff --git a/packages/@aws-cdk/aws-xray/package.json b/packages/@aws-cdk/aws-xray/package.json index cdb613288b36b..7a5d9bfb5c5cd 100644 --- a/packages/@aws-cdk/aws-xray/package.json +++ b/packages/@aws-cdk/aws-xray/package.json @@ -7,6 +7,13 @@ "jsii": { "outdir": "dist", "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, "targets": { "dotnet": { "namespace": "Amazon.CDK.AWS.XRay", diff --git a/packages/@aws-cdk/aws-xray/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-xray/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..e8deb6060d76d --- /dev/null +++ b/packages/@aws-cdk/aws-xray/rosetta/default.ts-fixture @@ -0,0 +1,11 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/cfnspec/CHANGELOG.md b/packages/@aws-cdk/cfnspec/CHANGELOG.md index 913546215a38d..46c876faf346e 100644 --- a/packages/@aws-cdk/cfnspec/CHANGELOG.md +++ b/packages/@aws-cdk/cfnspec/CHANGELOG.md @@ -1,3 +1,345 @@ +# CloudFormation Resource Specification v49.0.0 + +## New Resource Types + +* AWS::AppStream::AppBlock +* AWS::AppStream::Application +* AWS::AppStream::ApplicationFleetAssociation +* AWS::DataBrew::Ruleset + +## Attribute Changes + +* AWS::EC2::VPCDHCPOptionsAssociation Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-dhcp-options-assoc.html + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcdhcpoptionsassociation.html +* AWS::EC2::VPCDHCPOptionsAssociation Id (__added__) + +## Property Changes + +* AWS::APS::RuleGroupsNamespace Name.Required (__changed__) + * Old: false + * New: true +* AWS::AppStream::Fleet MaxConcurrentSessions (__added__) +* AWS::AppStream::Fleet Platform (__added__) +* AWS::AppStream::Fleet UsbDeviceFilterStrings (__added__) +* AWS::AppStream::Fleet ComputeCapacity.Required (__changed__) + * Old: true + * New: false +* AWS::Chatbot::SlackChannelConfiguration GuardrailPolicies (__added__) +* AWS::Chatbot::SlackChannelConfiguration UserRoleRequired (__added__) +* AWS::CloudFormation::StackSet ManagedExecution (__added__) +* AWS::CloudWatch::AnomalyDetector MetricMathAnomalyDetector (__added__) +* AWS::CloudWatch::AnomalyDetector SingleMetricAnomalyDetector (__added__) +* AWS::DataBrew::Job ValidationConfigurations (__added__) +* AWS::DataBrew::Job Recipe.PrimitiveType (__deleted__) +* AWS::EC2::VPCDHCPOptionsAssociation DhcpOptionsId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-dhcp-options-assoc.html#cfn-ec2-vpcdhcpoptionsassociation-dhcpoptionsid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcdhcpoptionsassociation.html#cfn-ec2-vpcdhcpoptionsassociation-dhcpoptionsid +* AWS::EC2::VPCDHCPOptionsAssociation DhcpOptionsId.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::EC2::VPCDHCPOptionsAssociation VpcId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-dhcp-options-assoc.html#cfn-ec2-vpcdhcpoptionsassociation-vpcid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcdhcpoptionsassociation.html#cfn-ec2-vpcdhcpoptionsassociation-vpcid +* AWS::EC2::VPCEndpointService PayerResponsibility (__deleted__) +* AWS::Lambda::EventSourceMapping FilterCriteria (__added__) +* AWS::Logs::LogGroup Tags (__added__) +* AWS::Route53::HostedZone Name.Required (__changed__) + * Old: true + * New: false + +## Property Type Changes + +* AWS::CloudWatch::AnomalyDetector.MetricMathAnomalyDetector (__added__) +* AWS::CloudWatch::AnomalyDetector.SingleMetricAnomalyDetector (__added__) +* AWS::DataBrew::Dataset.Metadata (__added__) +* AWS::DataBrew::Job.AllowedStatistics (__added__) +* AWS::DataBrew::Job.EntityDetectorConfiguration (__added__) +* AWS::DataBrew::Job.ValidationConfiguration (__added__) +* AWS::MSK::Cluster.ConnectivityInfo (__added__) +* AWS::MSK::Cluster.PublicAccess (__added__) +* AWS::DataBrew::Dataset.DatabaseInputDefinition QueryString (__added__) +* AWS::DataBrew::Dataset.DatabaseInputDefinition GlueConnectionName.Required (__changed__) + * Old: false + * New: true +* AWS::DataBrew::Dataset.Input Metadata (__added__) +* AWS::DataBrew::Job.ProfileConfiguration EntityDetectorConfiguration (__added__) +* AWS::MSK::Cluster.BrokerNodeGroupInfo ConnectivityInfo (__added__) +* AWS::Transfer::Server.IdentityProviderDetails Function (__added__) + + +# CloudFormation Resource Specification v48.0.0 + +## New Resource Types + +* AWS::Batch::SchedulingPolicy +* AWS::IoTWireless::FuotaTask +* AWS::IoTWireless::MulticastGroup + +## Attribute Changes + +* AWS::EC2::NetworkInterface Id (__deleted__) +* AWS::EC2::NetworkInterface SecondaryPrivateIpAddresses.DuplicatesAllowed (__deleted__) +* AWS::EC2::NetworkInterface Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html +* AWS::IoTAnalytics::Channel Id (__deleted__) +* AWS::IoTAnalytics::Dataset Id (__deleted__) +* AWS::IoTAnalytics::Datastore Id (__deleted__) +* AWS::IoTAnalytics::Pipeline Id (__deleted__) + +## Property Changes + +* AWS::ApiGateway::Stage Variables.DuplicatesAllowed (__deleted__) +* AWS::AppConfig::ConfigurationProfile Type (__added__) +* AWS::CloudFront::Function FunctionMetadata (__deleted__) +* AWS::CloudWatch::AnomalyDetector MetricName.Required (__changed__) + * Old: true + * New: false +* AWS::CloudWatch::AnomalyDetector Namespace.Required (__changed__) + * Old: true + * New: false +* AWS::CloudWatch::AnomalyDetector Stat.Required (__changed__) + * Old: true + * New: false +* AWS::EC2::CapacityReservation OutPostArn (__added__) +* AWS::EC2::CapacityReservation PlacementGroupArn (__added__) +* AWS::EC2::NetworkInterface Description.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-description + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-description +* AWS::EC2::NetworkInterface GroupSet.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-groupset + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-groupset +* AWS::EC2::NetworkInterface GroupSet.DuplicatesAllowed (__changed__) + * Old: true + * New: false +* AWS::EC2::NetworkInterface InterfaceType.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-interfacetype + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-ec2-networkinterface-interfacetype +* AWS::EC2::NetworkInterface Ipv6AddressCount.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-ipv6addresscount + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-ec2-networkinterface-ipv6addresscount +* AWS::EC2::NetworkInterface Ipv6Addresses.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-ipv6addresses + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-ec2-networkinterface-ipv6addresses +* AWS::EC2::NetworkInterface PrivateIpAddress.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-privateipaddress + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-privateipaddress +* AWS::EC2::NetworkInterface PrivateIpAddresses.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-privateipaddresses + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-privateipaddresses +* AWS::EC2::NetworkInterface PrivateIpAddresses.DuplicatesAllowed (__changed__) + * Old: true + * New: false +* AWS::EC2::NetworkInterface PrivateIpAddresses.UpdateType (__changed__) + * Old: Mutable + * New: Conditional +* AWS::EC2::NetworkInterface SecondaryPrivateIpAddressCount.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-secondaryprivateipaddresscount + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-secondaryprivateipcount +* AWS::EC2::NetworkInterface SourceDestCheck.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-sourcedestcheck + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-sourcedestcheck +* AWS::EC2::NetworkInterface SubnetId.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-subnetid + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-subnetid +* AWS::EC2::NetworkInterface Tags.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-tags + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-tags +* AWS::FSx::FileSystem FileSystemTypeVersion (__added__) +* AWS::FSx::FileSystem OntapConfiguration (__added__) +* AWS::FinSpace::Environment DataBundles (__added__) +* AWS::FinSpace::Environment SuperuserParameters (__added__) +* AWS::IoTAnalytics::Channel ChannelName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Channel Tags.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Dataset Actions.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Dataset ContentDeliveryRules.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Dataset DatasetName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Dataset LateDataRules.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Dataset Tags.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Dataset Triggers.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Datastore Tags.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Pipeline PipelineActivities.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Pipeline PipelineName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline Tags.DuplicatesAllowed (__deleted__) +* AWS::Lightsail::Instance Location (__deleted__) +* AWS::Lightsail::Instance State (__deleted__) +* AWS::MemoryDB::Cluster ClusterEndpoint (__deleted__) +* AWS::Redshift::Cluster Endpoint (__deleted__) +* AWS::S3ObjectLambda::AccessPoint ObjectLambdaConfiguration.Required (__changed__) + * Old: false + * New: true + +## Property Type Changes + +* AWS::CloudWatch::AnomalyDetector.Metric (__added__) +* AWS::CloudWatch::AnomalyDetector.MetricDataQueries (__added__) +* AWS::CloudWatch::AnomalyDetector.MetricDataQuery (__added__) +* AWS::CloudWatch::AnomalyDetector.MetricStat (__added__) +* AWS::FSx::FileSystem.DiskIopsConfiguration (__added__) +* AWS::FSx::FileSystem.OntapConfiguration (__added__) +* AWS::FinSpace::Environment.SuperuserParameters (__added__) +* AWS::ApiGateway::Stage.CanarySetting StageVariableOverrides.DuplicatesAllowed (__deleted__) +* AWS::ApiGateway::Stage.MethodSetting CacheDataEncrypted.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachedataencrypted + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachedataencrypted +* AWS::ApiGateway::Stage.MethodSetting CacheTtlInSeconds.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachettlinseconds + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachettlinseconds +* AWS::ApiGateway::Stage.MethodSetting CachingEnabled.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachingenabled + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachingenabled +* AWS::ApiGateway::Stage.MethodSetting DataTraceEnabled.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-datatraceenabled + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-datatraceenabled +* AWS::ApiGateway::Stage.MethodSetting HttpMethod.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-httpmethod + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-httpmethod +* AWS::ApiGateway::Stage.MethodSetting LoggingLevel.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-logginglevel + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-logginglevel +* AWS::ApiGateway::Stage.MethodSetting MetricsEnabled.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-metricsenabled + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-metricsenabled +* AWS::ApiGateway::Stage.MethodSetting ResourcePath.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-resourcepath + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-resourcepath +* AWS::ApiGateway::Stage.MethodSetting ThrottlingBurstLimit.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingburstlimit + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingburstlimit +* AWS::ApiGateway::Stage.MethodSetting ThrottlingRateLimit.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingratelimit + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingratelimit +* AWS::AppMesh::GatewayRoute.GatewayRouteSpec Priority (__added__) +* AWS::CloudFront::Distribution.CacheBehavior ResponseHeadersPolicyId (__added__) +* AWS::CloudFront::Distribution.DefaultCacheBehavior ResponseHeadersPolicyId (__added__) +* AWS::EC2::NetworkInterface.PrivateIpAddressSpecification Primary.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-networkinterface-privateipaddressspecification.html#cfn-ec2-networkinterface-privateipaddressspecification-primary + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-interface-privateipspec.html#cfn-ec2-networkinterface-privateipspecification-primary +* AWS::EC2::NetworkInterface.PrivateIpAddressSpecification PrivateIpAddress.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-networkinterface-privateipaddressspecification.html#cfn-ec2-networkinterface-privateipaddressspecification-privateipaddress + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-interface-privateipspec.html#cfn-ec2-networkinterface-privateipspecification-privateipaddress +* AWS::IoTAnalytics::Dataset.ContainerAction Variables.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Dataset.DatasetContentVersionValue DatasetName.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-datasetcontentversionvalue.html#cfn-iotanalytics-dataset-datasetcontentversionvalue-datasetname + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-variable-datasetcontentversionvalue.html#cfn-iotanalytics-dataset-variable-datasetcontentversionvalue-datasetname +* AWS::IoTAnalytics::Dataset.DatasetContentVersionValue DatasetName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Dataset.OutputFileUriValue FileName.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-outputfileurivalue.html#cfn-iotanalytics-dataset-outputfileurivalue-filename + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-variable-outputfileurivalue.html#cfn-iotanalytics-dataset-variable-outputfileurivalue-filename +* AWS::IoTAnalytics::Dataset.OutputFileUriValue FileName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Dataset.QueryAction Filters.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Dataset.Schedule ScheduleExpression.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-schedule.html#cfn-iotanalytics-dataset-schedule-scheduleexpression + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-trigger-schedule.html#cfn-iotanalytics-dataset-trigger-schedule-scheduleexpression +* AWS::IoTAnalytics::Datastore.DatastorePartitions Partitions.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Datastore.IotSiteWiseMultiLayerStorage CustomerManagedS3Storage.Required (__changed__) + * Old: false + * New: true +* AWS::IoTAnalytics::Datastore.SchemaDefinition Columns.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Pipeline.AddAttributes Attributes.PrimitiveItemType (__deleted__) +* AWS::IoTAnalytics::Pipeline.AddAttributes Attributes.Type (__deleted__) +* AWS::IoTAnalytics::Pipeline.AddAttributes Attributes.PrimitiveType (__added__) +* AWS::IoTAnalytics::Pipeline.AddAttributes Attributes.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.AddAttributes Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Channel ChannelName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Channel Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Datastore DatastoreName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Datastore Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceRegistryEnrich Attribute.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceRegistryEnrich Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceRegistryEnrich RoleArn.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceRegistryEnrich ThingName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceShadowEnrich Attribute.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceShadowEnrich Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceShadowEnrich RoleArn.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.DeviceShadowEnrich ThingName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Filter Filter.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Filter Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Lambda BatchSize.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Lambda LambdaName.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Lambda Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Math Attribute.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Math Math.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.Math Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.RemoveAttributes Attributes.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Pipeline.RemoveAttributes Attributes.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.RemoveAttributes Name.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.SelectAttributes Attributes.DuplicatesAllowed (__deleted__) +* AWS::IoTAnalytics::Pipeline.SelectAttributes Attributes.Required (__changed__) + * Old: true + * New: false +* AWS::IoTAnalytics::Pipeline.SelectAttributes Name.Required (__changed__) + * Old: true + * New: false +* AWS::S3ObjectLambda::AccessPoint.TransformationConfiguration Actions.Required (__changed__) + * Old: false + * New: true +* AWS::S3ObjectLambda::AccessPoint.TransformationConfiguration ContentTransformation.Required (__changed__) + * Old: false + * New: true +* AWS::SecretsManager::RotationSchedule.HostedRotationLambda SuperuserSecretArn (__added__) +* AWS::SecretsManager::RotationSchedule.HostedRotationLambda SuperuserSecretKmsKeyArn (__added__) + + # CloudFormation Resource Specification v47.0.0 ## New Resource Types diff --git a/packages/@aws-cdk/cfnspec/cfn.version b/packages/@aws-cdk/cfnspec/cfn.version index 645a41f3b46f7..cfb3b31ea7b28 100644 --- a/packages/@aws-cdk/cfnspec/cfn.version +++ b/packages/@aws-cdk/cfnspec/cfn.version @@ -1 +1 @@ -47.0.0 +49.0.0 diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json index 5d8a7aab62e63..5dd730e2bce6d 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json @@ -1783,7 +1783,6 @@ }, "StageVariableOverrides": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-canarysetting.html#cfn-apigateway-stage-canarysetting-stagevariableoverrides", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "Map", @@ -1798,64 +1797,64 @@ } }, "AWS::ApiGateway::Stage.MethodSetting": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html", "Properties": { "CacheDataEncrypted": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachedataencrypted", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachedataencrypted", "PrimitiveType": "Boolean", "Required": false, "UpdateType": "Mutable" }, "CacheTtlInSeconds": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachettlinseconds", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachettlinseconds", "PrimitiveType": "Integer", "Required": false, "UpdateType": "Mutable" }, "CachingEnabled": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachingenabled", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-cachingenabled", "PrimitiveType": "Boolean", "Required": false, "UpdateType": "Mutable" }, "DataTraceEnabled": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-datatraceenabled", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-datatraceenabled", "PrimitiveType": "Boolean", "Required": false, "UpdateType": "Mutable" }, "HttpMethod": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-httpmethod", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-httpmethod", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "LoggingLevel": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-logginglevel", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-logginglevel", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "MetricsEnabled": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-metricsenabled", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-metricsenabled", "PrimitiveType": "Boolean", "Required": false, "UpdateType": "Mutable" }, "ResourcePath": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-resourcepath", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-resourcepath", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "ThrottlingBurstLimit": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingburstlimit", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingburstlimit", "PrimitiveType": "Integer", "Required": false, "UpdateType": "Mutable" }, "ThrottlingRateLimit": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingratelimit", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-throttlingratelimit", "PrimitiveType": "Double", "Required": false, "UpdateType": "Mutable" @@ -4353,6 +4352,12 @@ "Required": false, "Type": "HttpGatewayRoute", "UpdateType": "Mutable" + }, + "Priority": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-gatewayroutespec.html#cfn-appmesh-gatewayroute-gatewayroutespec-priority", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" } } }, @@ -6889,6 +6894,69 @@ } } }, + "AWS::AppStream::AppBlock.S3Location": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-s3location.html", + "Properties": { + "S3Bucket": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-s3location.html#cfn-appstream-appblock-s3location-s3bucket", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "S3Key": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-s3location.html#cfn-appstream-appblock-s3location-s3key", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::AppStream::AppBlock.ScriptDetails": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-scriptdetails.html", + "Properties": { + "ExecutableParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-scriptdetails.html#cfn-appstream-appblock-scriptdetails-executableparameters", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "ExecutablePath": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-scriptdetails.html#cfn-appstream-appblock-scriptdetails-executablepath", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "ScriptS3Location": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-scriptdetails.html#cfn-appstream-appblock-scriptdetails-scripts3location", + "Required": true, + "Type": "S3Location", + "UpdateType": "Immutable" + }, + "TimeoutInSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-appblock-scriptdetails.html#cfn-appstream-appblock-scriptdetails-timeoutinseconds", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::AppStream::Application.S3Location": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-application-s3location.html", + "Properties": { + "S3Bucket": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-application-s3location.html#cfn-appstream-application-s3location-s3bucket", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "S3Key": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-application-s3location.html#cfn-appstream-application-s3location-s3key", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::AppStream::DirectoryConfig.ServiceAccountCredentials": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-directoryconfig-serviceaccountcredentials.html", "Properties": { @@ -10616,6 +10684,47 @@ } } }, + "AWS::Batch::SchedulingPolicy.FairsharePolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-schedulingpolicy-fairsharepolicy.html", + "Properties": { + "ComputeReservation": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-schedulingpolicy-fairsharepolicy.html#cfn-batch-schedulingpolicy-fairsharepolicy-computereservation", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Mutable" + }, + "ShareDecaySeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-schedulingpolicy-fairsharepolicy.html#cfn-batch-schedulingpolicy-fairsharepolicy-sharedecayseconds", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Mutable" + }, + "ShareDistribution": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-schedulingpolicy-fairsharepolicy.html#cfn-batch-schedulingpolicy-fairsharepolicy-sharedistribution", + "ItemType": "ShareAttributes", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::Batch::SchedulingPolicy.ShareAttributes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-schedulingpolicy-shareattributes.html", + "Properties": { + "ShareIdentifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-schedulingpolicy-shareattributes.html#cfn-batch-schedulingpolicy-shareattributes-shareidentifier", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "WeightFactor": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-schedulingpolicy-shareattributes.html#cfn-batch-schedulingpolicy-shareattributes-weightfactor", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Budgets::Budget.BudgetData": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-budgets-budget-budgetdata.html", "Properties": { @@ -11525,6 +11634,12 @@ "Required": false, "UpdateType": "Mutable" }, + "ResponseHeadersPolicyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-cachebehavior.html#cfn-cloudfront-distribution-cachebehavior-responseheaderspolicyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "SmoothStreaming": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-cachebehavior.html#cfn-cloudfront-distribution-cachebehavior-smoothstreaming", "PrimitiveType": "Boolean", @@ -11741,6 +11856,12 @@ "Required": false, "UpdateType": "Mutable" }, + "ResponseHeadersPolicyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-defaultcachebehavior.html#cfn-cloudfront-distribution-defaultcachebehavior-responseheaderspolicyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "SmoothStreaming": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-defaultcachebehavior.html#cfn-cloudfront-distribution-defaultcachebehavior-smoothstreaming", "PrimitiveType": "Boolean", @@ -13186,6 +13307,125 @@ } } }, + "AWS::CloudWatch::AnomalyDetector.Metric": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metric.html", + "Properties": { + "Dimensions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metric.html#cfn-cloudwatch-anomalydetector-metric-dimensions", + "ItemType": "Dimension", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "MetricName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metric.html#cfn-cloudwatch-anomalydetector-metric-metricname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Namespace": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metric.html#cfn-cloudwatch-anomalydetector-metric-namespace", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::CloudWatch::AnomalyDetector.MetricDataQueries": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataqueries.html", + "ItemType": "MetricDataQuery", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "AWS::CloudWatch::AnomalyDetector.MetricDataQuery": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html", + "Properties": { + "AccountId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html#cfn-cloudwatch-anomalydetector-metricdataquery-accountid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Expression": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html#cfn-cloudwatch-anomalydetector-metricdataquery-expression", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Id": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html#cfn-cloudwatch-anomalydetector-metricdataquery-id", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Label": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html#cfn-cloudwatch-anomalydetector-metricdataquery-label", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "MetricStat": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html#cfn-cloudwatch-anomalydetector-metricdataquery-metricstat", + "Required": false, + "Type": "MetricStat", + "UpdateType": "Immutable" + }, + "Period": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html#cfn-cloudwatch-anomalydetector-metricdataquery-period", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "ReturnData": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricdataquery.html#cfn-cloudwatch-anomalydetector-metricdataquery-returndata", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::CloudWatch::AnomalyDetector.MetricMathAnomalyDetector": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricmathanomalydetector.html", + "Properties": { + "MetricDataQueries": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricmathanomalydetector.html#cfn-cloudwatch-anomalydetector-metricmathanomalydetector-metricdataqueries", + "ItemType": "MetricDataQuery", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + } + } + }, + "AWS::CloudWatch::AnomalyDetector.MetricStat": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricstat.html", + "Properties": { + "Metric": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricstat.html#cfn-cloudwatch-anomalydetector-metricstat-metric", + "Required": true, + "Type": "Metric", + "UpdateType": "Immutable" + }, + "Period": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricstat.html#cfn-cloudwatch-anomalydetector-metricstat-period", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Immutable" + }, + "Stat": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricstat.html#cfn-cloudwatch-anomalydetector-metricstat-stat", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Unit": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-metricstat.html#cfn-cloudwatch-anomalydetector-metricstat-unit", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::CloudWatch::AnomalyDetector.Range": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-range.html", "Properties": { @@ -13203,6 +13443,36 @@ } } }, + "AWS::CloudWatch::AnomalyDetector.SingleMetricAnomalyDetector": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-singlemetricanomalydetector.html", + "Properties": { + "Dimensions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-singlemetricanomalydetector.html#cfn-cloudwatch-anomalydetector-singlemetricanomalydetector-dimensions", + "ItemType": "Dimension", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "MetricName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-singlemetricanomalydetector.html#cfn-cloudwatch-anomalydetector-singlemetricanomalydetector-metricname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Namespace": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-singlemetricanomalydetector.html#cfn-cloudwatch-anomalydetector-singlemetricanomalydetector-namespace", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Stat": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-anomalydetector-singlemetricanomalydetector.html#cfn-cloudwatch-anomalydetector-singlemetricanomalydetector-stat", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::CloudWatch::InsightRule.Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudwatch-insightrule-tags.html", "ItemType": "Tag", @@ -17751,6 +18021,12 @@ "GlueConnectionName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-dataset-databaseinputdefinition.html#cfn-databrew-dataset-databaseinputdefinition-glueconnectionname", "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "QueryString": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-dataset-databaseinputdefinition.html#cfn-databrew-dataset-databaseinputdefinition-querystring", + "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, @@ -17941,6 +18217,12 @@ "Type": "DatabaseInputDefinition", "UpdateType": "Mutable" }, + "Metadata": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-dataset-input.html#cfn-databrew-dataset-input-metadata", + "Required": false, + "Type": "Metadata", + "UpdateType": "Mutable" + }, "S3InputDefinition": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-dataset-input.html#cfn-databrew-dataset-input-s3inputdefinition", "Required": false, @@ -17960,6 +18242,17 @@ } } }, + "AWS::DataBrew::Dataset.Metadata": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-dataset-metadata.html", + "Properties": { + "SourceArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-dataset-metadata.html#cfn-databrew-dataset-metadata-sourcearn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::DataBrew::Dataset.PathOptions": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-dataset-pathoptions.html", "Properties": { @@ -18018,6 +18311,18 @@ } } }, + "AWS::DataBrew::Job.AllowedStatistics": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-allowedstatistics.html", + "Properties": { + "Statistics": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-allowedstatistics.html#cfn-databrew-job-allowedstatistics-statistics", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::DataBrew::Job.ColumnSelector": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-columnselector.html", "Properties": { @@ -18145,6 +18450,24 @@ } } }, + "AWS::DataBrew::Job.EntityDetectorConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-entitydetectorconfiguration.html", + "Properties": { + "AllowedStatistics": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-entitydetectorconfiguration.html#cfn-databrew-job-entitydetectorconfiguration-allowedstatistics", + "Required": false, + "Type": "AllowedStatistics", + "UpdateType": "Mutable" + }, + "EntityTypes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-entitydetectorconfiguration.html#cfn-databrew-job-entitydetectorconfiguration-entitytypes", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::DataBrew::Job.JobSample": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-jobsample.html", "Properties": { @@ -18252,6 +18575,12 @@ "Type": "StatisticsConfiguration", "UpdateType": "Mutable" }, + "EntityDetectorConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-profileconfiguration.html#cfn-databrew-job-profileconfiguration-entitydetectorconfiguration", + "Required": false, + "Type": "EntityDetectorConfiguration", + "UpdateType": "Mutable" + }, "ProfileColumns": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-profileconfiguration.html#cfn-databrew-job-profileconfiguration-profilecolumns", "ItemType": "ColumnSelector", @@ -18342,6 +18671,23 @@ } } }, + "AWS::DataBrew::Job.ValidationConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-validationconfiguration.html", + "Properties": { + "RulesetArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-validationconfiguration.html#cfn-databrew-job-validationconfiguration-rulesetarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ValidationMode": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-job-validationconfiguration.html#cfn-databrew-job-validationconfiguration-validationmode", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::DataBrew::Project.Sample": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-project-sample.html", "Properties": { @@ -19098,6 +19444,106 @@ } } }, + "AWS::DataBrew::Ruleset.ColumnSelector": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-columnselector.html", + "Properties": { + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-columnselector.html#cfn-databrew-ruleset-columnselector-name", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Regex": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-columnselector.html#cfn-databrew-ruleset-columnselector-regex", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::DataBrew::Ruleset.Rule": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-rule.html", + "Properties": { + "CheckExpression": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-rule.html#cfn-databrew-ruleset-rule-checkexpression", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ColumnSelectors": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-rule.html#cfn-databrew-ruleset-rule-columnselectors", + "ItemType": "ColumnSelector", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Disabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-rule.html#cfn-databrew-ruleset-rule-disabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-rule.html#cfn-databrew-ruleset-rule-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "SubstitutionMap": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-rule.html#cfn-databrew-ruleset-rule-substitutionmap", + "ItemType": "SubstitutionValue", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Threshold": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-rule.html#cfn-databrew-ruleset-rule-threshold", + "Required": false, + "Type": "Threshold", + "UpdateType": "Mutable" + } + } + }, + "AWS::DataBrew::Ruleset.SubstitutionValue": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-substitutionvalue.html", + "Properties": { + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-substitutionvalue.html#cfn-databrew-ruleset-substitutionvalue-value", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ValueReference": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-substitutionvalue.html#cfn-databrew-ruleset-substitutionvalue-valuereference", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::DataBrew::Ruleset.Threshold": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-threshold.html", + "Properties": { + "Type": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-threshold.html#cfn-databrew-ruleset-threshold-type", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Unit": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-threshold.html#cfn-databrew-ruleset-threshold-unit", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-databrew-ruleset-threshold.html#cfn-databrew-ruleset-threshold-value", + "PrimitiveType": "Double", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::DataPipeline::Pipeline.Field": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-datapipeline-pipeline-pipelineobjects-fields.html", "Properties": { @@ -22782,16 +23228,16 @@ } }, "AWS::EC2::NetworkInterface.PrivateIpAddressSpecification": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-networkinterface-privateipaddressspecification.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-interface-privateipspec.html", "Properties": { "Primary": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-networkinterface-privateipaddressspecification.html#cfn-ec2-networkinterface-privateipaddressspecification-primary", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-interface-privateipspec.html#cfn-ec2-networkinterface-privateipspecification-primary", "PrimitiveType": "Boolean", "Required": true, "UpdateType": "Mutable" }, "PrivateIpAddress": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-networkinterface-privateipaddressspecification.html#cfn-ec2-networkinterface-privateipaddressspecification-privateipaddress", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-interface-privateipspec.html#cfn-ec2-networkinterface-privateipspecification-privateipaddress", "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" @@ -29822,6 +30268,23 @@ } } }, + "AWS::FSx::FileSystem.DiskIopsConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration-diskiopsconfiguration.html", + "Properties": { + "Iops": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration-diskiopsconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-diskiopsconfiguration-iops", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Mode": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration-diskiopsconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-diskiopsconfiguration-mode", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::FSx::FileSystem.LustreConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-lustreconfiguration.html", "Properties": { @@ -29899,6 +30362,72 @@ } } }, + "AWS::FSx::FileSystem.OntapConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html", + "Properties": { + "AutomaticBackupRetentionDays": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-automaticbackupretentiondays", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "DailyAutomaticBackupStartTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-dailyautomaticbackupstarttime", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "DeploymentType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-deploymenttype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "DiskIopsConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-diskiopsconfiguration", + "Required": false, + "Type": "DiskIopsConfiguration", + "UpdateType": "Mutable" + }, + "EndpointIpAddressRange": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-endpointipaddressrange", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "FsxAdminPassword": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-fsxadminpassword", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "PreferredSubnetId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-preferredsubnetid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "RouteTableIds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-routetableids", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "ThroughputCapacity": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-throughputcapacity", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "WeeklyMaintenanceStartTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-ontapconfiguration.html#cfn-fsx-filesystem-ontapconfiguration-weeklymaintenancestarttime", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::FSx::FileSystem.SelfManagedActiveDirectoryConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-windowsconfiguration-selfmanagedactivedirectoryconfiguration.html", "Properties": { @@ -30054,6 +30583,29 @@ } } }, + "AWS::FinSpace::Environment.SuperuserParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-finspace-environment-superuserparameters.html", + "Properties": { + "EmailAddress": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-finspace-environment-superuserparameters.html#cfn-finspace-environment-superuserparameters-emailaddress", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "FirstName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-finspace-environment-superuserparameters.html#cfn-finspace-environment-superuserparameters-firstname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "LastName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-finspace-environment-superuserparameters.html#cfn-finspace-environment-superuserparameters-lastname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::FraudDetector::Detector.EntityType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-frauddetector-detector-entitytype.html", "Properties": { @@ -36567,7 +37119,8 @@ } }, "AWS::IoTAnalytics::Channel.ServiceManagedS3": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-channel-servicemanageds3.html" + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-channel-servicemanageds3.html", + "Properties": {} }, "AWS::IoTAnalytics::Dataset.Action": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-action.html", @@ -36615,7 +37168,6 @@ }, "Variables": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-containeraction.html#cfn-iotanalytics-dataset-containeraction-variables", - "DuplicatesAllowed": true, "ItemType": "Variable", "Required": false, "Type": "List", @@ -36658,12 +37210,12 @@ } }, "AWS::IoTAnalytics::Dataset.DatasetContentVersionValue": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-datasetcontentversionvalue.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-variable-datasetcontentversionvalue.html", "Properties": { "DatasetName": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-datasetcontentversionvalue.html#cfn-iotanalytics-dataset-datasetcontentversionvalue-datasetname", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-variable-datasetcontentversionvalue.html#cfn-iotanalytics-dataset-variable-datasetcontentversionvalue-datasetname", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" } } @@ -36770,12 +37322,12 @@ } }, "AWS::IoTAnalytics::Dataset.OutputFileUriValue": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-outputfileurivalue.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-variable-outputfileurivalue.html", "Properties": { "FileName": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-outputfileurivalue.html#cfn-iotanalytics-dataset-outputfileurivalue-filename", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-variable-outputfileurivalue.html#cfn-iotanalytics-dataset-variable-outputfileurivalue-filename", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" } } @@ -36785,7 +37337,6 @@ "Properties": { "Filters": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-queryaction.html#cfn-iotanalytics-dataset-queryaction-filters", - "DuplicatesAllowed": true, "ItemType": "Filter", "Required": false, "Type": "List", @@ -36863,10 +37414,10 @@ } }, "AWS::IoTAnalytics::Dataset.Schedule": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-schedule.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-trigger-schedule.html", "Properties": { "ScheduleExpression": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-schedule.html#cfn-iotanalytics-dataset-schedule-scheduleexpression", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-dataset-trigger-schedule.html#cfn-iotanalytics-dataset-trigger-schedule-scheduleexpression", "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" @@ -37032,7 +37583,6 @@ "Properties": { "Partitions": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-datastore-datastorepartitions.html#cfn-iotanalytics-datastore-datastorepartitions-partitions", - "DuplicatesAllowed": true, "ItemType": "DatastorePartition", "Required": false, "Type": "List", @@ -37085,14 +37635,15 @@ "Properties": { "CustomerManagedS3Storage": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-datastore-iotsitewisemultilayerstorage.html#cfn-iotanalytics-datastore-iotsitewisemultilayerstorage-customermanageds3storage", - "Required": false, + "Required": true, "Type": "CustomerManagedS3Storage", "UpdateType": "Mutable" } } }, "AWS::IoTAnalytics::Datastore.JsonConfiguration": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-datastore-jsonconfiguration.html" + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-datastore-jsonconfiguration.html", + "Properties": {} }, "AWS::IoTAnalytics::Datastore.ParquetConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-datastore-parquetconfiguration.html", @@ -37138,7 +37689,6 @@ "Properties": { "Columns": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-datastore-schemadefinition.html#cfn-iotanalytics-datastore-schemadefinition-columns", - "DuplicatesAllowed": true, "ItemType": "Column", "Required": false, "Type": "List", @@ -37147,7 +37697,8 @@ } }, "AWS::IoTAnalytics::Datastore.ServiceManagedS3": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-datastore-servicemanageds3.html" + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-datastore-servicemanageds3.html", + "Properties": {} }, "AWS::IoTAnalytics::Datastore.TimestampPartition": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-datastore-timestamppartition.html", @@ -37236,15 +37787,14 @@ "Properties": { "Attributes": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-addattributes.html#cfn-iotanalytics-pipeline-addattributes-attributes", - "PrimitiveItemType": "String", - "Required": true, - "Type": "Map", + "PrimitiveType": "Json", + "Required": false, "UpdateType": "Mutable" }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-addattributes.html#cfn-iotanalytics-pipeline-addattributes-name", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Next": { @@ -37261,13 +37811,13 @@ "ChannelName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-channel.html#cfn-iotanalytics-pipeline-channel-channelname", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-channel.html#cfn-iotanalytics-pipeline-channel-name", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Next": { @@ -37284,13 +37834,13 @@ "DatastoreName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-datastore.html#cfn-iotanalytics-pipeline-datastore-datastorename", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-datastore.html#cfn-iotanalytics-pipeline-datastore-name", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" } } @@ -37301,13 +37851,13 @@ "Attribute": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-deviceregistryenrich.html#cfn-iotanalytics-pipeline-deviceregistryenrich-attribute", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-deviceregistryenrich.html#cfn-iotanalytics-pipeline-deviceregistryenrich-name", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Next": { @@ -37319,13 +37869,13 @@ "RoleArn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-deviceregistryenrich.html#cfn-iotanalytics-pipeline-deviceregistryenrich-rolearn", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "ThingName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-deviceregistryenrich.html#cfn-iotanalytics-pipeline-deviceregistryenrich-thingname", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" } } @@ -37336,13 +37886,13 @@ "Attribute": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-deviceshadowenrich.html#cfn-iotanalytics-pipeline-deviceshadowenrich-attribute", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-deviceshadowenrich.html#cfn-iotanalytics-pipeline-deviceshadowenrich-name", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Next": { @@ -37354,13 +37904,13 @@ "RoleArn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-deviceshadowenrich.html#cfn-iotanalytics-pipeline-deviceshadowenrich-rolearn", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "ThingName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-deviceshadowenrich.html#cfn-iotanalytics-pipeline-deviceshadowenrich-thingname", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" } } @@ -37371,13 +37921,13 @@ "Filter": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-filter.html#cfn-iotanalytics-pipeline-filter-filter", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-filter.html#cfn-iotanalytics-pipeline-filter-name", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Next": { @@ -37394,19 +37944,19 @@ "BatchSize": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-lambda.html#cfn-iotanalytics-pipeline-lambda-batchsize", "PrimitiveType": "Integer", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "LambdaName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-lambda.html#cfn-iotanalytics-pipeline-lambda-lambdaname", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-lambda.html#cfn-iotanalytics-pipeline-lambda-name", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Next": { @@ -37423,19 +37973,19 @@ "Attribute": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-math.html#cfn-iotanalytics-pipeline-math-attribute", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Math": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-math.html#cfn-iotanalytics-pipeline-math-math", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-math.html#cfn-iotanalytics-pipeline-math-name", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Next": { @@ -37451,16 +38001,15 @@ "Properties": { "Attributes": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-removeattributes.html#cfn-iotanalytics-pipeline-removeattributes-attributes", - "DuplicatesAllowed": true, "PrimitiveItemType": "String", - "Required": true, + "Required": false, "Type": "List", "UpdateType": "Mutable" }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-removeattributes.html#cfn-iotanalytics-pipeline-removeattributes-name", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Next": { @@ -37476,16 +38025,15 @@ "Properties": { "Attributes": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-selectattributes.html#cfn-iotanalytics-pipeline-selectattributes-attributes", - "DuplicatesAllowed": true, "PrimitiveItemType": "String", - "Required": true, + "Required": false, "Type": "List", "UpdateType": "Mutable" }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-pipeline-selectattributes.html#cfn-iotanalytics-pipeline-selectattributes-name", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Next": { @@ -38651,6 +39199,52 @@ } } }, + "AWS::IoTWireless::FuotaTask.LoRaWAN": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-fuotatask-lorawan.html", + "Properties": { + "RfRegion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-fuotatask-lorawan.html#cfn-iotwireless-fuotatask-lorawan-rfregion", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "StartTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-fuotatask-lorawan.html#cfn-iotwireless-fuotatask-lorawan-starttime", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::IoTWireless::MulticastGroup.LoRaWAN": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-multicastgroup-lorawan.html", + "Properties": { + "DlClass": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-multicastgroup-lorawan.html#cfn-iotwireless-multicastgroup-lorawan-dlclass", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "NumberOfDevicesInGroup": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-multicastgroup-lorawan.html#cfn-iotwireless-multicastgroup-lorawan-numberofdevicesingroup", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "NumberOfDevicesRequested": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-multicastgroup-lorawan.html#cfn-iotwireless-multicastgroup-lorawan-numberofdevicesrequested", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "RfRegion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-multicastgroup-lorawan.html#cfn-iotwireless-multicastgroup-lorawan-rfregion", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::IoTWireless::PartnerAccount.SidewalkAccountInfo": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotwireless-partneraccount-sidewalkaccountinfo.html", "Properties": { @@ -44632,6 +45226,12 @@ "Type": "List", "UpdateType": "Immutable" }, + "ConnectivityInfo": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-brokernodegroupinfo.html#cfn-msk-cluster-brokernodegroupinfo-connectivityinfo", + "Required": false, + "Type": "ConnectivityInfo", + "UpdateType": "Mutable" + }, "InstanceType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-brokernodegroupinfo.html#cfn-msk-cluster-brokernodegroupinfo-instancetype", "PrimitiveType": "String", @@ -44710,6 +45310,17 @@ } } }, + "AWS::MSK::Cluster.ConnectivityInfo": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-connectivityinfo.html", + "Properties": { + "PublicAccess": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-connectivityinfo.html#cfn-msk-cluster-connectivityinfo-publicaccess", + "Required": false, + "Type": "PublicAccess", + "UpdateType": "Mutable" + } + } + }, "AWS::MSK::Cluster.EBSStorageInfo": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-ebsstorageinfo.html", "Properties": { @@ -44855,6 +45466,17 @@ } } }, + "AWS::MSK::Cluster.PublicAccess": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-publicaccess.html", + "Properties": { + "Type": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-publicaccess.html#cfn-msk-cluster-publicaccess-type", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::MSK::Cluster.S3": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-msk-cluster-s3.html", "Properties": { @@ -59118,14 +59740,14 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3objectlambda-accesspoint-transformationconfiguration.html#cfn-s3objectlambda-accesspoint-transformationconfiguration-actions", "DuplicatesAllowed": false, "PrimitiveItemType": "String", - "Required": false, + "Required": true, "Type": "List", "UpdateType": "Mutable" }, "ContentTransformation": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3objectlambda-accesspoint-transformationconfiguration.html#cfn-s3objectlambda-accesspoint-transformationconfiguration-contenttransformation", "PrimitiveType": "Json", - "Required": false, + "Required": true, "UpdateType": "Mutable" } } @@ -62910,6 +63532,18 @@ "Required": true, "UpdateType": "Mutable" }, + "SuperuserSecretArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html#cfn-secretsmanager-rotationschedule-hostedrotationlambda-superusersecretarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SuperuserSecretKmsKeyArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html#cfn-secretsmanager-rotationschedule-hostedrotationlambda-superusersecretkmskeyarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "VpcSecurityGroupIds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html#cfn-secretsmanager-rotationschedule-hostedrotationlambda-vpcsecuritygroupids", "PrimitiveType": "String", @@ -63624,6 +64258,12 @@ "Required": false, "UpdateType": "Mutable" }, + "Function": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-transfer-server-identityproviderdetails.html#cfn-transfer-server-identityproviderdetails-function", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "InvocationRole": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-transfer-server-identityproviderdetails.html#cfn-transfer-server-identityproviderdetails-invocationrole", "PrimitiveType": "String", @@ -66092,7 +66732,7 @@ } } }, - "ResourceSpecificationVersion": "47.0.0", + "ResourceSpecificationVersion": "49.0.0", "ResourceTypes": { "AWS::ACMPCA::Certificate": { "Attributes": { @@ -66292,7 +66932,7 @@ "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-aps-rulegroupsnamespace.html#cfn-aps-rulegroupsnamespace-name", "PrimitiveType": "String", - "Required": false, + "Required": true, "UpdateType": "Immutable" }, "Tags": { @@ -67658,7 +68298,6 @@ }, "Variables": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-stage.html#cfn-apigateway-stage-variables", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "Map", @@ -68564,6 +69203,12 @@ "Type": "List", "UpdateType": "Mutable" }, + "Type": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appconfig-configurationprofile.html#cfn-appconfig-configurationprofile-type", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "Validators": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appconfig-configurationprofile.html#cfn-appconfig-configurationprofile-validators", "ItemType": "Validators", @@ -69359,6 +70004,167 @@ } } }, + "AWS::AppStream::AppBlock": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "CreatedTime": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-appblock.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-appblock.html#cfn-appstream-appblock-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "DisplayName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-appblock.html#cfn-appstream-appblock-displayname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-appblock.html#cfn-appstream-appblock-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "SetupScriptDetails": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-appblock.html#cfn-appstream-appblock-setupscriptdetails", + "Required": true, + "Type": "ScriptDetails", + "UpdateType": "Immutable" + }, + "SourceS3Location": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-appblock.html#cfn-appstream-appblock-sources3location", + "Required": true, + "Type": "S3Location", + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-appblock.html#cfn-appstream-appblock-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppStream::Application": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "CreatedTime": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html", + "Properties": { + "AppBlockArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-appblockarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "AttributesToDelete": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-attributestodelete", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "DisplayName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-displayname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "IconS3Location": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-icons3location", + "Required": true, + "Type": "S3Location", + "UpdateType": "Mutable" + }, + "InstanceFamilies": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-instancefamilies", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Immutable" + }, + "LaunchParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-launchparameters", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "LaunchPath": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-launchpath", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Platforms": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-platforms", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "WorkingDirectory": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html#cfn-appstream-application-workingdirectory", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppStream::ApplicationFleetAssociation": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-applicationfleetassociation.html", + "Properties": { + "ApplicationArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-applicationfleetassociation.html#cfn-appstream-applicationfleetassociation-applicationarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "FleetName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-applicationfleetassociation.html#cfn-appstream-applicationfleetassociation-fleetname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::AppStream::DirectoryConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-directoryconfig.html", "Properties": { @@ -69388,7 +70194,7 @@ "Properties": { "ComputeCapacity": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-fleet.html#cfn-appstream-fleet-computecapacity", - "Required": true, + "Required": false, "Type": "ComputeCapacity", "UpdateType": "Mutable" }, @@ -69458,6 +70264,12 @@ "Required": true, "UpdateType": "Mutable" }, + "MaxConcurrentSessions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-fleet.html#cfn-appstream-fleet-maxconcurrentsessions", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, "MaxUserDurationInSeconds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-fleet.html#cfn-appstream-fleet-maxuserdurationinseconds", "PrimitiveType": "Integer", @@ -69470,6 +70282,12 @@ "Required": true, "UpdateType": "Immutable" }, + "Platform": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-fleet.html#cfn-appstream-fleet-platform", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "StreamView": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-fleet.html#cfn-appstream-fleet-streamview", "PrimitiveType": "String", @@ -69483,6 +70301,13 @@ "Type": "List", "UpdateType": "Mutable" }, + "UsbDeviceFilterStrings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-fleet.html#cfn-appstream-fleet-usbdevicefilterstrings", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, "VpcConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-fleet.html#cfn-appstream-fleet-vpcconfig", "Required": false, @@ -71501,6 +72326,35 @@ } } }, + "AWS::Batch::SchedulingPolicy": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-schedulingpolicy.html", + "Properties": { + "FairsharePolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-schedulingpolicy.html#cfn-batch-schedulingpolicy-fairsharepolicy", + "Required": false, + "Type": "FairsharePolicy", + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-schedulingpolicy.html#cfn-batch-schedulingpolicy-name", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-schedulingpolicy.html#cfn-batch-schedulingpolicy-tags", + "PrimitiveItemType": "String", + "Required": false, + "Type": "Map", + "UpdateType": "Immutable" + } + } + }, "AWS::Budgets::Budget": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-budgets-budget.html", "Properties": { @@ -71966,6 +72820,13 @@ "Required": true, "UpdateType": "Immutable" }, + "GuardrailPolicies": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-chatbot-slackchannelconfiguration.html#cfn-chatbot-slackchannelconfiguration-guardrailpolicies", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, "IamRoleArn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-chatbot-slackchannelconfiguration.html#cfn-chatbot-slackchannelconfiguration-iamrolearn", "PrimitiveType": "String", @@ -71996,6 +72857,12 @@ "Required": false, "Type": "List", "UpdateType": "Mutable" + }, + "UserRoleRequired": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-chatbot-slackchannelconfiguration.html#cfn-chatbot-slackchannelconfiguration-userrolerequired", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" } } }, @@ -72428,6 +73295,12 @@ "Required": false, "UpdateType": "Mutable" }, + "ManagedExecution": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-stackset.html#cfn-cloudformation-stackset-managedexecution", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, "OperationPreferences": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-stackset.html#cfn-cloudformation-stackset-operationpreferences", "Required": false, @@ -72683,12 +73556,6 @@ "Type": "FunctionConfig", "UpdateType": "Mutable" }, - "FunctionMetadata": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-function.html#cfn-cloudfront-function-functionmetadata", - "Required": false, - "Type": "FunctionMetadata", - "UpdateType": "Mutable" - }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-function.html#cfn-cloudfront-function-name", "PrimitiveType": "String", @@ -73105,22 +73972,34 @@ "Type": "List", "UpdateType": "Immutable" }, + "MetricMathAnomalyDetector": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-anomalydetector.html#cfn-cloudwatch-anomalydetector-metricmathanomalydetector", + "Required": false, + "Type": "MetricMathAnomalyDetector", + "UpdateType": "Immutable" + }, "MetricName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-anomalydetector.html#cfn-cloudwatch-anomalydetector-metricname", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" }, "Namespace": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-anomalydetector.html#cfn-cloudwatch-anomalydetector-namespace", "PrimitiveType": "String", - "Required": true, + "Required": false, + "UpdateType": "Immutable" + }, + "SingleMetricAnomalyDetector": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-anomalydetector.html#cfn-cloudwatch-anomalydetector-singlemetricanomalydetector", + "Required": false, + "Type": "SingleMetricAnomalyDetector", "UpdateType": "Immutable" }, "Stat": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-anomalydetector.html#cfn-cloudwatch-anomalydetector-stat", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" } } @@ -76548,7 +77427,6 @@ }, "Recipe": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-job.html#cfn-databrew-job-recipe", - "PrimitiveType": "Json", "Required": false, "Type": "Recipe", "UpdateType": "Mutable" @@ -76578,6 +77456,13 @@ "PrimitiveType": "String", "Required": true, "UpdateType": "Immutable" + }, + "ValidationConfigurations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-job.html#cfn-databrew-job-validationconfigurations", + "ItemType": "ValidationConfiguration", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" } } }, @@ -76656,6 +77541,44 @@ } } }, + "AWS::DataBrew::Ruleset": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-ruleset.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-ruleset.html#cfn-databrew-ruleset-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-ruleset.html#cfn-databrew-ruleset-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Rules": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-ruleset.html#cfn-databrew-ruleset-rules", + "ItemType": "Rule", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-ruleset.html#cfn-databrew-ruleset-tags", + "DuplicatesAllowed": true, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "TargetArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-ruleset.html#cfn-databrew-ruleset-targetarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::DataBrew::Schedule": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-schedule.html", "Properties": { @@ -78027,6 +78950,18 @@ "Required": true, "UpdateType": "Immutable" }, + "OutPostArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservation.html#cfn-ec2-capacityreservation-outpostarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "PlacementGroupArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservation.html#cfn-ec2-capacityreservation-placementgrouparn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "TagSpecifications": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservation.html#cfn-ec2-capacityreservation-tagspecifications", "ItemType": "TagSpecification", @@ -79353,48 +80288,44 @@ }, "AWS::EC2::NetworkInterface": { "Attributes": { - "Id": { - "PrimitiveType": "String" - }, "PrimaryPrivateIpAddress": { "PrimitiveType": "String" }, "SecondaryPrivateIpAddresses": { - "DuplicatesAllowed": true, "PrimitiveItemType": "String", "Type": "List" } }, - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html", "Properties": { "Description": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-description", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-description", "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" }, "GroupSet": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-groupset", - "DuplicatesAllowed": true, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-groupset", + "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "List", "UpdateType": "Mutable" }, "InterfaceType": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-interfacetype", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-ec2-networkinterface-interfacetype", "PrimitiveType": "String", "Required": false, "UpdateType": "Immutable" }, "Ipv6AddressCount": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-ipv6addresscount", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-ec2-networkinterface-ipv6addresscount", "PrimitiveType": "Integer", "Required": false, "UpdateType": "Mutable" }, "Ipv6Addresses": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-ipv6addresses", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-ec2-networkinterface-ipv6addresses", "DuplicatesAllowed": false, "ItemType": "InstanceIpv6Address", "Required": false, @@ -79402,39 +80333,39 @@ "UpdateType": "Mutable" }, "PrivateIpAddress": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-privateipaddress", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-privateipaddress", "PrimitiveType": "String", "Required": false, "UpdateType": "Immutable" }, "PrivateIpAddresses": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-privateipaddresses", - "DuplicatesAllowed": true, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-privateipaddresses", + "DuplicatesAllowed": false, "ItemType": "PrivateIpAddressSpecification", "Required": false, "Type": "List", - "UpdateType": "Mutable" + "UpdateType": "Conditional" }, "SecondaryPrivateIpAddressCount": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-secondaryprivateipaddresscount", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-secondaryprivateipcount", "PrimitiveType": "Integer", "Required": false, "UpdateType": "Mutable" }, "SourceDestCheck": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-sourcedestcheck", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-sourcedestcheck", "PrimitiveType": "Boolean", "Required": false, "UpdateType": "Mutable" }, "SubnetId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-subnetid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-subnetid", "PrimitiveType": "String", "Required": true, "UpdateType": "Immutable" }, "Tags": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html#cfn-ec2-networkinterface-tags", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html#cfn-awsec2networkinterface-tags", "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, @@ -80784,16 +81715,21 @@ } }, "AWS::EC2::VPCDHCPOptionsAssociation": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-dhcp-options-assoc.html", + "Attributes": { + "Id": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcdhcpoptionsassociation.html", "Properties": { "DhcpOptionsId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-dhcp-options-assoc.html#cfn-ec2-vpcdhcpoptionsassociation-dhcpoptionsid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcdhcpoptionsassociation.html#cfn-ec2-vpcdhcpoptionsassociation-dhcpoptionsid", "PrimitiveType": "String", "Required": true, - "UpdateType": "Mutable" + "UpdateType": "Immutable" }, "VpcId": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-dhcp-options-assoc.html#cfn-ec2-vpcdhcpoptionsassociation-vpcid", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcdhcpoptionsassociation.html#cfn-ec2-vpcdhcpoptionsassociation-vpcid", "PrimitiveType": "String", "Required": true, "UpdateType": "Immutable" @@ -80924,12 +81860,6 @@ "Required": false, "Type": "List", "UpdateType": "Mutable" - }, - "PayerResponsibility": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcendpointservice.html#cfn-ec2-vpcendpointservice-payerresponsibility", - "PrimitiveType": "String", - "Required": false, - "UpdateType": "Mutable" } } }, @@ -84802,6 +85732,12 @@ "Required": true, "UpdateType": "Immutable" }, + "FileSystemTypeVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fsx-filesystem.html#cfn-fsx-filesystem-filesystemtypeversion", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "KmsKeyId": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fsx-filesystem.html#cfn-fsx-filesystem-kmskeyid", "PrimitiveType": "String", @@ -84814,6 +85750,12 @@ "Type": "LustreConfiguration", "UpdateType": "Mutable" }, + "OntapConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fsx-filesystem.html#cfn-fsx-filesystem-ontapconfiguration", + "Required": false, + "Type": "OntapConfiguration", + "UpdateType": "Mutable" + }, "SecurityGroupIds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fsx-filesystem.html#cfn-fsx-filesystem-securitygroupids", "PrimitiveItemType": "String", @@ -84881,6 +85823,14 @@ }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-finspace-environment.html", "Properties": { + "DataBundles": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-finspace-environment.html#cfn-finspace-environment-databundles", + "DuplicatesAllowed": true, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, "Description": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-finspace-environment.html#cfn-finspace-environment-description", "PrimitiveType": "String", @@ -84910,6 +85860,12 @@ "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" + }, + "SuperuserParameters": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-finspace-environment.html#cfn-finspace-environment-superuserparameters", + "Required": false, + "Type": "SuperuserParameters", + "UpdateType": "Immutable" } } }, @@ -89700,17 +90656,12 @@ } }, "AWS::IoTAnalytics::Channel": { - "Attributes": { - "Id": { - "PrimitiveType": "String" - } - }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-channel.html", "Properties": { "ChannelName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-channel.html#cfn-iotanalytics-channel-channelname", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" }, "ChannelStorage": { @@ -89727,7 +90678,6 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-channel.html#cfn-iotanalytics-channel-tags", - "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, "Type": "List", @@ -89736,16 +90686,10 @@ } }, "AWS::IoTAnalytics::Dataset": { - "Attributes": { - "Id": { - "PrimitiveType": "String" - } - }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-dataset.html", "Properties": { "Actions": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-dataset.html#cfn-iotanalytics-dataset-actions", - "DuplicatesAllowed": true, "ItemType": "Action", "Required": true, "Type": "List", @@ -89753,7 +90697,6 @@ }, "ContentDeliveryRules": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-dataset.html#cfn-iotanalytics-dataset-contentdeliveryrules", - "DuplicatesAllowed": true, "ItemType": "DatasetContentDeliveryRule", "Required": false, "Type": "List", @@ -89762,12 +90705,11 @@ "DatasetName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-dataset.html#cfn-iotanalytics-dataset-datasetname", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" }, "LateDataRules": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-dataset.html#cfn-iotanalytics-dataset-latedatarules", - "DuplicatesAllowed": true, "ItemType": "LateDataRule", "Required": false, "Type": "List", @@ -89781,7 +90723,6 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-dataset.html#cfn-iotanalytics-dataset-tags", - "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, "Type": "List", @@ -89789,7 +90730,6 @@ }, "Triggers": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-dataset.html#cfn-iotanalytics-dataset-triggers", - "DuplicatesAllowed": true, "ItemType": "Trigger", "Required": false, "Type": "List", @@ -89804,11 +90744,6 @@ } }, "AWS::IoTAnalytics::Datastore": { - "Attributes": { - "Id": { - "PrimitiveType": "String" - } - }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-datastore.html", "Properties": { "DatastoreName": { @@ -89843,7 +90778,6 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-datastore.html#cfn-iotanalytics-datastore-tags", - "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, "Type": "List", @@ -89852,16 +90786,10 @@ } }, "AWS::IoTAnalytics::Pipeline": { - "Attributes": { - "Id": { - "PrimitiveType": "String" - } - }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-pipeline.html", "Properties": { "PipelineActivities": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-pipeline.html#cfn-iotanalytics-pipeline-pipelineactivities", - "DuplicatesAllowed": true, "ItemType": "Activity", "Required": true, "Type": "List", @@ -89870,12 +90798,11 @@ "PipelineName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-pipeline.html#cfn-iotanalytics-pipeline-pipelinename", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-pipeline.html#cfn-iotanalytics-pipeline-tags", - "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, "Type": "List", @@ -90470,6 +91397,147 @@ } } }, + "AWS::IoTWireless::FuotaTask": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "FuotaTaskStatus": { + "PrimitiveType": "String" + }, + "Id": { + "PrimitiveType": "String" + }, + "LoRaWAN.StartTime": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html", + "Properties": { + "AssociateMulticastGroup": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-associatemulticastgroup", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "AssociateWirelessDevice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-associatewirelessdevice", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "DisassociateMulticastGroup": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-disassociatemulticastgroup", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "DisassociateWirelessDevice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-disassociatewirelessdevice", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "FirmwareUpdateImage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-firmwareupdateimage", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "FirmwareUpdateRole": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-firmwareupdaterole", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "LoRaWAN": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-lorawan", + "Required": true, + "Type": "LoRaWAN", + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-name", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html#cfn-iotwireless-fuotatask-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::IoTWireless::MulticastGroup": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "Id": { + "PrimitiveType": "String" + }, + "LoRaWAN.NumberOfDevicesInGroup": { + "PrimitiveType": "Integer" + }, + "LoRaWAN.NumberOfDevicesRequested": { + "PrimitiveType": "Integer" + }, + "Status": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-multicastgroup.html", + "Properties": { + "AssociateWirelessDevice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-multicastgroup.html#cfn-iotwireless-multicastgroup-associatewirelessdevice", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-multicastgroup.html#cfn-iotwireless-multicastgroup-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "DisassociateWirelessDevice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-multicastgroup.html#cfn-iotwireless-multicastgroup-disassociatewirelessdevice", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "LoRaWAN": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-multicastgroup.html#cfn-iotwireless-multicastgroup-lorawan", + "Required": true, + "Type": "LoRaWAN", + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-multicastgroup.html#cfn-iotwireless-multicastgroup-name", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-multicastgroup.html#cfn-iotwireless-multicastgroup-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::IoTWireless::PartnerAccount": { "Attributes": { "Arn": { @@ -91652,6 +92720,12 @@ "Required": false, "UpdateType": "Immutable" }, + "FilterCriteria": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html#cfn-lambda-eventsourcemapping-filtercriteria", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, "FunctionName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html#cfn-lambda-eventsourcemapping-functionname", "PrimitiveType": "String", @@ -92430,24 +93504,12 @@ "Required": false, "UpdateType": "Mutable" }, - "Location": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-instance.html#cfn-lightsail-instance-location", - "Required": false, - "Type": "Location", - "UpdateType": "Mutable" - }, "Networking": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-instance.html#cfn-lightsail-instance-networking", "Required": false, "Type": "Networking", "UpdateType": "Mutable" }, - "State": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-instance.html#cfn-lightsail-instance-state", - "Required": false, - "Type": "State", - "UpdateType": "Mutable" - }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-instance.html#cfn-lightsail-instance-tags", "DuplicatesAllowed": false, @@ -92810,6 +93872,14 @@ "PrimitiveType": "Integer", "Required": false, "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#cfn-logs-loggroup-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" } } }, @@ -94592,12 +95662,6 @@ "Required": false, "UpdateType": "Mutable" }, - "ClusterEndpoint": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-memorydb-cluster.html#cfn-memorydb-cluster-clusterendpoint", - "Required": false, - "Type": "Endpoint", - "UpdateType": "Mutable" - }, "ClusterName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-memorydb-cluster.html#cfn-memorydb-cluster-clustername", "PrimitiveType": "String", @@ -99728,12 +100792,6 @@ "Required": false, "UpdateType": "Mutable" }, - "Endpoint": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-cluster.html#cfn-redshift-cluster-endpoint", - "Required": false, - "Type": "Endpoint", - "UpdateType": "Mutable" - }, "EnhancedVpcRouting": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-cluster.html#cfn-redshift-cluster-enhancedvpcrouting", "PrimitiveType": "Boolean", @@ -100608,7 +101666,7 @@ "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-hostedzone.html#cfn-route53-hostedzone-name", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" }, "QueryLoggingConfig": { @@ -101858,7 +102916,7 @@ }, "ObjectLambdaConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3objectlambda-accesspoint.html#cfn-s3objectlambda-accesspoint-objectlambdaconfiguration", - "Required": false, + "Required": true, "Type": "ObjectLambdaConfiguration", "UpdateType": "Mutable" } diff --git a/packages/@aws-cdk/cloud-assembly-schema/.gitignore b/packages/@aws-cdk/cloud-assembly-schema/.gitignore index eec83f25377a7..8efa23cb23766 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/.gitignore +++ b/packages/@aws-cdk/cloud-assembly-schema/.gitignore @@ -1,6 +1,7 @@ lib/**/*.js test/**/*.js scripts/*.js +.warnings.jsii.js *.js.map *.d.ts node_modules diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/context-queries.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/context-queries.ts index ebed927f1828e..97ede25af2498 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/context-queries.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/cloud-assembly/context-queries.ts @@ -416,8 +416,24 @@ export interface SecurityGroupContextQuery { /** * Security group id + * + * @default - None + */ + readonly securityGroupId?: string; + + /** + * Security group name + * + * @default - None */ - readonly securityGroupId: string; + readonly securityGroupName?: string; + + /** + * VPC ID + * + * @default - None + */ + readonly vpcId?: string; } /** diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json index d52512102e9a6..9241ae62ef0ff 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json @@ -761,14 +761,21 @@ "type": "string" }, "securityGroupId": { - "description": "Security group id", + "description": "Security group id (Default - None)", + "type": "string" + }, + "securityGroupName": { + "description": "Security group name (Default - None)", + "type": "string" + }, + "vpcId": { + "description": "VPC ID (Default - None)", "type": "string" } }, "required": [ "account", - "region", - "securityGroupId" + "region" ] }, "KeyContextQuery": { diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json index c1ee7b9a1f8ad..01d4f111912e9 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json @@ -1 +1 @@ -{"version":"14.0.0"} \ No newline at end of file +{"version":"15.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-diff/package.json b/packages/@aws-cdk/cloudformation-diff/package.json index 61b48d1100a3e..1e9b1c5a412ca 100644 --- a/packages/@aws-cdk/cloudformation-diff/package.json +++ b/packages/@aws-cdk/cloudformation-diff/package.json @@ -29,7 +29,7 @@ "diff": "^5.0.0", "fast-deep-equal": "^3.1.3", "string-width": "^4.2.3", - "table": "^6.7.2" + "table": "^6.7.3" }, "devDependencies": { "@aws-cdk/cdk-build-tools": "0.0.0", diff --git a/packages/@aws-cdk/cloudformation-include/README.md b/packages/@aws-cdk/cloudformation-include/README.md index 2c67aeb4d554b..560718ea1449c 100644 --- a/packages/@aws-cdk/cloudformation-include/README.md +++ b/packages/@aws-cdk/cloudformation-include/README.md @@ -46,8 +46,6 @@ Resources: It can be included in a CDK application with the following code: ```ts -import * as cfn_inc from '@aws-cdk/cloudformation-include'; - const cfnTemplate = new cfn_inc.CfnInclude(this, 'Template', { templateFile: 'my-template.json', }); @@ -82,8 +80,7 @@ If you know the class of the CDK object that corresponds to that resource, you can cast the returned object to the correct type: ```ts -import * as s3 from '@aws-cdk/aws-s3'; - +declare const cfnTemplate: cfn_inc.CfnInclude; const cfnBucket = cfnTemplate.getResource('Bucket') as s3.CfnBucket; // cfnBucket is of type s3.CfnBucket ``` @@ -98,6 +95,8 @@ Any modifications made to that resource will be reflected in the resulting CDK t for example, the name of the bucket can be changed: ```ts +declare const cfnTemplate: cfn_inc.CfnInclude; +const cfnBucket = cfnTemplate.getResource('Bucket') as s3.CfnBucket; cfnBucket.bucketName = 'my-bucket-name'; ``` @@ -107,7 +106,8 @@ including the higher-level ones for example: ```ts -import * as iam from '@aws-cdk/aws-iam'; +declare const cfnTemplate: cfn_inc.CfnInclude; +const cfnBucket = cfnTemplate.getResource('Bucket') as s3.CfnBucket; const role = new iam.Role(this, 'Role', { assumedBy: new iam.AnyPrincipal(), @@ -136,8 +136,7 @@ for example, for KMS Keys, that would be the `Kms.fromCfnKey()` method - and passing the L1 instance as an argument: ```ts -import * as kms from '@aws-cdk/aws-kms'; - +declare const cfnTemplate: cfn_inc.CfnInclude; const cfnKey = cfnTemplate.getResource('Key') as kms.CfnKey; const key = kms.Key.fromCfnKey(cfnKey); ``` @@ -203,16 +202,23 @@ Each L2 class has static factory methods with names like `from*Name()`, You can obtain an L2 resource from an L1 by passing the correct properties of the L1 as the arguments to those methods: ```ts +declare const cfnTemplate: cfn_inc.CfnInclude; + // using from*Name() +const cfnBucket = cfnTemplate.getResource('Bucket') as s3.CfnBucket; const bucket = s3.Bucket.fromBucketName(this, 'L2Bucket', cfnBucket.ref); // using from*Arn() +const cfnKey = cfnTemplate.getResource('Key') as kms.CfnKey; const key = kms.Key.fromKeyArn(this, 'L2Key', cfnKey.attrArn); // using from*Attributes() +declare const privateCfnSubnet1: ec2.CfnSubnet; +declare const privateCfnSubnet2: ec2.CfnSubnet; +const cfnVpc = cfnTemplate.getResource('Vpc') as ec2.CfnVPC; const vpc = ec2.Vpc.fromVpcAttributes(this, 'L2Vpc', { vpcId: cfnVpc.ref, - availabilityZones: cdk.Fn.getAzs(), + availabilityZones: core.Fn.getAzs(), privateSubnetIds: [privateCfnSubnet1.ref, privateCfnSubnet2.ref], }); ``` @@ -231,71 +237,68 @@ you can also retrieve and mutate all other template elements: * [Parameters](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html): - ```ts - import * as core from '@aws-cdk/core'; - - const param: core.CfnParameter = cfnTemplate.getParameter('MyParameter'); + ```ts + declare const cfnTemplate: cfn_inc.CfnInclude; + const param: core.CfnParameter = cfnTemplate.getParameter('MyParameter'); - // mutating the parameter - param.default = 'MyDefault'; - ``` + // mutating the parameter + param.default = 'MyDefault'; + ``` * [Conditions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html): - ```ts - import * as core from '@aws-cdk/core'; + ```ts + declare const cfnTemplate: cfn_inc.CfnInclude; + const condition: core.CfnCondition = cfnTemplate.getCondition('MyCondition'); - const condition: core.CfnCondition = cfnTemplate.getCondition('MyCondition'); - - // mutating the condition - condition.expression = core.Fn.conditionEquals(1, 2); - ``` + // mutating the condition + condition.expression = core.Fn.conditionEquals(1, 2); + ``` * [Mappings](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html): - ```ts - import * as core from '@aws-cdk/core'; - - const mapping: core.CfnMapping = cfnTemplate.getMapping('MyMapping'); + ```ts + declare const cfnTemplate: cfn_inc.CfnInclude; + const mapping: core.CfnMapping = cfnTemplate.getMapping('MyMapping'); - // mutating the mapping - mapping.setValue('my-region', 'AMI', 'ami-04681a1dbd79675a5'); - ``` + // mutating the mapping + mapping.setValue('my-region', 'AMI', 'ami-04681a1dbd79675a5'); + ``` * [Service Catalog template Rules](https://docs.aws.amazon.com/servicecatalog/latest/adminguide/reference-template_constraint_rules.html): - ```ts - import * as core from '@aws-cdk/core'; - - const rule: core.CfnRule = cfnTemplate.getRule('MyRule'); + ```ts + declare const cfnTemplate: cfn_inc.CfnInclude; + const rule: core.CfnRule = cfnTemplate.getRule('MyRule'); - // mutating the rule - rule.addAssertion(core.Fn.conditionContains(['m1.small'], myParameter.value), - 'MyParameter has to be m1.small'); - ``` + // mutating the rule + declare const myParameter: core.CfnParameter; + rule.addAssertion(core.Fn.conditionContains(['m1.small'], myParameter.valueAsString), + 'MyParameter has to be m1.small'); + ``` * [Outputs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html): - ```ts - import * as core from '@aws-cdk/core'; + ```ts + declare const cfnTemplate: cfn_inc.CfnInclude; + const output: core.CfnOutput = cfnTemplate.getOutput('MyOutput'); - const output: core.CfnOutput = cfnTemplate.getOutput('MyOutput'); - - // mutating the output - output.value = cfnBucket.attrArn; - ``` + // mutating the output + declare const cfnBucket: s3.CfnBucket; + output.value = cfnBucket.attrArn; + ``` * [Hooks for blue-green deployments](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/blue-green.html): - ```ts - import * as core from '@aws-cdk/core'; - - const hook: core.CfnHook = cfnTemplate.getHook('MyOutput'); + ```ts + declare const cfnTemplate: cfn_inc.CfnInclude; + const hook: core.CfnHook = cfnTemplate.getHook('MyOutput'); - // mutating the hook - const codeDeployHook = hook as core.CfnCodeDeployBlueGreenHook; - codeDeployHook.serviceRole = myRole.roleArn; - ``` + // mutating the hook + declare const myRole: iam.Role; + const codeDeployHook = hook as core.CfnCodeDeployBlueGreenHook; + codeDeployHook.serviceRole = myRole.roleArn; + ``` ## Parameter replacement @@ -304,7 +307,7 @@ you may want to remove them in favor of build-time values. You can do that using the `parameters` property: ```ts -new inc.CfnInclude(this, 'includeTemplate', { +new cfn_inc.CfnInclude(this, 'includeTemplate', { templateFile: 'path/to/my/template', parameters: { 'MyParam': 'my-value', @@ -350,7 +353,7 @@ You can include both the parent stack, and the nested stack in your CDK application as follows: ```ts -const parentTemplate = new inc.CfnInclude(this, 'ParentStack', { +const parentTemplate = new cfn_inc.CfnInclude(this, 'ParentStack', { templateFile: 'path/to/my-parent-template.json', loadNestedStacks: { 'ChildStack': { @@ -371,6 +374,8 @@ will be modified to point to that asset. The included nested stack can be accessed with the `getNestedStack` method: ```ts +declare const parentTemplate: cfn_inc.CfnInclude; + const includedChildStack = parentTemplate.getNestedStack('ChildStack'); const childStack: core.NestedStack = includedChildStack.stack; const childTemplate: cfn_inc.CfnInclude = includedChildStack.includedTemplate; @@ -380,10 +385,12 @@ Now you can reference resources from `ChildStack`, and modify them like any other included template: ```ts +declare const childTemplate: cfn_inc.CfnInclude; + const cfnBucket = childTemplate.getResource('MyBucket') as s3.CfnBucket; cfnBucket.bucketName = 'my-new-bucket-name'; -const role = new iam.Role(childStack, 'MyRole', { +const role = new iam.Role(this, 'MyRole', { assumedBy: new iam.AccountRootPrincipal(), }); @@ -401,6 +408,7 @@ You can also include the nested stack after the `CfnInclude` object was created, instead of doing it on construction: ```ts +declare const parentTemplate: cfn_inc.CfnInclude; const includedChildStack = parentTemplate.loadNestedStack('ChildTemplate', { templateFile: 'path/to/my-nested-template.json', }); @@ -413,7 +421,9 @@ but more like specialized fragments, implementing a particular pattern or best p If you have templates like that, you can use the `CfnInclude` class to vend them as CDK Constructs: -```ts +```ts nofixture +import { Construct } from 'constructs'; +import * as cfn_inc from '@aws-cdk/cloudformation-include'; import * as path from 'path'; export class MyConstruct extends Construct { diff --git a/packages/@aws-cdk/cloudformation-include/package.json b/packages/@aws-cdk/cloudformation-include/package.json index a526eb3bf61d1..fcaecdf08c0c5 100644 --- a/packages/@aws-cdk/cloudformation-include/package.json +++ b/packages/@aws-cdk/cloudformation-include/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/cloudformation-include/rosetta/default.ts-fixture b/packages/@aws-cdk/cloudformation-include/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..307736228527e --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/rosetta/default.ts-fixture @@ -0,0 +1,18 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as core from '@aws-cdk/core'; +import * as path from 'path'; +import * as cfn_inc from '@aws-cdk/cloudformation-include'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as iam from '@aws-cdk/aws-iam'; +import * as kms from '@aws-cdk/aws-kms'; +import * as ec2 from '@aws-cdk/aws-ec2'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/core/README.md b/packages/@aws-cdk/core/README.md index 611467116459e..d6b58901838c2 100644 --- a/packages/@aws-cdk/core/README.md +++ b/packages/@aws-cdk/core/README.md @@ -237,6 +237,8 @@ this purpose. use the region and account of the stack you're calling it on: ```ts +declare const stack: Stack; + // Builds "arn::lambda:::function:MyFunction" stack.formatArn({ service: 'lambda', @@ -252,6 +254,8 @@ but in case of a deploy-time value be aware that the result will be another deploy-time value which cannot be inspected in the CDK application. ```ts +declare const stack: Stack; + // Extracts the function name out of an AWS Lambda Function ARN const arnComponents = stack.parseArn(arn, ':'); const functionName = arnComponents.resourceName; @@ -383,7 +387,11 @@ examples ensures that only a single SNS topic is defined: function getOrCreate(scope: Construct): sns.Topic { const stack = Stack.of(scope); const uniqueid = 'GloballyUniqueIdForSingleton'; // For example, a UUID from `uuidgen` - return stack.node.tryFindChild(uniqueid) as sns.Topic ?? new sns.Topic(stack, uniqueid); + const existing = stack.node.tryFindChild(uniqueid); + if (existing) { + return existing as sns.Topic; + } + return new sns.Topic(stack, uniqueid); } ``` @@ -816,6 +824,8 @@ since the top-level key is an unresolved token. The call to `findInMap` will ret `{ "Fn::FindInMap": [ "RegionTable", { "Ref": "AWS::Region" }, "regionName" ] }`. ```ts +declare const regionTable: CfnMapping; + regionTable.findInMap(Aws.REGION, 'regionName'); ``` diff --git a/packages/@aws-cdk/core/lib/arn.ts b/packages/@aws-cdk/core/lib/arn.ts index 343eb553e2cc7..ac10aef173535 100644 --- a/packages/@aws-cdk/core/lib/arn.ts +++ b/packages/@aws-cdk/core/lib/arn.ts @@ -312,7 +312,7 @@ export class Arn { // Apparently we could just parse this right away. Validate that we got the right // resource type (to notify authors of incorrect assumptions right away). - const parsed = Arn.parse(arn, '/', true); + const parsed = Arn.split(arn, ArnFormat.SLASH_RESOURCE_NAME); if (!Token.isUnresolved(parsed.resource) && parsed.resource !== resourceType) { throw new Error(`Expected resource type '${resourceType}' in ARN, got '${parsed.resource}' in '${arn}'`); } diff --git a/packages/@aws-cdk/core/lib/assets.ts b/packages/@aws-cdk/core/lib/assets.ts index 7cd9b9c973f9f..3732cc4fc19b8 100644 --- a/packages/@aws-cdk/core/lib/assets.ts +++ b/packages/@aws-cdk/core/lib/assets.ts @@ -247,14 +247,14 @@ export interface FileAssetLocation { /** * The HTTP URL of this asset on Amazon S3. * - * @example https://s3-us-east-1.amazonaws.com/mybucket/myobject + * Example value: `https://s3-us-east-1.amazonaws.com/mybucket/myobject` */ readonly httpUrl: string; /** * The S3 URL of this asset on Amazon S3. * - * @example s3://mybucket/myobject + * Example value: `s3://mybucket/myobject` */ readonly s3ObjectUrl: string; diff --git a/packages/@aws-cdk/core/lib/bundling.ts b/packages/@aws-cdk/core/lib/bundling.ts index 1ce4633354511..b472515026eaf 100644 --- a/packages/@aws-cdk/core/lib/bundling.ts +++ b/packages/@aws-cdk/core/lib/bundling.ts @@ -2,6 +2,7 @@ import { spawnSync, SpawnSyncOptions } from 'child_process'; import * as crypto from 'crypto'; import { isAbsolute, join } from 'path'; import { FileSystem } from './fs'; +import { quiet, reset } from './private/jsii-deprecated'; /** * Bundling options @@ -16,7 +17,7 @@ export interface BundlingOptions { /** * The entrypoint to run in the Docker container. * - * @example ['/bin/sh', '-c'] + * Example value: `['/bin/sh', '-c']` * * @see https://docs.docker.com/engine/reference/builder/#entrypoint * @@ -27,7 +28,7 @@ export interface BundlingOptions { /** * The command to run in the Docker container. * - * @example ['npm', 'install'] + * Example value: `['npm', 'install']` * * @see https://docs.docker.com/engine/reference/run/ * @@ -295,9 +296,19 @@ export class DockerImage extends BundlingDockerImage { return new DockerImage(image); } - /** @param image The Docker image */ - constructor(public readonly image: string, _imageHash?: string) { + /** The Docker image */ + public readonly image: string; + + constructor(image: string, _imageHash?: string) { + // It is preferrable for the deprecated class to inherit a non-deprecated class. + // However, in this case, the opposite has occurred which is incompatible with + // a deprecation feature. See https://github.com/aws/jsii/issues/3102. + const deprecated = quiet(); + super(image, _imageHash); + + reset(deprecated); + this.image = image; } /** @@ -306,14 +317,30 @@ export class DockerImage extends BundlingDockerImage { * @return The overridden image name if set or image hash name in that order */ public toJSON() { - return super.toJSON(); + // It is preferrable for the deprecated class to inherit a non-deprecated class. + // However, in this case, the opposite has occurred which is incompatible with + // a deprecation feature. See https://github.com/aws/jsii/issues/3102. + const deprecated = quiet(); + + const json = super.toJSON(); + + reset(deprecated); + return json; } /** * Runs a Docker image */ public run(options: DockerRunOptions = {}) { - return super.run(options); + // It is preferrable for the deprecated class to inherit a non-deprecated class. + // However, in this case, the opposite has occurred which is incompatible with + // a deprecation feature. See https://github.com/aws/jsii/issues/3102. + const deprecated = quiet(); + + const result = super.run(options); + + reset(deprecated); + return result; } /** @@ -326,7 +353,15 @@ export class DockerImage extends BundlingDockerImage { * @returns the destination path */ public cp(imagePath: string, outputPath?: string): string { - return super.cp(imagePath, outputPath); + // It is preferrable for the deprecated class to inherit a non-deprecated class. + // However, in this case, the opposite has occurred which is incompatible with + // a deprecation feature. See https://github.com/aws/jsii/issues/3102. + const deprecated = quiet(); + + const result = super.cp(imagePath, outputPath); + + reset(deprecated); + return result; } } @@ -447,7 +482,7 @@ export interface DockerBuildOptions { /** * Set platform if server is multi-platform capable. _Requires Docker Engine API v1.38+_. * - * @example 'linux/amd64' + * Example value: `linux/amd64` * * @default - no platform specified */ diff --git a/packages/@aws-cdk/core/lib/cfn-output.ts b/packages/@aws-cdk/core/lib/cfn-output.ts index e296ecce34375..0dfab3d63f16e 100644 --- a/packages/@aws-cdk/core/lib/cfn-output.ts +++ b/packages/@aws-cdk/core/lib/cfn-output.ts @@ -134,7 +134,7 @@ export class CfnOutput extends CfnElement { */ public get importValue() { // We made _exportName mutable so this will have to be lazy. - return Fn.importValue(Lazy.stringValue({ + return Fn.importValue(Lazy.uncachedString({ produce: (ctx) => { if (Stack.of(ctx.scope) === this.stack) { throw new Error(`'importValue' property of '${this.node.path}' should only be used in a different Stack`); diff --git a/packages/@aws-cdk/core/lib/construct-compat.ts b/packages/@aws-cdk/core/lib/construct-compat.ts index cc5921fb73465..0e55678c2ef71 100644 --- a/packages/@aws-cdk/core/lib/construct-compat.ts +++ b/packages/@aws-cdk/core/lib/construct-compat.ts @@ -326,7 +326,9 @@ export class ConstructNode { * all components of the tree. * * @deprecated use `node.addr` to obtain a consistent 42 character address for - * this node (see https://github.com/aws/constructs/pull/314) + * this node (see https://github.com/aws/constructs/pull/314). + * Alternatively, to get a CloudFormation-compatible unique identifier, use + * `Names.uniqueId()`. */ public get uniqueId(): string { return this._actualNode.uniqueId; } @@ -343,7 +345,7 @@ export class ConstructNode { * will be excluded from the calculation. In those cases constructs in the * same tree may have the same addreess. * - * @example c83a2846e506bcc5f10682b564084bca2d275709ee + * Example value: `c83a2846e506bcc5f10682b564084bca2d275709ee` */ public get addr(): string { return this._actualNode.addr; } diff --git a/packages/@aws-cdk/core/lib/context-provider.ts b/packages/@aws-cdk/core/lib/context-provider.ts index 526414a9cf790..b8d6c16abac75 100644 --- a/packages/@aws-cdk/core/lib/context-provider.ts +++ b/packages/@aws-cdk/core/lib/context-provider.ts @@ -96,7 +96,7 @@ export class ContextProvider { // if context is missing or an error occurred during context retrieval, // report and return a dummy value. if (value === undefined || providerError !== undefined) { - stack.reportMissingContext({ + stack.reportMissingContextKey({ key, provider: options.provider as cxschema.ContextProvider, props: props as cxschema.ContextQueryProperties, diff --git a/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts b/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts index 06f257fa18ff3..013c1f49b8ab8 100644 --- a/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts +++ b/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts @@ -1,5 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; +import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import { AssetStaging } from '../asset-staging'; import { FileAssetPackaging } from '../assets'; @@ -36,12 +37,23 @@ export interface CustomResourceProviderProps { * A set of IAM policy statements to include in the inline policy of the * provider's lambda function. * + * **Please note**: these are direct IAM JSON policy blobs, *not* `iam.PolicyStatement` + * objects like you will see in the rest of the CDK. + * * @default - no additional inline policy * * @example - * - * [{ Effect: 'Allow', Action: 's3:PutObject*', Resource: '*' }] - * + * const provider = CustomResourceProvider.getOrCreateProvider(this, 'Custom::MyCustomResourceType', { + * codeDirectory: `${__dirname}/my-handler`, + * runtime: CustomResourceProviderRuntime.NODEJS_12_X, + * policyStatements: [ + * { + * Effect: 'Allow', + * Action: 's3:PutObject*', + * Resource: '*', + * } + * ], + * }); */ readonly policyStatements?: any[]; @@ -144,11 +156,15 @@ export class CustomResourceProvider extends CoreConstruct { * `serviceToken` when defining a custom resource. * * @example - * new CustomResource(this, 'MyCustomResource', { - * // ... - * serviceToken: myProvider.serviceToken, // <--- here - * }) + * declare const myProvider: CustomResourceProvider; * + * new CustomResource(this, 'MyCustomResource', { + * serviceToken: myProvider.serviceToken, + * properties: { + * myPropertyOne: 'one', + * myPropertyTwo: 'two', + * }, + * }); */ public readonly serviceToken: string; @@ -174,9 +190,11 @@ export class CustomResourceProvider extends CoreConstruct { sourcePath: props.codeDirectory, }); - const asset = stack.addFileAsset({ - fileName: staging.relativeStagedPath(stack), - sourceHash: staging.sourceHash, + const assetFileName = staging.relativeStagedPath(stack); + + const asset = stack.synthesizer.addFileAsset({ + fileName: assetFileName, + sourceHash: staging.assetHash, packaging: FileAssetPackaging.ZIP_DIRECTORY, }); @@ -227,6 +245,11 @@ export class CustomResourceProvider extends CoreConstruct { handler.addDependsOn(role); + if (this.node.tryGetContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT)) { + handler.addMetadata(cxapi.ASSET_RESOURCE_METADATA_PATH_KEY, assetFileName); + handler.addMetadata(cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY, 'Code'); + } + this.serviceToken = Token.asString(handler.getAtt('Arn')); } diff --git a/packages/@aws-cdk/core/lib/nested-stack.ts b/packages/@aws-cdk/core/lib/nested-stack.ts index fd8c47f6a0731..39cc59ebd366f 100644 --- a/packages/@aws-cdk/core/lib/nested-stack.ts +++ b/packages/@aws-cdk/core/lib/nested-stack.ts @@ -1,4 +1,5 @@ import * as crypto from 'crypto'; +import * as cxapi from '@aws-cdk/cx-api'; import { Construct, Node } from 'constructs'; import { FileAssetPackaging } from './assets'; import { Fn } from './cfn-fn'; @@ -155,8 +156,8 @@ export class NestedStack extends Stack { * - If this is referenced from the parent stack, it will return a token that parses the name from the stack ID. * - If this is referenced from the context of the nested stack, it will return `{ "Ref": "AWS::StackName" }` * + * Example value: `mystack-mynestedstack-sggfrhxhum7w` * @attribute - * @example mystack-mynestedstack-sggfrhxhum7w */ public get stackName() { return this._contextualStackName; @@ -169,8 +170,8 @@ export class NestedStack extends Stack { * - If this is referenced from the parent stack, it will return `{ "Ref": "LogicalIdOfNestedStackResource" }`. * - If this is referenced from the context of the nested stack, it will return `{ "Ref": "AWS::StackId" }` * + * Example value: `arn:aws:cloudformation:us-east-2:123456789012:stack/mystack-mynestedstack-sggfrhxhum7w/f449b250-b969-11e0-a185-5081d0136786` * @attribute - * @example "arn:aws:cloudformation:us-east-2:123456789012:stack/mystack-mynestedstack-sggfrhxhum7w/f449b250-b969-11e0-a185-5081d0136786" */ public get stackId() { return this._contextualStackId; @@ -207,12 +208,14 @@ export class NestedStack extends Stack { const cfn = JSON.stringify(this._toCloudFormation()); const templateHash = crypto.createHash('sha256').update(cfn).digest('hex'); - const templateLocation = this._parentStack.addFileAsset({ + const templateLocation = this._parentStack.synthesizer.addFileAsset({ packaging: FileAssetPackaging.FILE, sourceHash: templateHash, fileName: this.templateFile, }); + this.addResourceMetadata(this.resource, 'TemplateURL'); + // if bucketName/objectKey are cfn parameters from a stack other than the parent stack, they will // be resolved as cross-stack references like any other (see "multi" tests). this._templateUrl = `https://s3.${this._parentStack.region}.${this._parentStack.urlSuffix}/${templateLocation.bucketName}/${templateLocation.objectKey}`; @@ -230,6 +233,18 @@ export class NestedStack extends Stack { }, }); } + + private addResourceMetadata(resource: CfnResource, resourceProperty: string) { + if (!this.node.tryGetContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT)) { + return; // not enabled + } + + // tell tools such as SAM CLI that the "TemplateURL" property of this resource + // points to the nested stack template for local emulation + resource.cfnOptions.metadata = resource.cfnOptions.metadata || { }; + resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PATH_KEY] = this.templateFile; + resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY] = resourceProperty; + } } /** diff --git a/packages/@aws-cdk/core/lib/private/jsii-deprecated.ts b/packages/@aws-cdk/core/lib/private/jsii-deprecated.ts new file mode 100644 index 0000000000000..73ad8a6e824d6 --- /dev/null +++ b/packages/@aws-cdk/core/lib/private/jsii-deprecated.ts @@ -0,0 +1,13 @@ +export function quiet(): string | undefined { + const deprecated = process.env.JSII_DEPRECATED; + process.env.JSII_DEPRECATED = 'quiet'; + return deprecated; +} + +export function reset(deprecated: string | undefined) { + if (deprecated === undefined) { + delete process.env.JSII_DEPRECATED; + } else { + process.env.JSII_DEPRECATED = deprecated; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/removal-policy.ts b/packages/@aws-cdk/core/lib/removal-policy.ts index d815967fa2bf0..f5249949f99ab 100644 --- a/packages/@aws-cdk/core/lib/removal-policy.ts +++ b/packages/@aws-cdk/core/lib/removal-policy.ts @@ -19,8 +19,10 @@ * as shown in the following example: * * ```ts - * const cfnBucket = bucket.node.findChild('Resource') as cdk.CfnResource; - * cfnBucket.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); + * declare const bucket: s3.Bucket; + * + * const cfnBucket = bucket.node.findChild('Resource') as CfnResource; + * cfnBucket.applyRemovalPolicy(RemovalPolicy.DESTROY); * ``` */ export enum RemovalPolicy { diff --git a/packages/@aws-cdk/core/lib/resource.ts b/packages/@aws-cdk/core/lib/resource.ts index c61e9ba69955e..afcadc6529963 100644 --- a/packages/@aws-cdk/core/lib/resource.ts +++ b/packages/@aws-cdk/core/lib/resource.ts @@ -1,4 +1,4 @@ -import { ArnComponents } from './arn'; +import { ArnComponents, ArnFormat } from './arn'; import { CfnResource } from './cfn-resource'; import { IConstruct, Construct as CoreConstruct } from './construct-compat'; import { IStringProducer, Lazy } from './lazy'; @@ -145,7 +145,10 @@ export abstract class Resource extends CoreConstruct implements IResource { this.stack = Stack.of(this); - const parsedArn = props.environmentFromArn ? this.stack.parseArn(props.environmentFromArn) : undefined; + const parsedArn = props.environmentFromArn ? + // Since we only want the region and account, NO_RESOURE_NAME is good enough + this.stack.splitArn(props.environmentFromArn, ArnFormat.NO_RESOURCE_NAME) + : undefined; this.env = { account: props.account ?? parsedArn?.account ?? this.stack.account, region: props.region ?? parsedArn?.region ?? this.stack.region, diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts index 211413df2b5ed..a9b9e0d7acb5d 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts @@ -79,9 +79,9 @@ function collectStackMetadata(stack: Stack) { return; } - if (node.node.metadata.length > 0) { + if (node.node.metadataEntry.length > 0) { // Make the path absolute - output[ConstructNode.PATH_SEP + node.node.path] = node.node.metadata.map(md => stack.resolve(md) as cxschema.MetadataEntry); + output[ConstructNode.PATH_SEP + node.node.path] = node.node.metadataEntry.map(md => stack.resolve(md) as cxschema.MetadataEntry); } for (const child of node.node.children) { diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts index bf699b271878d..e9c08990f673b 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts @@ -75,7 +75,7 @@ export class LegacyStackSynthesizer extends StackSynthesizer { } this.cycle = true; try { - return this.stack.addFileAsset(asset); + return this.stack.synthesizer.addFileAsset(asset); } finally { this.cycle = false; } @@ -91,7 +91,7 @@ export class LegacyStackSynthesizer extends StackSynthesizer { } this.cycle = true; try { - return this.stack.addDockerImageAsset(asset); + return this.stack.synthesizer.addDockerImageAsset(asset); } finally { this.cycle = false; } diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index ee80e56334101..6645805519b81 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -280,7 +280,7 @@ export class Stack extends CoreConstruct implements ITaggable { * The name of the CloudFormation template file emitted to the output * directory during synthesis. * - * @example 'MyStack.template.json' + * Example value: `MyStack.template.json` */ public readonly templateFile: string; @@ -711,11 +711,13 @@ export class Stack extends CoreConstruct implements ITaggable { * * Duplicate values are removed when stack is synthesized. * - * @example stack.addTransform('AWS::Serverless-2016-10-31') - * * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html - * * @param transform The transform to add + * + * @example + * declare const stack: Stack; + * + * stack.addTransform('AWS::Serverless-2016-10-31') */ public addTransform(transform: string) { if (!this.templateOptions.transforms) { diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index a14759ad2d345..578fac2dbbff0 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -28,7 +28,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", @@ -174,7 +181,7 @@ "@types/aws-lambda": "^8.10.85", "@types/fs-extra": "^8.1.2", "@types/jest": "^27.0.2", - "@types/lodash": "^4.14.176", + "@types/lodash": "^4.14.177", "@types/minimatch": "^3.0.5", "@types/node": "^10.17.60", "@types/sinon": "^9.0.11", @@ -182,7 +189,7 @@ "jest": "^27.3.1", "lodash": "^4.17.21", "sinon": "^9.2.4", - "ts-mock-imports": "^1.3.7" + "ts-mock-imports": "^1.3.8" }, "dependencies": { "@aws-cdk/cloud-assembly-schema": "0.0.0", diff --git a/packages/@aws-cdk/core/rosetta/default.ts-fixture b/packages/@aws-cdk/core/rosetta/default.ts-fixture index 558cc09b1c049..26a25736acb17 100644 --- a/packages/@aws-cdk/core/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/core/rosetta/default.ts-fixture @@ -27,6 +27,7 @@ import { Duration, Fn, IConstruct, + RemovalPolicy, SecretValue, Size, SizeRoundingBehavior, @@ -47,15 +48,27 @@ declare const functionProps: lambda.FunctionProps; declare const isCompleteHandler: lambda.Function; declare const myBucket: s3.IBucket; declare const myFunction: lambda.IFunction; -declare const myProvider: CustomResourceProvider; declare const myTopic: sns.ITopic; declare const onEventHandler: lambda.Function; declare const resourceProps: CfnResourceProps; -declare const stack: Stack; declare class MyStack extends Stack {} declare class YourStack extends Stack {} +class StackThatProvidesABucket extends Stack { + public readonly bucket!: s3.IBucket; +} + +interface StackThatExpectsABucketProps extends StackProps { + readonly bucket: s3.IBucket; +} + +class StackThatExpectsABucket extends Stack { + constructor(scope: Construct, id: string, props: StackThatExpectsABucketProps) { + super(scope, id, props); + } +} + class fixture$construct extends Construct { public constructor(scope: Construct, id: string) { super(scope, id); diff --git a/packages/@aws-cdk/core/test/app.test.ts b/packages/@aws-cdk/core/test/app.test.ts index c73538067f9ef..d805d0d0f41fe 100644 --- a/packages/@aws-cdk/core/test/app.test.ts +++ b/packages/@aws-cdk/core/test/app.test.ts @@ -195,7 +195,7 @@ describe('app', () => { constructor(scope: App, id: string, props?: StackProps) { super(scope, id, props); - this.reportMissingContext({ + this.reportMissingContextKey({ key: 'missing-context-key', provider: ContextProvider.AVAILABILITY_ZONE_PROVIDER, props: { @@ -205,7 +205,7 @@ describe('app', () => { }, ); - this.reportMissingContext({ + this.reportMissingContextKey({ key: 'missing-context-key-2', provider: ContextProvider.AVAILABILITY_ZONE_PROVIDER, props: { diff --git a/packages/@aws-cdk/core/test/arn.test.ts b/packages/@aws-cdk/core/test/arn.test.ts index f1d1c862a5ca4..28d0ebf22a446 100644 --- a/packages/@aws-cdk/core/test/arn.test.ts +++ b/packages/@aws-cdk/core/test/arn.test.ts @@ -1,3 +1,4 @@ +import { describeDeprecated, testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Arn, ArnComponents, ArnFormat, Aws, CfnOutput, ScopedAws, Stack, Token } from '../lib'; import { Intrinsic } from '../lib/private/intrinsic'; import { evaluateCFN } from './evaluate-cfn'; @@ -53,7 +54,7 @@ describe('arn', () => { }); - test('resourcePathSep can be set to ":" instead of the default "/"', () => { + testDeprecated('resourcePathSep can be set to ":" instead of the default "/"', () => { const stack = new Stack(); const arn = stack.formatArn({ @@ -70,7 +71,7 @@ describe('arn', () => { }); - test('resourcePathSep can be set to "" instead of the default "/"', () => { + testDeprecated('resourcePathSep can be set to "" instead of the default "/"', () => { const stack = new Stack(); const arn = stack.formatArn({ @@ -98,7 +99,7 @@ describe('arn', () => { }); - describe('Arn.parse(s)', () => { + describeDeprecated('Arn.parse(s)', () => { describe('fails', () => { test('if doesn\'t start with "arn:"', () => { @@ -335,7 +336,7 @@ describe('arn', () => { }); - test('parse other fields if only some are tokens', () => { + testDeprecated('parse other fields if only some are tokens', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/core/test/aspect.test.ts b/packages/@aws-cdk/core/test/aspect.test.ts index 0ed634cf5e58c..076dbbc2e7c3c 100644 --- a/packages/@aws-cdk/core/test/aspect.test.ts +++ b/packages/@aws-cdk/core/test/aspect.test.ts @@ -50,10 +50,10 @@ describe('aspect', () => { }, }); app.synth(); - expect(root.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(root.node.metadata[0].data).toEqual('We detected an Aspect was added via another Aspect, and will not be applied'); + expect(root.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); + expect(root.node.metadataEntry[0].data).toEqual('We detected an Aspect was added via another Aspect, and will not be applied'); // warning is not added to child construct - expect(child.node.metadata.length).toEqual(0); + expect(child.node.metadataEntry.length).toEqual(0); }); @@ -63,13 +63,13 @@ describe('aspect', () => { const child = new MyConstruct(root, 'ChildConstruct'); Aspects.of(root).add(new MyAspect()); app.synth(); - expect(root.node.metadata[0].type).toEqual('foo'); - expect(root.node.metadata[0].data).toEqual('bar'); - expect(child.node.metadata[0].type).toEqual('foo'); - expect(child.node.metadata[0].data).toEqual('bar'); + expect(root.node.metadataEntry[0].type).toEqual('foo'); + expect(root.node.metadataEntry[0].data).toEqual('bar'); + expect(child.node.metadataEntry[0].type).toEqual('foo'); + expect(child.node.metadataEntry[0].data).toEqual('bar'); // no warning is added - expect(root.node.metadata.length).toEqual(1); - expect(child.node.metadata.length).toEqual(1); + expect(root.node.metadataEntry.length).toEqual(1); + expect(child.node.metadataEntry.length).toEqual(1); }); diff --git a/packages/@aws-cdk/core/test/assets.test.ts b/packages/@aws-cdk/core/test/assets.test.ts index 206362c79e809..936688145073e 100644 --- a/packages/@aws-cdk/core/test/assets.test.ts +++ b/packages/@aws-cdk/core/test/assets.test.ts @@ -8,14 +8,14 @@ describe('assets', () => { const stack = new Stack(); // WHEN - stack.addFileAsset({ + stack.synthesizer.addFileAsset({ fileName: 'file-name', packaging: FileAssetPackaging.ZIP_DIRECTORY, sourceHash: 'source-hash', }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); expect(assetMetadata && assetMetadata.data).toBeDefined(); @@ -52,7 +52,7 @@ describe('assets', () => { const stack = new Stack(); // WHEN - const assetLocation = stack.addFileAsset({ + const assetLocation = stack.synthesizer.addFileAsset({ fileName: 'file-name', packaging: FileAssetPackaging.ZIP_DIRECTORY, sourceHash: 'source-hash', @@ -75,14 +75,13 @@ describe('assets', () => { const stack = new Stack(); // WHEN - stack.addDockerImageAsset({ + stack.synthesizer.addDockerImageAsset({ sourceHash: 'source-hash', directoryName: 'directory-name', - repositoryName: 'repository-name', }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); expect(assetMetadata && assetMetadata.data).toBeDefined(); @@ -91,7 +90,6 @@ describe('assets', () => { expect(data.packaging).toEqual('container-image'); expect(data.path).toEqual('directory-name'); expect(data.sourceHash).toEqual('source-hash'); - expect(data.repositoryName).toEqual('repository-name'); expect(data.imageTag).toEqual('source-hash'); } @@ -104,13 +102,13 @@ describe('assets', () => { const stack = new Stack(); // WHEN - stack.addDockerImageAsset({ + stack.synthesizer.addDockerImageAsset({ sourceHash: 'source-hash', directoryName: 'directory-name', }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); expect(assetMetadata && assetMetadata.data).toBeDefined(); @@ -119,7 +117,6 @@ describe('assets', () => { expect(data.packaging).toEqual('container-image'); expect(data.path).toEqual('directory-name'); expect(data.sourceHash).toEqual('source-hash'); - expect(data.repositoryName).toEqual('aws-cdk/assets'); expect(data.imageTag).toEqual('source-hash'); } @@ -133,13 +130,13 @@ describe('assets', () => { stack.node.setContext('assets-ecr-repository-name', 'my-custom-repo-name'); // WHEN - stack.addDockerImageAsset({ + stack.synthesizer.addDockerImageAsset({ sourceHash: 'source-hash', directoryName: 'directory-name', }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadataEntry.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); expect(assetMetadata && assetMetadata.data).toBeDefined(); diff --git a/packages/@aws-cdk/core/test/bundling.test.ts b/packages/@aws-cdk/core/test/bundling.test.ts index a22968f4d17a0..5030603bb7c41 100644 --- a/packages/@aws-cdk/core/test/bundling.test.ts +++ b/packages/@aws-cdk/core/test/bundling.test.ts @@ -2,7 +2,7 @@ import * as child_process from 'child_process'; import * as crypto from 'crypto'; import * as path from 'path'; import * as sinon from 'sinon'; -import { BundlingDockerImage, DockerImage, FileSystem } from '../lib'; +import { DockerImage, FileSystem } from '../lib'; describe('bundling', () => { afterEach(() => { @@ -21,7 +21,7 @@ describe('bundling', () => { signal: null, }); - const image = BundlingDockerImage.fromRegistry('alpine'); + const image = DockerImage.fromRegistry('alpine'); image.run({ command: ['cool', 'command'], environment: { @@ -136,7 +136,7 @@ describe('bundling', () => { error: new Error('UnknownError'), }); - const image = BundlingDockerImage.fromRegistry('alpine'); + const image = DockerImage.fromRegistry('alpine'); expect(() => image.run()).toThrow(/UnknownError/); }); @@ -151,13 +151,13 @@ describe('bundling', () => { signal: null, }); - const image = BundlingDockerImage.fromRegistry('alpine'); + const image = DockerImage.fromRegistry('alpine'); expect(() => image.run()).toThrow(/\[Status -1\]/); }); test('BundlerDockerImage json is the bundler image name by default', () => { - const image = BundlingDockerImage.fromRegistry('alpine'); + const image = DockerImage.fromRegistry('alpine'); expect(image.toJSON()).toEqual('alpine'); @@ -199,7 +199,7 @@ describe('bundling', () => { }); const imagePath = path.join(__dirname, 'fs/fixtures/test1'); - BundlingDockerImage.fromAsset(imagePath, { + DockerImage.fromAsset(imagePath, { file: 'my-dockerfile', }); @@ -221,7 +221,7 @@ describe('bundling', () => { }); const imagePath = path.join(__dirname, 'fs/fixtures/test1'); - const image = BundlingDockerImage.fromAsset(imagePath, { + const image = DockerImage.fromAsset(imagePath, { file: 'my-dockerfile', }); expect(image).toBeDefined(); @@ -240,7 +240,7 @@ describe('bundling', () => { signal: null, }); - const image = BundlingDockerImage.fromRegistry('alpine'); + const image = DockerImage.fromRegistry('alpine'); image.run({ entrypoint: ['/cool/entrypoint', '--cool-entrypoint-arg'], command: ['cool', 'command'], @@ -281,7 +281,7 @@ describe('bundling', () => { }); // WHEN - BundlingDockerImage.fromRegistry('alpine').cp('/foo/bar', '/baz'); + DockerImage.fromRegistry('alpine').cp('/foo/bar', '/baz'); // THEN expect(spawnSyncStub.calledWith(sinon.match.any, ['create', 'alpine'], sinon.match.any)).toEqual(true); @@ -315,7 +315,7 @@ describe('bundling', () => { // WHEN expect(() => { - BundlingDockerImage.fromRegistry('alpine').cp('/foo/bar', '/baz'); + DockerImage.fromRegistry('alpine').cp('/foo/bar', '/baz'); }).toThrow(/Failed.*copy/i); // THEN diff --git a/packages/@aws-cdk/core/test/cloudformation-json.test.ts b/packages/@aws-cdk/core/test/cloudformation-json.test.ts index 977795fc35d9d..204019f128a68 100644 --- a/packages/@aws-cdk/core/test/cloudformation-json.test.ts +++ b/packages/@aws-cdk/core/test/cloudformation-json.test.ts @@ -79,11 +79,11 @@ describe('tokens that return literals', () => { test('String-encoded lazies do not have quotes applied if they return objects', () => { // This is unfortunately crazy behavior, but we have some clients already taking a - // dependency on the fact that `Lazy.stringValue({ produce: () => [...some list...] })` + // dependency on the fact that `Lazy.string({ produce: () => [...some list...] })` // does not apply quotes but just renders the list. // GIVEN - const someList = Lazy.stringValue({ produce: () => [1, 2, 3] as any }); + const someList = Lazy.string({ produce: () => [1, 2, 3] as any }); // WHEN expect(evaluateCFN(stack.resolve(stack.toJsonString({ someList })))).toEqual('{"someList":[1,2,3]}'); @@ -137,7 +137,7 @@ describe('tokens that return literals', () => { test('Doubly nested strings evaluate correctly in JSON context', () => { // WHEN - const fidoSays = Lazy.stringValue({ produce: () => 'woof' }); + const fidoSays = Lazy.string({ produce: () => 'woof' }); // WHEN const resolved = stack.resolve(stack.toJsonString({ @@ -150,7 +150,7 @@ describe('tokens that return literals', () => { test('Quoted strings in embedded JSON context are escaped', () => { // GIVEN - const fidoSays = Lazy.stringValue({ produce: () => '"woof"' }); + const fidoSays = Lazy.string({ produce: () => '"woof"' }); // WHEN const resolved = stack.resolve(stack.toJsonString({ diff --git a/packages/@aws-cdk/core/test/construct.test.ts b/packages/@aws-cdk/core/test/construct.test.ts index e8b7c5f00435d..b61d29d4763e1 100644 --- a/packages/@aws-cdk/core/test/construct.test.ts +++ b/packages/@aws-cdk/core/test/construct.test.ts @@ -1,3 +1,4 @@ +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { App as Root, Aws, Construct, ConstructNode, ConstructOrder, IConstruct, Lazy, ValidationError } from '../lib'; import { Annotations } from '../lib/annotations'; @@ -73,7 +74,7 @@ describe('construct', () => { }); - test('construct.uniqueId returns a tree-unique alphanumeric id of this construct', () => { + testDeprecated('construct.uniqueId returns a tree-unique alphanumeric id of this construct', () => { const root = new Root(); const child1 = new Construct(root, 'This is the first child'); @@ -88,7 +89,7 @@ describe('construct', () => { }); - test('cannot calculate uniqueId if the construct path is ["Default"]', () => { + testDeprecated('cannot calculate uniqueId if the construct path is ["Default"]', () => { const root = new Root(); const c = new Construct(root, 'Default'); expect(() => c.node.uniqueId).toThrow(/Unable to calculate a unique id for an empty set of components/); @@ -250,18 +251,18 @@ describe('construct', () => { const previousValue = reEnableStackTraceCollection(); const root = new Root(); const con = new Construct(root, 'MyConstruct'); - expect(con.node.metadata).toEqual([]); + expect(con.node.metadataEntry).toEqual([]); con.node.addMetadata('key', 'value'); con.node.addMetadata('number', 103); con.node.addMetadata('array', [123, 456]); restoreStackTraceColection(previousValue); - expect(con.node.metadata[0].type).toEqual('key'); - expect(con.node.metadata[0].data).toEqual('value'); - expect(con.node.metadata[1].data).toEqual(103); - expect(con.node.metadata[2].data).toEqual([123, 456]); - expect(con.node.metadata[0].trace && con.node.metadata[0].trace[1].indexOf('FIND_ME')).toEqual(-1); + expect(con.node.metadataEntry[0].type).toEqual('key'); + expect(con.node.metadataEntry[0].data).toEqual('value'); + expect(con.node.metadataEntry[1].data).toEqual(103); + expect(con.node.metadataEntry[2].data).toEqual([123, 456]); + expect(con.node.metadataEntry[0].trace && con.node.metadataEntry[0].trace[1].indexOf('FIND_ME')).toEqual(-1); }); @@ -274,7 +275,7 @@ describe('construct', () => { con.node.addMetadata('False', false); con.node.addMetadata('Empty', ''); - const exists = (key: string) => con.node.metadata.find(x => x.type === key); + const exists = (key: string) => con.node.metadataEntry.find(x => x.type === key); expect(exists('Null')).toBeUndefined(); expect(exists('Undefined')).toBeUndefined(); @@ -291,9 +292,9 @@ describe('construct', () => { Annotations.of(con).addWarning('This construct is deprecated, use the other one instead'); restoreStackTraceColection(previousValue); - expect(con.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); - expect(con.node.metadata[0].data).toEqual('This construct is deprecated, use the other one instead'); - expect(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0).toEqual(true); + expect(con.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); + expect(con.node.metadataEntry[0].data).toEqual('This construct is deprecated, use the other one instead'); + expect(con.node.metadataEntry[0].trace && con.node.metadataEntry[0].trace.length > 0).toEqual(true); }); @@ -304,9 +305,9 @@ describe('construct', () => { Annotations.of(con).addError('Stop!'); restoreStackTraceColection(previousValue); - expect(con.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.ERROR); - expect(con.node.metadata[0].data).toEqual('Stop!'); - expect(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0).toEqual(true); + expect(con.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.ERROR); + expect(con.node.metadataEntry[0].data).toEqual('Stop!'); + expect(con.node.metadataEntry[0].trace && con.node.metadataEntry[0].trace.length > 0).toEqual(true); }); @@ -317,9 +318,9 @@ describe('construct', () => { Annotations.of(con).addInfo('Hey there, how do you do?'); restoreStackTraceColection(previousValue); - expect(con.node.metadata[0].type).toEqual(cxschema.ArtifactMetadataEntryType.INFO); - expect(con.node.metadata[0].data).toEqual('Hey there, how do you do?'); - expect(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0).toEqual(true); + expect(con.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.INFO); + expect(con.node.metadataEntry[0].data).toEqual('Hey there, how do you do?'); + expect(con.node.metadataEntry[0].trace && con.node.metadataEntry[0].trace.length > 0).toEqual(true); }); diff --git a/packages/@aws-cdk/core/test/context.test.ts b/packages/@aws-cdk/core/test/context.test.ts index b8e0e85c169a4..b052e74e8f1b7 100644 --- a/packages/@aws-cdk/core/test/context.test.ts +++ b/packages/@aws-cdk/core/test/context.test.ts @@ -161,7 +161,7 @@ describe('context', () => { }); // THEN - const error = construct.node.metadata.find(m => m.type === 'aws:cdk:error'); + const error = construct.node.metadataEntry.find(m => m.type === 'aws:cdk:error'); expect(error && error.data).toEqual('I had a boo-boo'); diff --git a/packages/@aws-cdk/core/test/custom-resource-provider/custom-resource-provider.test.ts b/packages/@aws-cdk/core/test/custom-resource-provider/custom-resource-provider.test.ts index c8d9082447f54..ecda2d2741c5d 100644 --- a/packages/@aws-cdk/core/test/custom-resource-provider/custom-resource-provider.test.ts +++ b/packages/@aws-cdk/core/test/custom-resource-provider/custom-resource-provider.test.ts @@ -1,5 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; +import * as cxapi from '@aws-cdk/cx-api'; import { App, AssetStaging, CustomResourceProvider, CustomResourceProviderRuntime, DockerImageAssetLocation, DockerImageAssetSource, Duration, FileAssetLocation, FileAssetSource, ISynthesisSession, Size, Stack } from '../../lib'; import { toCloudFormation } from '../util'; @@ -23,7 +24,7 @@ describe('custom resource provider', () => { // The asset hash constantly changes, so in order to not have to chase it, just look // it up from the output. const staging = stack.node.tryFindChild('Custom:MyResourceTypeCustomResourceProvider')?.node.tryFindChild('Staging') as AssetStaging; - const assetHash = staging.sourceHash; + const assetHash = staging.assetHash; const paramNames = Object.keys(cfn.Parameters); const bucketParam = paramNames[0]; const keyParam = paramNames[1]; @@ -122,6 +123,28 @@ describe('custom resource provider', () => { }); + test('asset metadata added to custom resource that contains code definition', () => { + // GIVEN + const stack = new Stack(); + stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + stack.node.setContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT, true); + + // WHEN + CustomResourceProvider.getOrCreate(stack, 'Custom:MyResourceType', { + codeDirectory: TEST_HANDLER, + runtime: CustomResourceProviderRuntime.NODEJS_12_X, + }); + + // Then + const lambda = toCloudFormation(stack).Resources.CustomMyResourceTypeCustomResourceProviderHandler29FBDD2A; + expect(lambda).toHaveProperty('Metadata'); + expect(lambda.Metadata).toEqual({ + 'aws:asset:path': `${__dirname}/mock-provider`, + 'aws:asset:property': 'Code', + }); + + }); + test('custom resource provided creates asset in new-style synthesis with relative path', () => { // GIVEN diff --git a/packages/@aws-cdk/core/test/duration.test.ts b/packages/@aws-cdk/core/test/duration.test.ts index c7cf230308634..c22de1e808ee9 100644 --- a/packages/@aws-cdk/core/test/duration.test.ts +++ b/packages/@aws-cdk/core/test/duration.test.ts @@ -1,3 +1,4 @@ +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Duration, Lazy, Stack, Token } from '../lib'; describe('duration', () => { @@ -76,7 +77,7 @@ describe('duration', () => { }); - test('toISOString', () => { + testDeprecated('toISOString', () => { expect(Duration.millis(0).toISOString()).toEqual('PT0S'); expect(Duration.seconds(0).toISOString()).toEqual('PT0S'); expect(Duration.minutes(0).toISOString()).toEqual('PT0S'); diff --git a/packages/@aws-cdk/core/test/include.test.ts b/packages/@aws-cdk/core/test/include.test.ts index 3973107536bf6..235bcba228bc7 100644 --- a/packages/@aws-cdk/core/test/include.test.ts +++ b/packages/@aws-cdk/core/test/include.test.ts @@ -1,7 +1,8 @@ +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { CfnInclude, CfnOutput, CfnParameter, CfnResource, Stack } from '../lib'; import { toCloudFormation } from './util'; -describe('include', () => { +describeDeprecated('include', () => { test('the Include construct can be used to embed an existing template as-is into a stack', () => { const stack = new Stack(); diff --git a/packages/@aws-cdk/core/test/private/tree-metadata.test.ts b/packages/@aws-cdk/core/test/private/tree-metadata.test.ts index 752d6f3fc5f42..1e6f7d9739133 100644 --- a/packages/@aws-cdk/core/test/private/tree-metadata.test.ts +++ b/packages/@aws-cdk/core/test/private/tree-metadata.test.ts @@ -337,7 +337,7 @@ describe('tree metadata', () => { const treenode = app.node.findChild('Tree'); - const warn = treenode.node.metadata.find((md) => { + const warn = treenode.node.metadataEntry.find((md) => { return md.type === cxschema.ArtifactMetadataEntryType.WARN && /Forcing an inspect error/.test(md.data as string) && /mycfnresource/.test(md.data as string); diff --git a/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts b/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts index 80303a4bbcf22..4ced5a1e9afeb 100644 --- a/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts +++ b/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts @@ -287,7 +287,7 @@ describe('new style synthesis', () => { // THEN const asm = myapp.synth(); - const stackArtifact = asm.getStack(mystack.stackName); + const stackArtifact = asm.getStackByName(mystack.stackName); expect(stackArtifact.assumeRoleExternalId).toEqual('deploy-external-id'); diff --git a/packages/@aws-cdk/core/test/stack.test.ts b/packages/@aws-cdk/core/test/stack.test.ts index 7c67f545d4b87..160ff09278b50 100644 --- a/packages/@aws-cdk/core/test/stack.test.ts +++ b/packages/@aws-cdk/core/test/stack.test.ts @@ -1,5 +1,5 @@ +import { testDeprecated, testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import { App, CfnCondition, CfnInclude, CfnOutput, CfnParameter, CfnResource, Construct, Lazy, ScopedAws, Stack, validateString, @@ -241,7 +241,7 @@ describe('stack', () => { }); - test('Include should support non-hash top-level template elements like "Description"', () => { + testDeprecated('Include should support non-hash top-level template elements like "Description"', () => { const stack = new Stack(); const template = { @@ -661,6 +661,29 @@ describe('stack', () => { })); }); + test('asset metadata added to NestedStack resource that contains asset path and property', () => { + const app = new App(); + + // WHEN + const parentStack = new Stack(app, 'parent'); + parentStack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + const childStack = new NestedStack(parentStack, 'child'); + new CfnResource(childStack, 'ChildResource', { type: 'Resource::Child' }); + + const assembly = app.synth(); + expect(assembly.getStackByName(parentStack.stackName).template).toEqual(expect.objectContaining({ + Resources: { + childNestedStackchildNestedStackResource7408D03F: expect.objectContaining({ + Metadata: { + 'aws:asset:path': 'parentchild13F9359B.nested.template.json', + 'aws:asset:property': 'TemplateURL', + }, + }), + }, + })); + + }); + test('cross-stack reference (substack references parent stack)', () => { // GIVEN const app = new App(); diff --git a/packages/@aws-cdk/core/test/stage.test.ts b/packages/@aws-cdk/core/test/stage.test.ts index 4178060822d68..6c7f27c8cfec4 100644 --- a/packages/@aws-cdk/core/test/stage.test.ts +++ b/packages/@aws-cdk/core/test/stage.test.ts @@ -321,7 +321,7 @@ test('missing context in Stages is propagated up to root assembly', () => { new CfnResource(stack, 'Resource', { type: 'Something' }); // WHEN - stack.reportMissingContext({ + stack.reportMissingContextKey({ key: 'missing-context-key', provider: cxschema.ContextProvider.AVAILABILITY_ZONE_PROVIDER, props: { diff --git a/packages/@aws-cdk/core/test/staging.test.ts b/packages/@aws-cdk/core/test/staging.test.ts index f9313f5095d2d..9c2c0a0f111b1 100644 --- a/packages/@aws-cdk/core/test/staging.test.ts +++ b/packages/@aws-cdk/core/test/staging.test.ts @@ -1,10 +1,11 @@ import * as os from 'os'; import * as path from 'path'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { FileAssetPackaging } from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs-extra'; import * as sinon from 'sinon'; -import { App, AssetHashType, AssetStaging, BundlingDockerImage, BundlingOptions, BundlingOutput, FileSystem, Stack, Stage } from '../lib'; +import { App, AssetHashType, AssetStaging, DockerImage, BundlingOptions, BundlingOutput, FileSystem, Stack, Stage } from '../lib'; const STUB_INPUT_FILE = '/tmp/docker-stub.input'; const STUB_INPUT_CONCAT_FILE = '/tmp/docker-stub.input.concat'; @@ -48,9 +49,9 @@ describe('staging', () => { // WHEN const staging = new AssetStaging(stack, 's1', { sourcePath }); - expect(staging.sourceHash).toEqual(FIXTURE_TEST1_HASH); + expect(staging.assetHash).toEqual(FIXTURE_TEST1_HASH); expect(staging.sourcePath).toEqual(sourcePath); - expect(path.basename(staging.stagedPath)).toEqual(`asset.${FIXTURE_TEST1_HASH}`); + expect(path.basename(staging.absoluteStagedPath)).toEqual(`asset.${FIXTURE_TEST1_HASH}`); expect(path.basename(staging.relativeStagedPath(stack))).toEqual(`asset.${FIXTURE_TEST1_HASH}`); expect(staging.packaging).toEqual(FileAssetPackaging.ZIP_DIRECTORY); expect(staging.isArchive).toEqual(true); @@ -160,9 +161,9 @@ describe('staging', () => { // WHEN const staging = new AssetStaging(stack, 's1', { sourcePath }); - expect(staging.sourceHash).toEqual(FIXTURE_TEST1_HASH); + expect(staging.assetHash).toEqual(FIXTURE_TEST1_HASH); expect(staging.sourcePath).toEqual(sourcePath); - expect(staging.stagedPath).toEqual(sourcePath); + expect(staging.absoluteStagedPath).toEqual(sourcePath); expect(staging.relativeStagedPath(stack)).toEqual(sourcePath); }); @@ -225,9 +226,9 @@ describe('staging', () => { const withExtra = new AssetStaging(stack, 'withExtra', { sourcePath: directory, extraHash: 'boom' }); // THEN - expect(withoutExtra.sourceHash).not.toEqual(withExtra.sourceHash); - expect(withoutExtra.sourceHash).toEqual(FIXTURE_TEST1_HASH); - expect(withExtra.sourceHash).toEqual('c95c915a5722bb9019e2c725d11868e5a619b55f36172f76bcbcaa8bb2d10c5f'); + expect(withoutExtra.assetHash).not.toEqual(withExtra.assetHash); + expect(withoutExtra.assetHash).toEqual(FIXTURE_TEST1_HASH); + expect(withExtra.assetHash).toEqual('c95c915a5722bb9019e2c725d11868e5a619b55f36172f76bcbcaa8bb2d10c5f'); }); @@ -242,7 +243,7 @@ describe('staging', () => { new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -278,7 +279,7 @@ describe('staging', () => { const asset = new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -294,7 +295,7 @@ describe('staging', () => { 'tree.json', ]); - expect(asset.sourceHash).toEqual('b1e32e86b3523f2fa512eb99180ee2975a50a4439e63e8badd153f2a68d61aa4'); + expect(asset.assetHash).toEqual('b1e32e86b3523f2fa512eb99180ee2975a50a4439e63e8badd153f2a68d61aa4'); expect(asset.sourcePath).toEqual(directory); const resolvedStagePath = asset.relativeStagedPath(stack); @@ -315,7 +316,7 @@ describe('staging', () => { new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -323,7 +324,7 @@ describe('staging', () => { new AssetStaging(stack, 'AssetDuplicate', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -360,7 +361,7 @@ describe('staging', () => { sourcePath: directory, assetHashType: AssetHashType.OUTPUT, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -370,7 +371,7 @@ describe('staging', () => { assetHashType: AssetHashType.OUTPUT, bundling: { // Same bundling but with keys ordered differently command: [DockerStubCommand.SUCCESS], - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), }, }); @@ -408,7 +409,7 @@ describe('staging', () => { new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -416,7 +417,7 @@ describe('staging', () => { new AssetStaging(stack, 'AssetWithDifferentBundlingOptions', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], environment: { UNIQUE_ENV_VAR: 'SOMEVALUE', @@ -459,9 +460,9 @@ describe('staging', () => { // WHEN new AssetStaging(stack, 'Asset', { sourcePath: directory, - assetHashType: AssetHashType.BUNDLE, + assetHashType: AssetHashType.OUTPUT, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -494,7 +495,7 @@ describe('staging', () => { expect(() => new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.FAIL], }, })).toThrow(/Failed.*bundl.*asset.*-error/); @@ -523,7 +524,7 @@ describe('staging', () => { new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -540,7 +541,7 @@ describe('staging', () => { new AssetStaging(stack2, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -576,7 +577,7 @@ describe('staging', () => { expect(() => new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS_NO_OUTPUT], }, })).toThrow(/Bundling did not produce any output/); @@ -588,7 +589,7 @@ describe('staging', () => { }); - test('bundling with BUNDLE asset hash type', () => { + testDeprecated('bundling with BUNDLE asset hash type', () => { // GIVEN const app = new App(); const stack = new Stack(app, 'stack'); @@ -598,7 +599,7 @@ describe('staging', () => { const asset = new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, assetHashType: AssetHashType.BUNDLE, @@ -625,7 +626,7 @@ describe('staging', () => { const asset = new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], securityOpt: 'no-new-privileges', }, @@ -652,7 +653,7 @@ describe('staging', () => { const asset = new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, assetHashType: AssetHashType.OUTPUT, @@ -693,17 +694,17 @@ describe('staging', () => { expect(() => new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, assetHash: 'my-custom-hash', - assetHashType: AssetHashType.BUNDLE, - })).toThrow(/Cannot specify `bundle` for `assetHashType`/); + assetHashType: AssetHashType.OUTPUT, + })).toThrow(/Cannot specify `output` for `assetHashType`/); }); - test('throws with BUNDLE hash type and no bundling', () => { + testDeprecated('throws with BUNDLE hash type and no bundling', () => { // GIVEN const app = new App(); const stack = new Stack(app, 'stack'); @@ -761,7 +762,7 @@ describe('staging', () => { expect(() => new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('this-is-an-invalid-docker-image'), + image: DockerImage.fromRegistry('this-is-an-invalid-docker-image'), command: [DockerStubCommand.FAIL], }, })).toThrow(/Failed to bundle asset stack\/Asset/); @@ -785,7 +786,7 @@ describe('staging', () => { new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], local: { tryBundle(outputDir: string, options: BundlingOptions): boolean { @@ -820,7 +821,7 @@ describe('staging', () => { new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], local: { tryBundle(_bundleDir: string): boolean { @@ -846,9 +847,9 @@ describe('staging', () => { // WHEN const asset = new AssetStaging(stack, 'Asset', { sourcePath: directory, - assetHashType: AssetHashType.BUNDLE, + assetHashType: AssetHashType.OUTPUT, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -872,9 +873,9 @@ describe('staging', () => { // WHEN const asset = new AssetStaging(stack, 'Asset', { sourcePath: directory, - assetHashType: AssetHashType.BUNDLE, + assetHashType: AssetHashType.OUTPUT, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -898,9 +899,9 @@ describe('staging', () => { // WHEN const asset = new AssetStaging(stack, 'Asset', { sourcePath: directory, - assetHashType: AssetHashType.BUNDLE, + assetHashType: AssetHashType.OUTPUT, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SUCCESS], }, }); @@ -924,7 +925,7 @@ describe('staging', () => { const staging = new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SINGLE_ARCHIVE], }, }); @@ -967,7 +968,7 @@ describe('staging', () => { const staging1 = new AssetStaging(stack1, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SINGLE_ARCHIVE], outputType: BundlingOutput.ARCHIVED, }, @@ -981,7 +982,7 @@ describe('staging', () => { const staging2 = new AssetStaging(stack2, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SINGLE_ARCHIVE], outputType: BundlingOutput.ARCHIVED, }, @@ -1006,7 +1007,7 @@ describe('staging', () => { const staging = new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.SINGLE_ARCHIVE], outputType: BundlingOutput.NOT_ARCHIVED, }, @@ -1037,7 +1038,7 @@ describe('staging', () => { expect(() => new AssetStaging(stack, 'Asset', { sourcePath: directory, bundling: { - image: BundlingDockerImage.fromRegistry('alpine'), + image: DockerImage.fromRegistry('alpine'), command: [DockerStubCommand.MULTIPLE_FILES], outputType: BundlingOutput.ARCHIVED, }, diff --git a/packages/@aws-cdk/core/test/synthesis.test.ts b/packages/@aws-cdk/core/test/synthesis.test.ts index 42dbb2f3fbc4f..496fd76fdbd91 100644 --- a/packages/@aws-cdk/core/test/synthesis.test.ts +++ b/packages/@aws-cdk/core/test/synthesis.test.ts @@ -1,6 +1,7 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cdk from '../lib'; @@ -174,7 +175,7 @@ describe('synthesis', () => { }); - test('it should be possible to synthesize without an app', () => { + testDeprecated('it should be possible to synthesize without an app', () => { const calls = new Array(); class SynthesizeMe extends cdk.Construct { diff --git a/packages/@aws-cdk/core/test/tokens.test.ts b/packages/@aws-cdk/core/test/tokens.test.ts index 1aca7312357fa..48e07c1fc720f 100644 --- a/packages/@aws-cdk/core/test/tokens.test.ts +++ b/packages/@aws-cdk/core/test/tokens.test.ts @@ -671,7 +671,7 @@ describe('tokens', () => { test('creation stack is omitted without CDK_DEBUG=true', () => { function showMeInTheStackTrace() { - return Lazy.stringValue({ produce: () => { throw new Error('fooError'); } }); + return Lazy.string({ produce: () => { throw new Error('fooError'); } }); } const previousValue = process.env.CDK_DEBUG; diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index 0a73646e5294b..b41dc9d64ceed 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -87,7 +87,7 @@ "aws-sdk": "^2.848.0", "aws-sdk-mock": "^5.4.0", "fs-extra": "^9.1.0", - "nock": "^13.1.4", + "nock": "^13.2.1", "sinon": "^9.2.4" }, "dependencies": { diff --git a/packages/@aws-cdk/cx-api/lib/assets.ts b/packages/@aws-cdk/cx-api/lib/assets.ts index c15dbcd82776f..0b3eaa52cefb5 100644 --- a/packages/@aws-cdk/cx-api/lib/assets.ts +++ b/packages/@aws-cdk/cx-api/lib/assets.ts @@ -10,6 +10,9 @@ export const ASSET_RESOURCE_METADATA_ENABLED_CONTEXT = 'aws:cdk:enable-asset-met * to resources. */ export const ASSET_RESOURCE_METADATA_PATH_KEY = 'aws:asset:path'; +export const ASSET_RESOURCE_METADATA_DOCKERFILE_PATH_KEY = 'aws:asset:dockerfile-path'; +export const ASSET_RESOURCE_METADATA_DOCKER_BUILD_ARGS_KEY = 'aws:asset:docker-build-args'; +export const ASSET_RESOURCE_METADATA_DOCKER_BUILD_TARGET_KEY = 'aws:asset:docker-build-target'; export const ASSET_RESOURCE_METADATA_PROPERTY_KEY = 'aws:asset:property'; /** diff --git a/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts b/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts index ddd7538308ec3..2151e2345620c 100644 --- a/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts +++ b/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts @@ -1,4 +1,5 @@ import * as path from 'path'; +import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { CloudAssembly } from '../lib'; const FIXTURES = path.join(__dirname, 'fixtures'); @@ -105,7 +106,7 @@ test('fails for invalid dependencies', () => { expect(() => new CloudAssembly(path.join(FIXTURES, 'invalid-depends'))).toThrow('Artifact StackC depends on non-existing artifact StackX'); }); -test('stack artifacts can specify an explicit stack name that is different from the artifact id', () => { +testDeprecated('stack artifacts can specify an explicit stack name that is different from the artifact id', () => { const assembly = new CloudAssembly(path.join(FIXTURES, 'explicit-stack-name')); expect(assembly.getStackByName('TheStackName').stackName).toStrictEqual('TheStackName'); diff --git a/packages/@aws-cdk/example-construct-library/lib/example-resource.ts b/packages/@aws-cdk/example-construct-library/lib/example-resource.ts index 3c514d01ba19c..45a91919d69a0 100644 --- a/packages/@aws-cdk/example-construct-library/lib/example-resource.ts +++ b/packages/@aws-cdk/example-construct-library/lib/example-resource.ts @@ -217,7 +217,7 @@ abstract class ExampleResourceBase extends core.Resource implements IExampleReso // of course, you would put your resource-specific values here namespace: 'AWS/ExampleResource', metricName: 'Count', - dimensions: { ExampleResource: this.exampleResourceName }, + dimensionsMap: { ExampleResource: this.exampleResourceName }, ...props, }).attachTo(this); } diff --git a/packages/@aws-cdk/lambda-layer-awscli/README.md b/packages/@aws-cdk/lambda-layer-awscli/README.md index baad6362f5e21..e36677c222de0 100644 --- a/packages/@aws-cdk/lambda-layer-awscli/README.md +++ b/packages/@aws-cdk/lambda-layer-awscli/README.md @@ -15,8 +15,11 @@ This module exports a single class called `AwsCliLayer` which is a `lambda.Layer Usage: ```ts -const fn = new lambda.Function(...); -fn.addLayers(new AwsCliLayer(stack, 'AwsCliLayer')); +// AwsCliLayer bundles the AWS CLI in a lambda layer +import { AwsCliLayer } from '@aws-cdk/lambda-layer-awscli'; + +declare const fn: lambda.Function; +fn.addLayers(new AwsCliLayer(this, 'AwsCliLayer')); ``` The CLI will be installed under `/opt/awscli/aws`. diff --git a/packages/@aws-cdk/lambda-layer-awscli/package.json b/packages/@aws-cdk/lambda-layer-awscli/package.json index 6868ae29a97e8..1a66cd1bae0fb 100644 --- a/packages/@aws-cdk/lambda-layer-awscli/package.json +++ b/packages/@aws-cdk/lambda-layer-awscli/package.json @@ -29,7 +29,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/lambda-layer-awscli/rosetta/default.ts-fixture b/packages/@aws-cdk/lambda-layer-awscli/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..d3534321d7f54 --- /dev/null +++ b/packages/@aws-cdk/lambda-layer-awscli/rosetta/default.ts-fixture @@ -0,0 +1,12 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/lambda-layer-kubectl/README.md b/packages/@aws-cdk/lambda-layer-kubectl/README.md index 97329d16c8c50..2a90c534c9f24 100644 --- a/packages/@aws-cdk/lambda-layer-kubectl/README.md +++ b/packages/@aws-cdk/lambda-layer-kubectl/README.md @@ -18,8 +18,11 @@ This module exports a single class called `KubectlLayer` which is a `lambda.Laye Usage: ```ts -const fn = new lambda.Function(...); -fn.addLayers(new KubectlLayer(stack, 'KubectlLayer')); +// KubectlLayer bundles the 'kubectl' and 'helm' command lines +import { KubectlLayer } from '@aws-cdk/lambda-layer-kubectl'; + +declare const fn: lambda.Function; +fn.addLayers(new KubectlLayer(this, 'KubectlLayer')); ``` `kubectl` will be installed under `/opt/kubectl/kubectl`, and `helm` will be installed under `/opt/helm/helm`. diff --git a/packages/@aws-cdk/lambda-layer-kubectl/package.json b/packages/@aws-cdk/lambda-layer-kubectl/package.json index eb1c41990eff8..b62ace7ad6246 100644 --- a/packages/@aws-cdk/lambda-layer-kubectl/package.json +++ b/packages/@aws-cdk/lambda-layer-kubectl/package.json @@ -29,7 +29,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "repository": { "type": "git", diff --git a/packages/@aws-cdk/lambda-layer-kubectl/rosetta/default.ts-fixture b/packages/@aws-cdk/lambda-layer-kubectl/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..d3534321d7f54 --- /dev/null +++ b/packages/@aws-cdk/lambda-layer-kubectl/rosetta/default.ts-fixture @@ -0,0 +1,12 @@ +// Fixture with packages imported, but nothing else +import { Construct } from 'constructs'; +import { Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/pipelines/lib/blueprint/stack-deployment.ts b/packages/@aws-cdk/pipelines/lib/blueprint/stack-deployment.ts index 488551f4eefb7..b3e88cbfe026e 100644 --- a/packages/@aws-cdk/pipelines/lib/blueprint/stack-deployment.ts +++ b/packages/@aws-cdk/pipelines/lib/blueprint/stack-deployment.ts @@ -183,7 +183,7 @@ export class StackDeployment { * This is `undefined` if the stack template is not published. Use the * `DefaultStackSynthesizer` to ensure it is. * - * @example https://bucket.s3.amazonaws.com/object/key + * Example value: `https://bucket.s3.amazonaws.com/object/key` */ public readonly templateUrl?: string; diff --git a/packages/@aws-cdk/pipelines/lib/private/asset-singleton-role.ts b/packages/@aws-cdk/pipelines/lib/private/asset-singleton-role.ts index 5d52c1d5a47a9..454ab1b0b44ed 100644 --- a/packages/@aws-cdk/pipelines/lib/private/asset-singleton-role.ts +++ b/packages/@aws-cdk/pipelines/lib/private/asset-singleton-role.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import { PolicyStatement } from '@aws-cdk/aws-iam'; -import { ConcreteDependable, Stack } from '@aws-cdk/core'; +import { ArnFormat, ConcreteDependable, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; /** @@ -20,7 +20,7 @@ export class AssetSingletonRole extends iam.Role { resources: [Stack.of(this).formatArn({ service: 'logs', resource: 'log-group', - sep: ':', + arnFormat: ArnFormat.COLON_RESOURCE_NAME, resourceName: '/aws/codebuild/*', })], actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], diff --git a/packages/@aws-cdk/pipelines/package.json b/packages/@aws-cdk/pipelines/package.json index b3d838723ed75..ec3538c30b4c9 100644 --- a/packages/@aws-cdk/pipelines/package.json +++ b/packages/@aws-cdk/pipelines/package.json @@ -126,7 +126,14 @@ ] } }, - "projectReferences": true + "projectReferences": true, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + } }, "awscdkio": { "announce": false diff --git a/packages/@aws-cdk/pipelines/test/blueprint/logicalid-stability.test.ts b/packages/@aws-cdk/pipelines/test/blueprint/logicalid-stability.test.ts index 52d63d00d64b3..358d22454a035 100644 --- a/packages/@aws-cdk/pipelines/test/blueprint/logicalid-stability.test.ts +++ b/packages/@aws-cdk/pipelines/test/blueprint/logicalid-stability.test.ts @@ -1,3 +1,4 @@ +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Stack } from '@aws-cdk/core'; import { mkdict } from '../../lib/private/javascript'; import { PIPELINE_ENV, TestApp, LegacyTestGitHubNpmPipeline, ModernTestGitHubNpmPipeline, MegaAssetsApp, stackTemplate } from '../testhelpers'; @@ -8,73 +9,78 @@ let modernApp: TestApp; let legacyPipelineStack: Stack; let modernPipelineStack: Stack; -beforeEach(() => { - legacyApp = new TestApp({ - context: { - '@aws-cdk/core:newStyleStackSynthesis': '1', - 'aws:cdk:enable-path-metadata': true, - }, +describeDeprecated('logical id stability', () => { + // this test suite verifies logical id between the new and old (deprecated) APIs. + // so it must be in a 'describeDeprecated' block + + beforeEach(() => { + legacyApp = new TestApp({ + context: { + '@aws-cdk/core:newStyleStackSynthesis': '1', + 'aws:cdk:enable-path-metadata': true, + }, + }); + modernApp = new TestApp({ + context: { + '@aws-cdk/core:newStyleStackSynthesis': '1', + 'aws:cdk:enable-path-metadata': true, + }, + }); + legacyPipelineStack = new Stack(legacyApp, 'PipelineStack', { env: PIPELINE_ENV }); + modernPipelineStack = new Stack(modernApp, 'PipelineStack', { env: PIPELINE_ENV }); }); - modernApp = new TestApp({ - context: { - '@aws-cdk/core:newStyleStackSynthesis': '1', - 'aws:cdk:enable-path-metadata': true, - }, - }); - legacyPipelineStack = new Stack(legacyApp, 'PipelineStack', { env: PIPELINE_ENV }); - modernPipelineStack = new Stack(modernApp, 'PipelineStack', { env: PIPELINE_ENV }); -}); - -afterEach(() => { - legacyApp.cleanup(); - modernApp.cleanup(); -}); - -test('stateful or nameable resources have the same logicalID between old and new API', () => { - const legacyPipe = new LegacyTestGitHubNpmPipeline(legacyPipelineStack, 'Cdk'); - legacyPipe.addApplicationStage(new MegaAssetsApp(legacyPipelineStack, 'MyApp', { - numAssets: 2, - })); - const modernPipe = new ModernTestGitHubNpmPipeline(modernPipelineStack, 'Cdk', { - crossAccountKeys: true, + afterEach(() => { + legacyApp.cleanup(); + modernApp.cleanup(); }); - modernPipe.addStage(new MegaAssetsApp(modernPipelineStack, 'MyApp', { - numAssets: 2, - })); - const legacyTemplate = stackTemplate(legacyPipelineStack).template; - const modernTemplate = stackTemplate(modernPipelineStack).template; + test('stateful or nameable resources have the same logicalID between old and new API', () => { + const legacyPipe = new LegacyTestGitHubNpmPipeline(legacyPipelineStack, 'Cdk'); + legacyPipe.addApplicationStage(new MegaAssetsApp(legacyPipelineStack, 'MyApp', { + numAssets: 2, + })); - const legacyStateful = filterR(legacyTemplate.Resources, isStateful); - const modernStateful = filterR(modernTemplate.Resources, isStateful); + const modernPipe = new ModernTestGitHubNpmPipeline(modernPipelineStack, 'Cdk', { + crossAccountKeys: true, + }); + modernPipe.addStage(new MegaAssetsApp(modernPipelineStack, 'MyApp', { + numAssets: 2, + })); - expect(mapR(modernStateful, typeOfRes)).toEqual(mapR(legacyStateful, typeOfRes)); -}); + const legacyTemplate = stackTemplate(legacyPipelineStack).template; + const modernTemplate = stackTemplate(modernPipelineStack).template; -test('nameable resources have the same names between old and new API', () => { - const legacyPipe = new LegacyTestGitHubNpmPipeline(legacyPipelineStack, 'Cdk', { - pipelineName: 'asdf', - }); - legacyPipe.addApplicationStage(new MegaAssetsApp(legacyPipelineStack, 'MyApp', { - numAssets: 2, - })); + const legacyStateful = filterR(legacyTemplate.Resources, isStateful); + const modernStateful = filterR(modernTemplate.Resources, isStateful); - const modernPipe = new ModernTestGitHubNpmPipeline(modernPipelineStack, 'Cdk', { - pipelineName: 'asdf', - crossAccountKeys: true, + expect(mapR(modernStateful, typeOfRes)).toEqual(mapR(legacyStateful, typeOfRes)); }); - modernPipe.addStage(new MegaAssetsApp(modernPipelineStack, 'MyApp', { - numAssets: 2, - })); - - const legacyTemplate = stackTemplate(legacyPipelineStack).template; - const modernTemplate = stackTemplate(modernPipelineStack).template; - const legacyNamed = filterR(legacyTemplate.Resources, hasName); - const modernNamed = filterR(modernTemplate.Resources, hasName); - - expect(mapR(modernNamed, nameProps)).toEqual(mapR(legacyNamed, nameProps)); + test('nameable resources have the same names between old and new API', () => { + const legacyPipe = new LegacyTestGitHubNpmPipeline(legacyPipelineStack, 'Cdk', { + pipelineName: 'asdf', + }); + legacyPipe.addApplicationStage(new MegaAssetsApp(legacyPipelineStack, 'MyApp', { + numAssets: 2, + })); + + const modernPipe = new ModernTestGitHubNpmPipeline(modernPipelineStack, 'Cdk', { + pipelineName: 'asdf', + crossAccountKeys: true, + }); + modernPipe.addStage(new MegaAssetsApp(modernPipelineStack, 'MyApp', { + numAssets: 2, + })); + + const legacyTemplate = stackTemplate(legacyPipelineStack).template; + const modernTemplate = stackTemplate(modernPipelineStack).template; + + const legacyNamed = filterR(legacyTemplate.Resources, hasName); + const modernNamed = filterR(modernTemplate.Resources, hasName); + + expect(mapR(modernNamed, nameProps)).toEqual(mapR(legacyNamed, nameProps)); + }); }); diff --git a/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline-existing.test.ts b/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline-existing.test.ts index 09de231c8332c..42c951cfaed2d 100644 --- a/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline-existing.test.ts +++ b/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline-existing.test.ts @@ -1,45 +1,49 @@ import * as codePipeline from '@aws-cdk/aws-codepipeline'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cdkp from '../../lib'; -test('Does not allow setting a pipelineName if an existing CodePipeline is given', () => { - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'PipelineStack'); - const existingCodePipeline = new codePipeline.Pipeline(stack, 'CustomCodePipeline'); +describeDeprecated('codepipeline existing', () => { - expect(() => { - new cdkp.CdkPipeline(stack, 'CDKPipeline', { - pipelineName: 'CustomPipelineName', - codePipeline: existingCodePipeline, - cloudAssemblyArtifact: new codePipeline.Artifact(), - }); - }).toThrow("Cannot set 'pipelineName' if an existing CodePipeline is given using 'codePipeline'"); -}); + test('Does not allow setting a pipelineName if an existing CodePipeline is given', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'PipelineStack'); + const existingCodePipeline = new codePipeline.Pipeline(stack, 'CustomCodePipeline'); -test('Does not allow enabling crossAccountKeys if an existing CodePipeline is given', () => { - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'PipelineStack'); - const existingCodePipeline = new codePipeline.Pipeline(stack, 'CustomCodePipeline'); + expect(() => { + new cdkp.CdkPipeline(stack, 'CDKPipeline', { + pipelineName: 'CustomPipelineName', + codePipeline: existingCodePipeline, + cloudAssemblyArtifact: new codePipeline.Artifact(), + }); + }).toThrow("Cannot set 'pipelineName' if an existing CodePipeline is given using 'codePipeline'"); + }); - expect(() => { - new cdkp.CdkPipeline(stack, 'CDKPipeline', { - crossAccountKeys: true, - codePipeline: existingCodePipeline, - cloudAssemblyArtifact: new codePipeline.Artifact(), - }); - }).toThrow("Cannot set 'crossAccountKeys' if an existing CodePipeline is given using 'codePipeline'"); -}); + test('Does not allow enabling crossAccountKeys if an existing CodePipeline is given', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'PipelineStack'); + const existingCodePipeline = new codePipeline.Pipeline(stack, 'CustomCodePipeline'); -test('Does not allow enabling key rotation if an existing CodePipeline is given', () => { - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'PipelineStack'); - const existingCodePipeline = new codePipeline.Pipeline(stack, 'CustomCodePipeline'); + expect(() => { + new cdkp.CdkPipeline(stack, 'CDKPipeline', { + crossAccountKeys: true, + codePipeline: existingCodePipeline, + cloudAssemblyArtifact: new codePipeline.Artifact(), + }); + }).toThrow("Cannot set 'crossAccountKeys' if an existing CodePipeline is given using 'codePipeline'"); + }); - expect(() => { - new cdkp.CdkPipeline(stack, 'CDKPipeline', { - enableKeyRotation: true, - codePipeline: existingCodePipeline, - cloudAssemblyArtifact: new codePipeline.Artifact(), - }); - }).toThrow("Cannot set 'enableKeyRotation' if an existing CodePipeline is given using 'codePipeline'"); + test('Does not allow enabling key rotation if an existing CodePipeline is given', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'PipelineStack'); + const existingCodePipeline = new codePipeline.Pipeline(stack, 'CustomCodePipeline'); + + expect(() => { + new cdkp.CdkPipeline(stack, 'CDKPipeline', { + enableKeyRotation: true, + codePipeline: existingCodePipeline, + cloudAssemblyArtifact: new codePipeline.Artifact(), + }); + }).toThrow("Cannot set 'enableKeyRotation' if an existing CodePipeline is given using 'codePipeline'"); + }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/compliance/escape-hatching.test.ts b/packages/@aws-cdk/pipelines/test/compliance/escape-hatching.test.ts index 822a4f06f6164..9c7ec3ec6a41c 100644 --- a/packages/@aws-cdk/pipelines/test/compliance/escape-hatching.test.ts +++ b/packages/@aws-cdk/pipelines/test/compliance/escape-hatching.test.ts @@ -1,6 +1,7 @@ import { Match, Template } from '@aws-cdk/assertions'; import * as cp from '@aws-cdk/aws-codepipeline'; import * as cpa from '@aws-cdk/aws-codepipeline-actions'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { SecretValue, Stack } from '@aws-cdk/core'; import * as cdkp from '../../lib'; import { CodePipelineFileSet } from '../../lib'; @@ -128,7 +129,7 @@ describe('with custom Source stage in existing Pipeline', () => { }); }); -describe('with Source and Build stages in existing Pipeline', () => { +describeDeprecated('with Source and Build stages in existing Pipeline', () => { beforeEach(() => { codePipeline = new cp.Pipeline(pipelineStack, 'CodePipeline', { stages: [ diff --git a/packages/@aws-cdk/pipelines/test/testhelpers/compliance.ts b/packages/@aws-cdk/pipelines/test/testhelpers/compliance.ts index bf6603d4753cb..9856797c2bd13 100644 --- a/packages/@aws-cdk/pipelines/test/testhelpers/compliance.ts +++ b/packages/@aws-cdk/pipelines/test/testhelpers/compliance.ts @@ -1,3 +1,5 @@ +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; + interface SkippedSuite { legacy(reason?: string): void; @@ -16,8 +18,9 @@ interface Suite { // eslint-disable-next-line jest/no-export export function behavior(name: string, cb: (suite: Suite) => void) { - // 'describe()' adds a nice grouping in Jest - describe(name, () => { + // Since the goal of the compliance test suites is to compare modern and legacy (i.e. deprecated) APIs, + // use `describeDeprecated()` block here since usage of the legacy API is inevitable. + describeDeprecated(name, () => { const unwritten = new Set(['modern', 'legacy']); function scratchOff(flavor: string) { diff --git a/packages/@aws-cdk/region-info/lib/default.ts b/packages/@aws-cdk/region-info/lib/default.ts index abd1001678bf2..c0ae0cc2b28d5 100644 --- a/packages/@aws-cdk/region-info/lib/default.ts +++ b/packages/@aws-cdk/region-info/lib/default.ts @@ -82,6 +82,7 @@ export class Default { // Services with a regional principal case 'states': + case 'ssm': return `${service}.${region}.amazonaws.com`; // Services with a partitional principal diff --git a/packages/@aws-cdk/region-info/test/default.test.ts b/packages/@aws-cdk/region-info/test/default.test.ts index b39952842d75a..1e7c1b166c9b6 100644 --- a/packages/@aws-cdk/region-info/test/default.test.ts +++ b/packages/@aws-cdk/region-info/test/default.test.ts @@ -5,7 +5,7 @@ const urlSuffix = '.nowhere.null'; describe('servicePrincipal', () => { for (const suffix of ['', '.amazonaws.com', '.amazonaws.com.cn']) { - for (const service of ['states']) { + for (const service of ['states', 'ssm']) { test(`${service}${suffix}`, () => { expect(Default.servicePrincipal(`${service}${suffix}`, region, urlSuffix)).toBe(`${service}.${region}.amazonaws.com`); }); diff --git a/packages/aws-cdk-lib/.gitignore b/packages/aws-cdk-lib/.gitignore index 2d239b69afc3a..4802b31b55523 100644 --- a/packages/aws-cdk-lib/.gitignore +++ b/packages/aws-cdk-lib/.gitignore @@ -1,20 +1,13 @@ -*.js -*.d.ts -!deps.js -!gen.js -lib/ -tsconfig.json -.jsii -*.tsbuildinfo +# Ignore everything (because we're going to be generating into this +# directory) except certain source files. + +* +!LICENSE +!NOTICE +!README.md +!scripts/* -dist -.LAST_PACKAGE .LAST_BUILD *.snk -!.eslintrc.js - -# Ignore barrel import entry points -/*.ts - junit.xml -rosetta +!.eslintrc.js \ No newline at end of file diff --git a/packages/aws-cdk-lib/NOTICE b/packages/aws-cdk-lib/NOTICE index e3250743b9da7..cae673dc859a5 100644 --- a/packages/aws-cdk-lib/NOTICE +++ b/packages/aws-cdk-lib/NOTICE @@ -373,390 +373,6 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------- -** colors - https://www.npmjs.com/package/colors -Original Library - - Copyright (c) Marak Squires - -Additional Functionality - - Copyright (c) Sindre Sorhus (sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------- - -** diff - https://www.npmjs.com/package/diff -Copyright (c) 2009-2015, Kevin Decker - -All rights reserved. - -Redistribution and use of this software in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of Kevin Decker nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER -IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------- - -** fast-deep-equal - https://www.npmjs.com/package/fast-deep-equal -Copyright (c) 2017 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** string-width - https://www.npmjs.com/package/string-width -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** emoji-regex - https://www.npmjs.com/package/emoji-regex -Copyright Mathias Bynens - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** is-fullwidth-code-point - https://www.npmjs.com/package/is-fullwidth-code-point -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** strip-ansi - https://www.npmjs.com/package/strip-ansi -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** ansi-regex - https://www.npmjs.com/package/ansi-regex -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** table - https://www.npmjs.com/package/table -Copyright (c) 2018, Gajus Kuizinas (http://gajus.com/) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the Gajus Kuizinas (http://gajus.com/) nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ANUARY BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------- - -** ajv - https://www.npmjs.com/package/ajv -Copyright (c) 2015-2021 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** json-schema-traverse - https://www.npmjs.com/package/json-schema-traverse -Copyright (c) 2017 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** require-from-string - https://www.npmjs.com/package/require-from-string -Copyright (c) Vsevolod Strukchinsky (github.com/floatdrop) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------- - -** uri-js - https://www.npmjs.com/package/uri-js -Copyright 2011 Gary Court. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY GARY COURT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Gary Court. - ----------------- - -** lodash.truncate - https://www.npmjs.com/package/lodash -Copyright JS Foundation and other contributors - -Based on Underscore.js, copyright Jeremy Ashkenas, -DocumentCloud and Investigative Reporters & Editors - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/lodash/lodash - -The following license applies to all parts of this software except as -documented below: - -==== - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -==== - -Copyright and related rights for sample code are waived via CC0. Sample -code is defined as all source code displayed within the prose of the -documentation. - -CC0: http://creativecommons.org/publicdomain/zero/1.0/ - -==== - -Files located in the node_modules and vendor directories are externally -maintained libraries used by this software which have their own -licenses; we recommend you read them, as their terms may differ from the -terms above. - ----------------- - -** slice-ansi - https://www.npmjs.com/package/slice-ansi -Copyright (c) DC -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** ansi-styles - https://www.npmjs.com/package/ansi-styles -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** color-convert - https://www.npmjs.com/package/color-convert -Copyright (c) 2011-2016 Heather Arthur . -Copyright (c) 2016-2021 Josh Junon . - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** color-name - https://www.npmjs.com/package/color-name -Copyright (c) 2015 Dmitry Ivanov - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** astral-regex - https://www.npmjs.com/package/astral-regex -Copyright (c) Kevin Mårtensson (github.com/kevva) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - ** kubectl - https://github.com/kubernetes/kubectl Copyright 2017 The Kubernetes Authors. diff --git a/packages/aws-cdk-lib/README.md b/packages/aws-cdk-lib/README.md index 254a58738b26d..1de8bb1a9fb41 100644 --- a/packages/aws-cdk-lib/README.md +++ b/packages/aws-cdk-lib/README.md @@ -270,6 +270,8 @@ this purpose. use the region and account of the stack you're calling it on: ```ts +declare const stack: Stack; + // Builds "arn::lambda:::function:MyFunction" stack.formatArn({ service: 'lambda', @@ -285,6 +287,8 @@ but in case of a deploy-time value be aware that the result will be another deploy-time value which cannot be inspected in the CDK application. ```ts +declare const stack: Stack; + // Extracts the function name out of an AWS Lambda Function ARN const arnComponents = stack.parseArn(arn, ':'); const functionName = arnComponents.resourceName; @@ -416,7 +420,11 @@ examples ensures that only a single SNS topic is defined: function getOrCreate(scope: Construct): sns.Topic { const stack = Stack.of(scope); const uniqueid = 'GloballyUniqueIdForSingleton'; // For example, a UUID from `uuidgen` - return stack.node.tryFindChild(uniqueid) as sns.Topic ?? new sns.Topic(stack, uniqueid); + const existing = stack.node.tryFindChild(uniqueid); + if (existing) { + return existing as sns.Topic; + } + return new sns.Topic(stack, uniqueid); } ``` @@ -849,6 +857,8 @@ since the top-level key is an unresolved token. The call to `findInMap` will ret `{ "Fn::FindInMap": [ "RegionTable", { "Ref": "AWS::Region" }, "regionName" ] }`. ```ts +declare const regionTable: CfnMapping; + regionTable.findInMap(Aws.REGION, 'regionName'); ``` diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index 3e8a5932ffcc1..1449ca3bd050e 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -3,8 +3,8 @@ "private": "true", "version": "0.0.0", "description": "The AWS Cloud Development Kit library", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "index.js", + "types": "index.d.ts", "repository": { "type": "git", "url": "https://github.com/aws/aws-cdk.git", @@ -38,7 +38,8 @@ }, "stripDeprecated": true, "post": [ - "node ./scripts/verify-readme-import-rewrites.js" + "node ./scripts/verify-readme-import-rewrites.js", + "node ./scripts/verify-import-resolve-same.js" ] }, "cdk-package": { @@ -94,33 +95,23 @@ "bundledDependencies": [ "@balena/dockerignore", "case", - "colors", - "diff", - "fast-deep-equal", "fs-extra", "ignore", "jsonschema", "minimatch", "punycode", "semver", - "string-width", - "table", "yaml" ], "dependencies": { "@balena/dockerignore": "^1.0.2", "case": "1.6.3", - "colors": "^1.4.0", - "diff": "^5.0.0", - "fast-deep-equal": "^3.1.3", "fs-extra": "^9.1.0", "ignore": "^5.1.9", "jsonschema": "^1.4.0", "minimatch": "^3.0.4", "punycode": "^2.1.1", "semver": "^7.3.5", - "string-width": "^4.2.3", - "table": "^6.7.2", "yaml": "1.10.2" }, "devDependencies": { diff --git a/packages/aws-cdk-lib/scripts/verify-imports-resolve-same.ts b/packages/aws-cdk-lib/scripts/verify-imports-resolve-same.ts new file mode 100644 index 0000000000000..9d30bf5c291ac --- /dev/null +++ b/packages/aws-cdk-lib/scripts/verify-imports-resolve-same.ts @@ -0,0 +1,142 @@ +/* eslint-disable no-console */ +/** + * Verify that the two styles of imports we support: + * + * import { aws_ec2 } from 'aws-cdk-lib'; + * import * as aws_ec2 from 'aws-cdk-lib/aws-ec2'; + * + * Resolve to the same source file when analyzed using the TypeScript compiler. + * + * This is necessary for Rosetta's analysis and translation of examples: we need + * to know what submodule we're importing here, and we need to be able to deal + * with both styles since both are used interchangeably. + */ +import * as os from 'os'; +import * as path from 'path'; +import * as fs from 'fs-extra'; + +// eslint-disable-next-line import/no-extraneous-dependencies +import * as ts from 'typescript'; + +async function main() { + // First make a tempdir and symlink `aws-cdk-lib` into it so we can refer to it + // as if it was an installed module. + await withTemporaryDirectory(async (tmpDir) => { + await fs.mkdirp(path.join(tmpDir, 'node_modules')); + await fs.symlink(path.resolve(__dirname, '..'), path.join(tmpDir, 'node_modules', 'aws-cdk-lib')); + + const import1 = 'import { aws_ec2 } from "aws-cdk-lib";'; + const import2 = 'import * as aws_ec2 from "aws-cdk-lib/aws-ec2";'; + + const src1 = await compileAndResolve(path.join(tmpDir, 'program1.ts'), import1, 'aws_ec2'); + const src2 = await compileAndResolve(path.join(tmpDir, 'program2.ts'), import2, 'aws_ec2'); + + if (src1 !== src2) { + console.error('Import mismatch!'); + console.error('\n ', import1, '\n'); + console.error('resolves to', src1); + console.error('\n ', import2, '\n'); + console.error('resolves to', src2); + process.exitCode = 1; + } + }); +} + +async function compileAndResolve(fileName: string, contents: string, symbolName: string) { + await fs.writeFile(fileName, contents + `\n\nconsole.log(${symbolName});`, { encoding: 'utf-8' }); + const program = ts.createProgram({ rootNames: [fileName], options: STANDARD_COMPILER_OPTIONS }); + + const sourceFile = program.getSourceFile(fileName); + if (!sourceFile) { + throw new Error(`Could not find sourcefile back: ${fileName}`); + } + + const diags = [ + ...program.getGlobalDiagnostics(), + ...program.getDeclarationDiagnostics(sourceFile), + ...program.getSyntacticDiagnostics(sourceFile), + ...program.getSemanticDiagnostics(sourceFile), + ]; + if (diags.length > 0) { + console.error(ts.formatDiagnostics(diags, { + getNewLine: () => '\n', + getCurrentDirectory: () => path.dirname(fileName), + getCanonicalFileName: (f) => path.resolve(f), + })); + throw new Error('Compilation failed'); + } + + // Find the 'console.log()' back and resolve the symbol inside + const logStmt = assertNode(sourceFile.statements[1], ts.isExpressionStatement); + const logCall = assertNode(logStmt.expression, ts.isCallExpression); + const ident = assertNode(logCall.arguments[0], ts.isIdentifier); + + let sym = program.getTypeChecker().getSymbolAtLocation(ident); + + // Resolve alias if applicable + // eslint-disable-next-line no-bitwise + while (sym && ((sym.flags & ts.SymbolFlags.Alias) !== 0)) { + sym = program.getTypeChecker().getAliasedSymbol(sym); + } + + if (!sym) { + throw new Error(`Could not resolve: ${symbolName} in '${contents}'`); + } + + // Return the filename + const srcFile = sym.declarations?.[0].getSourceFile().fileName; + if (!srcFile) { + console.log(sym); + throw new Error(`Symbol ${symbolName} in '${contents}' does not resolve to a source location`); + } + return srcFile; +} + +export async function withTemporaryDirectory(callback: (dir: string) => Promise): Promise { + const tmpdir = await fs.mkdtemp(path.join(os.tmpdir(), path.basename(__filename))); + try { + return await callback(tmpdir); + } finally { + await fs.remove(tmpdir); + } +} + +function assertNode(x: ts.Node, assert: (x: ts.Node) => x is A): A { + if (!assert(x)) { + throw new Error(`Not the right type of node, expecting ${assert.name}, got ${ts.SyntaxKind[x.kind]}`); + } + return x; +} + +export const STANDARD_COMPILER_OPTIONS: ts.CompilerOptions = { + alwaysStrict: true, + charset: 'utf8', + declaration: true, + experimentalDecorators: true, + inlineSourceMap: true, + inlineSources: true, + lib: ['lib.es2016.d.ts', 'lib.es2017.object.d.ts', 'lib.es2017.string.d.ts'], + module: ts.ModuleKind.CommonJS, + noEmitOnError: true, + noFallthroughCasesInSwitch: true, + noImplicitAny: true, + noImplicitReturns: true, + noImplicitThis: true, + noUnusedLocals: false, // Important, becomes super annoying without this + noUnusedParameters: false, // Important, becomes super annoying without this + resolveJsonModule: true, + strict: true, + strictNullChecks: true, + strictPropertyInitialization: true, + stripInternal: true, + target: ts.ScriptTarget.ES2019, + // Incremental builds + incremental: true, + tsBuildInfoFile: '.tsbuildinfo', +}; + +main().catch((e) => { + // eslint-disable-next-line no-console + console.error(e); + process.exitCode = 1; +}); diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index a938b00f3c02f..3f90dfc8ff3e2 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -77,7 +77,7 @@ async function parseCommandLineArguments() { .option('bootstrap-bucket-name', { type: 'string', alias: ['b', 'toolkit-bucket-name'], desc: 'The name of the CDK toolkit bucket; bucket will be created and must not exist', default: undefined }) .option('bootstrap-kms-key-id', { type: 'string', desc: 'AWS KMS master key ID used for the SSE-KMS encryption', default: undefined, conflicts: 'bootstrap-customer-key' }) .option('bootstrap-customer-key', { type: 'boolean', desc: 'Create a Customer Master Key (CMK) for the bootstrap bucket (you will be charged but can customize permissions, modern bootstrapping only)', default: undefined, conflicts: 'bootstrap-kms-key-id' }) - .option('qualifier', { type: 'string', desc: 'Unique string to distinguish multiple bootstrap stacks', default: undefined }) + .option('qualifier', { type: 'string', desc: 'String which must be unique for each bootstrap stack. You must configure it on your CDK app if you change this from the default.', default: undefined }) .option('public-access-block-configuration', { type: 'boolean', desc: 'Block public access configuration on CDK toolkit bucket (enabled by default) ', default: undefined }) .option('tags', { type: 'array', alias: 't', desc: 'Tags to add for the stack (KEY=VALUE)', nargs: 1, requiresArg: true, default: [] }) .option('execute', { type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)', default: true }) diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk.ts b/packages/aws-cdk/lib/api/aws-auth/sdk.ts index c9c64d5e10ec1..bfd30f4324a31 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk.ts @@ -38,6 +38,17 @@ export interface ISDK { getEndpointSuffix(region: string): string; + /** + * Appends the given string as the extra information to put into the User-Agent header for any requests invoked by this SDK. + * If the string is 'undefined', this method has no effect. + */ + appendCustomUserAgent(userAgentData?: string): void; + + /** + * Removes the given string from the extra User-Agent header data used for requests invoked by this SDK. + */ + removeCustomUserAgent(userAgentData: string): void; + lambda(): AWS.Lambda; cloudFormation(): AWS.CloudFormation; ec2(): AWS.EC2; @@ -103,6 +114,21 @@ export class SDK implements ISDK { this.currentRegion = region; } + public appendCustomUserAgent(userAgentData?: string): void { + if (!userAgentData) { + return; + } + + const currentCustomUserAgent = this.config.customUserAgent; + this.config.customUserAgent = currentCustomUserAgent + ? `${currentCustomUserAgent} ${userAgentData}` + : userAgentData; + } + + public removeCustomUserAgent(userAgentData: string): void { + this.config.customUserAgent = this.config.customUserAgent?.replace(userAgentData, ''); + } + public lambda(): AWS.Lambda { return this.wrapServiceErrorHandling(new AWS.Lambda(this.config)); } diff --git a/packages/aws-cdk/lib/api/cloudformation-deployments.ts b/packages/aws-cdk/lib/api/cloudformation-deployments.ts index da8db43ae8005..fb7c5410faf3d 100644 --- a/packages/aws-cdk/lib/api/cloudformation-deployments.ts +++ b/packages/aws-cdk/lib/api/cloudformation-deployments.ts @@ -145,6 +145,13 @@ export interface DeployStackOptions { * @default - false for regular deployments, true for 'watch' deployments */ readonly hotswap?: boolean; + + /** + * The extra string to append to the User-Agent header when performing AWS SDK calls. + * + * @default - nothing extra is appended to the User-Agent header + */ + readonly extraUserAgent?: string; } export interface DestroyStackOptions { @@ -222,6 +229,7 @@ export class CloudFormationDeployments { ci: options.ci, rollback: options.rollback, hotswap: options.hotswap, + extraUserAgent: options.extraUserAgent, }); } diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index f73eebed57f54..40719507d6e20 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -182,6 +182,13 @@ export interface DeployStackOptions { * @default - false for regular deployments, true for 'watch' deployments */ readonly hotswap?: boolean; + + /** + * The extra string to append to the User-Agent header when performing AWS SDK calls. + * + * @default - nothing extra is appended to the User-Agent header + */ + readonly extraUserAgent?: string; } const LARGE_TEMPLATE_SIZE_KB = 50; @@ -191,6 +198,7 @@ export async function deployStack(options: DeployStackOptions): Promise { + // if we got here, and hotswap is enabled, that means changes couldn't be hotswapped, + // and we had to fall back on a full deployment. Note that fact in our User-Agent + if (options.hotswap) { + options.sdk.appendCustomUserAgent('cdk-hotswap/fallback'); + } + const cfn = options.sdk.cloudFormation(); const deployName = options.deployName ?? stackArtifact.stackName; diff --git a/packages/aws-cdk/lib/api/hotswap-deployments.ts b/packages/aws-cdk/lib/api/hotswap-deployments.ts index 08a22d3944437..0d8a4165f9401 100644 --- a/packages/aws-cdk/lib/api/hotswap-deployments.ts +++ b/packages/aws-cdk/lib/api/hotswap-deployments.ts @@ -3,7 +3,7 @@ import * as cxapi from '@aws-cdk/cx-api'; import { CloudFormation } from 'aws-sdk'; import { ISDK, Mode, SdkProvider } from './aws-auth'; import { DeployStackResult } from './deploy-stack'; -import { ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, ListStackResources, HotswappableChangeCandidate } from './hotswap/common'; +import { ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, HotswappableChangeCandidate, ListStackResources } from './hotswap/common'; import { isHotswappableEcsServiceChange } from './hotswap/ecs-services'; import { EvaluateCloudFormationTemplate } from './hotswap/evaluate-cloudformation-template'; import { isHotswappableLambdaFunctionChange } from './hotswap/lambda-functions'; @@ -138,10 +138,22 @@ async function applyAllHotswappableChanges( sdk: ISDK, hotswappableChanges: HotswapOperation[], ): Promise { return Promise.all(hotswappableChanges.map(hotswapOperation => { - return hotswapOperation.apply(sdk); + return applyHotswappableChange(sdk, hotswapOperation); })); } +async function applyHotswappableChange(sdk: ISDK, hotswapOperation: HotswapOperation): Promise { + // note the type of service that was successfully hotswapped in the User-Agent + const customUserAgent = `cdk-hotswap/success-${hotswapOperation.service}`; + sdk.appendCustomUserAgent(customUserAgent); + + try { + return await hotswapOperation.apply(sdk); + } finally { + sdk.removeCustomUserAgent(customUserAgent); + } +} + class LazyListStackResources implements ListStackResources { private stackResources: CloudFormation.StackResourceSummary[] | undefined; diff --git a/packages/aws-cdk/lib/api/hotswap/common.ts b/packages/aws-cdk/lib/api/hotswap/common.ts index 1e482d112aef4..5ac336e692624 100644 --- a/packages/aws-cdk/lib/api/hotswap/common.ts +++ b/packages/aws-cdk/lib/api/hotswap/common.ts @@ -11,6 +11,12 @@ export interface ListStackResources { * An interface that represents a change that can be deployed in a short-circuit manner. */ export interface HotswapOperation { + /** + * The name of the service being hotswapped. + * Used to set a custom User-Agent for SDK calls. + */ + readonly service: string; + apply(sdk: ISDK): Promise; } diff --git a/packages/aws-cdk/lib/api/hotswap/ecs-services.ts b/packages/aws-cdk/lib/api/hotswap/ecs-services.ts index f5bb06b7255c1..90cee85782989 100644 --- a/packages/aws-cdk/lib/api/hotswap/ecs-services.ts +++ b/packages/aws-cdk/lib/api/hotswap/ecs-services.ts @@ -76,6 +76,8 @@ interface EcsService { } class EcsServiceHotswapOperation implements HotswapOperation { + public readonly service = 'ecs-service'; + constructor( private readonly taskDefinitionResource: any, private readonly servicesReferencingTaskDef: EcsService[], diff --git a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts index 6aae68738acca..c45bb432ac65d 100644 --- a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts +++ b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts @@ -104,6 +104,8 @@ interface LambdaFunctionResource { } class LambdaFunctionHotswapOperation implements HotswapOperation { + public readonly service = 'lambda-function'; + constructor(private readonly lambdaFunctionResource: LambdaFunctionResource) { } diff --git a/packages/aws-cdk/lib/api/hotswap/stepfunctions-state-machines.ts b/packages/aws-cdk/lib/api/hotswap/stepfunctions-state-machines.ts index c4a7a4eae8750..dbaff58ab608b 100644 --- a/packages/aws-cdk/lib/api/hotswap/stepfunctions-state-machines.ts +++ b/packages/aws-cdk/lib/api/hotswap/stepfunctions-state-machines.ts @@ -48,6 +48,8 @@ interface StateMachineResource { } class StateMachineHotswapOperation implements HotswapOperation { + public readonly service = 'stepfunctions-state-machine'; + constructor(private readonly stepFunctionResource: StateMachineResource) { } diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index 95da6dd39e139..d6e27a8c8e54b 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -208,6 +208,7 @@ export class CdkToolkit { ci: options.ci, rollback: options.rollback, hotswap: options.hotswap, + extraUserAgent: options.extraUserAgent, }); const message = result.noOp @@ -579,6 +580,7 @@ export class CdkToolkit { private async invokeDeployFromWatch(options: WatchOptions): Promise { // 'watch' has different defaults than regular 'deploy' + const hotswap = options.hotswap === undefined ? true : options.hotswap; const deployOptions: DeployOptions = { ...options, requireApproval: RequireApproval.Never, @@ -587,7 +589,8 @@ export class CdkToolkit { // as that would lead to a cycle watch: false, cacheCloudAssembly: false, - hotswap: options.hotswap === undefined ? true : options.hotswap, + hotswap: hotswap, + extraUserAgent: `cdk-watch/hotswap-${hotswap ? 'on' : 'off'}`, }; try { @@ -719,6 +722,13 @@ interface WatchOptions { * @default - false for regular deployments, true for 'watch' deployments */ readonly hotswap?: boolean; + + /** + * The extra string to append to the User-Agent header when performing AWS SDK calls. + * + * @default - nothing extra is appended to the User-Agent header + */ + readonly extraUserAgent?: string; } export interface DeployOptions extends WatchOptions { diff --git a/packages/aws-cdk/lib/context-providers/security-groups.ts b/packages/aws-cdk/lib/context-providers/security-groups.ts index 7edde696fba45..f4c85d07c7ba3 100644 --- a/packages/aws-cdk/lib/context-providers/security-groups.ts +++ b/packages/aws-cdk/lib/context-providers/security-groups.ts @@ -12,11 +12,34 @@ export class SecurityGroupContextProviderPlugin implements ContextProviderPlugin const account: string = args.account!; const region: string = args.region!; + if (args.securityGroupId && args.securityGroupName) { + throw new Error('\'securityGroupId\' and \'securityGroupName\' can not be specified both when looking up a security group'); + } + + if (!args.securityGroupId && !args.securityGroupName) { + throw new Error('\'securityGroupId\' or \'securityGroupName\' must be specified to look up a security group'); + } + const options = { assumeRoleArn: args.lookupRoleArn }; const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).ec2(); + const filters: AWS.EC2.FilterList = []; + if (args.vpcId) { + filters.push({ + Name: 'vpc-id', + Values: [args.vpcId], + }); + } + if (args.securityGroupName) { + filters.push({ + Name: 'group-name', + Values: [args.securityGroupName], + }); + } + const response = await ec2.describeSecurityGroups({ - GroupIds: [args.securityGroupId], + GroupIds: args.securityGroupId ? [args.securityGroupId] : undefined, + Filters: filters.length > 0 ? filters : undefined, }).promise(); const securityGroups = response.SecurityGroups ?? []; @@ -24,6 +47,10 @@ export class SecurityGroupContextProviderPlugin implements ContextProviderPlugin throw new Error(`No security groups found matching ${JSON.stringify(args)}`); } + if (securityGroups.length > 1) { + throw new Error(`More than one security groups found matching ${JSON.stringify(args)}`); + } + const [securityGroup] = securityGroups; return { diff --git a/packages/aws-cdk/lib/init-templates/v1/app/csharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/app/csharp/cdk.template.json index 94c37dee310c0..6711bc81bde11 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/csharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/csharp/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/GlobalSuppressions.cs", + "src/*/*.csproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/fsharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/app/fsharp/cdk.template.json index a08c461d2a2e2..040844e83e006 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/fsharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/fsharp/cdk.template.json @@ -1,3 +1,14 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/*.fsproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/go/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/app/go/cdk.template.json index ad88cd7ef75f3..a25485ed0951b 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/go/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/go/cdk.template.json @@ -1,3 +1,13 @@ { - "app": "go mod download && go run %name%.go" -} \ No newline at end of file + "app": "go mod download && go run %name%.go", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "go.mod", + "go.sum", + "**/*test.go" + ] + } +} diff --git a/packages/aws-cdk/lib/init-templates/v1/app/java/cdk.json b/packages/aws-cdk/lib/init-templates/v1/app/java/cdk.json index b112918622f63..b21c3e47a9552 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/java/cdk.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/java/cdk.json @@ -1,3 +1,13 @@ { - "app": "mvn -e -q compile exec:java" + "app": "mvn -e -q compile exec:java", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "target", + "pom.xml", + "src/test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/javascript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/app/javascript/cdk.template.json index ca1d40ed37e2d..6056727247dff 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/javascript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/javascript/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "node bin/%name%.js" + "app": "node bin/%name%.js", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "jest.config.js", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/python/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/app/python/cdk.template.json index d7293493c4415..1c467275741e1 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/python/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/python/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "%python-executable% app.py" + "app": "%python-executable% app.py", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "requirements*.txt", + "source.bat", + "**/__init__.py", + "python/__pycache__", + "tests" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/app/typescript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/app/typescript/cdk.template.json index 4b132c728abd7..e9b5bea306944 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/typescript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/app/typescript/cdk.template.json @@ -1,3 +1,17 @@ { - "app": "npx ts-node --prefer-ts-exts bin/%name%.ts" + "app": "npx ts-node --prefer-ts-exts bin/%name%.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/csharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/csharp/cdk.template.json index 94c37dee310c0..6711bc81bde11 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/csharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/csharp/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/GlobalSuppressions.cs", + "src/*/*.csproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/fsharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/fsharp/cdk.template.json index a08c461d2a2e2..040844e83e006 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/fsharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/fsharp/cdk.template.json @@ -1,3 +1,14 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/*.fsproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/go/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/go/cdk.template.json index ad88cd7ef75f3..a25485ed0951b 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/go/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/go/cdk.template.json @@ -1,3 +1,13 @@ { - "app": "go mod download && go run %name%.go" -} \ No newline at end of file + "app": "go mod download && go run %name%.go", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "go.mod", + "go.sum", + "**/*test.go" + ] + } +} diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/java/cdk.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/java/cdk.json index b112918622f63..b21c3e47a9552 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/java/cdk.json +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/java/cdk.json @@ -1,3 +1,13 @@ { - "app": "mvn -e -q compile exec:java" + "app": "mvn -e -q compile exec:java", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "target", + "pom.xml", + "src/test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/cdk.template.json index ca1d40ed37e2d..6056727247dff 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/javascript/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "node bin/%name%.js" + "app": "node bin/%name%.js", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "jest.config.js", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/python/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/python/cdk.template.json index d7293493c4415..1c467275741e1 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/python/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/python/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "%python-executable% app.py" + "app": "%python-executable% app.py", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "requirements*.txt", + "source.bat", + "**/__init__.py", + "python/__pycache__", + "tests" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/cdk.template.json index 4b132c728abd7..e9b5bea306944 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/typescript/cdk.template.json @@ -1,3 +1,17 @@ { - "app": "npx ts-node --prefer-ts-exts bin/%name%.ts" + "app": "npx ts-node --prefer-ts-exts bin/%name%.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/csharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/app/csharp/cdk.template.json index 94c37dee310c0..6711bc81bde11 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/csharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/app/csharp/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/GlobalSuppressions.cs", + "src/*/*.csproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/fsharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/app/fsharp/cdk.template.json index a08c461d2a2e2..040844e83e006 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/fsharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/app/fsharp/cdk.template.json @@ -1,3 +1,14 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/*.fsproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/go/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/app/go/cdk.template.json index ad88cd7ef75f3..a25485ed0951b 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/go/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/app/go/cdk.template.json @@ -1,3 +1,13 @@ { - "app": "go mod download && go run %name%.go" -} \ No newline at end of file + "app": "go mod download && go run %name%.go", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "go.mod", + "go.sum", + "**/*test.go" + ] + } +} diff --git a/packages/aws-cdk/lib/init-templates/v2/app/java/cdk.json b/packages/aws-cdk/lib/init-templates/v2/app/java/cdk.json index b112918622f63..b21c3e47a9552 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/java/cdk.json +++ b/packages/aws-cdk/lib/init-templates/v2/app/java/cdk.json @@ -1,3 +1,13 @@ { - "app": "mvn -e -q compile exec:java" + "app": "mvn -e -q compile exec:java", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "target", + "pom.xml", + "src/test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/javascript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/app/javascript/cdk.template.json index ca1d40ed37e2d..6056727247dff 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/javascript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/app/javascript/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "node bin/%name%.js" + "app": "node bin/%name%.js", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "jest.config.js", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/python/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/app/python/cdk.template.json index d7293493c4415..1c467275741e1 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/python/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/app/python/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "%python-executable% app.py" + "app": "%python-executable% app.py", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "requirements*.txt", + "source.bat", + "**/__init__.py", + "python/__pycache__", + "tests" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/app/python/tests/unit/test_%name.PythonModule%_stack.template.py b/packages/aws-cdk/lib/init-templates/v2/app/python/tests/unit/test_%name.PythonModule%_stack.template.py index 40f77fd686a77..2bf2309dca779 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/python/tests/unit/test_%name.PythonModule%_stack.template.py +++ b/packages/aws-cdk/lib/init-templates/v2/app/python/tests/unit/test_%name.PythonModule%_stack.template.py @@ -1,14 +1,14 @@ -# import aws_cdk as core -# import aws_cdk.assertions as assertions +import aws_cdk as core +import aws_cdk.assertions as assertions -# from %name.PythonModule%.%name.PythonModule%_stack import %name.PascalCased%Stack +from %name.PythonModule%.%name.PythonModule%_stack import %name.PascalCased%Stack # example tests. To run these tests, uncomment this file along with the example # resource in %name.PythonModule%/%name.PythonModule%_stack.py -# def test_sqs_queue_created(): -# app = core.App() -# stack = %name.PascalCased%Stack(app, "%name.StackName%") -# template = assertions.Template.from_stack(stack) +def test_sqs_queue_created(): + app = core.App() + stack = %name.PascalCased%Stack(app, "%name.StackName%") + template = assertions.Template.from_stack(stack) # template.has_resource_properties("AWS::SQS::Queue", { # "VisibilityTimeout": 300 diff --git a/packages/aws-cdk/lib/init-templates/v2/app/typescript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/app/typescript/cdk.template.json index 4b132c728abd7..e9b5bea306944 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/typescript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/app/typescript/cdk.template.json @@ -1,3 +1,17 @@ { - "app": "npx ts-node --prefer-ts-exts bin/%name%.ts" + "app": "npx ts-node --prefer-ts-exts bin/%name%.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/cdk.template.json index 94c37dee310c0..6711bc81bde11 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.csproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/GlobalSuppressions.cs", + "src/*/*.csproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/cdk.template.json index a08c461d2a2e2..040844e83e006 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/cdk.template.json @@ -1,3 +1,14 @@ { - "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj" + "app": "dotnet run -p src/%name.PascalCased%/%name.PascalCased%.fsproj", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "src/*/obj", + "src/*/bin", + "src/*.sln", + "src/*/*.fsproj" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/go/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/sample-app/go/cdk.template.json index ad88cd7ef75f3..a25485ed0951b 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/go/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/go/cdk.template.json @@ -1,3 +1,13 @@ { - "app": "go mod download && go run %name%.go" -} \ No newline at end of file + "app": "go mod download && go run %name%.go", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "go.mod", + "go.sum", + "**/*test.go" + ] + } +} diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/java/cdk.json b/packages/aws-cdk/lib/init-templates/v2/sample-app/java/cdk.json index b112918622f63..b21c3e47a9552 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/java/cdk.json +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/java/cdk.json @@ -1,3 +1,13 @@ { - "app": "mvn -e -q compile exec:java" + "app": "mvn -e -q compile exec:java", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "target", + "pom.xml", + "src/test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/javascript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/sample-app/javascript/cdk.template.json index ca1d40ed37e2d..6056727247dff 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/javascript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/javascript/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "node bin/%name%.js" + "app": "node bin/%name%.js", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "jest.config.js", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/cdk.template.json index d7293493c4415..1c467275741e1 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/cdk.template.json @@ -1,3 +1,15 @@ { - "app": "%python-executable% app.py" + "app": "%python-executable% app.py", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "requirements*.txt", + "source.bat", + "**/__init__.py", + "python/__pycache__", + "tests" + ] + } } diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/cdk.template.json b/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/cdk.template.json index 4b132c728abd7..e9b5bea306944 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/cdk.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/typescript/cdk.template.json @@ -1,3 +1,17 @@ { - "app": "npx ts-node --prefer-ts-exts bin/%name%.ts" + "app": "npx ts-node --prefer-ts-exts bin/%name%.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } } diff --git a/packages/aws-cdk/lib/settings.ts b/packages/aws-cdk/lib/settings.ts index 6182ecc26e0c3..38723bffc0bc3 100644 --- a/packages/aws-cdk/lib/settings.ts +++ b/packages/aws-cdk/lib/settings.ts @@ -30,6 +30,7 @@ export enum Command { METADATA = 'metadata', INIT = 'init', VERSION = 'version', + WATCH = 'watch', } const BUNDLING_COMMANDS = [ @@ -37,6 +38,7 @@ const BUNDLING_COMMANDS = [ Command.DIFF, Command.SYNTH, Command.SYNTHESIZE, + Command.WATCH, ]; export type Arguments = { @@ -251,7 +253,7 @@ export class Settings { // Determine bundling stacks let bundlingStacks: string[]; if (BUNDLING_COMMANDS.includes(argv._[0])) { - // If we deploy, diff or synth a list of stacks exclusively we skip + // If we deploy, diff, synth or watch a list of stacks exclusively we skip // bundling for all other stacks. bundlingStacks = argv.exclusively ? argv.STACKS ?? ['*'] diff --git a/packages/aws-cdk/lib/util/asset-publishing.ts b/packages/aws-cdk/lib/util/asset-publishing.ts index 3635d2b639002..670231627ec92 100644 --- a/packages/aws-cdk/lib/util/asset-publishing.ts +++ b/packages/aws-cdk/lib/util/asset-publishing.ts @@ -27,6 +27,8 @@ export async function publishAssets(manifest: cdk_assets.AssetManifest, sdk: Sdk } class PublishingAws implements cdk_assets.IAws { + private sdkCache: Map = new Map(); + constructor( /** * The base SDK to work with @@ -66,16 +68,30 @@ class PublishingAws implements cdk_assets.IAws { /** * Get an SDK appropriate for the given client options */ - private sdk(options: cdk_assets.ClientOptions): Promise { + private async sdk(options: cdk_assets.ClientOptions): Promise { const env = { ...this.targetEnv, region: options.region ?? this.targetEnv.region, // Default: same region as the stack }; - return this.aws.forEnvironment(env, Mode.ForWriting, { + const cacheKey = JSON.stringify({ + env, // region, name, account + assumeRuleArn: options.assumeRoleArn, + assumeRoleExternalId: options.assumeRoleExternalId, + }); + + const maybeSdk = this.sdkCache.get(cacheKey); + if (maybeSdk) { + return maybeSdk; + } + + const sdk = await this.aws.forEnvironment(env, Mode.ForWriting, { assumeRoleArn: options.assumeRoleArn, assumeRoleExternalId: options.assumeRoleExternalId, }); + this.sdkCache.set(cacheKey, sdk); + + return sdk; } } diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 5dcc35395c517..d870b6d31a8b5 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -59,11 +59,11 @@ "jest": "^27.3.1", "make-runnable": "^1.3.10", "mockery": "^2.1.0", - "nock": "^13.1.4", + "nock": "^13.2.1", "@aws-cdk/pkglint": "0.0.0", "sinon": "^9.2.4", "ts-jest": "^27.0.7", - "ts-mock-imports": "^1.3.7", + "ts-mock-imports": "^1.3.8", "xml-js": "^1.6.11" }, "dependencies": { @@ -71,10 +71,10 @@ "@aws-cdk/cloudformation-diff": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/region-info": "0.0.0", - "@jsii/check-node": "1.42.0", + "@jsii/check-node": "1.44.2", "archiver": "^5.3.0", "aws-sdk": "^2.979.0", - "camelcase": "^6.2.0", + "camelcase": "^6.2.1", "cdk-assets": "0.0.0", "chokidar": "^3.5.2", "colors": "^1.4.0", @@ -87,7 +87,7 @@ "proxy-agent": "^5.0.0", "semver": "^7.3.5", "source-map-support": "^0.5.20", - "table": "^6.7.2", + "table": "^6.7.3", "uuid": "^8.3.2", "wrap-ansi": "^7.0.0", "yaml": "1.10.2", diff --git a/packages/aws-cdk/test/api/deploy-stack.test.ts b/packages/aws-cdk/test/api/deploy-stack.test.ts index 4770e2ad9528f..2b199ea225b87 100644 --- a/packages/aws-cdk/test/api/deploy-stack.test.ts +++ b/packages/aws-cdk/test/api/deploy-stack.test.ts @@ -81,10 +81,15 @@ test("calls tryHotswapDeployment() if 'hotswap' is true", async () => { await deployStack({ ...standardDeployStackArguments(), hotswap: true, + extraUserAgent: 'extra-user-agent', }); // THEN expect(tryHotswapDeployment).toHaveBeenCalled(); + // check that the extra User-Agent is honored + expect(sdk.appendCustomUserAgent).toHaveBeenCalledWith('extra-user-agent'); + // check that the fallback has been called if hotswapping failed + expect(sdk.appendCustomUserAgent).toHaveBeenCalledWith('cdk-hotswap/fallback'); }); test("does not call tryHotswapDeployment() if 'hotswap' is false", async () => { diff --git a/packages/aws-cdk/test/api/hotswap/ecs-services-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/ecs-services-hotswap-deployments.test.ts index 42ceba90b4839..717f68ecd5b29 100644 --- a/packages/aws-cdk/test/api/hotswap/ecs-services-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/ecs-services-hotswap-deployments.test.ts @@ -1,16 +1,16 @@ import * as AWS from 'aws-sdk'; import * as setup from './hotswap-test-setup'; -let mockSdkProvider: setup.CfnMockProvider; +let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; let mockRegisterTaskDef: jest.Mock; let mockUpdateService: (params: AWS.ECS.UpdateServiceRequest) => AWS.ECS.UpdateServiceResponse; beforeEach(() => { - mockSdkProvider = setup.setupHotswapTests(); + hotswapMockSdkProvider = setup.setupHotswapTests(); mockRegisterTaskDef = jest.fn(); mockUpdateService = jest.fn(); - mockSdkProvider.stubEcs({ + hotswapMockSdkProvider.stubEcs({ registerTaskDefinition: mockRegisterTaskDef, updateService: mockUpdateService, }, { @@ -81,7 +81,7 @@ test('should call registerTaskDefinition and updateService for a difference only }); // WHEN - const deployStackResult = await mockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -157,7 +157,7 @@ test('any other TaskDefinition property change besides ContainerDefinition canno }); // WHEN - const deployStackResult = await mockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); @@ -216,7 +216,7 @@ test('should call registerTaskDefinition and updateService for a difference only }); // WHEN - const deployStackResult = await mockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -276,7 +276,7 @@ test('a difference just in a TaskDefinition, without any services using it, is n }); // WHEN - const deployStackResult = await mockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); @@ -356,7 +356,7 @@ test('if anything besides an ECS Service references the changed TaskDefinition, }); // WHEN - const deployStackResult = await mockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts index 5d059df860cb8..28af0119bcb3d 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts @@ -1,24 +1,24 @@ import { Lambda, StepFunctions } from 'aws-sdk'; import * as setup from './hotswap-test-setup'; -let cfnMockProvider: setup.CfnMockProvider; +let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => Lambda.Types.FunctionConfiguration; let mockUpdateMachineDefinition: (params: StepFunctions.Types.UpdateStateMachineInput) => StepFunctions.Types.UpdateStateMachineOutput; let mockGetEndpointSuffix: () => string; beforeEach(() => { - cfnMockProvider = setup.setupHotswapTests(); + hotswapMockSdkProvider = setup.setupHotswapTests(); mockUpdateLambdaCode = jest.fn(); mockUpdateMachineDefinition = jest.fn(); mockGetEndpointSuffix = jest.fn(() => 'amazonaws.com'); - cfnMockProvider.setUpdateFunctionCodeMock(mockUpdateLambdaCode); - cfnMockProvider.setUpdateStateMachineMock(mockUpdateMachineDefinition); - cfnMockProvider.stubGetEndpointSuffix(mockGetEndpointSuffix); + hotswapMockSdkProvider.setUpdateFunctionCodeMock(mockUpdateLambdaCode); + hotswapMockSdkProvider.setUpdateStateMachineMock(mockUpdateMachineDefinition); + hotswapMockSdkProvider.stubGetEndpointSuffix(mockGetEndpointSuffix); }); test('returns a deployStackResult with noOp=true when it receives an empty set of changes', async () => { // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(setup.cdkStackArtifactOf()); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(setup.cdkStackArtifactOf()); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -52,7 +52,7 @@ test('A change to only a non-hotswappable resource results in a full deployment' }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); @@ -112,7 +112,7 @@ test('A change to both a hotswappable resource and a non-hotswappable resource r }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); @@ -146,7 +146,7 @@ test('changes only to CDK::Metadata result in a noOp', async () => { }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -167,7 +167,7 @@ test('resource deletions require full deployments', async () => { const cdkStackArtifact = setup.cdkStackArtifactOf(); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); @@ -233,7 +233,7 @@ test('can correctly reference AWS::Partition in hotswappable changes', async () }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -298,7 +298,7 @@ test('can correctly reference AWS::URLSuffix in hotswappable changes', async () }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -308,4 +308,10 @@ test('can correctly reference AWS::URLSuffix in hotswappable changes', async () S3Key: 'new-key', }); expect(mockGetEndpointSuffix).toHaveBeenCalledTimes(1); + + // the User-Agent is set correctly + expect(hotswapMockSdkProvider.mockSdkProvider.sdk.appendCustomUserAgent) + .toHaveBeenCalledWith('cdk-hotswap/success-lambda-function'); + expect(hotswapMockSdkProvider.mockSdkProvider.sdk.removeCustomUserAgent) + .toHaveBeenCalledWith('cdk-hotswap/success-lambda-function'); }); diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts index 87e06465c61dd..f96b419b94b20 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts @@ -13,7 +13,7 @@ import { FakeCloudformationStack } from '../fake-cloudformation-stack'; const STACK_NAME = 'withouterrors'; export const STACK_ID = 'stackId'; -let cfnMockProvider: CfnMockProvider; +let hotswapMockSdkProvider: HotswapMockSdkProvider; let currentCfnStack: FakeCloudformationStack; const currentCfnStackResources: CloudFormation.StackResourceSummary[] = []; @@ -21,13 +21,13 @@ export function setupHotswapTests() { jest.resetAllMocks(); // clear the array currentCfnStackResources.splice(0); - cfnMockProvider = new CfnMockProvider(); + hotswapMockSdkProvider = new HotswapMockSdkProvider(); currentCfnStack = new FakeCloudformationStack({ stackName: STACK_NAME, stackId: STACK_ID, }); - return cfnMockProvider; + return hotswapMockSdkProvider; } export function cdkStackArtifactOf(testStackArtifact: Partial = {}): cxapi.CloudFormationStackArtifact { @@ -55,8 +55,8 @@ export function stackSummaryOf(logicalId: string, resourceType: string, physical }; } -export class CfnMockProvider { - private mockSdkProvider: MockSdkProvider; +export class HotswapMockSdkProvider { + public readonly mockSdkProvider: MockSdkProvider; constructor() { this.mockSdkProvider = new MockSdkProvider({ realSdk: false }); @@ -91,14 +91,14 @@ export class CfnMockProvider { this.mockSdkProvider.stubEcs(stubs, additionalProperties); } + public stubGetEndpointSuffix(stub: () => string) { + this.mockSdkProvider.stubGetEndpointSuffix(stub); + } + public tryHotswapDeployment( stackArtifact: cxapi.CloudFormationStackArtifact, assetParams: { [key: string]: string } = {}, ): Promise { return deployments.tryHotswapDeployment(this.mockSdkProvider, assetParams, currentCfnStack, stackArtifact); } - - public stubGetEndpointSuffix(stub: () => string) { - this.mockSdkProvider.stubGetEndpointSuffix(stub); - } } diff --git a/packages/aws-cdk/test/api/hotswap/lambda-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-hotswap-deployments.test.ts index 1a81a8dc21ca8..8c9d5498967f7 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-hotswap-deployments.test.ts @@ -2,12 +2,12 @@ import { Lambda } from 'aws-sdk'; import * as setup from './hotswap-test-setup'; let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => Lambda.Types.FunctionConfiguration; -let cfnMockProvider: setup.CfnMockProvider; +let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { - cfnMockProvider = setup.setupHotswapTests(); + hotswapMockSdkProvider = setup.setupHotswapTests(); mockUpdateLambdaCode = jest.fn(); - cfnMockProvider.setUpdateFunctionCodeMock(mockUpdateLambdaCode); + hotswapMockSdkProvider.setUpdateFunctionCodeMock(mockUpdateLambdaCode); }); test('returns undefined when a new Lambda function is added to the Stack', async () => { @@ -23,7 +23,7 @@ test('returns undefined when a new Lambda function is added to the Stack', async }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); @@ -69,7 +69,7 @@ test('calls the updateLambdaCode() API when it receives only a code difference i }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -139,7 +139,7 @@ test("correctly evaluates the function's name when it references a different res }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -199,7 +199,7 @@ test("correctly falls back to taking the function's name from the current stack }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact, { AssetBucketParam: 'asset-bucket' }); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact, { AssetBucketParam: 'asset-bucket' }); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -256,7 +256,7 @@ test("will not perform a hotswap deployment if it cannot find a Ref target (outs // THEN await expect(() => - cfnMockProvider.tryHotswapDeployment(cdkStackArtifact), + hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact), ).rejects.toThrow(/Parameter or resource 'Param1' could not be found for evaluation/); }); @@ -309,7 +309,7 @@ test("will not perform a hotswap deployment if it doesn't know how to handle a s // THEN await expect(() => - cfnMockProvider.tryHotswapDeployment(cdkStackArtifact), + hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact), ).rejects.toThrow("We don't support the 'UnknownAttribute' attribute of the 'AWS::S3::Bucket' resource. This is a CDK limitation. Please report it at https://github.com/aws/aws-cdk/issues/new/choose"); }); @@ -352,7 +352,7 @@ test('calls the updateLambdaCode() API when it receives a code difference in a L // WHEN setup.pushStackResourceSummaries(setup.stackSummaryOf('Func', 'AWS::Lambda::Function', 'mock-function-resource-id')); - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -397,7 +397,7 @@ test('does not call the updateLambdaCode() API when it receives a change that is }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); @@ -442,7 +442,7 @@ test('does not call the updateLambdaCode() API when a resource with type that is }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); diff --git a/packages/aws-cdk/test/api/hotswap/state-machine-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/state-machine-hotswap-deployments.test.ts index 5ece4a3621c38..289921fb2bd76 100644 --- a/packages/aws-cdk/test/api/hotswap/state-machine-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/state-machine-hotswap-deployments.test.ts @@ -2,12 +2,12 @@ import { StepFunctions } from 'aws-sdk'; import * as setup from './hotswap-test-setup'; let mockUpdateMachineDefinition: (params: StepFunctions.Types.UpdateStateMachineInput) => StepFunctions.Types.UpdateStateMachineOutput; -let cfnMockProvider: setup.CfnMockProvider; +let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { - cfnMockProvider = setup.setupHotswapTests(); + hotswapMockSdkProvider = setup.setupHotswapTests(); mockUpdateMachineDefinition = jest.fn(); - cfnMockProvider.setUpdateStateMachineMock(mockUpdateMachineDefinition); + hotswapMockSdkProvider.setUpdateStateMachineMock(mockUpdateMachineDefinition); }); test('returns undefined when a new StateMachine is added to the Stack', async () => { @@ -23,7 +23,7 @@ test('returns undefined when a new StateMachine is added to the Stack', async () }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); @@ -57,7 +57,7 @@ test('calls the updateStateMachine() API when it receives only a definitionStrin }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -125,7 +125,7 @@ test('calls the updateStateMachine() API when it receives only a definitionStrin }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -169,7 +169,7 @@ test('calls the updateStateMachine() API when it receives a change to the defini // WHEN setup.pushStackResourceSummaries(setup.stackSummaryOf('Machine', 'AWS::StepFunctions::StateMachine', 'mock-machine-resource-id')); - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -211,7 +211,7 @@ test('does not call the updateStateMachine() API when it receives a change to a }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); @@ -244,7 +244,7 @@ test('does not call the updateStateMachine() API when a resource has a Definitio }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); @@ -281,7 +281,7 @@ test('can correctly hotswap old style synth changes', async () => { }); // WHEN - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact, { AssetParam2: 'asset-param-2' }); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact, { AssetParam2: 'asset-param-2' }); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -351,7 +351,7 @@ test('calls the updateStateMachine() API when it receives a change to the defini setup.stackSummaryOf('Machine', 'AWS::StepFunctions::StateMachine', 'mock-machine-resource-id'), setup.stackSummaryOf('Func', 'AWS::Lambda::Function', 'my-func'), ); - const deployStackResult = await cfnMockProvider.tryHotswapDeployment(cdkStackArtifact); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); @@ -415,7 +415,7 @@ test("will not perform a hotswap deployment if it cannot find a Ref target (outs // THEN await expect(() => - cfnMockProvider.tryHotswapDeployment(cdkStackArtifact), + hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact), ).rejects.toThrow(/Parameter or resource 'Param1' could not be found for evaluation/); }); @@ -478,6 +478,6 @@ test("will not perform a hotswap deployment if it doesn't know how to handle a s // THEN await expect(() => - cfnMockProvider.tryHotswapDeployment(cdkStackArtifact), + hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact), ).rejects.toThrow("We don't support the 'UnknownAttribute' attribute of the 'AWS::S3::Bucket' resource. This is a CDK limitation. Please report it at https://github.com/aws/aws-cdk/issues/new/choose"); }); diff --git a/packages/aws-cdk/test/context-providers/security-groups.test.ts b/packages/aws-cdk/test/context-providers/security-groups.test.ts index 7f24e684819b6..2d64961af7fe4 100644 --- a/packages/aws-cdk/test/context-providers/security-groups.test.ts +++ b/packages/aws-cdk/test/context-providers/security-groups.test.ts @@ -74,6 +74,157 @@ describe('security group context provider plugin', () => { expect(res.allowAllOutbound).toEqual(true); }); + test('looks up by security group id and vpc id', async () => { + // GIVEN + const provider = new SecurityGroupContextProviderPlugin(mockSDK); + + AWS.mock('EC2', 'describeSecurityGroups', (_params: aws.EC2.DescribeSecurityGroupsRequest, cb: AwsCallback) => { + expect(_params).toEqual({ + GroupIds: ['sg-1234'], + Filters: [ + { + Name: 'vpc-id', + Values: ['vpc-1234567'], + }, + ], + }); + cb(null, { + SecurityGroups: [ + { + GroupId: 'sg-1234', + IpPermissionsEgress: [ + { + IpProtocol: '-1', + IpRanges: [ + { CidrIp: '0.0.0.0/0' }, + ], + }, + { + IpProtocol: '-1', + Ipv6Ranges: [ + { CidrIpv6: '::/0' }, + ], + }, + ], + }, + ], + }); + }); + + // WHEN + const res = await provider.getValue({ + account: '1234', + region: 'us-east-1', + securityGroupId: 'sg-1234', + vpcId: 'vpc-1234567', + }); + + // THEN + expect(res.securityGroupId).toEqual('sg-1234'); + expect(res.allowAllOutbound).toEqual(true); + }); + + test('looks up by security group name', async () => { + // GIVEN + const provider = new SecurityGroupContextProviderPlugin(mockSDK); + + AWS.mock('EC2', 'describeSecurityGroups', (_params: aws.EC2.DescribeSecurityGroupsRequest, cb: AwsCallback) => { + expect(_params).toEqual({ + Filters: [ + { + Name: 'group-name', + Values: ['my-security-group'], + }, + ], + }); + cb(null, { + SecurityGroups: [ + { + GroupId: 'sg-1234', + IpPermissionsEgress: [ + { + IpProtocol: '-1', + IpRanges: [ + { CidrIp: '0.0.0.0/0' }, + ], + }, + { + IpProtocol: '-1', + Ipv6Ranges: [ + { CidrIpv6: '::/0' }, + ], + }, + ], + }, + ], + }); + }); + + // WHEN + const res = await provider.getValue({ + account: '1234', + region: 'us-east-1', + securityGroupName: 'my-security-group', + }); + + // THEN + expect(res.securityGroupId).toEqual('sg-1234'); + expect(res.allowAllOutbound).toEqual(true); + }); + + test('looks up by security group name and vpc id', async () => { + // GIVEN + const provider = new SecurityGroupContextProviderPlugin(mockSDK); + + AWS.mock('EC2', 'describeSecurityGroups', (_params: aws.EC2.DescribeSecurityGroupsRequest, cb: AwsCallback) => { + expect(_params).toEqual({ + Filters: [ + { + Name: 'vpc-id', + Values: ['vpc-1234567'], + }, + { + Name: 'group-name', + Values: ['my-security-group'], + }, + ], + }); + cb(null, { + SecurityGroups: [ + { + GroupId: 'sg-1234', + IpPermissionsEgress: [ + { + IpProtocol: '-1', + IpRanges: [ + { CidrIp: '0.0.0.0/0' }, + ], + }, + { + IpProtocol: '-1', + Ipv6Ranges: [ + { CidrIpv6: '::/0' }, + ], + }, + ], + }, + ], + }); + }); + + // WHEN + const res = await provider.getValue({ + account: '1234', + region: 'us-east-1', + securityGroupName: 'my-security-group', + vpcId: 'vpc-1234567', + }); + + // THEN + expect(res.securityGroupId).toEqual('sg-1234'); + expect(res.allowAllOutbound).toEqual(true); + }); + test('detects non all-outbound egress', async () => { // GIVEN const provider = new SecurityGroupContextProviderPlugin(mockSDK); @@ -109,6 +260,77 @@ describe('security group context provider plugin', () => { expect(res.allowAllOutbound).toEqual(false); }); + test('errors when more than one security group is found', async () => { + // GIVEN + const provider = new SecurityGroupContextProviderPlugin(mockSDK); + + AWS.mock('EC2', 'describeSecurityGroups', (_params: aws.EC2.DescribeSecurityGroupsRequest, cb: AwsCallback) => { + expect(_params).toEqual({ GroupIds: ['sg-1234'] }); + cb(null, { + SecurityGroups: [ + { + GroupId: 'sg-1234', + IpPermissionsEgress: [ + { + IpProtocol: '-1', + IpRanges: [ + { CidrIp: '10.0.0.0/16' }, + ], + }, + ], + }, + { + GroupId: 'sg-1234', + IpPermissionsEgress: [ + { + IpProtocol: '-1', + IpRanges: [ + { CidrIp: '10.0.0.0/16' }, + ], + }, + ], + }, + ], + }); + }); + // WHEN + await expect( + provider.getValue({ + account: '1234', + region: 'us-east-1', + securityGroupId: 'sg-1234', + }), + ).rejects.toThrow(/\More than one security groups found matching/i); + }); + + test('errors when securityGroupId and securityGroupName are specified both', async () => { + // GIVEN + const provider = new SecurityGroupContextProviderPlugin(mockSDK); + + // WHEN + await expect( + provider.getValue({ + account: '1234', + region: 'us-east-1', + securityGroupId: 'sg-1234', + securityGroupName: 'my-security-group', + }), + ).rejects.toThrow(/\'securityGroupId\' and \'securityGroupName\' can not be specified both when looking up a security group/i); + }); + + test('errors when neither securityGroupId nor securityGroupName are specified', async () => { + // GIVEN + const provider = new SecurityGroupContextProviderPlugin(mockSDK); + + // WHEN + await expect( + provider.getValue({ + account: '1234', + region: 'us-east-1', + }), + ).rejects.toThrow(/\'securityGroupId\' or \'securityGroupName\' must be specified to look up a security group/i); + }); + test('identifies allTrafficEgress from SecurityGroup permissions', () => { expect( hasAllTrafficEgress({ diff --git a/packages/aws-cdk/test/integ/test-cli-regression.bash b/packages/aws-cdk/test/integ/test-cli-regression.bash index 12cbe81791d65..1770ee269d87f 100644 --- a/packages/aws-cdk/test/integ/test-cli-regression.bash +++ b/packages/aws-cdk/test/integ/test-cli-regression.bash @@ -54,12 +54,13 @@ function run_regression_against_framework_version() { echo "Downloading aws-cdk ${PREVIOUS_VERSION} tarball from npm" npm pack aws-cdk@${PREVIOUS_VERSION} - tar -zxvf aws-cdk-${PREVIOUS_VERSION}.tgz + tar -zxf aws-cdk-${PREVIOUS_VERSION}.tgz rm -rf ${integ_under_test} echo "Copying integration tests of version ${PREVIOUS_VERSION} to ${integ_under_test} (dont worry, its gitignored)" cp -r ${temp_dir}/package/test/integ/cli "${integ_under_test}" + cp -r ${temp_dir}/package/test/integ/helpers "${integ_under_test}" patch_dir="${integdir}/cli-regression-patches/v${PREVIOUS_VERSION}" # delete possibly stale junit.xml file @@ -73,5 +74,10 @@ function run_regression_against_framework_version() { # the framework version to use is determined by the caller as the first argument. # its a variable name indirection. - FRAMEWORK_VERSION=${!FRAMEWORK_VERSION_IDENTIFIER} ${integ_under_test}/test.sh + export FRAMEWORK_VERSION=${!FRAMEWORK_VERSION_IDENTIFIER} + + # Show the versions we settled on + echo "♈️ Regression testing [cli $(cdk --version)] against [framework ${FRAMEWORK_VERSION}] using [tests ${PREVIOUS_VERSION}}]" + + ${integ_under_test}/test.sh } diff --git a/packages/aws-cdk/test/settings.test.ts b/packages/aws-cdk/test/settings.test.ts index 0f283b386006b..aef16e6bac946 100644 --- a/packages/aws-cdk/test/settings.test.ts +++ b/packages/aws-cdk/test/settings.test.ts @@ -100,6 +100,16 @@ test('bundling stacks defaults to * for deploy', () => { expect(settings.get(['bundlingStacks'])).toEqual(['*']); }); +test('bundling stacks defaults to * for watch', () => { + // GIVEN + const settings = Settings.fromCommandLineArguments({ + _: [Command.WATCH], + }); + + // THEN + expect(settings.get(['bundlingStacks'])).toEqual(['*']); +}); + test('bundling stacks with deploy exclusively', () => { // GIVEN const settings = Settings.fromCommandLineArguments({ @@ -112,6 +122,18 @@ test('bundling stacks with deploy exclusively', () => { expect(settings.get(['bundlingStacks'])).toEqual(['cool-stack']); }); +test('bundling stacks with watch exclusively', () => { + // GIVEN + const settings = Settings.fromCommandLineArguments({ + _: [Command.WATCH], + exclusively: true, + STACKS: ['cool-stack'], + }); + + // THEN + expect(settings.get(['bundlingStacks'])).toEqual(['cool-stack']); +}); + test('should include outputs-file in settings', () => { // GIVEN const settings = Settings.fromCommandLineArguments({ diff --git a/packages/aws-cdk/test/util/mock-sdk.ts b/packages/aws-cdk/test/util/mock-sdk.ts index c9afd8cd13baa..c97dccc98d7f8 100644 --- a/packages/aws-cdk/test/util/mock-sdk.ts +++ b/packages/aws-cdk/test/util/mock-sdk.ts @@ -130,6 +130,8 @@ export class MockSdk implements ISDK { public readonly kms = jest.fn(); public readonly stepFunctions = jest.fn(); public readonly getEndpointSuffix = jest.fn(); + public readonly appendCustomUserAgent = jest.fn(); + public readonly removeCustomUserAgent = jest.fn(); public currentAccount(): Promise { return Promise.resolve({ accountId: '123456789012', partition: 'aws' }); diff --git a/packages/awslint/package.json b/packages/awslint/package.json index eeeb3760ab9c9..733b922439ed3 100644 --- a/packages/awslint/package.json +++ b/packages/awslint/package.json @@ -18,11 +18,11 @@ "awslint": "bin/awslint" }, "dependencies": { - "@jsii/spec": "^1.42.0", - "camelcase": "^6.2.0", + "@jsii/spec": "^1.45.0", + "camelcase": "^6.2.1", "colors": "^1.4.0", "fs-extra": "^9.1.0", - "jsii-reflect": "^1.42.0", + "jsii-reflect": "^1.45.0", "yargs": "^16.2.0" }, "devDependencies": { @@ -37,7 +37,7 @@ "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^2.5.0", "@aws-cdk/eslint-plugin": "0.0.0", - "eslint-plugin-import": "^2.25.2", + "eslint-plugin-import": "^2.25.3", "eslint-plugin-jest": "^24.7.0", "jest": "^27.3.1" }, diff --git a/packages/cdk-dasm/package.json b/packages/cdk-dasm/package.json index 7098c1d22fcbd..b26d769698217 100644 --- a/packages/cdk-dasm/package.json +++ b/packages/cdk-dasm/package.json @@ -28,7 +28,7 @@ }, "license": "Apache-2.0", "dependencies": { - "codemaker": "^1.42.0", + "codemaker": "^1.44.2", "yaml": "1.10.2" }, "devDependencies": { diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 60bcab2c376c7..3279ac0ea734c 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -247,7 +247,7 @@ "@aws-cdk/region-info": "0.0.0", "constructs": "^3.3.69", "fs-extra": "^9.1.0", - "jsii-reflect": "^1.42.0", + "jsii-reflect": "^1.45.0", "jsonschema": "^1.4.0", "yaml": "1.10.2", "yargs": "^16.2.0" @@ -258,7 +258,7 @@ "@types/yaml": "1.9.7", "@types/yargs": "^15.0.14", "jest": "^27.3.1", - "jsii": "^1.42.0" + "jsii": "^1.45.0" }, "keywords": [ "aws", diff --git a/packages/monocdk/.gitignore b/packages/monocdk/.gitignore index 129f2f8e0bc37..f74c8e90c5eef 100644 --- a/packages/monocdk/.gitignore +++ b/packages/monocdk/.gitignore @@ -1,19 +1,17 @@ -*.js -*.d.ts -!deps.js -!gen.js -lib/ -tsconfig.json -.jsii -*.tsbuildinfo +# Ignore everything (because we're going to be generating into this +# directory) except certain source files. + +* +!LICENSE +!CONTRIBUTING.md +!NOTICE +!README.md +!scripts/* + dist .LAST_PACKAGE .LAST_BUILD *.snk -!.eslintrc.js - -# Ignore barrel import entry points -/*.ts - -junit.xml \ No newline at end of file +junit.xml +!.eslintrc.js \ No newline at end of file diff --git a/packages/monocdk/NOTICE b/packages/monocdk/NOTICE index a16b515240e23..bd46bd848ec36 100644 --- a/packages/monocdk/NOTICE +++ b/packages/monocdk/NOTICE @@ -372,387 +372,3 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------- - -** colors - https://www.npmjs.com/package/colors -Original Library - - Copyright (c) Marak Squires - -Additional Functionality - - Copyright (c) Sindre Sorhus (sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------- - -** diff - https://www.npmjs.com/package/diff -Copyright (c) 2009-2015, Kevin Decker - -All rights reserved. - -Redistribution and use of this software in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of Kevin Decker nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER -IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------- - -** fast-deep-equal - https://www.npmjs.com/package/fast-deep-equal -Copyright (c) 2017 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** string-width - https://www.npmjs.com/package/string-width -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** emoji-regex - https://www.npmjs.com/package/emoji-regex -Copyright Mathias Bynens - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** is-fullwidth-code-point - https://www.npmjs.com/package/is-fullwidth-code-point -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** strip-ansi - https://www.npmjs.com/package/strip-ansi -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** ansi-regex - https://www.npmjs.com/package/ansi-regex -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** table - https://www.npmjs.com/package/table -Copyright (c) 2018, Gajus Kuizinas (http://gajus.com/) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the Gajus Kuizinas (http://gajus.com/) nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ANUARY BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------- - -** ajv - https://www.npmjs.com/package/ajv -Copyright (c) 2015-2021 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** json-schema-traverse - https://www.npmjs.com/package/json-schema-traverse -Copyright (c) 2017 Evgeny Poberezkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----------------- - -** require-from-string - https://www.npmjs.com/package/require-from-string -Copyright (c) Vsevolod Strukchinsky (github.com/floatdrop) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------- - -** uri-js - https://www.npmjs.com/package/uri-js -Copyright 2011 Gary Court. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY GARY COURT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Gary Court. - ----------------- - -** lodash.truncate - https://www.npmjs.com/package/lodash -Copyright JS Foundation and other contributors - -Based on Underscore.js, copyright Jeremy Ashkenas, -DocumentCloud and Investigative Reporters & Editors - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/lodash/lodash - -The following license applies to all parts of this software except as -documented below: - -==== - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -==== - -Copyright and related rights for sample code are waived via CC0. Sample -code is defined as all source code displayed within the prose of the -documentation. - -CC0: http://creativecommons.org/publicdomain/zero/1.0/ - -==== - -Files located in the node_modules and vendor directories are externally -maintained libraries used by this software which have their own -licenses; we recommend you read them, as their terms may differ from the -terms above. - ----------------- - -** slice-ansi - https://www.npmjs.com/package/slice-ansi -Copyright (c) DC -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** ansi-styles - https://www.npmjs.com/package/ansi-styles -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** color-convert - https://www.npmjs.com/package/color-convert -Copyright (c) 2011-2016 Heather Arthur . -Copyright (c) 2016-2021 Josh Junon . - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** color-name - https://www.npmjs.com/package/color-name -Copyright (c) 2015 Dmitry Ivanov - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- - -** astral-regex - https://www.npmjs.com/package/astral-regex -Copyright (c) Kevin Mårtensson (github.com/kevva) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----------------- \ No newline at end of file diff --git a/packages/monocdk/package.json b/packages/monocdk/package.json index 1f2e65a012070..ea10f3a7ec3c3 100644 --- a/packages/monocdk/package.json +++ b/packages/monocdk/package.json @@ -2,8 +2,8 @@ "name": "monocdk", "version": "0.0.0", "description": "An experiment to bundle the entire CDK into a single module", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "index.js", + "types": "index.d.ts", "repository": { "type": "git", "url": "https://github.com/aws/aws-cdk.git", @@ -91,33 +91,23 @@ "bundledDependencies": [ "@balena/dockerignore", "case", - "colors", - "diff", - "fast-deep-equal", "fs-extra", "ignore", "jsonschema", "minimatch", "punycode", "semver", - "string-width", - "table", "yaml" ], "dependencies": { "@balena/dockerignore": "^1.0.2", "case": "1.6.3", - "colors": "^1.4.0", - "diff": "^5.0.0", - "fast-deep-equal": "^3.1.3", "fs-extra": "^9.1.0", "ignore": "^5.1.9", "jsonschema": "^1.4.0", "minimatch": "^3.0.4", "punycode": "^2.1.1", "semver": "^7.3.5", - "string-width": "^4.2.3", - "table": "^6.7.2", "yaml": "1.10.2" }, "devDependencies": { diff --git a/packages/monocdk/rosetta/README-custom-resource-provider.ts-fixture b/packages/monocdk/rosetta/README-custom-resource-provider.ts-fixture deleted file mode 100644 index ae4b1befd4b20..0000000000000 --- a/packages/monocdk/rosetta/README-custom-resource-provider.ts-fixture +++ /dev/null @@ -1,18 +0,0 @@ -import { CfnOutput, Construct, Token } from '@aws-cdk/core'; - -declare interface SumProps { - readonly lhs: number; - readonly rhs: number; -} -declare class Sum extends Construct { - public readonly result: number; - constructor(scope: Construct, id: string, props: SumProps); -} - -class fixture$construct extends Construct { - public constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/monocdk/rosetta/basic-constructs.ts-fixture b/packages/monocdk/rosetta/basic-constructs.ts-fixture deleted file mode 100644 index 19ffd84abf486..0000000000000 --- a/packages/monocdk/rosetta/basic-constructs.ts-fixture +++ /dev/null @@ -1,22 +0,0 @@ -// Fixture with packages imported, but nothing else -import * as cdk from '@aws-cdk/core'; -import * as appreg from '@aws-cdk/aws-servicecatalogappregistry'; - -class Fixture extends cdk.Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const application = new appreg.Application(stack, 'MyApplication', { - applicationName: 'MyApplication', - }); - - const attributeGroup = new appreg.AttributeGroup(stack, 'MyAttributeGroup', { - attributeGroupName: 'testAttributeGroup', - attributes: { - key: 'value', - }, - }); - - /// here - } -} diff --git a/packages/monocdk/rosetta/basic-portfolio.ts-fixture b/packages/monocdk/rosetta/basic-portfolio.ts-fixture deleted file mode 100644 index 3029872ea1f0d..0000000000000 --- a/packages/monocdk/rosetta/basic-portfolio.ts-fixture +++ /dev/null @@ -1,16 +0,0 @@ -// Fixture with packages imported, but nothing else -import * as cdk from '@aws-cdk/core'; -import * as servicecatalog from '@aws-cdk/aws-servicecatalog'; - -class Fixture extends cdk.Stack { - constructor(scope: cdk.Construct, id: string) { - super(scope, id); - - const portfolio = new servicecatalog.Portfolio(this, "MyFirstPortfolio", { - displayName: "MyFirstPortfolio", - providerName: "MyTeam", - }); - - /// here - } -} diff --git a/packages/monocdk/rosetta/client-vpn.ts-fixture b/packages/monocdk/rosetta/client-vpn.ts-fixture deleted file mode 100644 index 34c83a31ced35..0000000000000 --- a/packages/monocdk/rosetta/client-vpn.ts-fixture +++ /dev/null @@ -1,17 +0,0 @@ -// Fixture with packages imported and a VPC created -import { Construct, Stack } from '@aws-cdk/core'; -import iam = require('@aws-cdk/aws-iam'); -import ec2 = require('@aws-cdk/aws-ec2'); - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const vpc = new ec2.Vpc(this, 'VPC'); - const samlProvider = new iam.SamlProvider(this, 'Provider', { - metadataDocument: iam.SamlMetadataDocument.fromXml('xml'), - }) - - /// here - } -} diff --git a/packages/monocdk/rosetta/cluster.ts-fixture b/packages/monocdk/rosetta/cluster.ts-fixture deleted file mode 100644 index 82d98ca3e381e..0000000000000 --- a/packages/monocdk/rosetta/cluster.ts-fixture +++ /dev/null @@ -1,20 +0,0 @@ -// Fixture with cluster already created -import { Construct, SecretValue, Stack } from '@aws-cdk/core'; -import { Vpc } from '@aws-cdk/aws-ec2'; -import { Cluster, Table, TableAction, User } from '@aws-cdk/aws-redshift'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const vpc = new Vpc(this, 'Vpc'); - const cluster = new Cluster(this, 'Cluster', { - vpc, - masterUser: { - masterUsername: 'admin', - }, - }); - - /// here - } -} diff --git a/packages/monocdk/rosetta/default.ts-fixture b/packages/monocdk/rosetta/default.ts-fixture deleted file mode 100644 index 61a973840f007..0000000000000 --- a/packages/monocdk/rosetta/default.ts-fixture +++ /dev/null @@ -1,29 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Construct, CfnOutput, Stage, Stack, StackProps, StageProps } from '@aws-cdk/core'; -import cdk = require('@aws-cdk/core'); -import codepipeline = require('@aws-cdk/aws-codepipeline'); -import cpactions = require('@aws-cdk/aws-codepipeline-actions'); -import codebuild = require('@aws-cdk/aws-codebuild'); -import codecommit = require('@aws-cdk/aws-codecommit'); -import dynamodb = require('@aws-cdk/aws-dynamodb'); -import ecr = require('@aws-cdk/aws-ecr'); -import ec2 = require('@aws-cdk/aws-ec2'); -import iam = require('@aws-cdk/aws-iam'); -import pipelines = require('@aws-cdk/pipelines'); -import secretsmanager = require('@aws-cdk/aws-secretsmanager'); -import sns = require('@aws-cdk/aws-sns'); -import subscriptions = require('@aws-cdk/aws-sns-subscriptions'); -import s3 = require('@aws-cdk/aws-s3'); - -class MyApplicationStage extends Stage { - constructor(scope: Construct, id: string, props?: StageProps) { - super(scope, id, props); - } -} - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - /// here - } -} diff --git a/packages/monocdk/rosetta/init.ts-fixture b/packages/monocdk/rosetta/init.ts-fixture deleted file mode 100644 index ce18625a2744b..0000000000000 --- a/packages/monocdk/rosetta/init.ts-fixture +++ /dev/null @@ -1,3 +0,0 @@ -import { Template } from '@aws-cdk/assertions'; - -/// here \ No newline at end of file diff --git a/packages/monocdk/rosetta/migrate-opensearch.ts-fixture b/packages/monocdk/rosetta/migrate-opensearch.ts-fixture deleted file mode 100644 index bb93c1d40f369..0000000000000 --- a/packages/monocdk/rosetta/migrate-opensearch.ts-fixture +++ /dev/null @@ -1,16 +0,0 @@ -import * as cdk from '@aws-cdk/core'; -import * as es from '@aws-cdk/aws-elasticsearch'; -import * as iam from '@aws-cdk/aws-iam'; -import * as opensearch from '@aws-cdk/aws-opensearchservice'; - -declare const role: iam.IRole; -declare const elasticsearchVersion: es.ElasticsearchVersion; -declare const openSearchVersion: opensearch.EngineVersion; - -class Fixture extends cdk.Construct { - constructor(scope: cdk.Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/monocdk/rosetta/portfolio-product.ts-fixture b/packages/monocdk/rosetta/portfolio-product.ts-fixture deleted file mode 100644 index 20a1db30bf3ee..0000000000000 --- a/packages/monocdk/rosetta/portfolio-product.ts-fixture +++ /dev/null @@ -1,28 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Construct, Stack } from '@aws-cdk/core'; -import * as servicecatalog from '@aws-cdk/aws-servicecatalog'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const portfolio = new servicecatalog.Portfolio(this, "MyFirstPortfolio", { - displayName: "MyFirstPortfolio", - providerName: "MyTeam", - }); - - const product = new servicecatalog.CloudFormationProduct(this, 'MyFirstProduct', { - productName: "My Product", - owner: "Product Owner", - productVersions: [ - { - productVersionName: "v1", - cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromUrl( - 'https://raw.githubusercontent.com/awslabs/aws-cloudformation-templates/master/aws/services/ServiceCatalog/Product.yaml'), - }, - ] - }); - - /// here - } -} diff --git a/packages/monocdk/rosetta/with-bucket.ts-fixture b/packages/monocdk/rosetta/with-bucket.ts-fixture deleted file mode 100644 index d0851cff49639..0000000000000 --- a/packages/monocdk/rosetta/with-bucket.ts-fixture +++ /dev/null @@ -1,13 +0,0 @@ -// Fixture with a bucket already created -import { Construct, Stack } from '@aws-cdk/core'; -import { DeliveryStream, DestinationBindOptions, DestinationConfig, IDestination } from '@aws-cdk/aws-kinesisfirehose'; -import * as s3 from '@aws-cdk/aws-s3'; -declare const bucket: s3.Bucket; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/monocdk/rosetta/with-cluster.ts-fixture b/packages/monocdk/rosetta/with-cluster.ts-fixture deleted file mode 100644 index c638d8b4d04fa..0000000000000 --- a/packages/monocdk/rosetta/with-cluster.ts-fixture +++ /dev/null @@ -1,19 +0,0 @@ -import { Duration, Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as neptune from '@aws-cdk/aws-neptune'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const vpc = new ec2.Vpc(this, 'VPC', { maxAzs: 2 }); - - const cluster = new neptune.DatabaseCluster(this, 'Database', { - vpc, - instanceType: neptune.InstanceType.R5_LARGE, - }); - - /// here - } -} \ No newline at end of file diff --git a/packages/monocdk/rosetta/with-delivery-stream.ts-fixture b/packages/monocdk/rosetta/with-delivery-stream.ts-fixture deleted file mode 100644 index c7b75b20d2c1b..0000000000000 --- a/packages/monocdk/rosetta/with-delivery-stream.ts-fixture +++ /dev/null @@ -1,12 +0,0 @@ -// Fixture with a delivery stream already created -import { Construct, Stack } from '@aws-cdk/core'; -import { DeliveryStream, DestinationBindOptions, DestinationConfig, IDestination } from '@aws-cdk/aws-kinesisfirehose'; -declare const deliveryStream: DeliveryStream; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/monocdk/rosetta/with-destination.ts-fixture b/packages/monocdk/rosetta/with-destination.ts-fixture deleted file mode 100644 index 37d78bf7a43d3..0000000000000 --- a/packages/monocdk/rosetta/with-destination.ts-fixture +++ /dev/null @@ -1,12 +0,0 @@ -// Fixture with a destination already created -import { Construct, Stack } from '@aws-cdk/core'; -import { DeliveryStream, DestinationBindOptions, DestinationConfig, IDestination } from '@aws-cdk/aws-kinesisfirehose'; -declare const destination: IDestination; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/monocdk/rosetta/with-filesystem-instance.ts-fixture b/packages/monocdk/rosetta/with-filesystem-instance.ts-fixture deleted file mode 100644 index 092b572afa726..0000000000000 --- a/packages/monocdk/rosetta/with-filesystem-instance.ts-fixture +++ /dev/null @@ -1,30 +0,0 @@ -// Fixture with file system and an EC2 instance created in a VPC -import { Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; -import * as efs from '@aws-cdk/aws-efs'; -import * as ec2 from '@aws-cdk/aws-ec2'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const vpc = new ec2.Vpc(this, 'VPC'); - - const fileSystem = new efs.FileSystem(this, 'FileSystem', { - vpc, - }); - - const instance = new ec2.Instance(this, 'instance', { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.LARGE), - machineImage: new ec2.AmazonLinuxImage({ - generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2 - }), - vpc, - vpcSubnets: { - subnetType: ec2.SubnetType.PUBLIC, - } - }); - - /// here - } -} diff --git a/packages/monocdk/rosetta/with-lambda-trigger.ts-fixture b/packages/monocdk/rosetta/with-lambda-trigger.ts-fixture deleted file mode 100644 index de9aa90eedfc2..0000000000000 --- a/packages/monocdk/rosetta/with-lambda-trigger.ts-fixture +++ /dev/null @@ -1,26 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; -import * as cognito from '@aws-cdk/aws-cognito'; -import * as iam from '@aws-cdk/aws-iam'; -import * as lambda from '@aws-cdk/aws-lambda'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const postAuthFn = new lambda.Function(this, 'postAuthFn', { - code: lambda.Code.fromInline('post authentication'), - runtime: lambda.Runtime.NODEJS_12_X, - handler: 'index.handler', - }); - - const userpool = new cognito.UserPool(this, 'myuserpool', { - lambdaTriggers: { - postAuthentication: postAuthFn, - }, - }); - - /// here - } -} diff --git a/packages/monocdk/rosetta/with-objects.ts-fixture b/packages/monocdk/rosetta/with-objects.ts-fixture deleted file mode 100644 index 1251aad728423..0000000000000 --- a/packages/monocdk/rosetta/with-objects.ts-fixture +++ /dev/null @@ -1,49 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Construct, Stack } from '@aws-cdk/core'; -import appsync = require('@aws-cdk/aws-appsync'); -const pluralize = require('pluralize'); - -const args = { - after: appsync.GraphqlType.string(), - first: appsync.GraphqlType.int(), - before: appsync.GraphqlType.string(), - last: appsync.GraphqlType.int(), -}; - -const Node = new appsync.InterfaceType('Node', { - definition: { id: appsync.GraphqlType.string() } -}); - -const FilmNode = new appsync.ObjectType('FilmNode', { - interfaceTypes: [Node], - definition: { filmName: appsync.GraphqlType.string() } -}); - -function generateEdgeAndConnection(base: appsync.ObjectType) { - const edge = new appsync.ObjectType(`${base.name}Edge`, { - definition: { node: base.attribute(), cursor: appsync.GraphqlType.string() } - }); - const connection = new appsync.ObjectType(`${base.name}Connection`, { - definition: { - edges: edge.attribute({ isList: true }), - [pluralize(base.name)]: base.attribute({ isList: true }), - totalCount: appsync.GraphqlType.int(), - } - }); - return { edge: edge, connection: connection }; -} - -const demo = new appsync.ObjectType('Demo', { - definition: { - id: appsync.GraphqlType.string({ isRequired: true }), - version: appsync.GraphqlType.string({ isRequired: true }), - }, -}); - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - /// here - } -} diff --git a/packages/monocdk/rosetta/with-plan.ts-fixture b/packages/monocdk/rosetta/with-plan.ts-fixture deleted file mode 100644 index 8dbfd6ac72c89..0000000000000 --- a/packages/monocdk/rosetta/with-plan.ts-fixture +++ /dev/null @@ -1,16 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Duration, RemovalPolicy, Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; -import * as backup from '@aws-cdk/aws-backup'; -import * as dynamodb from '@aws-cdk/aws-dynamodb'; -import * as events from '@aws-cdk/aws-events'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const plan = backup.BackupPlan.dailyWeeklyMonthly5YearRetention(this, 'Plan'); - - /// here - } -} diff --git a/packages/monocdk/rosetta/with-vpc.ts-fixture b/packages/monocdk/rosetta/with-vpc.ts-fixture deleted file mode 100644 index dd8e539f8cf9f..0000000000000 --- a/packages/monocdk/rosetta/with-vpc.ts-fixture +++ /dev/null @@ -1,13 +0,0 @@ -// Fixture with packages imported and a VPC created -import { Construct, Stack } from '@aws-cdk/core'; -import ec2 = require('@aws-cdk/aws-ec2'); - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const vpc = new ec2.Vpc(this, 'VPC'); - - /// here - } -} diff --git a/packages/monocdk/rosetta/withRepoAndKinesisStream.ts-fixture b/packages/monocdk/rosetta/withRepoAndKinesisStream.ts-fixture deleted file mode 100644 index 115e1ece7e254..0000000000000 --- a/packages/monocdk/rosetta/withRepoAndKinesisStream.ts-fixture +++ /dev/null @@ -1,23 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Duration, RemovalPolicy, Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; - -import * as targets from '@aws-cdk/aws-events-targets'; -import * as events from '@aws-cdk/aws-events'; -import * as kinesis from '@aws-cdk/aws-kinesis'; -import * as codecommit from '@aws-cdk/aws-codecommit'; -import * as cdk from '@aws-cdk/core'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const repository = new codecommit.Repository(this, 'MyRepo', { - repositoryName: 'aws-cdk-events', - }); - - const stream = new kinesis.Stream(this, 'MyStream'); - - /// here - } -} diff --git a/packages/monocdk/rosetta/withRepoAndSqsQueue.ts-fixture b/packages/monocdk/rosetta/withRepoAndSqsQueue.ts-fixture deleted file mode 100644 index 98d029d8d8283..0000000000000 --- a/packages/monocdk/rosetta/withRepoAndSqsQueue.ts-fixture +++ /dev/null @@ -1,23 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Duration, RemovalPolicy, Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; - -import * as targets from '@aws-cdk/aws-events-targets'; -import * as events from '@aws-cdk/aws-events'; -import * as sqs from '@aws-cdk/aws-sqs'; -import * as codecommit from '@aws-cdk/aws-codecommit'; -import * as cdk from '@aws-cdk/core'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const repository = new codecommit.Repository(this, 'MyRepo', { - repositoryName: 'aws-cdk-events', - }); - - const queue = new sqs.Queue(this, 'MyQueue'); - - /// here - } -} diff --git a/packages/monocdk/rosetta/withRepoAndTopic.ts-fixture b/packages/monocdk/rosetta/withRepoAndTopic.ts-fixture deleted file mode 100644 index 30c1f29cc331b..0000000000000 --- a/packages/monocdk/rosetta/withRepoAndTopic.ts-fixture +++ /dev/null @@ -1,23 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Duration, RemovalPolicy, Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; - -import * as targets from '@aws-cdk/aws-events-targets'; -import * as events from '@aws-cdk/aws-events'; -import * as sns from '@aws-cdk/aws-sns'; -import * as codecommit from '@aws-cdk/aws-codecommit'; -import * as cdk from '@aws-cdk/core'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const repository = new codecommit.Repository(this, 'MyRepo', { - repositoryName: 'aws-cdk-events', - }); - - const topic = new sns.Topic(this, 'MyTopic'); - - /// here - } -} diff --git a/scripts/run-rosetta.sh b/scripts/run-rosetta.sh index 8e12df3da420d..1bec4074305e4 100755 --- a/scripts/run-rosetta.sh +++ b/scripts/run-rosetta.sh @@ -2,7 +2,18 @@ # # Run jsii-rosetta on all jsii packages, using the S3 build cache if available. # -# Usage: run-rosetta [PKGSFILE] +# Usage: run-rosetta [--infuse] [--pkgs-from PKGSFILE] +# +# Performs three steps, in that order: +# +# 1. Run `rosetta extract` to read and translate all examples from all JSII +# assemblies. +# +# 2. Run `rosetta infuse` to traverse all examples we have, and copy them +# to classes that don't have an example yet. +# +# 3. Run `tools/@aws-cdk/generate-examples` to find all types that *still* +# don't have examples associated with tme, and generate synthetic examples. # # If you already have a file with a list of all the JSII package directories # in it, pass it as the first argument. Otherwise, this script will run @@ -12,28 +23,75 @@ scriptdir=$(cd $(dirname $0) && pwd) ROSETTA=${ROSETTA:-npx jsii-rosetta} -if [[ "${1:-}" = "" ]]; then +infuse=false +jsii_pkgs_file="" +while [[ $# -gt 0 ]]; do + case $1 in + --infuse) + infuse="true" + ;; + --pkgs-from) + jsii_pkgs_file="$2" + shift + ;; + -h|--help) + echo "Usage: run-rosetta.sh [--infuse] [--pkgs-from FILE]" >&2 + exit 1 + ;; + *) + echo "Unrecognized argument: $1" >&2 + exit 1 + ;; + esac + shift +done + + +if [[ "${jsii_pkgs_file}" = "" ]]; then echo "Collecting package list..." >&2 TMPDIR=${TMPDIR:-$(dirname $(mktemp -u))} node $scriptdir/list-packages $TMPDIR/jsii.txt $TMPDIR/nonjsii.txt jsii_pkgs_file=$TMPDIR/jsii.txt -else - jsii_pkgs_file=$1 fi rosetta_cache_file=$HOME/.s3buildcache/rosetta-cache.tabl.json rosetta_cache_opts="" +genexample_cache_opts="" if [[ -f $rosetta_cache_file ]]; then rosetta_cache_opts="--cache-from ${rosetta_cache_file}" + genexample_cache_opts="--cache-from ${rosetta_cache_file}" fi +#---------------------------------------------------------------------- + +# Compile examples with respect to "decdk" directory, as all packages will +# be symlinked there so they can all be included. +echo "💎 Extracting code samples" >&2 $ROSETTA \ - --compile \ - --verbose \ - --output samples.tabl.json \ - $rosetta_cache_opts \ - --directory packages/decdk \ - $(cat $jsii_pkgs_file) + --compile \ + --verbose \ + --output samples.tabl.json \ + $rosetta_cache_opts \ + --directory packages/decdk \ + $(cat $jsii_pkgs_file) + + +if $infuse; then + echo "💎 Infusing examples back into assemblies" >&2 + $ROSETTA infuse \ + --verbose \ + samples.tabl.json \ + $(cat $jsii_pkgs_file) + + echo "💎 Generating synthetic examples for the remainder" >&2 + time $scriptdir/../tools/@aws-cdk/generate-examples/bin/generate-examples \ + $genexample_cache_opts \ + --append-to samples.tabl.json \ + $(cat $jsii_pkgs_file) + +fi + +#---------------------------------------------------------------------- if [[ -d $(dirname $rosetta_cache_file) ]]; then # If the cache directory is available, copy the current tablet into it diff --git a/scripts/transform.sh b/scripts/transform.sh index c0bb83b3a6edf..0c2cc2477468f 100755 --- a/scripts/transform.sh +++ b/scripts/transform.sh @@ -59,5 +59,6 @@ cd "$individual_packages_folder" createSymlinks "$individual_packages_folder" if [ "$skip_build" != "true" ]; then - PHASE=transform yarn lerna run --stream $runtarget + TRANSFORM_CONCURRENCY=${TRANSFORM_CONCURRENCY:-8} + PHASE=transform yarn lerna run --stream --concurrency ${TRANSFORM_CONCURRENCY} $runtarget fi diff --git a/tools/@aws-cdk/cdk-build-tools/bin/cdk-test.ts b/tools/@aws-cdk/cdk-build-tools/bin/cdk-test.ts index ccc6addd9ae5a..af35264465e38 100644 --- a/tools/@aws-cdk/cdk-build-tools/bin/cdk-test.ts +++ b/tools/@aws-cdk/cdk-build-tools/bin/cdk-test.ts @@ -30,13 +30,24 @@ async function main() { }, }; + const unitTestOptions = { + ...defaultShellOptions, + env: { + ...defaultShellOptions.env, + + // by default, fail when deprecated symbols are used in tests. + // tests that verify behaviour of deprecated symbols must use the `testDeprecated()` API. + JSII_DEPRECATED: 'fail', + }, + }; + if (options.test) { - await shell(options.test, defaultShellOptions); + await shell(options.test, unitTestOptions); } const testFiles = await unitTestFiles(); if (testFiles.length > 0) { - await shell([args.jest], defaultShellOptions); + await shell([args.jest], unitTestOptions); } // Run integration test if the package has integ test files diff --git a/tools/@aws-cdk/cdk-build-tools/config/jest.config.js b/tools/@aws-cdk/cdk-build-tools/config/jest.config.js index 367fff38462a4..1b907f3b6ffc6 100644 --- a/tools/@aws-cdk/cdk-build-tools/config/jest.config.js +++ b/tools/@aws-cdk/cdk-build-tools/config/jest.config.js @@ -21,7 +21,7 @@ module.exports = { coveragePathIgnorePatterns: [ "/lib/.*\\.generated\\.[jt]s", "/test/.*\\.[jt]s", - "/.*\\.jsii\\.js", + "/.warnings.jsii.js", ], reporters: [ "default", diff --git a/tools/@aws-cdk/cdk-build-tools/lib/deprecated-symbols.ts b/tools/@aws-cdk/cdk-build-tools/lib/deprecated-symbols.ts new file mode 100644 index 0000000000000..d64f3b4e1f5c3 --- /dev/null +++ b/tools/@aws-cdk/cdk-build-tools/lib/deprecated-symbols.ts @@ -0,0 +1,54 @@ +/* eslint-disable jest/no-export */ + +/** + * A proxy over the jest 'describe()' block. + * By default, unit tests in the CDK repo disallow the use of deprecated + * symbols (classes, interfaces, properties, methods, etc.) in the unit tests + * or within the "code under test". + * Use this block to override when the test is verifying the behaviour of + * deprecated APIs. + */ +export function describeDeprecated(name: string, fn: jest.EmptyFunction) { + describe(name, () => { + let deprecated: string | undefined; + beforeEach(() => { + deprecated = DeprecatedSymbols.quiet(); + }); + afterEach(() => { + DeprecatedSymbols.reset(deprecated); + }); + fn(); + }); +} + +/** + * A proxy over the jest 'test()' block. + * By default, unit tests in the CDK repo disallow the use of deprecated + * symbols (classes, interfaces, properties, methods, etc.) in the unit tests + * or within the "code under test". + * Use this block to override when the test is verifying the behaviour of + * deprecated APIs. + */ +export function testDeprecated(name: string, fn: () => any, timeout?: number) { + test(name, () => { + const deprecated = DeprecatedSymbols.quiet(); + fn(); + DeprecatedSymbols.reset(deprecated); + }, timeout); +} + +namespace DeprecatedSymbols { + export function quiet(): string | undefined { + const deprecated = process.env.JSII_DEPRECATED; + process.env.JSII_DEPRECATED = 'quiet'; + return deprecated; + } + + export function reset(deprecated: string | undefined) { + if (deprecated === undefined) { + delete process.env.JSII_DEPRECATED; + } else { + process.env.JSII_DEPRECATED = deprecated; + } + } +} \ No newline at end of file diff --git a/tools/@aws-cdk/cdk-build-tools/lib/index.ts b/tools/@aws-cdk/cdk-build-tools/lib/index.ts index 4494512263a5e..bfac1b81c127e 100644 --- a/tools/@aws-cdk/cdk-build-tools/lib/index.ts +++ b/tools/@aws-cdk/cdk-build-tools/lib/index.ts @@ -1 +1,3 @@ export { shell } from './os'; +export * from './feature-flag'; +export * from './deprecated-symbols'; diff --git a/tools/@aws-cdk/cdk-build-tools/lib/package-info.ts b/tools/@aws-cdk/cdk-build-tools/lib/package-info.ts index b264d3043b1b6..513d1b8dfdd11 100644 --- a/tools/@aws-cdk/cdk-build-tools/lib/package-info.ts +++ b/tools/@aws-cdk/cdk-build-tools/lib/package-info.ts @@ -97,7 +97,7 @@ export interface CompilerOverrides { */ export function packageCompiler(compilers: CompilerOverrides, options?: CDKBuildOptions): string[] { if (isJsii()) { - const args = ['--silence-warnings=reserved-word']; + const args = ['--silence-warnings=reserved-word', '--add-deprecation-warnings']; if (options?.stripDeprecated) { args.push(`--strip-deprecated ${path.join(__dirname, '..', '..', '..', '..', 'deprecated_apis.txt')}`); } diff --git a/tools/@aws-cdk/cdk-build-tools/package.json b/tools/@aws-cdk/cdk-build-tools/package.json index 72c138c72a25d..5d6761ff9d491 100644 --- a/tools/@aws-cdk/cdk-build-tools/package.json +++ b/tools/@aws-cdk/cdk-build-tools/package.json @@ -51,14 +51,14 @@ "eslint": "^7.32.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^2.5.0", - "eslint-plugin-import": "^2.25.2", + "eslint-plugin-import": "^2.25.3", "eslint-plugin-jest": "^24.7.0", "fs-extra": "^9.1.0", "jest": "^27.3.1", "jest-junit": "^13.0.0", - "jsii": "^1.42.0", - "jsii-pacmak": "^1.42.0", - "jsii-reflect": "^1.42.0", + "jsii": "^1.45.0", + "jsii-pacmak": "^1.45.0", + "jsii-reflect": "^1.45.0", "markdownlint-cli": "^0.29.0", "nyc": "^15.1.0", "semver": "^7.3.5", diff --git a/tools/@aws-cdk/cdk-integ-tools/bin/cdk-integ-assert.ts b/tools/@aws-cdk/cdk-integ-tools/bin/cdk-integ-assert.ts index 3c5cb049cf0d0..cdca064874099 100644 --- a/tools/@aws-cdk/cdk-integ-tools/bin/cdk-integ-assert.ts +++ b/tools/@aws-cdk/cdk-integ-tools/bin/cdk-integ-assert.ts @@ -6,7 +6,7 @@ import { DEFAULT_SYNTH_OPTIONS, IntegrationTests } from '../lib/integ-helpers'; /* eslint-disable no-console */ -const IGNORE_ASSETS_PRAGMA = 'pragma:ignore-assets'; +const VERIFY_ASSET_HASHES = 'pragma:include-assets-hashes'; async function main() { const tests = await new IntegrationTests('test').fromCliArgs(); // always assert all tests @@ -22,7 +22,8 @@ async function main() { let expected = await test.readExpected(); let actual = await test.cdkSynthFast(DEFAULT_SYNTH_OPTIONS); - if ((await test.pragmas()).includes(IGNORE_ASSETS_PRAGMA)) { + // We will always ignore asset hashes, unless specifically requested not to + if (!(await test.pragmas()).includes(VERIFY_ASSET_HASHES)) { expected = canonicalizeTemplate(expected); actual = canonicalizeTemplate(actual); } diff --git a/tools/@aws-cdk/cfn2ts/lib/augmentation-generator.ts b/tools/@aws-cdk/cfn2ts/lib/augmentation-generator.ts index 2c145546acdb2..ec7500f05a1dc 100644 --- a/tools/@aws-cdk/cfn2ts/lib/augmentation-generator.ts +++ b/tools/@aws-cdk/cfn2ts/lib/augmentation-generator.ts @@ -138,7 +138,7 @@ export class AugmentationGenerator { dimStrings.push(`${key}: ${field}`); } - this.code.line(` dimensions: { ${dimStrings.join(', ') } },`); + this.code.line(` dimensionsMap: { ${dimStrings.join(', ') } },`); this.code.line(' ...props'); this.code.line(' }).attachTo(this);'); this.code.line('};'); diff --git a/tools/@aws-cdk/cfn2ts/lib/canned-metrics-generator.ts b/tools/@aws-cdk/cfn2ts/lib/canned-metrics-generator.ts index 6a540f81c7895..3388c391f1f3b 100644 --- a/tools/@aws-cdk/cfn2ts/lib/canned-metrics-generator.ts +++ b/tools/@aws-cdk/cfn2ts/lib/canned-metrics-generator.ts @@ -64,7 +64,7 @@ export class CannedMetricsGenerator { this.code.line('return {'); this.code.line(` namespace: '${metric.namespace}',`); this.code.line(` metricName: '${metric.metricName}',`); - this.code.line(' dimensions,'); + this.code.line(' dimensionsMap: dimensions,'); this.code.line(` statistic: '${metric.defaultStat}',`); this.code.line('};'); this.code.closeBlock(); @@ -93,7 +93,7 @@ export class CannedMetricsGenerator { } private emitTypeDef() { - this.code.line('type MetricWithDims = { namespace: string, metricName: string, statistic: string, dimensions: D };'); + this.code.line('type MetricWithDims = { namespace: string, metricName: string, statistic: string, dimensionsMap: D };'); } } diff --git a/tools/@aws-cdk/cfn2ts/package.json b/tools/@aws-cdk/cfn2ts/package.json index d01b82acff872..7e386255d5681 100644 --- a/tools/@aws-cdk/cfn2ts/package.json +++ b/tools/@aws-cdk/cfn2ts/package.json @@ -32,7 +32,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-cdk/cfnspec": "0.0.0", - "codemaker": "^1.42.0", + "codemaker": "^1.44.2", "fast-json-patch": "^3.1.0", "fs-extra": "^9.1.0", "yargs": "^16.2.0" diff --git a/tools/@aws-cdk/eslint-plugin/package.json b/tools/@aws-cdk/eslint-plugin/package.json index 281d6e3bd4f01..06c52ff0197af 100644 --- a/tools/@aws-cdk/eslint-plugin/package.json +++ b/tools/@aws-cdk/eslint-plugin/package.json @@ -14,7 +14,7 @@ "build+extract": "npm run build" }, "devDependencies": { - "@types/eslint": "^7.28.2", + "@types/eslint": "^7.29.0", "@types/fs-extra": "^8.1.2", "@types/jest": "^27.0.2", "@types/node": "^10.17.60", diff --git a/tools/@aws-cdk/generate-examples/.eslintrc.js b/tools/@aws-cdk/generate-examples/.eslintrc.js new file mode 100644 index 0000000000000..2658ee8727166 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/tools/@aws-cdk/generate-examples/.gitignore b/tools/@aws-cdk/generate-examples/.gitignore new file mode 100644 index 0000000000000..405a05419d4b6 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/.gitignore @@ -0,0 +1,10 @@ +*.js +*.js.map +*.d.ts +dist + +*.snk +!.eslintrc.js +!config/*.js +junit.xml +!jest.config.js diff --git a/tools/@aws-cdk/generate-examples/.npmignore b/tools/@aws-cdk/generate-examples/.npmignore new file mode 100644 index 0000000000000..9aca5ee7d9678 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/.npmignore @@ -0,0 +1,14 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +.LAST_BUILD +*.snk +.eslintrc.js + +# exclude cdk artifacts +**/cdk.out +junit.xml \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/LICENSE b/tools/@aws-cdk/generate-examples/LICENSE new file mode 100644 index 0000000000000..28e4bdcec77ec --- /dev/null +++ b/tools/@aws-cdk/generate-examples/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tools/@aws-cdk/generate-examples/NOTICE b/tools/@aws-cdk/generate-examples/NOTICE new file mode 100644 index 0000000000000..5fc3826926b5b --- /dev/null +++ b/tools/@aws-cdk/generate-examples/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/tools/@aws-cdk/generate-examples/README.md b/tools/@aws-cdk/generate-examples/README.md new file mode 100644 index 0000000000000..a363579acebc0 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/README.md @@ -0,0 +1,9 @@ +# Generate synthetic examples + +This tool is designed to run during the build. It will find all classes in the +JSII assembly that don't yet have any example code associated with them, and +will generate a synthetic example that shows how to instantiate the type. + +This is a method of last resort: we'd obviously prefer hand-written examples, +but this will make sure L1s will at least get something usable (which otherwise +would not have any examples at all). diff --git a/tools/@aws-cdk/generate-examples/bin/generate-examples b/tools/@aws-cdk/generate-examples/bin/generate-examples new file mode 100755 index 0000000000000..8950f29a1fcbf --- /dev/null +++ b/tools/@aws-cdk/generate-examples/bin/generate-examples @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('./generate-examples.js'); \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/bin/generate-examples.ts b/tools/@aws-cdk/generate-examples/bin/generate-examples.ts new file mode 100644 index 0000000000000..d03462865612b --- /dev/null +++ b/tools/@aws-cdk/generate-examples/bin/generate-examples.ts @@ -0,0 +1,54 @@ +import * as yargs from 'yargs'; + +import { generateMissingExamples } from '../lib/generate-missing-examples'; + +async function main() { + const args = yargs + .usage('$0 [ASSEMBLY..]') + .option('cache-from', { + alias: 'C', + type: 'string', + describe: 'Reuse translations from the given tablet file', + requiresArg: true, + default: undefined, + }) + .option('append-to', { + alias: 'a', + type: 'string', + describe: 'Append translations to the given tablet file', + requiresArg: true, + default: undefined, + }) + .option('directory', { + alias: 'd', + type: 'string', + describe: 'Directory to run the compilation in (with dependencies set up)', + requiresArg: true, + default: undefined, + }) + .option('strict', { + alias: 's', + type: 'boolean', + describe: 'Whether to exit with an error if there are diagnostics', + default: false, + }) + .help() + .strict() + .showHelpOnFail(false) + .argv; + + const assemblyDirs = args._.map(x => `${x}`); + + await generateMissingExamples(assemblyDirs.length > 0 ? assemblyDirs : ['.'], { + cacheFromTablet: args['cache-from'], + appendToTablet: args['append-to'], + directory: args.directory, + strict: args.strict, + }); +} + +main().catch(e => { + // eslint-disable-next-line no-console + console.error(e); + process.exitCode = 1; +}); \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/jest.config.js b/tools/@aws-cdk/generate-examples/jest.config.js new file mode 100644 index 0000000000000..c4f65f19ab3d7 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/jest.config.js @@ -0,0 +1,10 @@ +const baseConfig = require('../cdk-build-tools/config/jest.config'); +module.exports = { + ...baseConfig, + coverageThreshold: { + global: { + ...baseConfig.coverageThreshold.global, + branches: 60, + }, + }, +}; diff --git a/tools/@aws-cdk/generate-examples/lib/assemblies.ts b/tools/@aws-cdk/generate-examples/lib/assemblies.ts new file mode 100644 index 0000000000000..5efa530849ea2 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/lib/assemblies.ts @@ -0,0 +1,40 @@ +import * as path from 'path'; +import * as spec from '@jsii/spec'; +import * as fs from 'fs-extra'; +import { TypeScriptSnippet } from 'jsii-rosetta'; + +/** + * Replaces the file where the original assembly file *should* be found with a new assembly file. + * Recalculates the fingerprint of the assembly to avoid tampering detection. + */ +export async function replaceAssembly(assembly: spec.Assembly, directory: string): Promise { + const fileName = path.join(directory, '.jsii'); + await fs.writeJson(fileName, _fingerprint(assembly), { + encoding: 'utf8', + spaces: 2, + }); +} + +/** + * Replaces the old fingerprint with '***********'. + * + * @rmuller says fingerprinting is useless, as we do not actually check + * if an assembly is changed. Instead of keeping the old (wrong) fingerprint + * or spending extra time calculating a new fingerprint, we replace with '**********' + * that demonstrates the fingerprint has changed. + */ +function _fingerprint(assembly: spec.Assembly): spec.Assembly { + assembly.fingerprint = '*'.repeat(10); + return assembly; +} + +/** + * Insert an example into the docs of a type + */ +export function insertExample(example: TypeScriptSnippet, type: spec.Type): void { + if (type.docs) { + type.docs.example = example.visibleSource; + } else { + type.docs = { example: example.visibleSource }; + } +} diff --git a/tools/@aws-cdk/generate-examples/lib/code.ts b/tools/@aws-cdk/generate-examples/lib/code.ts new file mode 100644 index 0000000000000..ae5e75082be19 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/lib/code.ts @@ -0,0 +1,83 @@ +import { Declaration } from './declaration'; +import { sortBy } from './utils'; + +/** + * Information on a segment of code and the declarations necessary to make the code valid. + */ +export class Code { + public static concatAll(...xs: Array): Code { + return xs.map(Code.force).reduce((a, b) => a.append(b), new Code('')); + } + + private static force(x: Code | string): Code { + if (x instanceof Code) { + return x; + } + return new Code(x); + } + + /** + * Construct a Code, consisting of a code fragment and a list of declarations that are meant + * to be rendered at the top of the code snippet. + */ + constructor(public readonly code: string, public readonly declarations: Declaration[] = []) { + } + + /** + * Appends and returns a new Code that safely combines two code fragments along + * with their declarations. + */ + public append(rhs: Code | string): Code { + if (typeof rhs === 'string') { + return new Code(this.code + rhs, this.declarations); + } + + return new Code(this.code + rhs.code, [...this.declarations, ...rhs.declarations]); + } + + public toString() { + return this.render(); + } + + private render(separator = '\n\n') { + return (this.renderDeclarations().join('\n') + separator + this.code).trimStart(); + } + + /** + * Renders variable declarations. Assumes that there are no duplicates in the declarations. + */ + public renderDeclarations(): string[] { + sortBy(this.declarations, (d) => d.sortKey); + const decs = deduplicate(this.declarations); + // Add separator only if necessary + const decStrings = [...decs.map((d) => d.render())]; + // only supports two groups and not more + for (let i = 0; i < decs.length-1; i++) { + if (decs[i].sortKey[0] !== decs[i+1].sortKey[0]) { + decStrings.splice(i+1, 0, ''); + break; + } + } + return decStrings; + } + + public renderCode(): string { + return this.code; + } +} + +/** + * Deduplicates a sorted array of Declarations. + */ +function deduplicate(declarations: Declaration[]): Declaration[] { + if (declarations.length === 0) { return declarations; } + + const newDeclarations: Declaration[] = []; + newDeclarations.push(declarations[0]); + for (let i = 1; i < declarations.length; i++) { + if (!declarations[i].equals(declarations[i-1])) { + newDeclarations.push(declarations[i]); + } + } + return newDeclarations; +} diff --git a/tools/@aws-cdk/generate-examples/lib/declaration.ts b/tools/@aws-cdk/generate-examples/lib/declaration.ts new file mode 100644 index 0000000000000..df4282db96096 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/lib/declaration.ts @@ -0,0 +1,82 @@ +import * as reflect from 'jsii-reflect'; + +import { module, typeNamespacedName } from './module-utils'; + +export abstract class Declaration { + constructor(public readonly sortKey: Array) {} + + public abstract equals(rhs: Declaration): boolean; + public abstract render(): string; +} + +/** + * An Import statement that will get rendered at the top of the code snippet. + */ +export class Import extends Declaration { + public readonly importName: string; + public readonly moduleName: string; + public readonly submoduleName?: string; + public readonly type: reflect.Type; + + public constructor(type: reflect.Type) { + const { importName, moduleName, submoduleName } = module(type); + + super([0, moduleName]); + + this.importName = importName; + this.moduleName = moduleName; + this.submoduleName = submoduleName; + this.type = type; + } + + public equals(rhs: Declaration): boolean { + return this.render() === rhs.render(); + } + + public render(): string { + let what; + if (!this.submoduleName) { + what = `* as ${this.importName}`; + } else if (this.submoduleName === this.importName) { + what = `{ ${this.importName} }`; + } else { + what = `{ ${this.submoduleName} as ${this.importName} }`; + } + return `import ${what} from '${this.moduleName}';`; + } +} + +/** + * A declared constant that will be rendered at the top of the code snippet after the imports. + */ +export class Assumption extends Declaration { + public constructor(private readonly type: reflect.Type, private readonly name: string) { + super([1, name]); + } + + public equals(rhs: Declaration): boolean { + return this.render() === rhs.render(); + } + + public render(): string { + return `declare const ${this.name}: ${module(this.type).importName}.${typeNamespacedName(this.type)};`; + } +} + +/** + * An assumption for an 'any' time. This will be treated the same as 'Assumption' but with a + * different render result. + */ +export class AnyAssumption extends Declaration { + public constructor(private readonly name: string) { + super([1, name]); + } + + public equals(rhs: Declaration): boolean { + return this.render() === rhs.render(); + } + + public render(): string { + return `declare const ${this.name}: any;`; + } +} \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/lib/generate-missing-examples.ts b/tools/@aws-cdk/generate-examples/lib/generate-missing-examples.ts new file mode 100644 index 0000000000000..3d3e6f74531bf --- /dev/null +++ b/tools/@aws-cdk/generate-examples/lib/generate-missing-examples.ts @@ -0,0 +1,155 @@ +/* eslint-disable no-console */ +import { promises as fs } from 'fs'; +import { Assembly, TypeSystem } from 'jsii-reflect'; + +// This import should come from @jsii/spec. Replace when that is possible. +import { LanguageTablet, RosettaTranslator, SnippetLocation, SnippetParameters, TypeScriptSnippet, typeScriptSnippetFromCompleteSource } from 'jsii-rosetta'; +import { insertExample, replaceAssembly } from './assemblies'; +import { generateAssignmentStatement } from './generate'; + +const COMMENT_WARNING = [ + '// The code below shows an example of how to instantiate this type.', + '// The values are placeholders you should change.', +]; + +export interface GenerateExamplesOptions { + readonly cacheFromTablet?: string; + readonly appendToTablet?: string; + readonly directory?: string; + readonly strict?: boolean; +} + +export async function generateMissingExamples(assemblyLocations: string[], options: GenerateExamplesOptions) { + const typesystem = new TypeSystem(); + + // load all assemblies into typesystem + const loadedAssemblies = await Promise.all(assemblyLocations.map(async (assemblyLocation) => { + if (!(await statFile(assemblyLocation))?.isDirectory) { + throw new Error(`Assembly location not a directory: ${assemblyLocation}`); + } + + return { assemblyLocation, assembly: await typesystem.load(assemblyLocation, { validate: false }) }; + })); + + const snippets = loadedAssemblies.flatMap(({ assembly }) => { + // Classes and structs + const documentableTypes = [ + ...assembly.classes.filter(c => !c.docs.example), + ...assembly.interfaces.filter(i => !i.docs.example && i.datatype), + ]; + + console.log(`${assembly.name}: ${documentableTypes.length} classes to document`); + if (documentableTypes.length === 0) { return []; } + + const failed = new Array(); + const generatedSnippets = documentableTypes.flatMap((classType) => { + const example = generateAssignmentStatement(classType); + if (!example) { + failed.push(classType.name); + return []; + } + + // To successfully compile, we need to generate the right 'Construct' import + const completeSource = [ + ...COMMENT_WARNING, + ...example.renderDeclarations(), + '', + '/// !hide', + correctConstructImport(assembly), + 'class MyConstruct extends Construct {', + 'constructor(scope: Construct, id: string) {', + 'super(scope, id);', + '/// !show', + example.renderCode(), + '/// !hide', + '} }', + ].join('\n').trimLeft(); + const location: SnippetLocation = { api: { api: 'type', fqn: classType.fqn }, field: { field: 'example' } }; + + const tsSnippet: TypeScriptSnippet = typeScriptSnippetFromCompleteSource( + completeSource, + location, + true, + { + [SnippetParameters.$COMPILATION_DIRECTORY]: options.directory ?? process.cwd(), + }); + + insertExample(tsSnippet, classType.spec); + return [tsSnippet]; + }); + + console.log([ + `${assembly.name}: annotated ${generatedSnippets.length} classes`, + ...(failed.length > 0 ? [`failed: ${failed.join(', ')}`] : []), + ].join(', ')); + + return generatedSnippets; + }); + + const rosetta = new RosettaTranslator({ + includeCompilerDiagnostics: true, + assemblies: loadedAssemblies.map(({ assembly }) => assembly.spec), + }); + + if (options.cacheFromTablet) { + await rosetta.loadCache(options.cacheFromTablet); + } + + // Will mutate the 'snippets' array + const { remaining } = rosetta.readFromCache(snippets); + + console.log(`Translating ${remaining.length} snippets`); + const results = await rosetta.translateAll(remaining); + if (results.diagnostics.length > 0) { + for (const diag of results.diagnostics) { + console.log(diag.formattedMessage); + } + + if (options.strict) { + process.exitCode = 1; + } + } + + // Copy everything from the rosetta tablet into our output tablet + if (options.appendToTablet) { + console.log(`Appending to ${options.appendToTablet}`); + const outputTablet = new LanguageTablet(); + if ((await statFile(options.appendToTablet)) !== undefined) { + await outputTablet.load(options.appendToTablet); + } + + for (const key of rosetta.tablet.snippetKeys) { + const snip = rosetta.tablet.tryGetSnippet(key); + if (snip) { + outputTablet.addSnippet(snip); + } + } + + await outputTablet.save(options.appendToTablet); + } + + console.log(`Saving ${loadedAssemblies.length} assemblies`); + await Promise.all((loadedAssemblies).map(({ assembly, assemblyLocation }) => + replaceAssembly(assembly.spec, assemblyLocation))); +} + +async function statFile(fileName: string) { + try { + return await fs.stat(fileName); + } catch (e) { + if (e.code === 'ENOENT') { return undefined; } + throw e; + } +} + +function correctConstructImport(assembly: Assembly) { + if (assembly.name === 'monocdk') { + return 'import { Construct } from "monocdk";'; + } + + if (assembly.dependencies.some(d => d.assembly.name === '@aws-cdk/core')) { + return 'import { Construct } from "@aws-cdk/core";'; + } + + return 'import { Construct } from "constructs";'; +} \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/lib/generate.ts b/tools/@aws-cdk/generate-examples/lib/generate.ts new file mode 100644 index 0000000000000..728b6cd3d7f1d --- /dev/null +++ b/tools/@aws-cdk/generate-examples/lib/generate.ts @@ -0,0 +1,387 @@ +import * as spec from '@jsii/spec'; +import * as reflect from 'jsii-reflect'; +import { TypeSystem } from 'jsii-reflect'; + +import { Code } from './code'; +import { AnyAssumption, Assumption, Import } from './declaration'; +import { escapeIdentifier, typeReference } from './module-utils'; +import { sortBy } from './utils'; + +/** + * Special types that have a standard way of coming up with an example value + */ +const SPECIAL_TYPE_EXAMPLES: Record = { + '@aws-cdk/core.Duration': 'cdk.Duration.minutes(30)', + 'aws-cdk-lib.Duration': 'cdk.Duration.minutes(30)', +}; + +/** + * Context on the example that we are building. + * This object persists throughout the recursive call + * and provides the function with the same information + * on the typesystem and which types have already been + * rendered. This helps to prevent infinite recursion. + */ +class ExampleContext { + private readonly _typeSystem: TypeSystem; + private readonly _rendered: Set = new Set(); + + constructor(typeSystem: TypeSystem) { + this._typeSystem = typeSystem; + } + + public get typeSystem() { + return this._typeSystem; + } + + public get rendered() { + return this._rendered; + } +} + +export function generateAssignmentStatement(type: reflect.ClassType | reflect.InterfaceType): Code | undefined { + const context = new ExampleContext(type.system); + + if (type.isClassType()) { + const expression = exampleValueForClass(context, type, 0); + if (!expression) { return undefined; } + return Code.concatAll( + `const ${lowercaseFirstLetter(type.name)} = `, + expression, + ';', + ); + } + + if (type.isInterfaceType()) { + const expression = exampleValueForStruct(context, type, 0); + if (!expression) { return undefined; } + + return Code.concatAll( + `const ${lowercaseFirstLetter(type.name)}: `, + typeReference(type), + ' = ', + expression, + ';', + ); + } + + return undefined; +} + +function exampleValueForClass(context: ExampleContext, classType: reflect.ClassType, level: number): Code | undefined { + const staticFactoryMethods = getStaticFactoryMethods(classType); + const staticFactoryProperties = getStaticFactoryProperties(classType); + const initializer = getAccessibleConstructor(classType); + + if (initializer && initializer.parameters.length >= 3) { + return generateClassInstantiationExample(context, initializer, level); + } + + if (staticFactoryMethods.length >= 3) { + return generateStaticFactoryMethodExample(context, staticFactoryMethods[0], level); + } + + if (staticFactoryProperties.length >= 3) { + return generateStaticFactoryPropertyExample(staticFactoryProperties[0]); + } + + if (initializer) { + return generateClassInstantiationExample(context, initializer, level); + } + + if (staticFactoryMethods.length >= 1) { + return generateStaticFactoryMethodExample(context, staticFactoryMethods[0], level); + } + + if (staticFactoryProperties.length >= 1) { + return generateStaticFactoryPropertyExample(staticFactoryProperties[0]); + } + + return undefined; +} + +function getAccessibleConstructor(classType: reflect.ClassType): reflect.Initializer | undefined { + if (classType.abstract || !classType.initializer || classType.initializer.protected) { + return undefined; + } + return classType.initializer; +} + +/** + * Return the list of static methods on classtype that return either classtype or a supertype of classtype. + */ +function getStaticFactoryMethods(classType: reflect.ClassType): reflect.Method[] { + return classType.allMethods.filter(method => + method.static && extendsRef(classType, method.returns.type), + ); +} + +/** + * Return the list of static methods on classtype that return either classtype or a supertype of classtype. + */ +function getStaticFactoryProperties(classType: reflect.ClassType): reflect.Property[] { + return classType.allProperties.filter(prop => + prop.static && extendsRef(classType, prop.type), + ); +} + +function generateClassInstantiationExample(context: ExampleContext, initializer: reflect.Initializer, level: number): Code { + return Code.concatAll( + 'new ', + typeReference(initializer.parentType), + '(', + parameterList(context, initializer.parameters, level), + ')', + ); +} + +function parameterList(context: ExampleContext, parameters: reflect.Parameter[], level: number) { + const length = parameters.length; + return Code.concatAll( + ...parameters.map((p, i) => { + if (length - 1 === i) { + return exampleValueForParameter(context, p, i, level); + } else { + return exampleValueForParameter(context, p, i, level).append(', '); + } + }), + ); +} + +function generateStaticFactoryMethodExample( + context: ExampleContext, + staticFactoryMethod: reflect.Method, + level: number, +) { + return Code.concatAll( + typeReference(staticFactoryMethod.parentType), + '.', + staticFactoryMethod.name, + '(', + parameterList(context, staticFactoryMethod.parameters, level), + ')', + ); +} + +function generateStaticFactoryPropertyExample(staticFactoryProperty: reflect.Property) { + return Code.concatAll( + typeReference(staticFactoryProperty.parentType), + '.', + staticFactoryProperty.name, + ); +} + +/** + * Generate an example value of the given parameter. + */ +function exampleValueForParameter(context: ExampleContext, param: reflect.Parameter, position: number, level: number): Code { + if (param.name === 'scope' && position === 0) { + return new Code('this'); + } + + if (param.name === 'id' && position === 1) { + return new Code(`'My${param.parentType.name}'`); + } + if (param.optional) { + return new Code('/* all optional props */ ').append(exampleValue(context, param.type, param.name, level)); + } + return exampleValue(context, param.type, param.name, level); +} + +/** + * Generate an example value of the given type. + */ +function exampleValue(context: ExampleContext, typeRef: reflect.TypeReference, name: string, level: number): Code { + // Process primitive types, base case + if (typeRef.primitive !== undefined) { + switch (typeRef.primitive) { + case spec.PrimitiveType.String: + return new Code(`'${name}'`); + case spec.PrimitiveType.Number: + return new Code('123'); + case spec.PrimitiveType.Boolean: + return new Code('false'); + case spec.PrimitiveType.Date: + return new Code('new Date()'); + default: + return new Code(name, [new AnyAssumption(name)]); + } + } + + // Just pick the first type if it is a union type + if (typeRef.unionOfTypes !== undefined) { + const newType = getBaseUnionType(typeRef.unionOfTypes); + return exampleValue(context, newType, name, level); + } + // If its a collection create a collection of one element + if (typeRef.arrayOfType !== undefined) { + return Code.concatAll('[', exampleValue(context, typeRef.arrayOfType, name, level), ']'); + } + + if (typeRef.mapOfType !== undefined) { + return exampleValueForMap(context, typeRef.mapOfType, name, level); + } + + if (typeRef.fqn) { + const fqn = typeRef.fqn; + // See if we have information on this type in the assembly + const newType = context.typeSystem.findFqn(fqn); + + if (fqn in SPECIAL_TYPE_EXAMPLES) { + return new Code(SPECIAL_TYPE_EXAMPLES[fqn], [new Import(newType)]); + } + + if (newType.isEnumType()) { + return Code.concatAll( + typeReference(newType), + '.', + newType.members[0].name); + } + + // If this is struct and we're not already rendering it (recursion breaker), expand + if (isStructType(newType)) { + if (context.rendered.has(newType.fqn)) { + // Recursion breaker -- if we go by the default behavior end up saying something like: + // + // const myProperty = { + // stringProp: 'stringProp', + // deepProp: myProperty, // <-- value recursion! + // }; + // + // Which TypeScript's type analyzer can't automatically derive a type for. We need to + // annotate SOMETHING. A simple fix is to use a different variable name so the value + // isn't self-recursive. + + return addAssumedVariableDeclaration(newType, '_'); + } + + + context.rendered.add(newType.fqn); + const ret = exampleValueForStruct(context, newType, level); + context.rendered.delete(newType.fqn); + return ret; + } + + // For all other types we will assume you already have a variable of the appropriate type. + return addAssumedVariableDeclaration(newType); + } + + throw new Error('If this happens, then reflect.typeRefernce must have a new value'); +} + +function getBaseUnionType(types: reflect.TypeReference[]): reflect.TypeReference { + for (const newType of types) { + if (newType.fqn?.endsWith('.IResolvable')) { + continue; + } + return newType; + } + return types[0]; +} + +/** + * Add an assumption and import for a variable that will be declared as a constant. + * If the variable is an IXxx Interface, guess a possible implementation of that interface + * by checking if stripping the I results in an Xxx type that extends IXxx. + */ +function addAssumedVariableDeclaration(type: reflect.Type, suffix = ''): Code { + let newType = type; + if (type.isInterfaceType() && !type.datatype) { + // guess corresponding non-interface type if possible + newType = guessConcreteType(type); + } + const variableName = escapeIdentifier(lowercaseFirstLetter(stripLeadingI(newType.name))) + suffix; + return new Code(variableName, [new Assumption(newType, variableName), new Import(newType)]); +} + +/** + * Remove a leading 'I' from a name, if it's being followed by another capital letter + */ +function stripLeadingI(name: string) { + return name.replace(/^I([A-Z])/, '$1'); +} + +/** + * This function tries to guess the corresponding type to an IXxx Interface. + * If it does not find that this type exists, it will return the original type. + */ +function guessConcreteType(type: reflect.InterfaceType): reflect.Type { + const concreteClassName = type.name.substr(1); // Strip off the leading 'I' + + const parts = type.fqn.split('.'); + parts[parts.length - 1] = concreteClassName; + const newFqn = parts.join('.'); + + const newType = type.system.tryFindFqn(newFqn); + return newType && newType.extends(type) ? newType : type; +} + +/** + * Helper function to generate an example value for a map. + */ +function exampleValueForMap(context: ExampleContext, map: reflect.TypeReference, name: string, level: number): Code { + return Code.concatAll( + '{\n', + new Code(`${tab(level + 1)}${name}Key: `).append(exampleValue(context, map, name, level + 1)).append(',\n'), + `${tab(level)}}`, + ); +} + +/** + * Helper function to generate an example value for a struct. + */ +function exampleValueForStruct(context: ExampleContext, struct: reflect.InterfaceType, level: number): Code { + if (struct.allProperties.length === 0) { + return new Code('{ }'); + } + + const properties = [...struct.allProperties]; // Make a copy that we can sort + sortBy(properties, (p) => [p.optional ? 1 : 0, p.name]); + + const renderedProperties = properties.map((p) => + Code.concatAll( + `${tab(level + 1)}${p.name}: `, + exampleValue(context, p.type, p.name, level + 1), + ',\n', + ), + ); + + // Add an empty line between required and optional properties + for (let i = 0; i < properties.length - 1; i++) { + if (properties[i].optional !== properties[i + 1].optional) { + renderedProperties.splice(i + 1, 0, new Code(`\n${tab(level+1)}// the properties below are optional\n`)); + break; + } + } + + return Code.concatAll( + '{\n', + ...renderedProperties, + `${tab(level)}}`, + ); +} + +/** + * Returns whether the given type represents a struct + */ +function isStructType(type: reflect.Type): type is reflect.InterfaceType { + return type.isInterfaceType() && type.datatype; +} + +function extendsRef(subtype: reflect.ClassType, supertypeRef: reflect.TypeReference): boolean { + if (!supertypeRef.fqn) { + // Not a named type, can never extend + return false; + } + + const superType = subtype.system.findFqn(supertypeRef.fqn); + return subtype.extends(superType); +} + +function lowercaseFirstLetter(str: string): string { + return str.charAt(0).toLowerCase() + str.slice(1); +} + +function tab(level: number): string { + return ' '.repeat(level); +} \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/lib/index.ts b/tools/@aws-cdk/generate-examples/lib/index.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tools/@aws-cdk/generate-examples/lib/module-utils.ts b/tools/@aws-cdk/generate-examples/lib/module-utils.ts new file mode 100644 index 0000000000000..e3151ab51c88e --- /dev/null +++ b/tools/@aws-cdk/generate-examples/lib/module-utils.ts @@ -0,0 +1,115 @@ +import * as reflect from 'jsii-reflect'; +import { Code } from './code'; +import { Import } from './declaration'; + +/** + * Customary module import names that differ from what would be automatically generated. + */ +const SPECIAL_PACKAGE_ROOT_IMPORT_NAMES: Record = { + 'aws-cdk-lib': 'cdk', + '@aws-cdk/core': 'cdk', + '@aws-cdk/aws-applicationautoscaling': 'appscaling', + '@aws-cdk/aws-elasticloadbalancing': 'elb', + '@aws-cdk/aws-elasticloadbalancingv2': 'elbv2', +}; + +const SPECIAL_NAMESPACE_IMPORT_NAMES: Record = { + 'aws-cdk-lib.aws_applicationautoscaling': 'appscaling', + 'aws-cdk-lib.aws_elasticloadbalancing': 'elb', + 'aws-cdk-lib.aws_elasticloadbalancingv2': 'elbv2', +}; + +interface ImportedModule { + readonly importName: string; + readonly moduleName: string; + readonly submoduleName?: string; +} + +/** + * Parses the given type for human-readable information on the module + * that the type is from. Meant to serve as a single source of truth + * for parsing the type for module information. + */ +export function module(type: reflect.Type): ImportedModule { + const parts = analyzeTypeName(type); + + if (parts.submoduleNameParts.length > 0) { + const specialNameKey = [parts.assemblyName, ...parts.submoduleNameParts].join('.'); + + const importName = SPECIAL_NAMESPACE_IMPORT_NAMES[specialNameKey] ?? parts.submoduleNameParts.join('.'); + return { + importName: escapeIdentifier(importName.replace(/^aws_/g, '').replace(/[^a-z0-9_]/g, '_')), + moduleName: parts.assemblyName, + submoduleName: parts.submoduleNameParts.join('.'), + }; + } + + // Split '@aws-cdk/aws-s3' into ['@aws-cdk', 'aws-s3'] + const slashParts = type.assembly.name.split('/'); + const nonNamespacedPart = SPECIAL_PACKAGE_ROOT_IMPORT_NAMES[parts.assemblyName] ?? slashParts[1] ?? slashParts[0]; + return { + importName: escapeIdentifier(nonNamespacedPart.replace(/^aws-/g, '').replace(/[^a-z0-9_]/g, '_')), + moduleName: type.assembly.name, + }; +} + +/** + * Namespaced name inside a module + */ +export function typeNamespacedName(type: reflect.Type): string { + const parts = analyzeTypeName(type); + + return [ + ...parts.namespaceNameParts, + parts.simpleName, + ].join('.'); +} + +const KEYWORDS = ['function', 'default']; + +export function escapeIdentifier(ident: string): string { + return KEYWORDS.includes(ident) ? `${ident}_` : ident; +} + +export function moduleReference(type: reflect.Type) { + const imp = new Import(type); + return new Code(imp.importName, [imp]); +} + +export function typeReference(type: reflect.Type) { + return Code.concatAll( + moduleReference(type), + '.', + typeNamespacedName(type)); +} + +/** + * A type name consists of 4 parts which are all treated differently + */ +interface TypeNameParts { + readonly assemblyName: string; + readonly submoduleNameParts: string[]; + readonly namespaceNameParts: string[]; + readonly simpleName: string; +} + +function analyzeTypeName(type: reflect.Type): TypeNameParts { + // Need to divide the namespace into submodule and non-submodule + + // For type 'asm.b.c.d.Type' contains ['asm', 'b', 'c', 'd'] + const nsParts = type.fqn.split('.').slice(0, -1); + + const moduleFqns = new Set(type.assembly.allSubmodules.map((s) => s.fqn)); + + let split = nsParts.length; + while (split > 1 && !moduleFqns.has(nsParts.slice(0, split).join('.'))) { + split--; + } + + return { + assemblyName: type.assembly.name, + submoduleNameParts: nsParts.slice(1, split), + namespaceNameParts: nsParts.slice(split, nsParts.length), + simpleName: type.name, + }; +} \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/lib/utils.ts b/tools/@aws-cdk/generate-examples/lib/utils.ts new file mode 100644 index 0000000000000..526664f645a22 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/lib/utils.ts @@ -0,0 +1,28 @@ +export function sortBy(xs: A[], keyFn: (x: A) => Array) { + return xs.sort((a, b) => { + const aKey = keyFn(a); + const bKey = keyFn(b); + + for (let i = 0; i < Math.min(aKey.length, bKey.length); i++) { + // Compare aKey[i] to bKey[i] + const av = aKey[i]; + const bv = bKey[i]; + + if (av === bv) { continue; } + + if (typeof av !== typeof bv) { + throw new Error(`Type of sort key ${JSON.stringify(aKey)} not same as ${JSON.stringify(bKey)}`); + } + + if (typeof av === 'number' && typeof bv === 'number') { + return av - bv; + } + + if (typeof av === 'string' && typeof bv === 'string') { + return av.localeCompare(bv); + } + } + + return aKey.length - bKey.length; + }); +} \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/package.json b/tools/@aws-cdk/generate-examples/package.json new file mode 100644 index 0000000000000..ebb81537ffaf3 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/package.json @@ -0,0 +1,51 @@ +{ + "name": "@aws-cdk/generate-examples", + "version": "0.0.0", + "private": true, + "description": "Generate missing examples", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "repository": { + "type": "git", + "url": "git://github.com/aws/aws-cdk" + }, + "pkglint": { + "ignore": true + }, + "bin": { + "generate-examples": "bin/generate-examples" + }, + "scripts": { + "build": "tsc -b && chmod +x bin/generate-examples", + "test": "jest", + "build+test": "npm run build && npm test", + "build+test+package": "npm run build && npm test", + "watch": "tsc -b -w", + "lint": "tsc -b && eslint . --ext=.ts" + }, + "keywords": [ + "aws", + "cdk" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com" + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/jest": "^26.0.24", + "@types/yargs": "^15.0.14", + "jest": "^26.6.3", + "typescript": "~3.9.10" + }, + "nozem": { + "ostools": ["chmod", "cp"] + }, + "dependencies": { + "@jsii/spec": "1.45.0", + "jsii-reflect": "1.45.0", + "jsii-rosetta": "1.45.0", + "fs-extra": "^9.1.0", + "yargs": "^16.2.0" + } +} diff --git a/tools/@aws-cdk/generate-examples/test/code.test.ts b/tools/@aws-cdk/generate-examples/test/code.test.ts new file mode 100644 index 0000000000000..114cc8858faa1 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/test/code.test.ts @@ -0,0 +1,51 @@ +import { Code } from '../lib/code'; +import { AnyAssumption } from '../lib/declaration'; + +test('created successfully', () => { + const code = new Code('hello world', [new AnyAssumption('from Mars')]); + + expect(code.code).toEqual('hello world'); + expect(code.declarations.length).toEqual(1); + expect(code.declarations[0]).toBeInstanceOf(AnyAssumption); +}); + +test('can append', () => { + const code = new Code('hello ', [new AnyAssumption('from Mars')]) + .append('world') + .append(new Code('.', [new AnyAssumption('from Jupiter')])); + + expect(code.code).toEqual('hello world.'); + expect(code.declarations.length).toEqual(2); +}); + +test('concatAll works as expected', () => { + const code = Code.concatAll( + 'hello', + new Code(' world', [new AnyAssumption('from Mars')]), + ); + + expect(code.code).toEqual('hello world'); + expect(code.declarations.length).toEqual(1); +}); + +describe('code declarations on toString', () => { + test('deduplicated', () => { + const code = new Code('', [ + new AnyAssumption('duplicate'), + new AnyAssumption('duplicate'), + new AnyAssumption('unique'), + ]); + + expect(code.toString()).toEqual('declare const duplicate: any;\ndeclare const unique: any;\n\n'); + }); + + test('sorted', () => { + const code = new Code('', [ + new AnyAssumption('third'), + new AnyAssumption('second'), + new AnyAssumption('first'), + ]); + + expect(code.toString()).toEqual('declare const first: any;\ndeclare const second: any;\ndeclare const third: any;\n\n'); + }); +}); diff --git a/tools/@aws-cdk/generate-examples/test/generate-missing-examples.test.ts b/tools/@aws-cdk/generate-examples/test/generate-missing-examples.test.ts new file mode 100644 index 0000000000000..963b98bdc586b --- /dev/null +++ b/tools/@aws-cdk/generate-examples/test/generate-missing-examples.test.ts @@ -0,0 +1,60 @@ +import * as path from 'path'; + +import { LanguageTablet, TargetLanguage } from 'jsii-rosetta'; +import { generateMissingExamples } from '../lib/generate-missing-examples'; +import { DUMMY_ASSEMBLY_TARGETS, AssemblyFixture } from './testutil'; + +test('test end-to-end and translation to Python', async () => { + const assembly = await AssemblyFixture.fromSource( + { + 'index.ts': ` + export interface MyClassProps { + readonly someString: string; + readonly someNumber: number; + } + + export class MyClass { + constructor(value: string, props: MyClassProps) { + Array.isArray(value); + Array.isArray(props); + } + } + `, + }, + { + name: 'my_assembly', + jsii: DUMMY_ASSEMBLY_TARGETS, + }, + ); + try { + const outputTablet = path.join(assembly.directory, 'test.tbl.json'); + + await generateMissingExamples([ + assembly.directory, + ], { + directory: assembly.directory, + appendToTablet: outputTablet, + }); + + const tablet = await LanguageTablet.fromFile(outputTablet); + + const pythons = tablet.snippetKeys + .map((key) => tablet.tryGetSnippet(key)!) + .map((snip) => snip.get(TargetLanguage.PYTHON)?.source); + + const classInstantiation = pythons.find((s) => s?.includes('= my_assembly.MyClass(')); + + expect(classInstantiation).toEqual([ + '# The code below shows an example of how to instantiate this type.', + '# The values are placeholders you should change.', + 'import my_assembly as my_assembly', + '', + 'my_class = my_assembly.MyClass(\"value\",', + ' some_number=123,', + ' some_string=\"someString\"', + ')', + ].join('\n')); + } finally { + await assembly.cleanup(); + } +}); \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/test/generate.test.ts b/tools/@aws-cdk/generate-examples/test/generate.test.ts new file mode 100644 index 0000000000000..b15e3e8a31d04 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/test/generate.test.ts @@ -0,0 +1,323 @@ +import * as reflect from 'jsii-reflect'; + +import { generateAssignmentStatement } from '../lib/generate'; +import { AssemblyFixture, DUMMY_ASSEMBLY_TARGETS, MultipleSources } from './testutil'; + +describe('generateClassAssignment ', () => { + test('generates example for class with static methods', + expectedDocTest({ + sources: { + 'index.ts': ` + export class ClassA { + public static firstMethod() { return new ClassA(); } + public static secondMethod() { return new ClassA(); } + public static thirdMethod() { return new ClassA(); } + private constructor() {} + }`, + }, + typeName: 'ClassA', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const classA = my_assembly.ClassA.firstMethod();', + ], + }), + ); + + test('generates example for class with static properties', + expectedDocTest({ + sources: { + 'index.ts': ` + export class ClassA { + public static readonly FIRST_PROPERTY = new ClassA(); + public static readonly SECOND_PROPERTY = new ClassA(); + public static readonly THIRD_PROPERTY = new ClassA(); + private constructor() {} + }`, + }, + typeName: 'ClassA', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const classA = my_assembly.ClassA.FIRST_PROPERTY;', + ], + }), + ); + + test('generates example for class instantiation', + expectedDocTest({ + sources: { + 'index.ts': ` + export class ClassA { + public constructor(public readonly a: string, public readonly b: string) {} + }`, + }, + typeName: 'ClassA', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const classA = new my_assembly.ClassA(\'a\', \'b\');', + ], + }), + ); + + test('generates example for more complicated class instantiation', + expectedDocTest({ + sources: { + 'index.ts': ` + export class ClassA { + public constructor(public readonly scope: string, public readonly id: string, public readonly props: ClassAProps) {} + } + export interface ClassAProps { + readonly prop1: number, + readonly prop2: IProperty, + readonly prop3: string[], + readonly prop4: number | string, + readonly prop5?: any, + readonly prop6?: boolean, + readonly prop7?: { [key: string]: string }, + } + export interface IProperty { + readonly prop: string, + } + export class Property implements IProperty { + readonly prop: string; + public constructor() { this.prop = 'a'; } + } + `, + }, + typeName: 'ClassA', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'declare const prop5: any;', + 'declare const property: my_assembly.Property;', + '', + 'const classA = new my_assembly.ClassA(this, \'MyClassA\', {', + ' prop1: 123,', + ' prop2: property,', + ' prop3: [\'prop3\'],', + ' prop4: \'prop4\',', + '', + ' // the properties below are optional', + ' prop5: prop5,', + ' prop6: false,', + ' prop7: {', + ' prop7Key: \'prop7\',', + ' },', + '});', + ], + }), + ); + + test('returns undefined if class has no statics and private initializer', async () => { + const assembly = await AssemblyFixture.fromSource( + { + 'index.ts': ` + export class ClassA { + private constructor() {} + }`, + }, + { + name: 'my_assembly', + jsii: DUMMY_ASSEMBLY_TARGETS, + }, + ); + + const ts = new reflect.TypeSystem(); + await ts.load(assembly.directory); + + const type = ts.findClass('my_assembly.ClassA'); + expect(generateAssignmentStatement(type)).toBeUndefined(); + + await assembly.cleanup(); + }); + + test('optional properties are added in the correct spot', + expectedDocTest({ + sources: { + 'index.ts': ` + export class ClassA { + public constructor(public readonly scope: string, public readonly id: string, public readonly props: ClassAProps) {} + } + export interface ClassAProps { + readonly prop1: number, + readonly prop2?: number, + } + `, + }, + typeName: 'ClassA', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const classA = new my_assembly.ClassA(this, \'MyClassA\', {', + ' prop1: 123,', + '', + ' // the properties below are optional', + ' prop2: 123,', + '});', + ], + }), + ); + + test( + 'comment added when all properties are optional', + expectedDocTest({ + sources: { + 'index.ts': ` + export class ClassA { + public constructor(public readonly scope: string, public readonly id: string, public readonly props: ClassAProps = {}) {} + } + export interface ClassAProps { + readonly prop1?: number, + readonly prop2?: number, + } + `, + }, + typeName: 'ClassA', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const classA = new my_assembly.ClassA(this, \'MyClassA\', /* all optional props */ {', + ' prop1: 123,', + ' prop2: 123,', + '});', + ], + }), + ); +}); + +test( + 'generate example for struct', + expectedDocTest({ + sources: { + 'index.ts': ` + export interface SomeStruct { + readonly required: string; + readonly optional?: number; + } + `, + }, + typeName: 'SomeStruct', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const someStruct: my_assembly.SomeStruct = {', + ' required: \'required\',', + '', + ' // the properties below are optional', + ' optional: 123,', + '};', + ], + }), +); + +test( + 'rendering an enum value', + expectedDocTest({ + sources: { + 'index.ts': ` + export interface SomeStruct { + readonly someEnum: MyEnum; + } + export enum MyEnum { + VALUE1 = 1, + VALUE2 = 2, + } + `, + }, + typeName: 'SomeStruct', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const someStruct: my_assembly.SomeStruct = {', + ' someEnum: my_assembly.MyEnum.VALUE1,', + '};', + ], + }), +); + +test('rendering types in namespaces', expectedDocTest({ + sources: { + // This merges a class and a namespace (making the struct appear + // namespaced inside the class -- we do this for L1 structs) + 'index.ts': ` + export class SomeClass { + constructor(props: SomeClass.SomeStruct) { + Array.isArray(props); + } + } + + export namespace SomeClass { + export interface SomeStruct { + readonly someEnum: MyEnum; + } + export enum MyEnum { + VALUE1 = 1, + VALUE2 = 2, + } + } + `, + }, + typeName: 'SomeClass.SomeStruct', + expected: [ + 'import * as my_assembly from \'my_assembly\';', + '', + 'const someStruct: my_assembly.SomeClass.SomeStruct = {', + ' someEnum: my_assembly.SomeClass.MyEnum.VALUE1,', + '};', + ], +})); + +test('rendering types in submodules', expectedDocTest({ + sources: { + 'index.ts': 'export * as sub from \'./other\';', + 'other.ts': ` + export interface SomeStruct { + readonly someEnum: MyEnum; + } + export enum MyEnum { + VALUE1 = 1, + VALUE2 = 2, + } + `, + }, + typeName: 'sub.SomeStruct', + expected: [ + 'import { sub } from \'my_assembly\';', + '', + 'const someStruct: sub.SomeStruct = {', + ' someEnum: sub.MyEnum.VALUE1,', + '};', + ], +})); + +interface DocTest { + readonly sources: MultipleSources; + readonly typeName: string; + readonly expected: string[]; +} + +function expectedDocTest(testParams: DocTest) { + return async () => { + const assembly = await AssemblyFixture.fromSource( + testParams.sources, + { + name: 'my_assembly', + jsii: DUMMY_ASSEMBLY_TARGETS, + }, + ); + try { + const ts = new reflect.TypeSystem(); + await ts.load(assembly.directory); + + const type = ts.findFqn(`my_assembly.${testParams.typeName}`); + if (!type.isClassType() && !type.isInterfaceType()) { + throw new Error('Expecting class or interface'); + } + expect(generateAssignmentStatement(type)?.toString()?.split('\n')).toEqual(testParams.expected); + } finally { + await assembly.cleanup(); + } + }; +} \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/test/module-utils.test.ts b/tools/@aws-cdk/generate-examples/test/module-utils.test.ts new file mode 100644 index 0000000000000..ecb9bea9326ce --- /dev/null +++ b/tools/@aws-cdk/generate-examples/test/module-utils.test.ts @@ -0,0 +1,161 @@ +import * as reflect from 'jsii-reflect'; + +import { module } from '../lib/module-utils'; +import { AssemblyFixture, DUMMY_ASSEMBLY_TARGETS } from './testutil'; + +describe('v1 names are correct: ', () => { + test('core', async () => { + // GIVEN + const mod = '@aws-cdk/core'; + const { ts, assembly } = await v1BuildAssemblyHelper(mod); + + // THEN + const { importName, moduleName } = module(ts.findClass(`${mod}.ClassA`)); + expect(importName).toEqual('cdk'); + expect(moduleName).toEqual(mod); + + await assembly.cleanup(); + }); + + test('special package root', async () => { + // GIVEN + const mod = '@aws-cdk/aws-elasticloadbalancingv2'; + const { ts, assembly } = await v1BuildAssemblyHelper(mod); + + // THEN + const { importName, moduleName } = module(ts.findClass(`${mod}.ClassA`)); + expect(importName).toEqual('elbv2'); + expect(moduleName).toEqual(mod); + + await assembly.cleanup(); + }); + + test('with "aws-"', async () => { + // GIVEN + const mod = '@aws-cdk/aws-s3'; + const { ts, assembly } = await v1BuildAssemblyHelper(mod); + + // THEN + const { importName, moduleName } = module(ts.findClass(`${mod}.ClassA`)); + expect(importName).toEqual('s3'); + expect(moduleName).toEqual(mod); + + await assembly.cleanup(); + }); + + test('without "aws-"', async () => { + // GIVEN + const mod = '@aws-cdk/pipelines'; + const { ts, assembly } = await v1BuildAssemblyHelper(mod); + + // THEN + const { importName, moduleName } = module(ts.findClass(`${mod}.ClassA`)); + expect(importName).toEqual('pipelines'); + expect(moduleName).toEqual(mod); + + await assembly.cleanup(); + }); +}); + +describe('v2 names are correct: ', () => { + test('core', async () => { + // GIVEN + const mod = 'aws-cdk-lib'; + const { ts, assembly } = await v2BuildAssemblyHelper(mod); + + // THEN + const { importName, moduleName } = module(ts.findClass(`${mod}.ClassA`)); + expect(importName).toEqual('cdk'); + expect(moduleName).toEqual(mod); + + await assembly.cleanup(); + }); + + test('special namespace', async () => { + // GIVEN + const mod = 'aws-cdk-lib/aws_elasticloadbalancingv2'; + const { ts, assembly } = await v2BuildAssemblyHelper(mod); + + // THEN + expect(module( + ts.findClass('aws-cdk-lib.aws_elasticloadbalancingv2.ClassB'), + )).toEqual({ + moduleName: 'aws-cdk-lib', + submoduleName: 'aws_elasticloadbalancingv2', + importName: 'elbv2', + }); + + await assembly.cleanup(); + }); + + test('with "aws_"', async () => { + // GIVEN + const mod = 'aws-cdk-lib/aws_s3'; + const { ts, assembly } = await v2BuildAssemblyHelper(mod); + + // THEN + expect(module(ts.findClass('aws-cdk-lib.aws_s3.ClassB'))).toEqual({ + moduleName: 'aws-cdk-lib', + submoduleName: 'aws_s3', + importName: 's3', + }); + + await assembly.cleanup(); + }); + + test('without "aws_"', async () => { + // GIVEN + const mod = 'aws-cdk-lib/pipelines'; + const { ts, assembly } = await v2BuildAssemblyHelper(mod); + + // THEN + expect(module(ts.findClass('aws-cdk-lib.pipelines.ClassB'))).toEqual({ + moduleName: 'aws-cdk-lib', + submoduleName: 'pipelines', + importName: 'pipelines', + }); + + await assembly.cleanup(); + }); +}); + +async function v1BuildAssemblyHelper(name: string) { + const assembly = await AssemblyFixture.fromSource( + { + 'index.ts': ` + export class ClassA { } + `, + }, + { + name, + jsii: DUMMY_ASSEMBLY_TARGETS, + }, + ); + + const ts = new reflect.TypeSystem(); + await ts.load(assembly.directory); + return { ts, assembly }; +} + +async function v2BuildAssemblyHelper(name: string) { + const [assemblyName, submoduleName] = name.split('/'); + const assembly = await AssemblyFixture.fromSource( + { + 'index.ts': ` + export * as ${submoduleName ?? 'dummy'} from "./submod"; + export class ClassA { } + `, + 'submod.ts': ` + export class ClassB { } + `, + }, + { + name: assemblyName, + jsii: DUMMY_ASSEMBLY_TARGETS, + }, + ); + + const ts = new reflect.TypeSystem(); + await ts.load(assembly.directory); + return { ts, assembly }; +} \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/test/testutil.ts b/tools/@aws-cdk/generate-examples/test/testutil.ts new file mode 100644 index 0000000000000..f4cee0fe3bd5f --- /dev/null +++ b/tools/@aws-cdk/generate-examples/test/testutil.ts @@ -0,0 +1,65 @@ +import * as os from 'os'; +import * as path from 'path'; +import * as fs from 'fs-extra'; +import { PackageInfo, compileJsiiForTest } from 'jsii'; + +export type MultipleSources = { [key: string]: string; 'index.ts': string }; + +export class AssemblyFixture { + public static async fromSource( + source: string | MultipleSources, + packageInfo: Partial & { name: string }, + ) { + const { assembly, files } = await compileJsiiForTest(source, (pi) => { + Object.assign(pi, packageInfo); + }); + + // The following is silly, however: the helper has compiled the given source to + // an assembly, and output files, and then removed their traces from disk. + // But for the purposes of Rosetta, we need those files back on disk. So write + // them back out again >_< + // + // In fact we will drop them in 'node_modules/' so they can be imported + // as if they were installed. + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'jsii-rosetta')); + const modDir = path.join(tmpDir, 'node_modules', packageInfo.name); + await fs.ensureDir(modDir); + + await fs.writeJSON(path.join(modDir, '.jsii'), assembly); + await fs.writeJSON(path.join(modDir, 'package.json'), { + name: packageInfo.name, + jsii: packageInfo.jsii, + }); + for (const [fileName, fileContents] of Object.entries(files)) { + // eslint-disable-next-line no-await-in-loop + await fs.writeFile(path.join(modDir, fileName), fileContents); + } + + return new AssemblyFixture(modDir); + } + + private constructor(public readonly directory: string) {} + + public async cleanup() { + await fs.remove(this.directory); + } +} + +export const DUMMY_ASSEMBLY_TARGETS = { + dotnet: { + namespace: 'Example.Test.Demo', + packageId: 'Example.Test.Demo', + }, + go: { moduleName: 'example.test/demo' }, + java: { + maven: { + groupId: 'example.test', + artifactId: 'demo', + }, + package: 'example.test.demo', + }, + python: { + distName: 'example-test.demo', + module: 'example_test_demo', + }, +}; diff --git a/tools/@aws-cdk/generate-examples/test/utils.test.ts b/tools/@aws-cdk/generate-examples/test/utils.test.ts new file mode 100644 index 0000000000000..51913f88869c5 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/test/utils.test.ts @@ -0,0 +1,7 @@ +import { sortBy } from '../lib/utils'; + +test('sortBy sorts successfully', () => { + const alist = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + sortBy(alist, (i) => [i*-1]); + expect(alist).toEqual([10, 9, 8, 7, 6, 5, 4, 3, 2, 1]); +}); \ No newline at end of file diff --git a/tools/@aws-cdk/generate-examples/tsconfig.json b/tools/@aws-cdk/generate-examples/tsconfig.json new file mode 100644 index 0000000000000..c321dc85b07b0 --- /dev/null +++ b/tools/@aws-cdk/generate-examples/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2018", + "module": "commonjs", + "lib": ["es2018"], + "strict": true, + "alwaysStrict": true, + "declaration": true, + "inlineSourceMap": true, + "inlineSources": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "resolveJsonModule": true, + "composite": true, + "incremental": true, + "experimentalDecorators": true + }, + "include": ["**/*.ts"] +} \ No newline at end of file diff --git a/tools/@aws-cdk/pkglint/lib/rules.ts b/tools/@aws-cdk/pkglint/lib/rules.ts index eda6436884d77..dab684878d98f 100644 --- a/tools/@aws-cdk/pkglint/lib/rules.ts +++ b/tools/@aws-cdk/pkglint/lib/rules.ts @@ -1653,7 +1653,7 @@ export class NoExperimentalDependents extends ValidationRule { ['@aws-cdk/aws-apigatewayv2-authorizers', ['@aws-cdk/aws-apigatewayv2']], ['@aws-cdk/aws-events-targets', ['@aws-cdk/aws-kinesisfirehose']], ['@aws-cdk/aws-kinesisfirehose-destinations', ['@aws-cdk/aws-kinesisfirehose']], - ['@aws-cdk/aws-iot-actions', ['@aws-cdk/aws-iot']], + ['@aws-cdk/aws-iot-actions', ['@aws-cdk/aws-iot', '@aws-cdk/aws-kinesisfirehose']], ]); private readonly excludedModules = ['@aws-cdk/cloudformation-include']; diff --git a/tools/@aws-cdk/pkglint/package.json b/tools/@aws-cdk/pkglint/package.json index c9725365adfc6..7bb152feb4287 100644 --- a/tools/@aws-cdk/pkglint/package.json +++ b/tools/@aws-cdk/pkglint/package.json @@ -47,7 +47,7 @@ "@typescript-eslint/parser": "^4.33.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^2.5.0", - "eslint-plugin-import": "^2.25.2", + "eslint-plugin-import": "^2.25.3", "eslint-plugin-jest": "^24.7.0", "eslint": "^7.32.0", "jest": "^27.3.1", diff --git a/tools/@aws-cdk/ubergen/bin/ubergen.ts b/tools/@aws-cdk/ubergen/bin/ubergen.ts index 772e6b358b403..69f146a1067e6 100644 --- a/tools/@aws-cdk/ubergen/bin/ubergen.ts +++ b/tools/@aws-cdk/ubergen/bin/ubergen.ts @@ -6,19 +6,24 @@ import * as cfnspec from '@aws-cdk/cfnspec'; import * as fs from 'fs-extra'; import * as ts from 'typescript'; -const LIB_ROOT = path.resolve(process.cwd(), 'lib'); +// The directory where our 'package.json' lives +const MONOPACKAGE_ROOT = process.cwd(); + +// The directory where we're going to collect all the libraries. Currently +// purposely the same as the monopackage root so that our two import styles +// resolve to the same files. +const LIB_ROOT = MONOPACKAGE_ROOT; + const ROOT_PATH = findWorkspacePath(); -const UBER_PACKAGE_JSON_PATH = path.resolve(process.cwd(), 'package.json'); +const UBER_PACKAGE_JSON_PATH = path.join(MONOPACKAGE_ROOT, 'package.json'); async function main() { console.log(`🌴 workspace root path is: ${ROOT_PATH}`); - const uberPackageJson = await fs.readJson(UBER_PACKAGE_JSON_PATH); - const libraries = await findLibrariesToPackage(uberPackageJson); await verifyDependencies(uberPackageJson, libraries); await prepareSourceFiles(libraries, uberPackageJson); - await combineRosettaFixtures(libraries); + await combineRosettaFixtures(libraries, uberPackageJson); } main().then( @@ -224,7 +229,10 @@ async function prepareSourceFiles(libraries: readonly LibraryReference[], packag console.log('\t 👩🏻‍🔬 \'excludeExperimentalModules\' enabled. Regenerating all experimental modules as L1s using cfn2ts...'); } - await fs.remove(LIB_ROOT); + // Should not remove collection directory if we're currently in it. The OS would be unhappy. + if (LIB_ROOT !== process.cwd()) { + await fs.remove(LIB_ROOT); + } const indexStatements = new Array(); for (const library of libraries) { @@ -246,19 +254,32 @@ async function prepareSourceFiles(libraries: readonly LibraryReference[], packag console.log('\t🍺 Success!'); } -async function combineRosettaFixtures(libraries: readonly LibraryReference[]) { +async function combineRosettaFixtures(libraries: readonly LibraryReference[], uberPackageJson: PackageJson) { console.log('📝 Combining Rosetta fixtures...'); - const uberRosettaDir = path.resolve(LIB_ROOT, '..', 'rosetta'); + const uberRosettaDir = path.resolve(MONOPACKAGE_ROOT, 'rosetta'); await fs.remove(uberRosettaDir); + await fs.mkdir(uberRosettaDir); for (const library of libraries) { const packageRosettaDir = path.join(library.root, 'rosetta'); + const uberRosettaTargetDir = library.shortName === 'core' ? uberRosettaDir : path.join(uberRosettaDir, library.shortName.replace(/-/g, '_')); if (await fs.pathExists(packageRosettaDir)) { - await fs.copy(packageRosettaDir, uberRosettaDir, { - overwrite: true, - recursive: true, - }); + if (!fs.existsSync(uberRosettaTargetDir)) { + await fs.mkdir(uberRosettaTargetDir); + } + const files = await fs.readdir(packageRosettaDir); + for (const file of files) { + await fs.writeFile( + path.join(uberRosettaTargetDir, file), + await rewriteImportsInRosettaFixtures( + path.join(packageRosettaDir, file), + libraries, + uberPackageJson.name, + ), + { encoding: 'utf8' }, + ); + } } } @@ -314,12 +335,6 @@ async function transformPackage( }, { spaces: 2 }, ); - - await fs.writeFile( - path.resolve(LIB_ROOT, '..', `${library.shortName}.ts`), - `export * from './lib/${library.shortName}';\n`, - { encoding: 'utf8' }, - ); } return true; } @@ -378,10 +393,11 @@ async function copyOrTransformFiles(from: string, to: string, libraries: readonl await fs.mkdirp(destination); return copyOrTransformFiles(source, destination, libraries, uberPackageJson); } + if (name.endsWith('.ts')) { return fs.writeFile( destination, - await rewriteImports(source, to, libraries), + await rewriteImportsInLibs(source, to, libraries), { encoding: 'utf8' }, ); } else if (name === 'cfn-types-2-classes.json') { @@ -401,7 +417,7 @@ async function copyOrTransformFiles(from: string, to: string, libraries: readonl } else if (name === 'README.md') { // Rewrite the README to both adjust imports and remove the redundant stability banner. // (All modules included in ubergen-ed packages must be stable, so the banner is unnecessary.) - const newReadme = (await rewriteReadmeImports(source)) + const newReadme = (await rewriteReadmeImports(source, uberPackageJson.name)) .replace(/[\s\S]+/gm, ''); return fs.writeFile( @@ -418,11 +434,11 @@ async function copyOrTransformFiles(from: string, to: string, libraries: readonl } /** - * Rewrites the imports in README.md from v1 ('@aws-cdk/...') to v2 ('aws-cdk-lib'). + * Rewrites the imports in README.md from v1 ('@aws-cdk/...') to v2 ('aws-cdk-lib') or monocdk ('monocdk'). * Uses the module imports (import { aws_foo as foo } from 'aws-cdk-lib') for module imports, * and "barrel" imports for types (import { Bucket } from 'aws-cdk-lib/aws-s3'). */ -async function rewriteReadmeImports(fromFile: string): Promise { +async function rewriteReadmeImports(fromFile: string, libName: string): Promise { const readmeOriginal = await fs.readFile(fromFile, { encoding: 'utf8' }); return readmeOriginal // import * as s3 from '@aws-cdk/aws-s3' @@ -434,21 +450,51 @@ async function rewriteReadmeImports(fromFile: string): Promise { function rewriteCdkImports(_match: string, prefix: string, alias: string, module: string, suffix: string): string { if (module === 'core') { - return `${prefix}import * as ${alias} from 'aws-cdk-lib';${suffix}`; + return `${prefix}import * as ${alias} from '${libName}';${suffix}`; } else { - return `${prefix}import { ${module.replace(/-/g, '_')} as ${alias} } from 'aws-cdk-lib';${suffix}`; + return `${prefix}import { ${module.replace(/-/g, '_')} as ${alias} } from '${libName}';${suffix}`; } } function rewriteCdkTypeImports(_match: string, prefix: string, types: string, module: string, suffix: string): string { if (module === 'core') { - return `${prefix}import ${types} from 'aws-cdk-lib';${suffix}`; + return `${prefix}import ${types} from '${libName}';${suffix}`; } else { - return `${prefix}import ${types} from 'aws-cdk-lib/${module}';${suffix}`; + return `${prefix}import ${types} from '${libName}/${module}';${suffix}`; } } } -async function rewriteImports(fromFile: string, targetDir: string, libraries: readonly LibraryReference[]): Promise { +/** + * Rewrites imports in libaries, using the relative path (i.e. '../../assertions'). + */ +async function rewriteImportsInLibs(fromFile: string, targetDir: string, libraries: readonly LibraryReference[]): Promise { + return rewriteImports(fromFile, libraries, (importedFile) => path.relative(targetDir, importedFile)); +} + +/** + * Rewrites imports in rosetta fixtures, using the external path (i.e. 'aws-cdk-lib/assertions'). + */ +// eslint-disable-next-line max-len +async function rewriteImportsInRosettaFixtures(fromFile: string, libraries: readonly LibraryReference[], libName: string): Promise { + return rewriteImports(fromFile, libraries, (importedFile) => externalPath(libName, importedFile)); +} + +function externalPath(libName: string, filePath: string) { + const paths = filePath.split(path.sep); + const module = paths[paths.length-1]; + if (module === 'core') { + return libName; + } else { + return path.join(libName, module); + } +} + +/** + * Rewrites imports from a file, to target the target directory, using the given libraries. + * The function `newImport` determines the rewritten file path from the given file. + */ +// eslint-disable-next-line max-len +async function rewriteImports(fromFile: string, libraries: readonly LibraryReference[], newImport: (file: string) => string): Promise { const sourceFile = ts.createSourceFile( fromFile, await fs.readFile(fromFile, { encoding: 'utf8' }), @@ -513,9 +559,9 @@ async function rewriteImports(fromFile: string, targetDir: string, libraries: re const importedFile = moduleSpecifier === sourceLibrary.packageJson.name ? path.join(LIB_ROOT, sourceLibrary.shortName) : path.join(LIB_ROOT, sourceLibrary.shortName, moduleSpecifier.substr(sourceLibrary.packageJson.name.length + 1)); - return ts.createStringLiteral( - path.relative(targetDir, importedFile), - ); + + const importFilePath = newImport(importedFile); + return ts.createStringLiteral(importFilePath); } } diff --git a/version.v1.json b/version.v1.json index e3f080f0508a7..a5a81be0d1930 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.132.0" + "version": "1.133.0" } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 73a4ba1d5067b..36d2acb3df4ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -40,9 +40,9 @@ "@babel/highlight" "^7.16.0" "@babel/compat-data@^7.16.0": - version "7.16.0" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.0.tgz#ea269d7f78deb3a7826c39a4048eecda541ebdaa" - integrity sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew== + version "7.16.4" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e" + integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q== "@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.7.5": version "7.16.0" @@ -75,13 +75,13 @@ source-map "^0.5.0" "@babel/helper-compilation-targets@^7.16.0": - version "7.16.0" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.0.tgz#01d615762e796c17952c29e3ede9d6de07d235a8" - integrity sha512-S7iaOT1SYlqK0sQaCi21RX4+13hmdmnxIEAnQUB/eh7GeAnRjOUgTYpLkUOiRXzD+yog1JxP0qyAQZ7ZxVxLVg== + version "7.16.3" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz#5b480cd13f68363df6ec4dc8ac8e2da11363cbf0" + integrity sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA== dependencies: "@babel/compat-data" "^7.16.0" "@babel/helper-validator-option" "^7.14.5" - browserslist "^4.16.6" + browserslist "^4.17.5" semver "^6.3.0" "@babel/helper-function-name@^7.16.0": @@ -182,12 +182,12 @@ integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== "@babel/helpers@^7.16.0": - version "7.16.0" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.0.tgz#875519c979c232f41adfbd43a3b0398c2e388183" - integrity sha512-dVRM0StFMdKlkt7cVcGgwD8UMaBfWJHl3A83Yfs8GQ3MO0LHIIIMvK7Fa0RGOGUQ10qikLaX6D7o5htcQWgTMQ== + version "7.16.3" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.3.tgz#27fc64f40b996e7074dc73128c3e5c3e7f55c43c" + integrity sha512-Xn8IhDlBPhvYTvgewPKawhADichOsbkZuzN7qz2BusOM0brChsyXMDJvldWaYMMUNiCQdQzNEioXTp3sC8Nt8w== dependencies: "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.0" + "@babel/traverse" "^7.16.3" "@babel/types" "^7.16.0" "@babel/highlight@^7.10.4", "@babel/highlight@^7.16.0": @@ -199,10 +199,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.0", "@babel/parser@^7.7.2": - version "7.16.2" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.16.2.tgz#3723cd5c8d8773eef96ce57ea1d9b7faaccd12ac" - integrity sha512-RUVpT0G2h6rOZwqLDTrKk7ksNv7YpAilTnYe1/Q+eDjxEceRMKVWbCsX7t8h6C1qCFi/1Y8WZjcEPBAFG27GPw== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.0", "@babel/parser@^7.16.3", "@babel/parser@^7.7.2": + version "7.16.4" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz#d5f92f57cf2c74ffe9b37981c0e72fee7311372e" + integrity sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -304,17 +304,17 @@ "@babel/parser" "^7.16.0" "@babel/types" "^7.16.0" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.16.0", "@babel/traverse@^7.7.2": - version "7.16.0" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.0.tgz#965df6c6bfc0a958c1e739284d3c9fa4a6e3c45b" - integrity sha512-qQ84jIs1aRQxaGaxSysII9TuDaguZ5yVrEuC0BN2vcPlalwfLovVmCjbFDPECPXcYM/wLvNFfp8uDOliLxIoUQ== +"@babel/traverse@^7.1.0", "@babel/traverse@^7.16.0", "@babel/traverse@^7.16.3", "@babel/traverse@^7.7.2": + version "7.16.3" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz#f63e8a938cc1b780f66d9ed3c54f532ca2d14787" + integrity sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag== dependencies: "@babel/code-frame" "^7.16.0" "@babel/generator" "^7.16.0" "@babel/helper-function-name" "^7.16.0" "@babel/helper-hoist-variables" "^7.16.0" "@babel/helper-split-export-declaration" "^7.16.0" - "@babel/parser" "^7.16.0" + "@babel/parser" "^7.16.3" "@babel/types" "^7.16.0" debug "^4.1.0" globals "^11.1.0" @@ -337,6 +337,14 @@ resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@cnakazawa/watch@^1.0.3": + version "1.0.4" + resolved "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" + integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== + dependencies: + exec-sh "^0.3.2" + minimist "^1.2.0" + "@cspotcode/source-map-consumer@0.8.0": version "0.8.0" resolved "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" @@ -404,6 +412,18 @@ resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== +"@jest/console@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" + integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^26.6.2" + jest-util "^26.6.2" + slash "^3.0.0" + "@jest/console@^27.3.1": version "27.3.1" resolved "https://registry.npmjs.org/@jest/console/-/console-27.3.1.tgz#e8ea3a475d3f8162f23d69efbfaa9cbe486bee93" @@ -416,6 +436,40 @@ jest-util "^27.3.1" slash "^3.0.0" +"@jest/core@^26.6.3": + version "26.6.3" + resolved "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" + integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== + dependencies: + "@jest/console" "^26.6.2" + "@jest/reporters" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-changed-files "^26.6.2" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-resolve-dependencies "^26.6.3" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + jest-watcher "^26.6.2" + micromatch "^4.0.2" + p-each-series "^2.1.0" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + "@jest/core@^27.3.1": version "27.3.1" resolved "https://registry.npmjs.org/@jest/core/-/core-27.3.1.tgz#04992ef1b58b17c459afb87ab56d81e63d386925" @@ -450,6 +504,16 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/environment@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" + integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== + dependencies: + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + "@jest/environment@^27.3.1": version "27.3.1" resolved "https://registry.npmjs.org/@jest/environment/-/environment-27.3.1.tgz#2182defbce8d385fd51c5e7c7050f510bd4c86b1" @@ -460,6 +524,18 @@ "@types/node" "*" jest-mock "^27.3.0" +"@jest/fake-timers@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" + integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== + dependencies: + "@jest/types" "^26.6.2" + "@sinonjs/fake-timers" "^6.0.1" + "@types/node" "*" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-util "^26.6.2" + "@jest/fake-timers@^27.3.1": version "27.3.1" resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.3.1.tgz#1fad860ee9b13034762cdb94266e95609dfce641" @@ -472,6 +548,15 @@ jest-mock "^27.3.0" jest-util "^27.3.1" +"@jest/globals@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" + integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/types" "^26.6.2" + expect "^26.6.2" + "@jest/globals@^27.3.1": version "27.3.1" resolved "https://registry.npmjs.org/@jest/globals/-/globals-27.3.1.tgz#ce1dfb03d379237a9da6c1b99ecfaca1922a5f9e" @@ -481,6 +566,38 @@ "@jest/types" "^27.2.5" expect "^27.3.1" +"@jest/reporters@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" + integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + graceful-fs "^4.2.4" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^4.0.3" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + jest-haste-map "^26.6.2" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^4.0.1" + terminal-link "^2.0.0" + v8-to-istanbul "^7.0.0" + optionalDependencies: + node-notifier "^8.0.0" + "@jest/reporters@^27.3.1": version "27.3.1" resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-27.3.1.tgz#28b5c1f5789481e23788048fa822ed15486430b9" @@ -512,6 +629,15 @@ terminal-link "^2.0.0" v8-to-istanbul "^8.1.0" +"@jest/source-map@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" + integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.4" + source-map "^0.6.0" + "@jest/source-map@^27.0.6": version "27.0.6" resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz#be9e9b93565d49b0548b86e232092491fb60551f" @@ -521,6 +647,16 @@ graceful-fs "^4.2.4" source-map "^0.6.0" +"@jest/test-result@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" + integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== + dependencies: + "@jest/console" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + "@jest/test-result@^27.3.1": version "27.3.1" resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-27.3.1.tgz#89adee8b771877c69b3b8d59f52f29dccc300194" @@ -531,6 +667,17 @@ "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" +"@jest/test-sequencer@^26.6.3": + version "26.6.3" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" + integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== + dependencies: + "@jest/test-result" "^26.6.2" + graceful-fs "^4.2.4" + jest-haste-map "^26.6.2" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + "@jest/test-sequencer@^27.3.1": version "27.3.1" resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.3.1.tgz#4b3bde2dbb05ee74afdae608cf0768e3354683b1" @@ -541,6 +688,27 @@ jest-haste-map "^27.3.1" jest-runtime "^27.3.1" +"@jest/transform@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" + integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^26.6.2" + babel-plugin-istanbul "^6.0.0" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.4" + jest-haste-map "^26.6.2" + jest-regex-util "^26.0.0" + jest-util "^26.6.2" + micromatch "^4.0.2" + pirates "^4.0.1" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + "@jest/transform@^27.3.1": version "27.3.1" resolved "https://registry.npmjs.org/@jest/transform/-/transform-27.3.1.tgz#ff80eafbeabe811e9025e4b6f452126718455220" @@ -562,6 +730,17 @@ source-map "^0.6.1" write-file-atomic "^3.0.0" +"@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + "@jest/types@^27.2.5": version "27.2.5" resolved "https://registry.npmjs.org/@jest/types/-/types-27.2.5.tgz#420765c052605e75686982d24b061b4cbba22132" @@ -573,18 +752,26 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" -"@jsii/check-node@1.42.0": - version "1.42.0" - resolved "https://registry.npmjs.org/@jsii/check-node/-/check-node-1.42.0.tgz#10dd84fbefa020344c9574079361c1a18754872a" - integrity sha512-URX4s0iOmuxbERL2rO10JlwedYbAT/3vM2HqswgjtJUbZTFgHsmg+Tzh3JglJzKuCg8Xm4m6CP4UlFMPqPRcqA== +"@jsii/check-node@1.44.2": + version "1.44.2" + resolved "https://registry.npmjs.org/@jsii/check-node/-/check-node-1.44.2.tgz#d7786e7ca739cc9a5cd2cd3f1b93c4375ff884e8" + integrity sha512-rVwrKXkuV4qmo0TmPbYMAu2SCC80xPDzY7cS+TDx80wfU5Dcr66lhpUW04hWcYwrVsUYXxtEYLxAbzeNYeJeoA== + dependencies: + chalk "^4.1.2" + semver "^7.3.5" + +"@jsii/check-node@1.45.0": + version "1.45.0" + resolved "https://registry.npmjs.org/@jsii/check-node/-/check-node-1.45.0.tgz#1a679806e0def800a1265bfd4b459c5ed39f36b2" + integrity sha512-YtB4EEnlVe2jSmnyD7PAh8TaY6JORhTsZm+p6/p4t997cA9tG9/dyeVMpRA5dtmfLUpBFOOkZdV1+qxa53crwQ== dependencies: chalk "^4.1.2" semver "^7.3.5" -"@jsii/spec@^1.42.0": - version "1.42.0" - resolved "https://registry.npmjs.org/@jsii/spec/-/spec-1.42.0.tgz#39e787e5ac2ddc96256b73421603bc734c56f7d8" - integrity sha512-SS2Q1Ds/yiTejd/0KO5lC6SUGqlfjuqZ6nAxJxLU76JQ99v1spRJeS7oi/2OW+ZmTEwBy81DgjOxA8bwUc0U/Q== +"@jsii/spec@1.45.0", "@jsii/spec@^1.45.0": + version "1.45.0" + resolved "https://registry.npmjs.org/@jsii/spec/-/spec-1.45.0.tgz#94a1ade6f2792d3d0ffc9e8722eb3d4e0a640c2a" + integrity sha512-+hLFh08nKhUep1UIaKDHi2ywM7SipCZX5BBf/xJ3db+P9qnN4dAu/H46mbOrdQGChZx9OwSuLWm5xEYZQ52efA== dependencies: jsonschema "^1.4.0" @@ -1592,7 +1779,7 @@ resolved "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.85.tgz#26cd76897b1972247cbc1a34b6f21d023e987437" integrity sha512-cMRXVxb+NMb6EekKel1fPBfz2ZqE5cGhIS14G7FVUM4Bqilx0lHKnZbsDLWLSeckDpkvlp5six2F7UWyEEJSoQ== -"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.7": version "7.1.16" resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz#bc12c74b7d65e82d29876b5d0baf5c625ac58702" integrity sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ== @@ -1630,10 +1817,10 @@ resolved "https://registry.npmjs.org/@types/changelog-parser/-/changelog-parser-2.7.1.tgz#da124373fc8abfb6951fef83718ea5f041fea527" integrity sha512-OFZB7OlG6nrkcnvJhcyV2Zm/PUGk40oHyfaEBRjlm+ghrKxbFQI+xao/IzYL0G72fpLCTGGs3USrhe38/FF6QQ== -"@types/eslint@^7.28.2": - version "7.28.2" - resolved "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.2.tgz#0ff2947cdd305897c52d5372294e8c76f351db68" - integrity sha512-KubbADPkfoU75KgKeKLsFHXnU4ipH7wYg0TRT33NK3N3yiu7jlFAAoygIWBV+KbuHx/G+AvuGX6DllnK35gfJA== +"@types/eslint@^7.29.0": + version "7.29.0" + resolved "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz#e56ddc8e542815272720bb0b4ccc2aff9c3e1c78" + integrity sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -1691,6 +1878,14 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/jest@^26.0.24": + version "26.0.24" + resolved "https://registry.npmjs.org/@types/jest/-/jest-26.0.24.tgz#943d11976b16739185913a1936e0de0c4a7d595a" + integrity sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w== + dependencies: + jest-diff "^26.0.0" + pretty-format "^26.0.0" + "@types/jest@^27.0.2": version "27.0.2" resolved "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz#ac383c4d4aaddd29bbf2b916d8d105c304a5fcd7" @@ -1709,10 +1904,10 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= -"@types/lodash@^4.14.176": - version "4.14.176" - resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.176.tgz#641150fc1cda36fbfa329de603bbb175d7ee20c0" - integrity sha512-xZmuPTa3rlZoIbtDUyJKZQimJV3bxCmzMIO2c9Pz9afyDro6kr7R79GwcB6mRhuoPmV2p1Vb66WOJH7F886WKQ== +"@types/lodash@^4.14.177": + version "4.14.177" + resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.177.tgz#f70c0d19c30fab101cad46b52be60363c43c4578" + integrity sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw== "@types/md5@^2.3.1": version "2.3.1" @@ -1749,9 +1944,9 @@ integrity sha512-uv53RrNdhbkV/3VmVCtfImfYCWC3GTTRn3R11Whni3EJ+gb178tkZBVNj2edLY5CMrB749dQi+SJkg87jsN8UQ== "@types/node@*", "@types/node@>= 8", "@types/node@^16.9.2": - version "16.11.6" - resolved "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" - integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== + version "16.11.7" + resolved "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42" + integrity sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw== "@types/node@^10.17.60": version "10.17.60" @@ -1768,10 +1963,10 @@ resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/prettier@^2.1.5": - version "2.4.1" - resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.1.tgz#e1303048d5389563e130f5bdd89d37a99acb75eb" - integrity sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw== +"@types/prettier@^2.0.0", "@types/prettier@^2.1.5": + version "2.4.2" + resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.2.tgz#4c62fae93eb479660c3bd93f9d24d561597a8281" + integrity sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA== "@types/promptly@^3.0.2": version "3.0.2" @@ -1803,9 +1998,9 @@ "@types/sinonjs__fake-timers" "*" "@types/sinonjs__fake-timers@*": - version "6.0.4" - resolved "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.4.tgz#0ecc1b9259b76598ef01942f547904ce61a6a77d" - integrity sha512-IFQTJARgMUBF+xVd2b+hIgXWrZEjND3vJtRCvIelcFB5SIXfjV4bOHbHJ0eXKh+0COrBRc8MqteKAz/j88rE0A== + version "8.1.0" + resolved "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.0.tgz#443f49b74f1f57e20d9abac7e18b59e9ee98c5cf" + integrity sha512-TZ3vsL7wvXRNTRehor/zKtyWX9Ew3TrT20QQHPx+rieOJivRntZntWhUu1/qKnC8FK4q++RiEl/kje+PAVHhfg== "@types/stack-utils@^2.0.0": version "2.0.1" @@ -1855,7 +2050,7 @@ resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw== -"@types/yargs@^15.0.14": +"@types/yargs@^15.0.0", "@types/yargs@^15.0.14": version "15.0.14" resolved "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== @@ -2045,9 +2240,9 @@ ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: uri-js "^4.2.2" ajv@^8.0.1: - version "8.6.3" - resolved "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz#11a66527761dc3e9a3845ea775d2d3c0414e8764" - integrity sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw== + version "8.8.1" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.8.1.tgz#e73dd88eeb4b10bbcd82bee136e6fbe801664d18" + integrity sha512-6CiMNDrzv0ZR916u2T+iRunnD60uWmNn8SkdB44/6stVORUg0aAkWO7PkOhpCmjmW8f2I/G/xnowD66fxGyQJg== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -2071,7 +2266,7 @@ ansi-regex@^2.0.0: resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= -ansi-regex@^5.0.1: +ansi-regex@^5.0.0, ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== @@ -2095,6 +2290,14 @@ ansi-styles@^5.0.0: resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -2184,6 +2387,21 @@ argparse@^2.0.1: resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + array-differ@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" @@ -2210,6 +2428,11 @@ array-union@^2.1.0: resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + array.prototype.flat@^1.2.5: version "1.2.5" resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13" @@ -2246,6 +2469,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + ast-types@^0.13.2: version "0.13.4" resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" @@ -2278,6 +2506,11 @@ atob-lite@^2.0.0: resolved "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696" integrity sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY= +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" @@ -2293,9 +2526,9 @@ aws-sdk-mock@^5.4.0: traverse "^0.6.6" aws-sdk@^2.596.0, aws-sdk@^2.848.0, aws-sdk@^2.928.0, aws-sdk@^2.979.0: - version "2.1023.0" - resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1023.0.tgz#0de16e4e8878ccec4fcd0146322dcf94fdbe09ba" - integrity sha512-RAI8sUfK+00yL9i3xz5kbM3+t/0mjjnKhKyauXAlJN4seDYtIX5+BqMghpkZwvLBdi6idXIuz+FHWETHZccyuA== + version "2.1030.0" + resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1030.0.tgz#24a856af3d2b8b37c14a8f59974993661c66fd82" + integrity sha512-to0STOb8DsSGuSsUb/WCbg/UFnMGfIYavnJH5ZlRCHzvCFjTyR+vfE8ku+qIZvfFM4+5MNTQC/Oxfun2X/TuyA== dependencies: buffer "4.9.2" events "1.1.1" @@ -2324,6 +2557,20 @@ axios@^0.21.1: dependencies: follow-redirects "^1.14.0" +babel-jest@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" + integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== + dependencies: + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/babel__core" "^7.1.7" + babel-plugin-istanbul "^6.0.0" + babel-preset-jest "^26.6.2" + chalk "^4.0.0" + graceful-fs "^4.2.4" + slash "^3.0.0" + babel-jest@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-27.3.1.tgz#0636a3404c68e07001e434ac4956d82da8a80022" @@ -2349,6 +2596,16 @@ babel-plugin-istanbul@^6.0.0: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" +babel-plugin-jest-hoist@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" + integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" + "@types/babel__traverse" "^7.0.6" + babel-plugin-jest-hoist@^27.2.0: version "27.2.0" resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.2.0.tgz#79f37d43f7e5c4fdc4b2ca3e10cc6cf545626277" @@ -2377,6 +2634,14 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" +babel-preset-jest@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" + integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== + dependencies: + babel-plugin-jest-hoist "^26.6.2" + babel-preset-current-node-syntax "^1.0.0" + babel-preset-jest@^27.2.0: version "27.2.0" resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.2.0.tgz#556bbbf340608fed5670ab0ea0c8ef2449fba885" @@ -2395,6 +2660,19 @@ base64-js@^1.0.2, base64-js@^1.3.1: resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +base@^0.11.1: + version "0.11.2" + resolved "https://registry.npmjs.org/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -2434,6 +2712,22 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +braces@^2.3.1: + version "2.3.2" + resolved "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -2446,13 +2740,13 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.16.6: - version "4.17.6" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.17.6.tgz#c76be33e7786b497f66cad25a73756c8b938985d" - integrity sha512-uPgz3vyRTlEiCv4ee9KlsKgo2V6qPk7Jsn0KAn2OBqbqKo3iNcPEC1Ti6J4dwnz+aIRfEEEuOzC9IBk8tXUomw== +browserslist@^4.17.5: + version "4.18.1" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.18.1.tgz#60d3920f25b6860eb917c6c7b185576f4d8b017f" + integrity sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ== dependencies: - caniuse-lite "^1.0.30001274" - electron-to-chromium "^1.3.886" + caniuse-lite "^1.0.30001280" + electron-to-chromium "^1.3.896" escalade "^3.1.1" node-releases "^2.0.1" picocolors "^1.0.0" @@ -2518,10 +2812,10 @@ byte-size@^7.0.0: resolved "https://registry.npmjs.org/byte-size/-/byte-size-7.0.1.tgz#b1daf3386de7ab9d706b941a748dbfc71130dee3" integrity sha512-crQdqyCwhokxwV1UyDzLZanhkugAgft7vt0qbbdt60C6Zf3CAiGmtUCylbtYwrU6loOUw3euGrNtW1J651ot1A== -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +bytes@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" + integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg== cacache@^15.0.5, cacache@^15.2.0: version "15.3.0" @@ -2547,6 +2841,21 @@ cacache@^15.0.5, cacache@^15.2.0: tar "^6.0.2" unique-filename "^1.1.1" +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + caching-transform@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" @@ -2584,15 +2893,22 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.2.0: - version "6.2.0" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" - integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== +camelcase@^6.0.0, camelcase@^6.2.0, camelcase@^6.2.1: + version "6.2.1" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz#250fd350cfd555d0d2160b1d51510eaf8326e86e" + integrity sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA== -caniuse-lite@^1.0.30001274: - version "1.0.30001278" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001278.tgz#51cafc858df77d966b17f59b5839250b24417fff" - integrity sha512-mpF9KeH8u5cMoEmIic/cr7PNS+F5LWBk0t2ekGT60lFf0Wq+n9LspAj0g3P+o7DQhD3sUdlMln4YFAWhFYn9jg== +caniuse-lite@^1.0.30001280: + version "1.0.30001282" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001282.tgz#38c781ee0a90ccfe1fe7fefd00e43f5ffdcb96fd" + integrity sha512-YhF/hG6nqBEllymSIjLtR2iWDDnChvhnVJqp+vloyt2tEHFG1yBR+ac2B/rOw0qOK0m0lEXU2dv4E/sMk5P9Kg== + +capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== + dependencies: + rsvp "^4.8.4" case@1.6.3, case@^1.6.3: version "1.6.3" @@ -2695,11 +3011,26 @@ ci-info@^3.2.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== +cjs-module-lexer@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" + integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== + cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -2773,10 +3104,19 @@ co@^4.6.0: resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= -codemaker@^1.42.0: - version "1.42.0" - resolved "https://registry.npmjs.org/codemaker/-/codemaker-1.42.0.tgz#dab2ae818e424803d7aa5f837838d7ec5f1dab4b" - integrity sha512-pjLw1YeWKdY09tDmr6HmeZCGd6G+Ku1UP3cK/oX79x5iEL2ZEm8kJrGQisasK6pk/Er75sDZA86c5Cn7sIx4GQ== +codemaker@^1.44.2: + version "1.44.2" + resolved "https://registry.npmjs.org/codemaker/-/codemaker-1.44.2.tgz#620b093f36d3fc989776abe2b8f1fed8cabcb7c4" + integrity sha512-yS9//oDu07/TXyIsuQRqfqzB8z9Z9hq9sz/kxK5+ibUDiMr4q/k9HDn9UuK+OZPo0wisffYJCxe/9UjrETiy9Q== + dependencies: + camelcase "^6.2.0" + decamelize "^5.0.1" + fs-extra "^9.1.0" + +codemaker@^1.45.0: + version "1.45.0" + resolved "https://registry.npmjs.org/codemaker/-/codemaker-1.45.0.tgz#e10e356ad477aaaf7f883d54520c23f1f3691ef3" + integrity sha512-PsMKXTuIphccwZah2qPg0yw6HisDotg54ctHX/52xk9v9kSKbPuIhNSUonYyuEmfPigjYqVdkEpyoIKooah9kA== dependencies: camelcase "^6.2.0" decamelize "^5.0.1" @@ -2787,6 +3127,14 @@ collect-v8-coverage@^1.0.0: resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -2859,6 +3207,11 @@ compare-func@^2.0.0: array-ify "^1.0.0" dot-prop "^5.1.0" +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + compress-commons@^4.1.0: version "4.1.1" resolved "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" @@ -3099,6 +3452,11 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + core-util-is@1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -3231,7 +3589,7 @@ debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: dependencies: ms "2.1.2" -debug@^2.2.0, debug@^2.6.9: +debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -3340,6 +3698,28 @@ define-properties@^1.1.3: dependencies: object-keys "^1.0.12" +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + degenerator@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/degenerator/-/degenerator-3.0.1.tgz#7ef78ec0c8577a544477308ddf1d2d6e88d51f5b" @@ -3403,6 +3783,11 @@ dezalgo@^1.0.0: asap "^2.0.0" wrappy "1" +diff-sequences@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" + integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== + diff-sequences@^27.0.6: version "27.0.6" resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz#3305cb2e55a033924054695cc66019fd7f8e5723" @@ -3505,10 +3890,15 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -electron-to-chromium@^1.3.886: - version "1.3.890" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.890.tgz#e7143b659f73dc4d0512d1ae4baeb0fb9e7bc835" - integrity sha512-VWlVXSkv0cA/OOehrEyqjUTHwV8YXCPTfPvbtoeU2aHR21vI4Ejh5aC4AxUwOmbLbBgb6Gd3URZahoCxtBqCYQ== +electron-to-chromium@^1.3.896: + version "1.3.900" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.900.tgz#5be2c5818a2a012c511b4b43e87b6ab7a296d4f5" + integrity sha512-SuXbQD8D4EjsaBaJJxySHbC+zq8JrFfxtb4GIr4E9n1BcROyMcRrJCYQNpJ9N+Wjf5mFp7Wp0OHykd14JNEzzQ== + +emittery@^0.7.1: + version "0.7.2" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" + integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== emittery@^0.8.1: version "0.8.1" @@ -3632,113 +4022,113 @@ es6-error@^4.0.1: resolved "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -esbuild-android-arm64@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.13.12.tgz#e1f199dc05405cdc6670c00fb6c793822bf8ae4c" - integrity sha512-TSVZVrb4EIXz6KaYjXfTzPyyRpXV5zgYIADXtQsIenjZ78myvDGaPi11o4ZSaHIwFHsuwkB6ne5SZRBwAQ7maw== - -esbuild-darwin-64@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.13.12.tgz#f5c59e622955c01f050e5a7ac9c1d41db714b94d" - integrity sha512-c51C+N+UHySoV2lgfWSwwmlnLnL0JWj/LzuZt9Ltk9ub1s2Y8cr6SQV5W3mqVH1egUceew6KZ8GyI4nwu+fhsw== - -esbuild-darwin-arm64@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.12.tgz#8abae74c2956a8aa568fc52c78829338c4a4b988" - integrity sha512-JvAMtshP45Hd8A8wOzjkY1xAnTKTYuP/QUaKp5eUQGX+76GIie3fCdUUr2ZEKdvpSImNqxiZSIMziEiGB5oUmQ== - -esbuild-freebsd-64@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.12.tgz#6ad2ab8c0364ee7dd2d6e324d876a8e60ae75d12" - integrity sha512-r6On/Skv9f0ZjTu6PW5o7pdXr8aOgtFOEURJZYf1XAJs0IQ+gW+o1DzXjVkIoT+n1cm3N/t1KRJfX71MPg/ZUA== - -esbuild-freebsd-arm64@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.12.tgz#6f38155f4c300ac4c8adde1fde3cc6a4440a8294" - integrity sha512-F6LmI2Q1gii073kmBE3NOTt/6zLL5zvZsxNLF8PMAwdHc+iBhD1vzfI8uQZMJA1IgXa3ocr3L3DJH9fLGXy6Yw== - -esbuild-linux-32@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.13.12.tgz#b1d15e330188a8c21de75c3f0058628a3eefade7" - integrity sha512-U1UZwG3UIwF7/V4tCVAo/nkBV9ag5KJiJTt+gaCmLVWH3bPLX7y+fNlhIWZy8raTMnXhMKfaTvWZ9TtmXzvkuQ== - -esbuild-linux-64@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.13.12.tgz#25bd64b66162b02348e32d8f12e4c9ee61f1d070" - integrity sha512-YpXSwtu2NxN3N4ifJxEdsgd6Q5d8LYqskrAwjmoCT6yQnEHJSF5uWcxv783HWN7lnGpJi9KUtDvYsnMdyGw71Q== - -esbuild-linux-arm64@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.12.tgz#ba582298457cc5c9ac823a275de117620c06537f" - integrity sha512-sgDNb8kb3BVodtAlcFGgwk+43KFCYjnFOaOfJibXnnIojNWuJHpL6aQJ4mumzNWw8Rt1xEtDQyuGK9f+Y24jGA== - -esbuild-linux-arm@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.13.12.tgz#6bc81c957bff22725688cc6359c29a25765be09b" - integrity sha512-SyiT/JKxU6J+DY2qUiSLZJqCAftIt3uoGejZ0HDnUM2MGJqEGSGh7p1ecVL2gna3PxS4P+j6WAehCwgkBPXNIw== - -esbuild-linux-mips64le@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.12.tgz#ef3c4aba3e585d847cbade5945a8b4a5c62c7ce2" - integrity sha512-qQJHlZBG+QwVIA8AbTEtbvF084QgDi4DaUsUnA+EolY1bxrG+UyOuGflM2ZritGhfS/k7THFjJbjH2wIeoKA2g== - -esbuild-linux-ppc64le@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.12.tgz#a21fb64e80c38bef06122e48283990fc6db578e1" - integrity sha512-2dSnm1ldL7Lppwlo04CGQUpwNn5hGqXI38OzaoPOkRsBRWFBozyGxTFSee/zHFS+Pdh3b28JJbRK3owrrRgWNw== - -esbuild-netbsd-64@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.12.tgz#1ea7fc8cfce88a20a4047b867ef184049a6641ae" - integrity sha512-D4raxr02dcRiQNbxOLzpqBzcJNFAdsDNxjUbKkDMZBkL54Z0vZh4LRndycdZAMcIdizC/l/Yp/ZsBdAFxc5nbA== - -esbuild-openbsd-64@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.12.tgz#adde32f2f1b05dc4bd4fc544d6ea5a4379f9ca4d" - integrity sha512-KuLCmYMb2kh05QuPJ+va60bKIH5wHL8ypDkmpy47lzwmdxNsuySeCMHuTv5o2Af1RUn5KLO5ZxaZeq4GEY7DaQ== - -esbuild-sunos-64@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.13.12.tgz#a7ecaf52b7364fbee76dc8aa707fa3e1cff3342c" - integrity sha512-jBsF+e0woK3miKI8ufGWKG3o3rY9DpHvCVRn5eburMIIE+2c+y3IZ1srsthKyKI6kkXLvV4Cf/E7w56kLipMXw== - -esbuild-windows-32@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.13.12.tgz#a8756033dc905c4b7bea19be69f7ee68809f8770" - integrity sha512-L9m4lLFQrFeR7F+eLZXG82SbXZfUhyfu6CexZEil6vm+lc7GDCE0Q8DiNutkpzjv1+RAbIGVva9muItQ7HVTkQ== - -esbuild-windows-64@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.13.12.tgz#ae694aa66ca078acb8509b2da31197ed1f40f798" - integrity sha512-k4tX4uJlSbSkfs78W5d9+I9gpd+7N95W7H2bgOMFPsYREVJs31+Q2gLLHlsnlY95zBoPQMIzHooUIsixQIBjaQ== - -esbuild-windows-arm64@0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.12.tgz#782c5a8bd6d717ea55aaafe648f9926ca36a4a88" - integrity sha512-2tTv/BpYRIvuwHpp2M960nG7uvL+d78LFW/ikPItO+2GfK51CswIKSetSpDii+cjz8e9iSPgs+BU4o8nWICBwQ== - -esbuild@^0.13.12: - version "0.13.12" - resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.13.12.tgz#9cac641594bf03cf34145258c093d743ebbde7ca" - integrity sha512-vTKKUt+yoz61U/BbrnmlG9XIjwpdIxmHB8DlPR0AAW6OdS+nBQBci6LUHU2q9WbBobMEIQxxDpKbkmOGYvxsow== +esbuild-android-arm64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.13.14.tgz#c85083ece26be3d67e6c720e088968a98409e023" + integrity sha512-Q+Xhfp827r+ma8/DJgpMRUbDZfefsk13oePFEXEIJ4gxFbNv5+vyiYXYuKm43/+++EJXpnaYmEnu4hAKbAWYbA== + +esbuild-darwin-64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.13.14.tgz#8e4e237ad847cc54a1d3a5caee26a746b9f0b81f" + integrity sha512-YmOhRns6QBNSjpVdTahi/yZ8dscx9ai7a6OY6z5ACgOuQuaQ2Qk2qgJ0/siZ6LgD0gJFMV8UINFV5oky5TFNQQ== + +esbuild-darwin-arm64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.14.tgz#b3b5ebd40b2cb06ee0f6fb342dd4bdcca54ad273" + integrity sha512-Lp00VTli2jqZghSa68fx3fEFCPsO1hK59RMo1PRap5RUjhf55OmaZTZYnCDI0FVlCtt+gBwX5qwFt4lc6tI1xg== + +esbuild-freebsd-64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.14.tgz#175ecb2fa8141428cf70ea2d5f4c27534bad53e0" + integrity sha512-BKosI3jtvTfnmsCW37B1TyxMUjkRWKqopR0CE9AF2ratdpkxdR24Vpe3gLKNyWiZ7BE96/SO5/YfhbPUzY8wKw== + +esbuild-freebsd-arm64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.14.tgz#a7d64e41d1fa581f8db7775e5200f18e67d70c4d" + integrity sha512-yd2uh0yf+fWv5114+SYTl4/1oDWtr4nN5Op+PGxAkMqHfYfLjFKpcxwCo/QOS/0NWqPVE8O41IYZlFhbEN2B8Q== + +esbuild-linux-32@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.13.14.tgz#14bdd4f6b6cfd35c65c835894651ba335c2117da" + integrity sha512-a8rOnS1oWSfkkYWXoD2yXNV4BdbDKA7PNVQ1klqkY9SoSApL7io66w5H44mTLsfyw7G6Z2vLlaLI2nz9MMAowA== + +esbuild-linux-64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.13.14.tgz#7fd56851b2982fdd0cd8447ee9858c2c5711708a" + integrity sha512-yPZSoMs9W2MC3Dw+6kflKt5FfQm6Dicex9dGIr1OlHRsn3Hm7yGMUTctlkW53KknnZdOdcdd5upxvbxqymczVQ== + +esbuild-linux-arm64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.14.tgz#a55634d70679ba509adeafd68eebb9fd1ec5af6c" + integrity sha512-Lvo391ln9PzC334e+jJ2S0Rt0cxP47eoH5gFyv/E8HhOnEJTvm7A+RRnMjjHnejELacTTfYgFGQYPjLsi/jObQ== + +esbuild-linux-arm@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.13.14.tgz#bb96a99677e608b31ff61f37564326d38e846ca2" + integrity sha512-8chZE4pkKRvJ/M/iwsNQ1KqsRg2RyU5eT/x2flNt/f8F2TVrDreR7I0HEeCR50wLla3B1C3wTIOzQBmjuc6uWg== + +esbuild-linux-mips64le@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.14.tgz#6a55362a8fd1e593dea2ecc41877beed8b8184b9" + integrity sha512-MZhgxbmrWbpY3TOE029O6l5tokG9+Yoj2hW7vdit/d/VnmneqeGrSHADuDL6qXM8L5jaCiaivb4VhsyVCpdAbQ== + +esbuild-linux-ppc64le@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.14.tgz#9e0048587ece0a7f184ab147f20d077098045e7f" + integrity sha512-un7KMwS7fX1Un6BjfSZxTT8L5cV/8Uf4SAhM7WYy2XF8o8TI+uRxxD03svZnRNIPsN2J5cl6qV4n7Iwz+yhhVw== + +esbuild-netbsd-64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.14.tgz#dcab16a4bbcfa16e2e8535dadc5f64fdc891c63b" + integrity sha512-5ekKx/YbOmmlTeNxBjh38Uh5TGn5C4uyqN17i67k18pS3J+U2hTVD7rCxcFcRS1AjNWumkVL3jWqYXadFwMS0Q== + +esbuild-openbsd-64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.14.tgz#3c7453b155ebb68dc34d5aec3bd6505337bdda08" + integrity sha512-9bzvwewHjct2Cv5XcVoE1yW5YTW12Sk838EYfA46abgnhxGoFSD1mFcaztp5HHC43AsF+hQxbSFG/RilONARUA== + +esbuild-sunos-64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.13.14.tgz#85addf5fef6b5db154a955d4f2e88953359d75ce" + integrity sha512-mjMrZB76M6FmoiTvj/RGWilrioR7gVwtFBRVugr9qLarXMIU1W/pQx+ieEOtflrW61xo8w1fcxyHsVVGRvoQ0w== + +esbuild-windows-32@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.13.14.tgz#f77f98f30a5c636c44db2428ecdf9bcbbaedb1a7" + integrity sha512-GZa6mrx2rgfbH/5uHg0Rdw50TuOKbdoKCpEBitzmG5tsXBdce+cOL+iFO5joZc6fDVCLW3Y6tjxmSXRk/v20Hg== + +esbuild-windows-64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.13.14.tgz#bc778674c40d65150d12385e0f23eb3a0badbd0d" + integrity sha512-Lsgqah24bT7ClHjLp/Pj3A9wxjhIAJyWQcrOV4jqXAFikmrp2CspA8IkJgw7HFjx6QrJuhpcKVbCAe/xw0i2yw== + +esbuild-windows-arm64@0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.14.tgz#91a8dad35ab2c4dd27cd83860742955b25a354d7" + integrity sha512-KP8FHVlWGhM7nzYtURsGnskXb/cBCPTfj0gOKfjKq2tHtYnhDZywsUG57nk7TKhhK0fL11LcejHG3LRW9RF/9A== + +esbuild@^0.13.14: + version "0.13.14" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.13.14.tgz#98a3f7f42809abdc2b57c84565d0f713382dc1a5" + integrity sha512-xu4D+1ji9x53ocuomcY+KOrwAnWzhBu/wTEjpdgZ8I1c8i5vboYIeigMdzgY1UowYBKa2vZgVgUB32bu7gkxeg== optionalDependencies: - esbuild-android-arm64 "0.13.12" - esbuild-darwin-64 "0.13.12" - esbuild-darwin-arm64 "0.13.12" - esbuild-freebsd-64 "0.13.12" - esbuild-freebsd-arm64 "0.13.12" - esbuild-linux-32 "0.13.12" - esbuild-linux-64 "0.13.12" - esbuild-linux-arm "0.13.12" - esbuild-linux-arm64 "0.13.12" - esbuild-linux-mips64le "0.13.12" - esbuild-linux-ppc64le "0.13.12" - esbuild-netbsd-64 "0.13.12" - esbuild-openbsd-64 "0.13.12" - esbuild-sunos-64 "0.13.12" - esbuild-windows-32 "0.13.12" - esbuild-windows-64 "0.13.12" - esbuild-windows-arm64 "0.13.12" + esbuild-android-arm64 "0.13.14" + esbuild-darwin-64 "0.13.14" + esbuild-darwin-arm64 "0.13.14" + esbuild-freebsd-64 "0.13.14" + esbuild-freebsd-arm64 "0.13.14" + esbuild-linux-32 "0.13.14" + esbuild-linux-64 "0.13.14" + esbuild-linux-arm "0.13.14" + esbuild-linux-arm64 "0.13.14" + esbuild-linux-mips64le "0.13.14" + esbuild-linux-ppc64le "0.13.14" + esbuild-netbsd-64 "0.13.14" + esbuild-openbsd-64 "0.13.14" + esbuild-sunos-64 "0.13.14" + esbuild-windows-32 "0.13.14" + esbuild-windows-64 "0.13.14" + esbuild-windows-arm64 "0.13.14" escalade@^3.1.1: version "3.1.1" @@ -3808,7 +4198,7 @@ eslint-import-resolver-typescript@^2.5.0: resolve "^1.20.0" tsconfig-paths "^3.9.0" -eslint-module-utils@^2.7.0: +eslint-module-utils@^2.7.1: version "2.7.1" resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c" integrity sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ== @@ -3825,19 +4215,19 @@ eslint-plugin-es@^3.0.0: eslint-utils "^2.0.0" regexpp "^3.0.0" -eslint-plugin-import@^2.25.2: - version "2.25.2" - resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.2.tgz#b3b9160efddb702fc1636659e71ba1d10adbe9e9" - integrity sha512-qCwQr9TYfoBHOFcVGKY9C9unq05uOxxdklmBXLVvcwo68y5Hta6/GzCZEMx2zQiu0woKNEER0LE7ZgaOfBU14g== +eslint-plugin-import@^2.25.3: + version "2.25.3" + resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz#a554b5f66e08fb4f6dc99221866e57cfff824766" + integrity sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg== dependencies: array-includes "^3.1.4" array.prototype.flat "^1.2.5" debug "^2.6.9" doctrine "^2.1.0" eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.0" + eslint-module-utils "^2.7.1" has "^1.0.3" - is-core-module "^2.7.0" + is-core-module "^2.8.0" is-glob "^4.0.3" minimatch "^3.0.4" object.values "^1.1.5" @@ -4009,6 +4399,11 @@ events@1.1.1: resolved "https://registry.npmjs.org/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= +exec-sh@^0.3.2: + version "0.3.6" + resolved "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" + integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== + execa@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" @@ -4022,6 +4417,21 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execa@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + execa@^5.0.0: version "5.1.1" resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -4047,6 +4457,31 @@ exit@^0.1.2: resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expect@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" + integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== + dependencies: + "@jest/types" "^26.6.2" + ansi-styles "^4.0.0" + jest-get-type "^26.3.0" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-regex-util "^26.0.0" + expect@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/expect/-/expect-27.3.1.tgz#d0f170b1f5c8a2009bab0beffd4bb94f043e38e7" @@ -4059,6 +4494,21 @@ expect@^27.3.1: jest-message-util "^27.3.1" jest-regex-util "^27.0.6" +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + extend@~3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -4073,6 +4523,20 @@ external-editor@^3.0.3: iconv-lite "^0.4.24" tmp "^0.0.33" +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -4174,6 +4638,16 @@ fill-keys@^1.0.2: is-object "~1.0.1" merge-descriptors "~1.0.0" +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -4246,15 +4720,20 @@ flatted@^2.0.1: integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== flatted@^3.1.0: - version "3.2.2" - resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" - integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== + version "3.2.4" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" + integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== follow-redirects@^1.11.0, follow-redirects@^1.14.0: version "1.14.5" resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381" integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA== +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + foreach@^2.0.5: version "2.0.5" resolved "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" @@ -4291,6 +4770,13 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + fromentries@^1.2.0: version "1.3.2" resolved "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" @@ -4355,7 +4841,7 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^2.3.2, fsevents@~2.3.2: +fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -4443,6 +4929,13 @@ get-stream@^4.0.0: dependencies: pump "^3.0.0" +get-stream@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-stream@^6.0.0: version "6.0.1" resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -4468,6 +4961,11 @@ get-uri@3: fs-extra "^8.1.0" ftp "^0.3.10" +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + getpass@^0.1.1: version "0.1.7" resolved "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -4582,6 +5080,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= + handlebars@^4.7.6: version "4.7.7" resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" @@ -4644,6 +5147,37 @@ has-unicode@^2.0.0, has-unicode@^2.0.1: resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + has@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -4693,16 +5227,16 @@ http-cache-semantics@^4.1.0: resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== -http-errors@1.7.3: - version "1.7.3" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== +http-errors@1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" + integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== dependencies: depd "~1.1.2" inherits "2.0.4" - setprototypeof "1.1.1" + setprototypeof "1.2.0" statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" + toidentifier "1.0.1" http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1: version "4.0.1" @@ -4730,6 +5264,11 @@ https-proxy-agent@5, https-proxy-agent@^5.0.0: agent-base "6" debug "4" +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -4888,6 +5427,20 @@ ip@^1.1.5: resolved "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + is-arguments@^1.0.4, is-arguments@^1.1.0: version "1.1.1" resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" @@ -4923,7 +5476,7 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-buffer@~1.1.6: +is-buffer@^1.1.5, is-buffer@~1.1.6: version "1.1.6" resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -4940,13 +5493,27 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.7.0: +is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.0: version "2.8.0" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== dependencies: has "^1.0.3" +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + is-date-object@^1.0.1, is-date-object@^1.0.2: version "1.0.5" resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" @@ -4954,11 +5521,41 @@ is-date-object@^1.0.1, is-date-object@^1.0.2: dependencies: has-tostringtag "^1.0.0" +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + is-docker@^2.0.0: version "2.2.1" resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -5003,6 +5600,13 @@ is-number-object@^1.0.4: dependencies: has-tostringtag "^1.0.0" +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -5028,7 +5632,7 @@ is-plain-obj@^2.0.0: resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== -is-plain-object@^2.0.4: +is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== @@ -5139,7 +5743,7 @@ is-windows@^1.0.2: resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-wsl@^2.1.1: +is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -5151,7 +5755,7 @@ isarray@0.0.1: resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= -isarray@^1.0.0, isarray@~1.0.0: +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -5166,7 +5770,14 @@ isexe@^2.0.0: resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -isobject@^3.0.1: +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= @@ -5248,6 +5859,15 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +jest-changed-files@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" + integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== + dependencies: + "@jest/types" "^26.6.2" + execa "^4.0.0" + throat "^5.0.0" + jest-changed-files@^27.3.0: version "27.3.0" resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.3.0.tgz#22a02cc2b34583fc66e443171dc271c0529d263c" @@ -5282,6 +5902,25 @@ jest-circus@^27.3.1: stack-utils "^2.0.3" throat "^6.0.1" +jest-cli@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" + integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== + dependencies: + "@jest/core" "^26.6.3" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + import-local "^3.0.2" + is-ci "^2.0.0" + jest-config "^26.6.3" + jest-util "^26.6.2" + jest-validate "^26.6.2" + prompts "^2.0.1" + yargs "^15.4.1" + jest-cli@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-27.3.1.tgz#b576f9d146ba6643ce0a162d782b40152b6b1d16" @@ -5300,6 +5939,30 @@ jest-cli@^27.3.1: prompts "^2.0.1" yargs "^16.2.0" +jest-config@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" + integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^26.6.3" + "@jest/types" "^26.6.2" + babel-jest "^26.6.3" + chalk "^4.0.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.4" + jest-environment-jsdom "^26.6.2" + jest-environment-node "^26.6.2" + jest-get-type "^26.3.0" + jest-jasmine2 "^26.6.3" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + micromatch "^4.0.2" + pretty-format "^26.6.2" + jest-config@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-config/-/jest-config-27.3.1.tgz#cb3b7f6aaa8c0a7daad4f2b9573899ca7e09bbad" @@ -5327,6 +5990,16 @@ jest-config@^27.3.1: micromatch "^4.0.4" pretty-format "^27.3.1" +jest-diff@^26.0.0, jest-diff@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" + integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== + dependencies: + chalk "^4.0.0" + diff-sequences "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + jest-diff@^27.0.0, jest-diff@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-27.3.1.tgz#d2775fea15411f5f5aeda2a5e02c2f36440f6d55" @@ -5337,6 +6010,13 @@ jest-diff@^27.0.0, jest-diff@^27.3.1: jest-get-type "^27.3.1" pretty-format "^27.3.1" +jest-docblock@^26.0.0: + version "26.0.0" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" + integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== + dependencies: + detect-newline "^3.0.0" + jest-docblock@^27.0.6: version "27.0.6" resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz#cc78266acf7fe693ca462cbbda0ea4e639e4e5f3" @@ -5344,6 +6024,17 @@ jest-docblock@^27.0.6: dependencies: detect-newline "^3.0.0" +jest-each@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" + integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== + dependencies: + "@jest/types" "^26.6.2" + chalk "^4.0.0" + jest-get-type "^26.3.0" + jest-util "^26.6.2" + pretty-format "^26.6.2" + jest-each@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-each/-/jest-each-27.3.1.tgz#14c56bb4f18dd18dc6bdd853919b5f16a17761ff" @@ -5355,6 +6046,19 @@ jest-each@^27.3.1: jest-util "^27.3.1" pretty-format "^27.3.1" +jest-environment-jsdom@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" + integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" + jsdom "^16.4.0" + jest-environment-jsdom@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.3.1.tgz#63ac36d68f7a9303494df783494856222b57f73e" @@ -5368,6 +6072,18 @@ jest-environment-jsdom@^27.3.1: jest-util "^27.3.1" jsdom "^16.6.0" +jest-environment-node@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" + integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" + jest-environment-node@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.3.1.tgz#af7d0eed04edafb740311b303f3fe7c8c27014bb" @@ -5380,11 +6096,37 @@ jest-environment-node@^27.3.1: jest-mock "^27.3.0" jest-util "^27.3.1" +jest-get-type@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" + integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== + jest-get-type@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.3.1.tgz#a8a2b0a12b50169773099eee60a0e6dd11423eff" integrity sha512-+Ilqi8hgHSAdhlQ3s12CAVNd8H96ZkQBfYoXmArzZnOfAtVAJEiPDBirjByEblvG/4LPJmkL+nBqPO3A1YJAEg== +jest-haste-map@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" + integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== + dependencies: + "@jest/types" "^26.6.2" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.4" + jest-regex-util "^26.0.0" + jest-serializer "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" + micromatch "^4.0.2" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.1.2" + jest-haste-map@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.3.1.tgz#7656fbd64bf48bda904e759fc9d93e2c807353ee" @@ -5405,6 +6147,30 @@ jest-haste-map@^27.3.1: optionalDependencies: fsevents "^2.3.2" +jest-jasmine2@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" + integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^26.6.2" + is-generator-fn "^2.0.0" + jest-each "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + pretty-format "^26.6.2" + throat "^5.0.0" + jest-jasmine2@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.3.1.tgz#df6d3d07c7dafc344feb43a0072a6f09458d32b0" @@ -5439,6 +6205,14 @@ jest-junit@^13.0.0: uuid "^8.3.2" xml "^1.0.1" +jest-leak-detector@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" + integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== + dependencies: + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + jest-leak-detector@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.3.1.tgz#7fb632c2992ef707a1e73286e1e704f9cc1772b2" @@ -5447,6 +6221,16 @@ jest-leak-detector@^27.3.1: jest-get-type "^27.3.1" pretty-format "^27.3.1" +jest-matcher-utils@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" + integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw== + dependencies: + chalk "^4.0.0" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + jest-matcher-utils@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.3.1.tgz#257ad61e54a6d4044e080d85dbdc4a08811e9c1c" @@ -5457,6 +6241,21 @@ jest-matcher-utils@^27.3.1: jest-get-type "^27.3.1" pretty-format "^27.3.1" +jest-message-util@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" + integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/types" "^26.6.2" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + micromatch "^4.0.2" + pretty-format "^26.6.2" + slash "^3.0.0" + stack-utils "^2.0.2" + jest-message-util@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.3.1.tgz#f7c25688ad3410ab10bcb862bcfe3152345c6436" @@ -5472,6 +6271,14 @@ jest-message-util@^27.3.1: slash "^3.0.0" stack-utils "^2.0.3" +jest-mock@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" + integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock@^27.3.0: version "27.3.0" resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-27.3.0.tgz#ddf0ec3cc3e68c8ccd489bef4d1f525571a1b867" @@ -5485,11 +6292,25 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== +jest-regex-util@^26.0.0: + version "26.0.0" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" + integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== + jest-regex-util@^27.0.6: version "27.0.6" resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz#02e112082935ae949ce5d13b2675db3d8c87d9c5" integrity sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ== +jest-resolve-dependencies@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" + integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== + dependencies: + "@jest/types" "^26.6.2" + jest-regex-util "^26.0.0" + jest-snapshot "^26.6.2" + jest-resolve-dependencies@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.3.1.tgz#85b99bdbdfa46e2c81c6228fc4c91076f624f6e2" @@ -5499,6 +6320,20 @@ jest-resolve-dependencies@^27.3.1: jest-regex-util "^27.0.6" jest-snapshot "^27.3.1" +jest-resolve@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" + integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== + dependencies: + "@jest/types" "^26.6.2" + chalk "^4.0.0" + graceful-fs "^4.2.4" + jest-pnp-resolver "^1.2.2" + jest-util "^26.6.2" + read-pkg-up "^7.0.1" + resolve "^1.18.1" + slash "^3.0.0" + jest-resolve@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.3.1.tgz#0e5542172a1aa0270be6f66a65888647bdd74a3e" @@ -5515,6 +6350,32 @@ jest-resolve@^27.3.1: resolve.exports "^1.1.0" slash "^3.0.0" +jest-runner@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" + integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== + dependencies: + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.7.1" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-config "^26.6.3" + jest-docblock "^26.0.0" + jest-haste-map "^26.6.2" + jest-leak-detector "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + jest-runtime "^26.6.3" + jest-util "^26.6.2" + jest-worker "^26.6.2" + source-map-support "^0.5.6" + throat "^5.0.0" + jest-runner@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-27.3.1.tgz#1d594dcbf3bd8600a7e839e790384559eaf96e3e" @@ -5543,6 +6404,39 @@ jest-runner@^27.3.1: source-map-support "^0.5.6" throat "^6.0.1" +jest-runtime@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" + integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== + dependencies: + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/globals" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + cjs-module-lexer "^0.6.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.4" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + slash "^3.0.0" + strip-bom "^4.0.0" + yargs "^15.4.1" + jest-runtime@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.3.1.tgz#80fa32eb85fe5af575865ddf379874777ee993d7" @@ -5575,6 +6469,14 @@ jest-runtime@^27.3.1: strip-bom "^4.0.0" yargs "^16.2.0" +jest-serializer@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" + integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.4" + jest-serializer@^27.0.6: version "27.0.6" resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz#93a6c74e0132b81a2d54623251c46c498bb5bec1" @@ -5583,6 +6485,28 @@ jest-serializer@^27.0.6: "@types/node" "*" graceful-fs "^4.2.4" +jest-snapshot@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" + integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^26.6.2" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.0.0" + chalk "^4.0.0" + expect "^26.6.2" + graceful-fs "^4.2.4" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + jest-haste-map "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + natural-compare "^1.4.0" + pretty-format "^26.6.2" + semver "^7.3.2" + jest-snapshot@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.3.1.tgz#1da5c0712a252d70917d46c037054f5918c49ee4" @@ -5613,6 +6537,18 @@ jest-snapshot@^27.3.1: pretty-format "^27.3.1" semver "^7.3.2" +jest-util@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" + integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" + jest-util@^27.0.0, jest-util@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-util/-/jest-util-27.3.1.tgz#a58cdc7b6c8a560caac9ed6bdfc4e4ff23f80429" @@ -5625,6 +6561,18 @@ jest-util@^27.0.0, jest-util@^27.3.1: graceful-fs "^4.2.4" picomatch "^2.2.3" +jest-validate@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" + integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== + dependencies: + "@jest/types" "^26.6.2" + camelcase "^6.0.0" + chalk "^4.0.0" + jest-get-type "^26.3.0" + leven "^3.1.0" + pretty-format "^26.6.2" + jest-validate@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-27.3.1.tgz#3a395d61a19cd13ae9054af8cdaf299116ef8a24" @@ -5637,6 +6585,19 @@ jest-validate@^27.3.1: leven "^3.1.0" pretty-format "^27.3.1" +jest-watcher@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" + integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== + dependencies: + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + jest-util "^26.6.2" + string-length "^4.0.1" + jest-watcher@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.3.1.tgz#ba5e0bc6aa843612b54ddb7f009d1cbff7e05f3e" @@ -5650,6 +6611,15 @@ jest-watcher@^27.3.1: jest-util "^27.3.1" string-length "^4.0.1" +jest-worker@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + jest-worker@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.3.1.tgz#0def7feae5b8042be38479799aeb7b5facac24b2" @@ -5659,6 +6629,15 @@ jest-worker@^27.3.1: merge-stream "^2.0.0" supports-color "^8.0.0" +jest@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" + integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q== + dependencies: + "@jest/core" "^26.6.3" + import-local "^3.0.2" + jest-cli "^26.6.3" + jest@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/jest/-/jest-27.3.1.tgz#b5bab64e8f56b6f7e275ba1836898b0d9f1e5c8a" @@ -5703,7 +6682,7 @@ jsbn@~0.1.0: resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@^16.6.0: +jsdom@^16.4.0, jsdom@^16.6.0: version "16.7.0" resolved "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== @@ -5741,72 +6720,73 @@ jsesc@^2.5.1: resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -jsii-diff@^1.42.0: - version "1.42.0" - resolved "https://registry.npmjs.org/jsii-diff/-/jsii-diff-1.42.0.tgz#08fab1b90575239daa5f3ae853e2e921d4d26e90" - integrity sha512-ErMGIBt14Z12b2/FGCv8/H8X19nKr/fcq0UA3xJ6/dIvJyATGNlzxcWHmTUcSoaIxlKjFHYErPvZNUrJslbH4Q== +jsii-diff@^1.45.0: + version "1.45.0" + resolved "https://registry.npmjs.org/jsii-diff/-/jsii-diff-1.45.0.tgz#af1ef5fa79b3e2ac393c29f9f3dbd19a2d543b8f" + integrity sha512-XZh+oaToQJX5/y9VvIrFtEb4rXaV69pp/ennmkaSpLCFEkBXGNJoysREXQeVnmLvZUJfpUxvtMhL6HlPVpmSuQ== dependencies: - "@jsii/check-node" "1.42.0" - "@jsii/spec" "^1.42.0" + "@jsii/check-node" "1.45.0" + "@jsii/spec" "^1.45.0" fs-extra "^9.1.0" - jsii-reflect "^1.42.0" + jsii-reflect "^1.45.0" log4js "^6.3.0" typescript "~3.9.10" yargs "^16.2.0" -jsii-pacmak@^1.42.0: - version "1.42.0" - resolved "https://registry.npmjs.org/jsii-pacmak/-/jsii-pacmak-1.42.0.tgz#eee5c15222042b85340ce8e497a70c8c4a48fba6" - integrity sha512-JqgvmI2gIEedB+BvfG7kXkxc5o38TI1VwdQTgUW5hbr0631AgKs/hrpWqcUQ9aNQFwTyzaKWPb0vF8bDitCF6A== +jsii-pacmak@^1.45.0: + version "1.45.0" + resolved "https://registry.npmjs.org/jsii-pacmak/-/jsii-pacmak-1.45.0.tgz#341509f88c03b2104d8d6f56557cf787ff379fa5" + integrity sha512-w952TgVg/kRQyoF8zHp6G6y8On3LM5RJXBJ7doWvicWG9v9HXgHA5zfFgkSFCNtM3IcN7htDtg2kVpE7fv+IBw== dependencies: - "@jsii/check-node" "1.42.0" - "@jsii/spec" "^1.42.0" + "@jsii/check-node" "1.45.0" + "@jsii/spec" "^1.45.0" clone "^2.1.2" - codemaker "^1.42.0" + codemaker "^1.45.0" commonmark "^0.30.0" escape-string-regexp "^4.0.0" fs-extra "^9.1.0" - jsii-reflect "^1.42.0" - jsii-rosetta "^1.42.0" + jsii-reflect "^1.45.0" + jsii-rosetta "^1.45.0" semver "^7.3.5" spdx-license-list "^6.4.0" xmlbuilder "^15.1.1" yargs "^16.2.0" -jsii-reflect@^1.42.0: - version "1.42.0" - resolved "https://registry.npmjs.org/jsii-reflect/-/jsii-reflect-1.42.0.tgz#cab5e6ef64b0ae678efaabf10cefdb76c55818b5" - integrity sha512-gwVZqk2vEnEEYfOU2awHaqZqJd9lA+rEBrlaiMlun42Ve2ZY5HMDBtP/DxgFJG68LCzdlS0xQuHleuBlLYWy0A== +jsii-reflect@1.45.0, jsii-reflect@^1.45.0: + version "1.45.0" + resolved "https://registry.npmjs.org/jsii-reflect/-/jsii-reflect-1.45.0.tgz#8e4bfd062e2a6c9d2571996405069cd392e7668c" + integrity sha512-2oaDz3AKA/0p631TQJCCLUmZ11PXNP+e2GJ01mjUEpwNfEvdLGbfsQ+ThOhVlqP9yP8pmWAOjsQGiXhX4I27lA== dependencies: - "@jsii/check-node" "1.42.0" - "@jsii/spec" "^1.42.0" + "@jsii/check-node" "1.45.0" + "@jsii/spec" "^1.45.0" colors "^1.4.0" fs-extra "^9.1.0" - oo-ascii-tree "^1.42.0" + oo-ascii-tree "^1.45.0" yargs "^16.2.0" -jsii-rosetta@^1.42.0: - version "1.42.0" - resolved "https://registry.npmjs.org/jsii-rosetta/-/jsii-rosetta-1.42.0.tgz#84f80a91446b0a6e4ed8c981b1807eb7fce0b79e" - integrity sha512-F7GLNdoHBAYN4eqw7c6Tv12lqGOoMazsjuXDJRubjjbbwZ0tM6a78rHhrZwE4w1XV7mIkTxKmkj4DnbSIPW8wg== +jsii-rosetta@1.45.0, jsii-rosetta@^1.45.0: + version "1.45.0" + resolved "https://registry.npmjs.org/jsii-rosetta/-/jsii-rosetta-1.45.0.tgz#34c1eb252e5f16078c48f9b805fceb59d9204834" + integrity sha512-pwuqPmvAfXhU1LMZrcxbvVzeJyrMHEoJSH3P+qCP67TR5X8SGs5efH3HBIbIboGFXclg3c/WSHg+fEgzFs/tKg== dependencies: - "@jsii/check-node" "1.42.0" - "@jsii/spec" "^1.42.0" + "@jsii/check-node" "1.45.0" + "@jsii/spec" "1.45.0" "@xmldom/xmldom" "^0.7.5" commonmark "^0.30.0" fs-extra "^9.1.0" + jsii "1.45.0" sort-json "^2.0.0" typescript "~3.9.10" workerpool "^6.1.5" yargs "^16.2.0" -jsii@^1.42.0: - version "1.42.0" - resolved "https://registry.npmjs.org/jsii/-/jsii-1.42.0.tgz#2f8a534c9f981149f4455ec1272bfab7ba1fa6a3" - integrity sha512-Ctbaudn3t3wJ3ihsgCLuEjQGM5CfZl1PJDXfOlELUV6ELwTbvT3TCbyVdt/CCWTOObigQR8OftAB3jl7ymqd3w== +jsii@1.45.0, jsii@^1.45.0: + version "1.45.0" + resolved "https://registry.npmjs.org/jsii/-/jsii-1.45.0.tgz#d37c3dc46594f9c5dc2acb988d8e56179464943c" + integrity sha512-QaCV8d1pFMcFM+cVRRMXI/tfhgylH2T0+vrn51pIMkmA1ixqrBN4gQwTAskrNcjIKVHVndW5eR8IT4MFDMMOqw== dependencies: - "@jsii/check-node" "1.42.0" - "@jsii/spec" "^1.42.0" + "@jsii/check-node" "1.45.0" + "@jsii/spec" "^1.45.0" case "^1.6.3" colors "^1.4.0" deep-equal "^2.0.5" @@ -5945,7 +6925,26 @@ just-extend@^4.0.2: resolved "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== -kind-of@^6.0.2, kind-of@^6.0.3: +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: version "6.0.3" resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== @@ -6330,6 +7329,11 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + map-obj@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" @@ -6340,6 +7344,13 @@ map-obj@^4.0.0: resolved "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + markdown-it@12.2.0: version "12.2.0" resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-12.2.0.tgz#091f720fd5db206f80de7a8d1f1a7035fd0d38db" @@ -6429,6 +7440,25 @@ merge2@^1.3.0: resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.4" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" @@ -6437,17 +7467,17 @@ micromatch@^4.0.2, micromatch@^4.0.4: braces "^3.0.1" picomatch "^2.2.3" -mime-db@1.50.0: - version "1.50.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz#abd4ac94e98d3c0e185016c67ab45d5fde40c11f" - integrity sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A== +mime-db@1.51.0: + version "1.51.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" + integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.33" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz#1fa12a904472fafd068e48d9e8401f74d3f70edb" - integrity sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g== + version "2.1.34" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" + integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== dependencies: - mime-db "1.50.0" + mime-db "1.51.0" mime@^2.6.0: version "2.6.0" @@ -6480,7 +7510,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@>=1.2.2, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.5: +minimist@>=1.2.2, minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.5: version "1.2.5" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -6562,6 +7592,14 @@ minizlib@^2.0.0, minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + mkdirp-infer-owner@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" @@ -6634,6 +7672,23 @@ mute-stream@0.0.8, mute-stream@~0.0.4: resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -6681,10 +7736,10 @@ nise@^5.1.0: just-extend "^4.0.2" path-to-regexp "^1.7.0" -nock@^13.1.4: - version "13.1.4" - resolved "https://registry.npmjs.org/nock/-/nock-13.1.4.tgz#367c917d4c532a889404b85ade92762c29e80262" - integrity sha512-hr5+mknLpIbTOXifB13lx9mAKF1zQPUCMh53Galx79ic5opvNOd55jiB0iGCp2xqh+hwnFbNE/ddBKHsJNQrbw== +nock@^13.2.1: + version "13.2.1" + resolved "https://registry.npmjs.org/nock/-/nock-13.2.1.tgz#fcf5bdb9bb9f0554a84c25d3333166c0ffd80858" + integrity sha512-CoHAabbqq/xZEknubuyQMjq6Lfi5b7RtK6SoNK6m40lebGp3yiMagWtIoYaw2s9sISD7wPuCfwFpivVHX/35RA== dependencies: debug "^4.1.0" json-stringify-safe "^5.0.1" @@ -6741,6 +7796,18 @@ node-modules-regexp@^1.0.0: resolved "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= +node-notifier@^8.0.0: + version "8.0.2" + resolved "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5" + integrity sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg== + dependencies: + growly "^1.3.0" + is-wsl "^2.2.0" + semver "^7.3.2" + shellwords "^0.1.1" + uuid "^8.3.0" + which "^2.0.2" + node-preload@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" @@ -6788,6 +7855,13 @@ normalize-package-data@^3.0.0, normalize-package-data@^3.0.2: semver "^7.3.4" validate-npm-package-license "^3.0.1" +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -6893,7 +7967,7 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npm-run-path@^4.0.1: +npm-run-path@^4.0.0, npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== @@ -6963,6 +8037,15 @@ object-assign@^4.1.0: resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + object-inspect@^1.11.0, object-inspect@^1.9.0: version "1.11.0" resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" @@ -6981,6 +8064,13 @@ object-keys@^1.0.12, object-keys@^1.1.1: resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + object.assign@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" @@ -7000,6 +8090,13 @@ object.getownpropertydescriptors@^2.0.3: define-properties "^1.1.3" es-abstract "^1.19.1" +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + object.values@^1.1.5: version "1.1.5" resolved "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" @@ -7028,10 +8125,10 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -oo-ascii-tree@^1.42.0: - version "1.42.0" - resolved "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-1.42.0.tgz#e9ab47834b004ec2789a5e8b3e477b3fac770804" - integrity sha512-qlynjsWdGidfoWT2uEIr0iNsNmHU2ZhKwtjpJw4VSd3jlxoDpWDDmd5cud/ZBhFT2F1UFSbz+Gl9YtlPYMgQ5Q== +oo-ascii-tree@^1.45.0: + version "1.45.0" + resolved "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-1.45.0.tgz#10480ea817ae0593404a1def81c7806544473fdd" + integrity sha512-f/2vTaoy+tUKv0VeQv5EYPHw8Yf/87aWn9iztzgnWHWqV3A8SGabWMbpDswHMZZTXM+Qnwdj+TjyhAMjt3vMhw== open@^7.4.2: version "7.4.2" @@ -7091,6 +8188,11 @@ osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-each-series@^2.1.0: + version "2.2.0" + resolved "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" + integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -7320,6 +8422,11 @@ parse5@6.0.1: resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + patch-package@^6.4.7: version "6.4.7" resolved "https://registry.npmjs.org/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" @@ -7444,6 +8551,11 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -7454,6 +8566,16 @@ prelude-ls@~1.1.2: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= +pretty-format@^26.0.0, pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== + dependencies: + "@jest/types" "^26.6.2" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^17.0.1" + pretty-format@^27.0.0, pretty-format@^27.3.1: version "27.3.1" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.3.1.tgz#7e9486365ccdd4a502061fa761d3ab9ca1b78df5" @@ -7635,12 +8757,12 @@ quick-lru@^4.0.1: integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== raw-body@^2.2.0: - version "2.4.1" - resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" - integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA== + version "2.4.2" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32" + integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ== dependencies: - bytes "3.1.0" - http-errors "1.7.3" + bytes "3.1.1" + http-errors "1.8.1" iconv-lite "0.4.24" unpipe "1.0.0" @@ -7808,6 +8930,14 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + regexp.prototype.flags@^1.3.0: version "1.3.1" resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" @@ -7833,6 +8963,21 @@ remove-markdown@^0.2.2: resolved "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.2.2.tgz#66b0ceeba9fb77ca9636bb1b0307ce21a32a12a6" integrity sha1-ZrDO66n7d8qWNrsbAwfOIaMqEqY= +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.4" + resolved "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" + integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" @@ -7891,12 +9036,17 @@ resolve-from@^5.0.0: resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + resolve.exports@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== -resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.20.0: +resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.18.1, resolve@^1.20.0: version "1.20.0" resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== @@ -7912,6 +9062,11 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + retry@^0.12.0: version "0.12.0" resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" @@ -7941,6 +9096,11 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +rsvp@^4.8.4: + version "4.8.5" + resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== + run-async@^2.4.0: version "2.4.1" resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -7980,11 +9140,33 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sane@^4.0.3: + version "4.1.0" + resolved "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + sax@1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" @@ -8036,10 +9218,20 @@ set-immediate-shim@~1.0.1: resolved "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== shallow-clone@^3.0.0: version "3.0.1" @@ -8072,6 +9264,11 @@ shebang-regex@^3.0.0: resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== + side-channel@^1.0.3, side-channel@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -8144,6 +9341,36 @@ smart-buffer@^4.1.0: resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + socks-proxy-agent@5, socks-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz#032fb583048a29ebffec2e6a73fca0761f48177e" @@ -8193,6 +9420,17 @@ sort-keys@^4.0.0: dependencies: is-plain-obj "^2.0.0" +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + source-map-support@^0.5.17, source-map-support@^0.5.20, source-map-support@^0.5.6: version "0.5.20" resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" @@ -8201,7 +9439,12 @@ source-map-support@^0.5.17, source-map-support@^0.5.20, source-map-support@^0.5. buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.5.0: +source-map-url@^0.4.0: + version "0.4.1" + resolved "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== + +source-map@^0.5.0, source-map@^0.5.6: version "0.5.7" resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -8250,9 +9493,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.10" - resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz#0d9becccde7003d6c658d487dd48a32f0bf3014b" - integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA== + version "3.0.11" + resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" + integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== spdx-license-list@^6.4.0: version "6.4.0" @@ -8264,6 +9507,13 @@ split-on-first@^1.0.0: resolved "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + split2@^3.0.0: version "3.2.2" resolved "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" @@ -8305,7 +9555,7 @@ ssri@^8.0.0, ssri@^8.0.1: dependencies: minipass "^3.1.1" -stack-utils@^2.0.3: +stack-utils@^2.0.2, stack-utils@^2.0.3: version "2.0.5" resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== @@ -8333,6 +9583,14 @@ standard-version@^9.3.2: stringify-package "^1.0.1" yargs "^16.0.0" +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + "statuses@>= 1.5.0 < 2": version "1.5.0" resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" @@ -8503,7 +9761,7 @@ symbol-tree@^3.2.4: resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -table@*, table@^6.0.9, table@^6.7.2: +table@*, table@^6.0.9, table@^6.7.3: version "6.7.3" resolved "https://registry.npmjs.org/table/-/table-6.7.3.tgz#255388439715a738391bd2ee4cbca89a4d05a9b7" integrity sha512-5DkIxeA7XERBqMwJq0aHZOdMadBx4e6eDoFRuyT5VR82J0Ycg2DwM6GfA/EQAhJ+toRTaS1lIdSQCqgrmhPnlw== @@ -8606,6 +9864,11 @@ text-table@^0.2.0: resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +throat@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" + integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== + throat@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" @@ -8648,6 +9911,21 @@ to-fast-properties@^2.0.0: resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -8655,10 +9933,20 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== tough-cookie@^4.0.0: version "4.0.0" @@ -8713,10 +10001,10 @@ ts-jest@^27.0.7: semver "7.x" yargs-parser "20.x" -ts-mock-imports@^1.3.7: - version "1.3.7" - resolved "https://registry.npmjs.org/ts-mock-imports/-/ts-mock-imports-1.3.7.tgz#8c3210a641f40fd5cadbd1c9c88574b51df59bde" - integrity sha512-zy4B3QSGaOhjaX9j0h9YKwM1oHG4Kd1KIUJBeXlXIQrFnATNLgh4+NyRcaAHsPeqwe3TWeRtHXkNXPxySEKk3w== +ts-mock-imports@^1.3.8: + version "1.3.8" + resolved "https://registry.npmjs.org/ts-mock-imports/-/ts-mock-imports-1.3.8.tgz#6b26887c651effe947ea91f928338d1899146fb9" + integrity sha512-A5n0iEg4zh2/qToo54XOa/wT31fAI0B8DHYU4RDcA6HIddZQPRkTsYri3Hl69+OSLjOKWjyP3/vYOIp3SAIZXg== ts-node@^10.2.1: version "10.4.0" @@ -8911,6 +10199,16 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -8952,6 +10250,14 @@ unpipe@1.0.0: resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + upath@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" @@ -8964,6 +10270,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + url@0.10.3: version "0.10.3" resolved "https://registry.npmjs.org/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" @@ -8972,6 +10283,11 @@ url@0.10.3: punycode "1.3.2" querystring "0.2.0" +use@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + utf8@^2.1.1: version "2.1.2" resolved "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz#1fa0d9270e9be850d9b05027f63519bf46457d96" @@ -8999,7 +10315,7 @@ uuid@^3.3.2, uuid@^3.3.3: resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.3.2: +uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -9009,6 +10325,15 @@ v8-compile-cache@^2.0.3: resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== +v8-to-istanbul@^7.0.0: + version "7.1.2" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1" + integrity sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + v8-to-istanbul@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz#0aeb763894f1a0a1676adf8a8b7612a38902446c" @@ -9066,7 +10391,7 @@ w3c-xmlserializer@^2.0.0: dependencies: xml-name-validator "^3.0.0" -walker@^1.0.7: +walker@^1.0.7, walker@~1.0.5: version "1.0.8" resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== @@ -9383,7 +10708,7 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs@^15.0.2: +yargs@^15.0.2, yargs@^15.4.1: version "15.4.1" resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==