diff --git a/.github/workflows/yarn-upgrade.yml b/.github/workflows/yarn-upgrade.yml index b3d8541568a97..13d551cf740cf 100644 --- a/.github/workflows/yarn-upgrade.yml +++ b/.github/workflows/yarn-upgrade.yml @@ -25,7 +25,7 @@ jobs: run: echo "::set-output name=dir::$(yarn cache dir)" - name: Restore Yarn cache - uses: actions/cache@v2.1.4 + uses: actions/cache@v2.1.5 with: path: ${{ steps.yarn-cache.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} @@ -46,11 +46,12 @@ jobs: # We special-case aws-sdk because of breaking changes with TS interface exports in recent minor versions - https://github.com/aws/aws-sdk-js/issues/3453 # We special-case typescript because it's not semantically versionned # We special-case constructs because we want to stay in control of the minimum compatible version + # We special-case lerna because we have a patch on it that stops applying if Lerna upgrades. Remove this once https://github.com/lerna/lerna/pull/2874 releases. run: |- # Upgrade dependencies at repository root ncu --upgrade --filter=@types/node,@types/fs-extra --target=minor ncu --upgrade --filter=typescript --target=patch - ncu --upgrade --reject=@types/node,@types/fs-extra,constructs,typescript --target=minor + ncu --upgrade --reject=@types/node,@types/fs-extra,constructs,typescript,lerna --target=minor # Upgrade all the packages lerna exec --parallel ncu -- --upgrade --filter=@types/node,@types/fs-extra --target=minor lerna exec --parallel ncu -- --upgrade --filter=typescript --target=patch diff --git a/CHANGELOG.md b/CHANGELOG.md index f641643650a6b..da55ad6cf5bc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,78 @@ 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.100.0](https://github.com/aws/aws-cdk/compare/v1.99.0...v1.100.0) (2021-04-20) + + +### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES + +* **appmesh:** HTTP2 `VirtualNodeListener`s must be now created with `Http2VirtualNodeListenerOptions` +* **appmesh**: HTTP2 `VirtualGatewayListener`s must be now created with `Http2VirtualGatewayListenerOptions` +* **codepipeline-actions:** the Action `ServiceCatalogDeployAction` has been renamed to `ServiceCatalogDeployActionBeta1` +* **codepipeline-actions**: the type `ServiceCatalogDeployActionProps` has been renamed to `ServiceCatalogDeployActionBeta1Props` +* **events-targets:** The `BatchJob` integration now requires the arn and the Resource for the `jobQueue` and the `jobDefinition` +* **lambda-event-sources:** `cluster` was removed from `ManagedKafkaEventSourceProps` and replaced with `clusterArn` +* **route53-targets:** `ApiGatewayv2Domain` was replaced with `ApiGatewayv2DomainProperties` which accepts `regionalDomainName` and `regionalHostedZoneId` +* **stepfunctions-tasks:** `CallApiGatewayHttpApiEndpoint` API now requires the `apiId` and it's containing `Stack` +* **stepfunctions-tasks:** `BatchSubmitJob` now accept `jobDefinitionArn`, `jobQueueArn` and their respective `Resource` +* **stepfunctions-tasks:** `RunBatchJob` now accept `jobDefinitionArn`, `jobQueueArn` and their respective `Resource` + +### Features + +* **apigateway:** integration timeout ([#14154](https://github.com/aws/aws-cdk/issues/14154)) ([d02770e](https://github.com/aws/aws-cdk/commit/d02770ead89d87e55d36490f5d1fa2a4b8a591f2)), closes [#14123](https://github.com/aws/aws-cdk/issues/14123) +* **appmesh:** add Connection Pools for VirtualNode and VirtualGateway ([#13917](https://github.com/aws/aws-cdk/issues/13917)) ([8a949dc](https://github.com/aws/aws-cdk/commit/8a949dc24b13f8b7da17c102501050bac7323bf7)), closes [#11647](https://github.com/aws/aws-cdk/issues/11647) +* **certificatemanager:** allow tagging DnsValidatedCertificate ([#13990](https://github.com/aws/aws-cdk/issues/13990)) ([8360feb](https://github.com/aws/aws-cdk/commit/8360feb58fdc7b1150eca87767e3b71a5e30f50d)), closes [#12382](https://github.com/aws/aws-cdk/issues/12382) [#12382](https://github.com/aws/aws-cdk/issues/12382) +* **codebuild:** allow setting concurrent build limit ([#14185](https://github.com/aws/aws-cdk/issues/14185)) ([3107d03](https://github.com/aws/aws-cdk/commit/3107d03ed2de331ba0eae8ca028aa9a7dbf5a881)) +* **codepipeline:** introduce the Action abstract class ([#14009](https://github.com/aws/aws-cdk/issues/14009)) ([4b6a6cc](https://github.com/aws/aws-cdk/commit/4b6a6cc0e11fd2057b9e23105791098b47c5ca35)) +* **ecs:** add support for elastic inference accelerators in ECS task defintions ([#13950](https://github.com/aws/aws-cdk/issues/13950)) ([23986d7](https://github.com/aws/aws-cdk/commit/23986d70c5cd69ce212b5ffdc1bcf059f438f15b)), closes [#12460](https://github.com/aws/aws-cdk/issues/12460) +* **eks:** Pass bootstrap.sh args to avoid DescribeCluster call and make nodes join the cluster faster ([#12659](https://github.com/aws/aws-cdk/issues/12659)) ([f5616cc](https://github.com/aws/aws-cdk/commit/f5616cc4692975b22db5db4625562dfd0d641045)) +* **secretsmanager:** replicate secrets to multiple regions ([#14266](https://github.com/aws/aws-cdk/issues/14266)) ([b3c288d](https://github.com/aws/aws-cdk/commit/b3c288d7c5781ecb5de90c962a2b68191ed072e1)), closes [#14061](https://github.com/aws/aws-cdk/issues/14061) + + +### Bug Fixes + +* **codepipeline:** incorrect determination of the Action's account when using an imported resource ([#14224](https://github.com/aws/aws-cdk/issues/14224)) ([d88e915](https://github.com/aws/aws-cdk/commit/d88e915c45378cac6a1c7eb31b015391e74f6503)), closes [#14165](https://github.com/aws/aws-cdk/issues/14165) +* **core:** `toJsonString()` does not deal correctly with list tokens ([#14138](https://github.com/aws/aws-cdk/issues/14138)) ([1a6d39f](https://github.com/aws/aws-cdk/commit/1a6d39fc3f22e2fc36949226e8a07f59a92a0bbf)), closes [#14088](https://github.com/aws/aws-cdk/issues/14088) +* **pipelines:** incorrect BuildSpec in synth step if synthesized with `--output` ([#14211](https://github.com/aws/aws-cdk/issues/14211)) ([0f5c74f](https://github.com/aws/aws-cdk/commit/0f5c74f76ad023b163777b8b95f8dbc357994087)), closes [#13303](https://github.com/aws/aws-cdk/issues/13303) +* **rds:** database instances cannot be to be referenced in a different region ([#13865](https://github.com/aws/aws-cdk/issues/13865)) ([74c7fff](https://github.com/aws/aws-cdk/commit/74c7ffffb48fe5578a405b319cc0df973ceb9989)), closes [#13832](https://github.com/aws/aws-cdk/issues/13832) + +## [1.99.0](https://github.com/aws/aws-cdk/compare/v1.98.0...v1.99.0) (2021-04-13) + + +### Features + +* **elasticloadbalancing:** rename 'sslCertificateId' property of LB listener to 'sslCertificateArn'; deprecate sslCertificateId property ([#13766](https://github.com/aws/aws-cdk/issues/13766)) ([1a30272](https://github.com/aws/aws-cdk/commit/1a30272c8bd99a919bde695b5b1b1f5cb458cb64)), closes [#9303](https://github.com/aws/aws-cdk/issues/9303) [#9303](https://github.com/aws/aws-cdk/issues/9303) + + +### Bug Fixes + +* **aws-cloudfront:** distribution comment length not validated ([#14020](https://github.com/aws/aws-cdk/issues/14020)) ([#14094](https://github.com/aws/aws-cdk/issues/14094)) ([54fddc6](https://github.com/aws/aws-cdk/commit/54fddc64c7b541f9192fb904fa9a3b44b8aacf90)) +* **aws-ecs-patterns:** fixes [#11123](https://github.com/aws/aws-cdk/issues/11123) allow for https listeners to use non Route 53 DNS if a certificate is provided ([#14004](https://github.com/aws/aws-cdk/issues/14004)) ([e6c85e4](https://github.com/aws/aws-cdk/commit/e6c85e4167cdb38ed056eda17b869e179a6dd1c5)) +* **cfn-include:** allow deploy-time values in Parameter substitutions in Fn::Sub expressions ([#14068](https://github.com/aws/aws-cdk/issues/14068)) ([111d26a](https://github.com/aws/aws-cdk/commit/111d26a30d220a319bbb7b1b1696aafac865e009)), closes [#14047](https://github.com/aws/aws-cdk/issues/14047) +* **fsx:** Weekday.SUNDAY incorrectly evaluates to 0 (should be 7) ([#14081](https://github.com/aws/aws-cdk/issues/14081)) ([708f23e](https://github.com/aws/aws-cdk/commit/708f23e78fb0eff2aa17593c530500eb0b94067a)), closes [#14080](https://github.com/aws/aws-cdk/issues/14080) + +## [1.98.0](https://github.com/aws/aws-cdk/compare/v1.97.0...v1.98.0) (2021-04-12) + + +### Features + +* **codepipeline-actions:** introduce the CodeStarConnectionsSourceAction ([#13781](https://github.com/aws/aws-cdk/issues/13781)) ([8782e67](https://github.com/aws/aws-cdk/commit/8782e672d6a8f8bbe201f2572c4b0fca7589168d)), closes [#10632](https://github.com/aws/aws-cdk/issues/10632) +* **efs:** graduate to stable 🚀 ([#14033](https://github.com/aws/aws-cdk/issues/14033)) ([3c03d87](https://github.com/aws/aws-cdk/commit/3c03d878dd81454628545b1529691ac083862247)) +* **elasticloadbalancingv2:** add grpc code matcher for alb ([#13948](https://github.com/aws/aws-cdk/issues/13948)) ([a37f178](https://github.com/aws/aws-cdk/commit/a37f178b52a91d43b237013d7cb42c44c1774307)), closes [#13570](https://github.com/aws/aws-cdk/issues/13570) [#13947](https://github.com/aws/aws-cdk/issues/13947) +* **region-info:** graduate to stable 🚀 ([#14013](https://github.com/aws/aws-cdk/issues/14013)) ([0d2755b](https://github.com/aws/aws-cdk/commit/0d2755b97486e4222d1f3b020b8126fefeda20d0)) +* **route-53:** add ability to create NS Records ([#13895](https://github.com/aws/aws-cdk/issues/13895)) ([02c7c1d](https://github.com/aws/aws-cdk/commit/02c7c1d9aab6ed8f806052d3102a037e112b8786)), closes [#13816](https://github.com/aws/aws-cdk/issues/13816) + + +### Bug Fixes + +* **apigateway:** cannot remove first api key from usage plan ([#13817](https://github.com/aws/aws-cdk/issues/13817)) ([036d869](https://github.com/aws/aws-cdk/commit/036d869dc1382d3fb2d8541f5adf534ea3424667)), closes [#11876](https://github.com/aws/aws-cdk/issues/11876) +* **cloudfront:** cannot use same EdgeFunction in multiple stacks ([#13790](https://github.com/aws/aws-cdk/issues/13790)) ([8e2325c](https://github.com/aws/aws-cdk/commit/8e2325cfb7dc5377755b561532b6c81caebc688f)) +* **lambda-nodejs:** esbuild define parameters are incorrectly encoded ([#14065](https://github.com/aws/aws-cdk/issues/14065)) ([5378a77](https://github.com/aws/aws-cdk/commit/5378a7770d5897737ecf4da25d47747c2bbddd94)), closes [#13842](https://github.com/aws/aws-cdk/issues/13842) +* **rds:** deploy fails with "SubnetGroup not found" ([#13986](https://github.com/aws/aws-cdk/issues/13986)) ([ad326da](https://github.com/aws/aws-cdk/commit/ad326da3ae392b78dcfc349f246acdf3a389f283)), closes [#13976](https://github.com/aws/aws-cdk/issues/13976) +* **route53:** cannot set TTL to 0 ([#14060](https://github.com/aws/aws-cdk/issues/14060)) ([ecc9bf3](https://github.com/aws/aws-cdk/commit/ecc9bf386ca088ca82a332c649f13613b9793628)), closes [#14039](https://github.com/aws/aws-cdk/issues/14039) +* **s3:** SSL enforcement doesn't apply on top level bucket requests ([#13961](https://github.com/aws/aws-cdk/issues/13961)) ([d0e831a](https://github.com/aws/aws-cdk/commit/d0e831a2f2b60eae021d10a77d1d636615b0cf04)), closes [#13760](https://github.com/aws/aws-cdk/issues/13760) +* **stepfunctions:** state machine name validation fails when tokens are used. ([#13970](https://github.com/aws/aws-cdk/issues/13970)) ([58de0de](https://github.com/aws/aws-cdk/commit/58de0de5a54b9d8fb4658566e85ef408c8861088)), closes [#13946](https://github.com/aws/aws-cdk/issues/13946) [#13912](https://github.com/aws/aws-cdk/issues/13912) + ## [1.97.0](https://github.com/aws/aws-cdk/compare/v1.96.0...v1.97.0) (2021-04-06) diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index 0f5c6dc507097..47f19a6719d17 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -61,3 +61,16 @@ weakened:@aws-cdk/cloud-assembly-schema.FileSource # These are fine, since they shouldn't be widely used. weakened:@aws-cdk/core.FileAssetLocation weakened:@aws-cdk/aws-events.RuleTargetConfig + +# replace interface with untyped properties to order to break stable to experimental dependencies +removed:@aws-cdk/aws-stepfunctions-tasks.CallApiGatewayHttpApiEndpointProps.api +strengthened:@aws-cdk/aws-stepfunctions-tasks.CallApiGatewayHttpApiEndpointProps +removed:@aws-cdk/aws-route53-targets.ApiGatewayv2Domain +removed:@aws-cdk/aws-stepfunctions-tasks.RunBatchJobProps.jobDefinition +removed:@aws-cdk/aws-stepfunctions-tasks.RunBatchJobProps.jobDefinition +removed:@aws-cdk/aws-stepfunctions-tasks.RunBatchJobProps.jobQueue +removed:@aws-cdk/aws-stepfunctions-tasks.BatchSubmitJobProps.jobQueue +removed:@aws-cdk/aws-stepfunctions-tasks.BatchSubmitJobProps.jobDefinition +strengthened:@aws-cdk/aws-stepfunctions-tasks.BatchSubmitJobProps +removed:@aws-cdk/aws-lambda-event-sources.ManagedKafkaEventSourceProps.cluster +strengthened:@aws-cdk/aws-lambda-event-sources.ManagedKafkaEventSourceProps diff --git a/package.json b/package.json index bcc83bf7f0957..099ff34111ce5 100644 --- a/package.json +++ b/package.json @@ -11,17 +11,19 @@ "pack": "./pack.sh", "compat": "./scripts/check-api-compatibility.sh", "bump": "./bump.sh", - "build-all": "tsc -b" + "build-all": "tsc -b", + "postinstall": "patch-package --error-on-fail" }, "devDependencies": { "conventional-changelog-cli": "^2.1.1", "fs-extra": "^9.1.0", "graceful-fs": "^4.2.6", "jest-junit": "^12.0.0", - "jsii-diff": "^1.27.0", - "jsii-pacmak": "^1.27.0", - "jsii-rosetta": "^1.27.0", + "jsii-diff": "^1.28.0", + "jsii-pacmak": "^1.28.0", + "jsii-rosetta": "^1.28.0", "lerna": "^4.0.0", + "patch-package": "^6.4.7", "standard-version": "^9.2.0", "typescript": "~3.9.9" }, diff --git a/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts b/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts index 01be9ebd17c59..7b138cfcf23d1 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts @@ -7,12 +7,10 @@ import { Construct } from '@aws-cdk/core'; /** * Represents an OpenAPI definition asset. - * @experimental */ export abstract class ApiDefinition { /** * Creates an API definition from a specification file in an S3 bucket - * @experimental */ public static fromBucket(bucket: s3.IBucket, key: string, objectVersion?: string): S3ApiDefinition { return new S3ApiDefinition(bucket, key, objectVersion); @@ -70,7 +68,6 @@ export abstract class ApiDefinition { /** * Loads the API specification from a local disk asset. - * @experimental */ public static fromAsset(file: string, options?: s3_assets.AssetOptions): AssetApiDefinition { return new AssetApiDefinition(file, options); @@ -88,7 +85,6 @@ export abstract class ApiDefinition { /** * S3 location of the API definition file - * @experimental */ export interface ApiDefinitionS3Location { /** The S3 bucket */ @@ -104,7 +100,6 @@ export interface ApiDefinitionS3Location { /** * Post-Binding Configuration for a CDK construct - * @experimental */ export interface ApiDefinitionConfig { /** @@ -124,7 +119,6 @@ export interface ApiDefinitionConfig { /** * OpenAPI specification from an S3 archive. - * @experimental */ export class S3ApiDefinition extends ApiDefinition { private bucketName: string; @@ -152,7 +146,6 @@ export class S3ApiDefinition extends ApiDefinition { /** * OpenAPI specification from an inline JSON object. - * @experimental */ export class InlineApiDefinition extends ApiDefinition { constructor(private definition: any) { @@ -176,7 +169,6 @@ export class InlineApiDefinition extends ApiDefinition { /** * OpenAPI specification from a local file. - * @experimental */ export class AssetApiDefinition extends ApiDefinition { private asset?: s3_assets.Asset; diff --git a/packages/@aws-cdk/aws-apigateway/lib/apigatewayv2.ts b/packages/@aws-cdk/aws-apigateway/lib/apigatewayv2.ts index 98ee1ba04acbf..e9394f5ade6ed 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/apigatewayv2.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/apigatewayv2.ts @@ -318,7 +318,6 @@ export class CfnApiV2 extends cdk.CfnResource implements cdk.IInspectable { * * @param inspector - tree inspector to collect and process attributes * - * @stability experimental */ public inspect(inspector: cdk.TreeInspector) { inspector.addAttribute('aws:cdk:cloudformation:type', CfnApiV2.CFN_RESOURCE_TYPE_NAME); @@ -637,7 +636,6 @@ export class CfnApiMappingV2 extends cdk.CfnResource implements cdk.IInspectable * * @param inspector - tree inspector to collect and process attributes * - * @stability experimental */ public inspect(inspector: cdk.TreeInspector) { inspector.addAttribute('aws:cdk:cloudformation:type', CfnApiMappingV2.CFN_RESOURCE_TYPE_NAME); @@ -869,7 +867,6 @@ export class CfnAuthorizerV2 extends cdk.CfnResource implements cdk.IInspectable * * @param inspector - tree inspector to collect and process attributes * - * @stability experimental */ public inspect(inspector: cdk.TreeInspector) { inspector.addAttribute('aws:cdk:cloudformation:type', CfnAuthorizerV2.CFN_RESOURCE_TYPE_NAME); @@ -1065,7 +1062,6 @@ export class CfnDeploymentV2 extends cdk.CfnResource implements cdk.IInspectable * * @param inspector - tree inspector to collect and process attributes * - * @stability experimental */ public inspect(inspector: cdk.TreeInspector) { inspector.addAttribute('aws:cdk:cloudformation:type', CfnDeploymentV2.CFN_RESOURCE_TYPE_NAME); @@ -1212,7 +1208,6 @@ export class CfnDomainNameV2 extends cdk.CfnResource implements cdk.IInspectable * * @param inspector - tree inspector to collect and process attributes * - * @stability experimental */ public inspect(inspector: cdk.TreeInspector) { inspector.addAttribute('aws:cdk:cloudformation:type', CfnDomainNameV2.CFN_RESOURCE_TYPE_NAME); @@ -1576,7 +1571,6 @@ export class CfnIntegrationV2 extends cdk.CfnResource implements cdk.IInspectabl * * @param inspector - tree inspector to collect and process attributes * - * @stability experimental */ public inspect(inspector: cdk.TreeInspector) { inspector.addAttribute('aws:cdk:cloudformation:type', CfnIntegrationV2.CFN_RESOURCE_TYPE_NAME); @@ -1786,7 +1780,6 @@ export class CfnIntegrationResponseV2 extends cdk.CfnResource implements cdk.IIn * * @param inspector - tree inspector to collect and process attributes * - * @stability experimental */ public inspect(inspector: cdk.TreeInspector) { inspector.addAttribute('aws:cdk:cloudformation:type', CfnIntegrationResponseV2.CFN_RESOURCE_TYPE_NAME); @@ -1959,7 +1952,6 @@ export class CfnModelV2 extends cdk.CfnResource implements cdk.IInspectable { * * @param inspector - tree inspector to collect and process attributes * - * @stability experimental */ public inspect(inspector: cdk.TreeInspector) { inspector.addAttribute('aws:cdk:cloudformation:type', CfnModelV2.CFN_RESOURCE_TYPE_NAME); @@ -2233,7 +2225,6 @@ export class CfnRouteV2 extends cdk.CfnResource implements cdk.IInspectable { * * @param inspector - tree inspector to collect and process attributes * - * @stability experimental */ public inspect(inspector: cdk.TreeInspector) { inspector.addAttribute('aws:cdk:cloudformation:type', CfnRouteV2.CFN_RESOURCE_TYPE_NAME); @@ -2475,7 +2466,6 @@ export class CfnRouteResponseV2 extends cdk.CfnResource implements cdk.IInspecta * * @param inspector - tree inspector to collect and process attributes * - * @stability experimental */ public inspect(inspector: cdk.TreeInspector) { inspector.addAttribute('aws:cdk:cloudformation:type', CfnRouteResponseV2.CFN_RESOURCE_TYPE_NAME); @@ -2784,7 +2774,6 @@ export class CfnStageV2 extends cdk.CfnResource implements cdk.IInspectable { * * @param inspector - tree inspector to collect and process attributes * - * @stability experimental */ public inspect(inspector: cdk.TreeInspector) { inspector.addAttribute('aws:cdk:cloudformation:type', CfnStageV2.CFN_RESOURCE_TYPE_NAME); diff --git a/packages/@aws-cdk/aws-apigateway/lib/integration.ts b/packages/@aws-cdk/aws-apigateway/lib/integration.ts index 475358d672540..4ff6a28c76a63 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integration.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integration.ts @@ -1,5 +1,5 @@ import * as iam from '@aws-cdk/aws-iam'; -import { Lazy } from '@aws-cdk/core'; +import { Lazy, Duration } from '@aws-cdk/core'; import { Method } from './method'; import { IVpcLink, VpcLink } from './vpc-link'; @@ -80,6 +80,14 @@ export interface IntegrationOptions { */ readonly requestTemplates?: { [contentType: string]: string }; + /** + * The maximum amount of time an integration will run before it returns without a response. + * Must be between 50 milliseconds and 29 seconds. + * + * @default Duration.seconds(29) + */ + readonly timeout?: Duration; + /** * The response that API Gateway provides after a method's backend completes * processing a request. API Gateway intercepts the response from the @@ -193,6 +201,10 @@ export class Integration { if (options.connectionType === ConnectionType.INTERNET && options.vpcLink !== undefined) { throw new Error('cannot set \'vpcLink\' where \'connectionType\' is INTERNET'); } + + if (options.timeout && !options.timeout.isUnresolved() && (options.timeout.toMilliseconds() < 50 || options.timeout.toMilliseconds() > 29000)) { + throw new Error('Integration timeout must be between 50 milliseconds and 29 seconds.'); + } } /** diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index e4cfa78c0584e..ff9c4aaef47dd 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -289,6 +289,7 @@ export class Method extends Resource { connectionType: options.connectionType, connectionId: options.vpcLink ? options.vpcLink.vpcLinkId : undefined, credentials, + timeoutInMillis: options.timeout?.toMilliseconds(), }; } diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 1afd9f7e9d090..9d74e2a1ec7a9 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -236,7 +236,6 @@ export interface RestApiProps extends RestApiOptions { /** * Props to instantiate a new SpecRestApi - * @experimental */ export interface SpecRestApiProps extends RestApiBaseProps { /** @@ -587,7 +586,6 @@ export abstract class RestApiBase extends Resource implements IRestApi { * By default, the API will automatically be deployed and accessible from a * public endpoint. * - * @experimental * * @resource AWS::ApiGateway::RestApi */ @@ -680,7 +678,6 @@ export class RestApi extends RestApiBase { /** * Import an existing RestApi that can be configured with additional Methods and Resources. - * @experimental */ public static fromRestApiAttributes(scope: Construct, id: string, attrs: RestApiAttributes): IRestApi { class Import extends RestApiBase { diff --git a/packages/@aws-cdk/aws-apigateway/test/integration.test.ts b/packages/@aws-cdk/aws-apigateway/test/integration.test.ts index 56cf4e87f94cd..f89a1fdf042ed 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integration.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integration.test.ts @@ -184,4 +184,50 @@ describe('integration', () => { }, }); }); + + test('validates timeout is valid', () => { + + expect(() => new apigw.Integration({ + type: apigw.IntegrationType.HTTP_PROXY, + integrationHttpMethod: 'ANY', + options: { + timeout: cdk.Duration.millis(2), + }, + })).toThrow(/Integration timeout must be between 50 milliseconds and 29 seconds/); + + expect(() => new apigw.Integration({ + type: apigw.IntegrationType.HTTP_PROXY, + integrationHttpMethod: 'ANY', + options: { + timeout: cdk.Duration.seconds(50), + }, + })).toThrow(/Integration timeout must be between 50 milliseconds and 29 seconds/); + }); + + test('sets timeout', () => { + + // GIVEN + const stack = new cdk.Stack(); + const api = new apigw.RestApi(stack, 'restapi'); + + // WHEN + const integration = new apigw.Integration({ + type: apigw.IntegrationType.HTTP_PROXY, + integrationHttpMethod: 'ANY', + options: { + timeout: cdk.Duration.seconds(1), + }, + }); + api.root.addMethod('ANY', integration); + + // THEN + expect(stack).toHaveResourceLike('AWS::ApiGateway::Method', { + HttpMethod: 'ANY', + Integration: { + TimeoutInMillis: 1000, + }, + }); + + }); + }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appmesh/README.md b/packages/@aws-cdk/aws-appmesh/README.md index fe3973e71149e..0adcc52901ba2 100644 --- a/packages/@aws-cdk/aws-appmesh/README.md +++ b/packages/@aws-cdk/aws-appmesh/README.md @@ -306,6 +306,38 @@ const node = mesh.addVirtualNode('virtual-node', { }); ``` +## Adding a connection pool to a listener + +The `connectionPool` property can be added to a Virtual Node listener or Virtual Gateway listener to add a request connection pool. There are different +connection pool properties per listener protocol types. + +```typescript +// A Virtual Node with a gRPC listener with a connection pool set +const node = new appmesh.VirtualNode(stack, 'node', { + mesh, + dnsHostName: 'node', + listeners: [appmesh.VirtualNodeListener.http({ + port: 80, + connectionPool: { + maxConnections: 100, + maxPendingRequests: 10, + }, + })], +}); + +// A Virtual Gateway with a gRPC listener with a connection pool set +const gateway = new appmesh.VirtualGateway(this, 'gateway', { + mesh: mesh, + listeners: [appmesh.VirtualGatewayListener.grpc({ + port: 8080, + connectionPool: { + maxRequests: 10, + }, + })], + virtualGatewayName: 'gateway', +}); +``` + ## Adding a Route A `route` is associated with a virtual router, and it's used to match requests for a virtual router and distribute traffic accordingly to its associated virtual nodes. diff --git a/packages/@aws-cdk/aws-appmesh/lib/private/utils.ts b/packages/@aws-cdk/aws-appmesh/lib/private/utils.ts index 0f63fdaf05253..7b5cfe620de1c 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/private/utils.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/private/utils.ts @@ -38,4 +38,30 @@ const HEALTH_CHECK_PROPERTY_THRESHOLDS: {[key in (keyof AppMeshHealthCheck)]?: [ port: [1, 65535], timeoutMillis: [2000, 60000], unhealthyThreshold: [2, 10], -}; \ No newline at end of file +}; + +/** + * Generated Connection pool config + */ +export interface ConnectionPoolConfig { + /** + * The maximum connections in the pool + * + * @default - none + */ + readonly maxConnections?: number; + + /** + * The maximum pending requests in the pool + * + * @default - none + */ + readonly maxPendingRequests?: number; + + /** + * The maximum requests in the pool + * + * @default - none + */ + readonly maxRequests?: number; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts b/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts index c06a0f40e5a28..5fb77b8cc4145 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts @@ -299,3 +299,58 @@ class VirtualServiceBackend extends Backend { }; } } + +/** + * Connection pool properties for HTTP listeners + */ +export interface HttpConnectionPool { + /** + * The maximum connections in the pool + * + * @default - none + */ + readonly maxConnections: number; + + /** + * The maximum pending requests in the pool + * + * @default - none + */ + readonly maxPendingRequests: number; +} + +/** + * Connection pool properties for TCP listeners + */ +export interface TcpConnectionPool { + /** + * The maximum connections in the pool + * + * @default - none + */ + readonly maxConnections: number; +} + +/** + * Connection pool properties for gRPC listeners + */ +export interface GrpcConnectionPool { + /** + * The maximum requests in the pool + * + * @default - none + */ + readonly maxRequests: number; +} + +/** + * Connection pool properties for HTTP2 listeners + */ +export interface Http2ConnectionPool { + /** + * The maximum requests in the pool + * + * @default - none + */ + readonly maxRequests: number; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway-listener.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway-listener.ts index 9f081ffdefd60..f4a083e3ea35e 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway-listener.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway-listener.ts @@ -1,7 +1,13 @@ import * as cdk from '@aws-cdk/core'; import { CfnVirtualGateway } from './appmesh.generated'; -import { validateHealthChecks } from './private/utils'; -import { HealthCheck, Protocol } from './shared-interfaces'; +import { validateHealthChecks, ConnectionPoolConfig } from './private/utils'; +import { + GrpcConnectionPool, + HealthCheck, + Http2ConnectionPool, + HttpConnectionPool, + Protocol, +} from './shared-interfaces'; import { TlsCertificate, TlsCertificateConfig } from './tls-certificate'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main @@ -9,9 +15,9 @@ import { TlsCertificate, TlsCertificateConfig } from './tls-certificate'; import { Construct } from '@aws-cdk/core'; /** - * Represents the properties needed to define HTTP Listeners for a VirtualGateway + * Represents the properties needed to define a Listeners for a VirtualGateway */ -export interface HttpGatewayListenerOptions { +interface VirtualGatewayListenerCommonOptions { /** * Port to listen for connections on * @@ -35,29 +41,39 @@ export interface HttpGatewayListenerOptions { } /** - * Represents the properties needed to define GRPC Listeners for a VirtualGateway + * Represents the properties needed to define HTTP Listeners for a VirtualGateway */ -export interface GrpcGatewayListenerOptions { +export interface HttpGatewayListenerOptions extends VirtualGatewayListenerCommonOptions { /** - * Port to listen for connections on + * Connection pool for http listeners * - * @default - 8080 + * @default - None */ - readonly port?: number + readonly connectionPool?: HttpConnectionPool; +} +/** + * Represents the properties needed to define HTTP2 Listeners for a VirtualGateway + */ +export interface Http2GatewayListenerOptions extends VirtualGatewayListenerCommonOptions { /** - * The health check information for the listener + * Connection pool for http listeners * - * @default - no healthcheck + * @default - None */ - readonly healthCheck?: HealthCheck; + readonly connectionPool?: Http2ConnectionPool; +} +/** + * Represents the properties needed to define GRPC Listeners for a VirtualGateway + */ +export interface GrpcGatewayListenerOptions extends VirtualGatewayListenerCommonOptions { /** - * Represents the listener certificate + * Connection pool for http listeners * - * @default - none + * @default - None */ - readonly tlsCertificate?: TlsCertificate; + readonly connectionPool?: GrpcConnectionPool; } /** @@ -78,21 +94,21 @@ export abstract class VirtualGatewayListener { * Returns an HTTP Listener for a VirtualGateway */ public static http(options: HttpGatewayListenerOptions = {}): VirtualGatewayListener { - return new VirtualGatewayListenerImpl(Protocol.HTTP, options.healthCheck, options.port, options.tlsCertificate); + return new VirtualGatewayListenerImpl(Protocol.HTTP, options.healthCheck, options.port, options.tlsCertificate, options.connectionPool); } /** * Returns an HTTP2 Listener for a VirtualGateway */ - public static http2(options: HttpGatewayListenerOptions = {}): VirtualGatewayListener { - return new VirtualGatewayListenerImpl(Protocol.HTTP2, options.healthCheck, options.port, options.tlsCertificate); + public static http2(options: Http2GatewayListenerOptions = {}): VirtualGatewayListener { + return new VirtualGatewayListenerImpl(Protocol.HTTP2, options.healthCheck, options.port, options.tlsCertificate, options.connectionPool); } /** * Returns a GRPC Listener for a VirtualGateway */ public static grpc(options: GrpcGatewayListenerOptions = {}): VirtualGatewayListener { - return new VirtualGatewayListenerImpl(Protocol.GRPC, options.healthCheck, options.port, options.tlsCertificate); + return new VirtualGatewayListenerImpl(Protocol.GRPC, options.healthCheck, options.port, options.tlsCertificate, options.connectionPool); } /** @@ -110,7 +126,8 @@ class VirtualGatewayListenerImpl extends VirtualGatewayListener { constructor(private readonly protocol: Protocol, private readonly healthCheck: HealthCheck | undefined, private readonly port: number = 8080, - private readonly tlsCertificate: TlsCertificate | undefined) { + private readonly tlsCertificate: TlsCertificate | undefined, + private readonly connectionPool: ConnectionPoolConfig | undefined) { super(); } @@ -128,6 +145,7 @@ class VirtualGatewayListenerImpl extends VirtualGatewayListener { }, healthCheck: this.healthCheck ? renderHealthCheck(this.healthCheck, this.protocol, this.port): undefined, tls: tlsConfig ? renderTls(tlsConfig) : undefined, + connectionPool: this.connectionPool ? renderConnectionPool(this.connectionPool, this.protocol) : undefined, }, }; } @@ -171,3 +189,14 @@ function renderHealthCheck(hc: HealthCheck, listenerProtocol: Protocol, return healthCheck; } + +function renderConnectionPool(connectionPool: ConnectionPoolConfig, listenerProtocol: Protocol): +CfnVirtualGateway.VirtualGatewayConnectionPoolProperty { + return ({ + [listenerProtocol]: { + maxRequests: connectionPool?.maxRequests !== undefined ? connectionPool.maxRequests : undefined, + maxConnections: connectionPool?.maxConnections !== undefined ? connectionPool.maxConnections : undefined, + maxPendingRequests: connectionPool?.maxPendingRequests !== undefined ? connectionPool.maxPendingRequests : undefined, + }, + }); +} diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-node-listener.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-node-listener.ts index 9ff768d07294c..7a046a138c3ca 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-node-listener.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-node-listener.ts @@ -1,7 +1,10 @@ import * as cdk from '@aws-cdk/core'; import { CfnVirtualNode } from './appmesh.generated'; -import { validateHealthChecks } from './private/utils'; -import { HealthCheck, Protocol, HttpTimeout, GrpcTimeout, TcpTimeout, OutlierDetection } from './shared-interfaces'; +import { validateHealthChecks, ConnectionPoolConfig } from './private/utils'; +import { + GrpcConnectionPool, GrpcTimeout, HealthCheck, Http2ConnectionPool, HttpConnectionPool, + HttpTimeout, OutlierDetection, Protocol, TcpConnectionPool, TcpTimeout, +} from './shared-interfaces'; import { TlsCertificate, TlsCertificateConfig } from './tls-certificate'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main @@ -51,16 +54,38 @@ interface VirtualNodeListenerCommonOptions { readonly outlierDetection?: OutlierDetection; } +interface CommonHttpVirtualNodeListenerOptions extends VirtualNodeListenerCommonOptions { + /** + * Timeout for HTTP protocol + * + * @default - None + */ + readonly timeout?: HttpTimeout; +} + /** * Represent the HTTP Node Listener prorperty */ -export interface HttpVirtualNodeListenerOptions extends VirtualNodeListenerCommonOptions { +export interface HttpVirtualNodeListenerOptions extends CommonHttpVirtualNodeListenerOptions { + /** - * Timeout for HTTP protocol + * Connection pool for http listeners * * @default - None */ - readonly timeout?: HttpTimeout; + readonly connectionPool?: HttpConnectionPool; +} + +/** + * Represent the HTTP2 Node Listener prorperty + */ +export interface Http2VirtualNodeListenerOptions extends CommonHttpVirtualNodeListenerOptions { + /** + * Connection pool for http2 listeners + * + * @default - None + */ + readonly connectionPool?: Http2ConnectionPool; } /** @@ -73,6 +98,13 @@ export interface GrpcVirtualNodeListenerOptions extends VirtualNodeListenerCommo * @default - None */ readonly timeout?: GrpcTimeout; + + /** + * Connection pool for http listeners + * + * @default - None + */ + readonly connectionPool?: GrpcConnectionPool; } /** @@ -85,6 +117,13 @@ export interface TcpVirtualNodeListenerOptions extends VirtualNodeListenerCommon * @default - None */ readonly timeout?: TcpTimeout; + + /** + * Connection pool for http listeners + * + * @default - None + */ + readonly connectionPool?: TcpConnectionPool; } /** @@ -95,35 +134,38 @@ export abstract class VirtualNodeListener { * Returns an HTTP Listener for a VirtualNode */ public static http(props: HttpVirtualNodeListenerOptions = {}): VirtualNodeListener { - return new VirtualNodeListenerImpl(Protocol.HTTP, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection); + return new VirtualNodeListenerImpl(Protocol.HTTP, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection, + props.connectionPool); } /** * Returns an HTTP2 Listener for a VirtualNode */ - public static http2(props: HttpVirtualNodeListenerOptions = {}): VirtualNodeListener { - return new VirtualNodeListenerImpl(Protocol.HTTP2, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection); + public static http2(props: Http2VirtualNodeListenerOptions = {}): VirtualNodeListener { + return new VirtualNodeListenerImpl(Protocol.HTTP2, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection, + props.connectionPool); } /** * Returns an GRPC Listener for a VirtualNode */ public static grpc(props: GrpcVirtualNodeListenerOptions = {}): VirtualNodeListener { - return new VirtualNodeListenerImpl(Protocol.GRPC, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection); + return new VirtualNodeListenerImpl(Protocol.GRPC, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection, + props.connectionPool); } /** * Returns an TCP Listener for a VirtualNode */ public static tcp(props: TcpVirtualNodeListenerOptions = {}): VirtualNodeListener { - return new VirtualNodeListenerImpl(Protocol.TCP, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection); + return new VirtualNodeListenerImpl(Protocol.TCP, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection, + props.connectionPool); } /** * Binds the current object when adding Listener to a VirtualNode */ public abstract bind(scope: Construct): VirtualNodeListenerConfig; - } class VirtualNodeListenerImpl extends VirtualNodeListener { @@ -132,7 +174,8 @@ class VirtualNodeListenerImpl extends VirtualNodeListener { private readonly timeout: HttpTimeout | undefined, private readonly port: number = 8080, private readonly tlsCertificate: TlsCertificate | undefined, - private readonly outlierDetection: OutlierDetection | undefined) { super(); } + private readonly outlierDetection: OutlierDetection | undefined, + private readonly connectionPool: ConnectionPoolConfig | undefined) { super(); } public bind(scope: Construct): VirtualNodeListenerConfig { const tlsConfig = this.tlsCertificate?.bind(scope); @@ -146,6 +189,7 @@ class VirtualNodeListenerImpl extends VirtualNodeListener { timeout: this.timeout ? this.renderTimeout(this.timeout) : undefined, tls: tlsConfig ? this.renderTls(tlsConfig) : undefined, outlierDetection: this.outlierDetection ? this.renderOutlierDetection(this.outlierDetection) : undefined, + connectionPool: this.connectionPool ? this.renderConnectionPool(this.connectionPool) : undefined, }, }; } @@ -215,5 +259,15 @@ class VirtualNodeListenerImpl extends VirtualNodeListener { maxServerErrors: outlierDetection.maxServerErrors, }; } + + private renderConnectionPool(connectionPool: ConnectionPoolConfig): CfnVirtualNode.VirtualNodeConnectionPoolProperty { + return ({ + [this.protocol]: { + maxRequests: connectionPool?.maxRequests !== undefined ? connectionPool.maxRequests : undefined, + maxConnections: connectionPool?.maxConnections !== undefined ? connectionPool.maxConnections : undefined, + maxPendingRequests: connectionPool?.maxPendingRequests !== undefined ? connectionPool.maxPendingRequests : undefined, + }, + }); + } } diff --git a/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts b/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts index f5a5201bb6700..96d3c7cba9210 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts @@ -423,6 +423,131 @@ export = { }, }, + 'Can add an http connection pool to listener'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + new appmesh.VirtualGateway(stack, 'virtual-gateway', { + virtualGatewayName: 'virtual-gateway', + mesh: mesh, + listeners: [ + appmesh.VirtualGatewayListener.http({ + port: 80, + connectionPool: { + maxConnections: 100, + maxPendingRequests: 10, + }, + }), + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualGateway', { + VirtualGatewayName: 'virtual-gateway', + Spec: { + Listeners: [ + { + ConnectionPool: { + HTTP: { + MaxConnections: 100, + MaxPendingRequests: 10, + }, + }, + }, + ], + }, + })); + + test.done(); + }, + + 'Can add an grpc connection pool to listener'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + new appmesh.VirtualGateway(stack, 'virtual-gateway', { + virtualGatewayName: 'virtual-gateway', + mesh: mesh, + listeners: [ + appmesh.VirtualGatewayListener.grpc({ + port: 80, + connectionPool: { + maxRequests: 10, + }, + }), + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualGateway', { + VirtualGatewayName: 'virtual-gateway', + Spec: { + Listeners: [ + { + ConnectionPool: { + GRPC: { + MaxRequests: 10, + }, + }, + }, + ], + }, + })); + + test.done(); + }, + + 'Can add an http2 connection pool to listener'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + new appmesh.VirtualGateway(stack, 'virtual-gateway', { + virtualGatewayName: 'virtual-gateway', + mesh: mesh, + listeners: [ + appmesh.VirtualGatewayListener.http2({ + port: 80, + connectionPool: { + maxRequests: 10, + }, + }), + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualGateway', { + VirtualGatewayName: 'virtual-gateway', + Spec: { + Listeners: [ + { + ConnectionPool: { + HTTP2: { + MaxRequests: 10, + }, + }, + }, + ], + }, + })); + + test.done(); + }, + 'Can import VirtualGateways using an ARN'(test: Test) { const app = new cdk.App(); // GIVEN diff --git a/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts b/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts index b025d68581ae9..f143b0025c1db 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts @@ -557,6 +557,164 @@ export = { test.done(); }, }, + + 'Can add an http connection pool to listener'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + new appmesh.VirtualNode(stack, 'test-node', { + mesh, + listeners: [ + appmesh.VirtualNodeListener.http({ + port: 80, + connectionPool: { + maxConnections: 100, + maxPendingRequests: 10, + }, + }), + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', { + Spec: { + Listeners: [ + { + ConnectionPool: { + HTTP: { + MaxConnections: 100, + MaxPendingRequests: 10, + }, + }, + }, + ], + }, + })); + + test.done(); + }, + + 'Can add an tcp connection pool to listener'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + new appmesh.VirtualNode(stack, 'test-node', { + mesh, + listeners: [ + appmesh.VirtualNodeListener.tcp({ + port: 80, + connectionPool: { + maxConnections: 100, + }, + }), + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', { + Spec: { + Listeners: [ + { + ConnectionPool: { + TCP: { + MaxConnections: 100, + }, + }, + }, + ], + }, + })); + + test.done(); + }, + + 'Can add an grpc connection pool to listener'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + new appmesh.VirtualNode(stack, 'test-node', { + mesh, + listeners: [ + appmesh.VirtualNodeListener.grpc({ + port: 80, + connectionPool: { + maxRequests: 10, + }, + }), + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', { + Spec: { + Listeners: [ + { + ConnectionPool: { + GRPC: { + MaxRequests: 10, + }, + }, + }, + ], + }, + })); + + test.done(); + }, + + 'Can add an http2 connection pool to listener'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + new appmesh.VirtualNode(stack, 'test-node', { + mesh, + listeners: [ + appmesh.VirtualNodeListener.http2({ + port: 80, + connectionPool: { + maxRequests: 10, + }, + }), + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', { + Spec: { + Listeners: [ + { + ConnectionPool: { + HTTP2: { + MaxRequests: 10, + }, + }, + }, + ], + }, + })); + + test.done(); + }, }, 'Can import Virtual Nodes using an ARN'(test: Test) { diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index d15051d191a25..e0a636217f38b 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -26,7 +26,7 @@ APIs that use GraphQL. ## Example -## DynamoDB +### DynamoDB Example of a GraphQL API with `AWS_IAM` authorization resolving into a DynamoDb backend data source. @@ -95,7 +95,7 @@ demoDS.createResolver({ }); ``` -## Aurora Serverless +### Aurora Serverless AppSync provides a data source for executing SQL commands against Amazon Aurora Serverless clusters. You can use AppSync resolvers to execute SQL statements @@ -240,13 +240,13 @@ httpDs.createResolver({ }); ``` -### Schema +## Schema Every GraphQL Api needs a schema to define the Api. CDK offers `appsync.Schema` for static convenience methods for various types of schema declaration: code-first or schema-first. -#### Code-First +### Code-First When declaring your GraphQL Api, CDK defaults to a code-first approach if the `schema` property is not configured. @@ -274,7 +274,7 @@ const api = new appsync.GraphqlApi(stack, 'api', { See the [code-first schema](#Code-First-Schema) section for more details. -#### Schema-First +### Schema-First You can define your GraphQL Schema from a file on disk. For convenience, use the `appsync.Schema.fromAsset` to specify the file representing your schema. @@ -286,7 +286,7 @@ const api = appsync.GraphqlApi(stack, 'api', { }); ``` -### Imports +## Imports Any GraphQL Api that has been created outside the stack can be imported from another stack into your CDK app. Utilizing the `fromXxx` function, you have @@ -304,7 +304,7 @@ If you don't specify `graphqlArn` in `fromXxxAttributes`, CDK will autogenerate the expected `arn` for the imported api, given the `apiId`. For creating data sources and resolvers, an `apiId` is sufficient. -### Permissions +## Permissions When using `AWS_IAM` as the authorization type for GraphQL API, an IAM Role with correct permissions must be used for access to API. @@ -358,7 +358,7 @@ const api = new appsync.GraphqlApi(stack, 'API', { api.grant(role, appsync.IamResource.custom('types/Mutation/fields/updateExample'), 'appsync:GraphQL') ``` -#### IamResource +### IamResource In order to use the `grant` functions, you need to use the class `IamResource`. @@ -368,7 +368,7 @@ In order to use the `grant` functions, you need to use the class `IamResource`. - `IamResource.all()` permits ALL resources. -#### Generic Permissions +### Generic Permissions Alternatively, you can use more generic `grant` functions to accomplish the same usage. @@ -386,7 +386,7 @@ api.grantMutation(role, 'updateExample'); api.grant(role, appsync.IamResource.ofType('Mutation', 'updateExample'), 'appsync:GraphQL'); ``` -### Pipeline Resolvers and AppSync Functions +## Pipeline Resolvers and AppSync Functions AppSync Functions are local functions that perform certain operations onto a backend data source. Developers can compose operations (Functions) and execute @@ -418,7 +418,7 @@ const pipelineResolver = new appsync.Resolver(stack, 'pipeline', { Learn more about Pipeline Resolvers and AppSync Functions [here](https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html). -### Code-First Schema +## Code-First Schema CDK offers the ability to generate your schema in a code-first approach. A code-first approach offers a developer workflow with: @@ -429,7 +429,7 @@ A code-first approach offers a developer workflow with: The code-first approach allows for **dynamic** schema generation. You can generate your schema based on variables and templates to reduce code duplication. -#### Code-First Example +### Code-First Example To showcase the code-first approach. Let's try to model the following schema segment. @@ -545,7 +545,7 @@ create the base Object Type (i.e. Film) and from there we can generate its respe Check out a more in-depth example [here](https://github.com/BryanPan342/starwars-code-first). -#### GraphQL Types +## GraphQL Types One of the benefits of GraphQL is its strongly typed nature. We define the types within an object, query, mutation, interface, etc. as **GraphQL Types**. @@ -562,7 +562,7 @@ More concretely, GraphQL Types are simply the types appended to variables. Referencing the object type `Demo` in the previous example, the GraphQL Types is `String!` and is applied to both the names `id` and `version`. -#### Directives +### Directives `Directives` are attached to a field or type and affect the execution of queries, mutations, and types. With AppSync, we use `Directives` to configure authorization. @@ -577,12 +577,12 @@ through `Cognito User Pools` To learn more about authorization and directives, read these docs [here](https://docs.aws.amazon.com/appsync/latest/devguide/security.html). -#### Field and Resolvable Fields +### Field and Resolvable Fields While `GraphqlType` is a base implementation for GraphQL fields, we have abstractions on top of `GraphqlType` that provide finer grain support. -#### Field +### Field `Field` extends `GraphqlType` and will allow you to define arguments. [**Interface Types**](#Interface-Types) are not resolvable and this class will allow you to define arguments, but not its resolvers. @@ -609,7 +609,7 @@ const type = new appsync.InterfaceType('Node', { }); ``` -#### Resolvable Fields +### Resolvable Fields `ResolvableField` extends `Field` and will allow you to define arguments and its resolvers. [**Object Types**](#Object-Types) can have fields that resolve and perform operations on @@ -671,7 +671,7 @@ const query = new appsync.ObjectType('Query', { Learn more about fields and resolvers [here](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-overview.html). -#### Intermediate Types +### Intermediate Types Intermediate Types are defined by Graphql Types and Fields. They have a set of defined fields, where each field corresponds to another type in the system. Intermediate @@ -703,7 +703,7 @@ const node = new appsync.InterfaceType('Node', { To learn more about **Interface Types**, read the docs [here](https://graphql.org/learn/schema/#interfaces). -##### Object Types +#### Object Types **Object Types** are types that you declare. For example, in the [code-first example](#code-first-example) the `demo` variable is an **Object Type**. **Object Types** are defined by @@ -775,7 +775,7 @@ You can create Object Types in three ways: To learn more about **Object Types**, read the docs [here](https://graphql.org/learn/schema/#object-types-and-fields). -### Enum Types +#### Enum Types **Enum Types** are a special type of Intermediate Type. They restrict a particular set of allowed values for other Intermediate Types. @@ -832,7 +832,7 @@ api.addType(review); To learn more about **Input Types**, read the docs [here](https://graphql.org/learn/schema/#input-types). -### Union Types +#### Union Types **Union Types** are a special type of Intermediate Type. They are similar to Interface Types, but they cannot specify any common fields between types. @@ -860,7 +860,7 @@ api.addType(search); To learn more about **Union Types**, read the docs [here](https://graphql.org/learn/schema/#union-types). -#### Query +### Query Every schema requires a top level Query type. By default, the schema will look for the `Object Type` named `Query`. The top level `Query` is the **only** exposed @@ -883,7 +883,7 @@ api.addQuery('allFilms', new appsync.ResolvableField({ To learn more about top level operations, check out the docs [here](https://docs.aws.amazon.com/appsync/latest/devguide/graphql-overview.html). -#### Mutation +### Mutation Every schema **can** have a top level Mutation type. By default, the schema will look for the `ObjectType` named `Mutation`. The top level `Mutation` Type is the only exposed @@ -906,7 +906,7 @@ api.addMutation('addFilm', new appsync.ResolvableField({ To learn more about top level operations, check out the docs [here](https://docs.aws.amazon.com/appsync/latest/devguide/graphql-overview.html). -#### Subscription +### Subscription Every schema **can** have a top level Subscription type. The top level `Subscription` Type is the only exposed type that users can access to invoke a response to a mutation. `Subscriptions` diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 2d9addb93cf24..9dcc75719b77b 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -242,7 +242,6 @@ export interface GraphqlApiProps { * * @default - schema will be generated code-first (i.e. addType, addObjectType, etc.) * - * @experimental */ readonly schema?: Schema; /** @@ -583,7 +582,6 @@ export class GraphqlApi extends GraphqlApiBase { * @param delimiter the delimiter between schema and addition * @default - '' * - * @experimental */ public addToSchema(addition: string, delimiter?: string): void { this.schema.addToSchema(addition, delimiter); @@ -594,7 +592,6 @@ export class GraphqlApi extends GraphqlApiBase { * * @param type the intermediate type to add to the schema * - * @experimental */ public addType(type: IIntermediateType): IIntermediateType { return this.schema.addType(type); diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts index b824d2b29c4e6..fa876c395bc25 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts @@ -186,7 +186,6 @@ interface DirectiveOptions { * * i.e. @aws_iam or @aws_subscribe * - * @experimental */ export class Directive { /** diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts index b276644873a7d..f8cf13a698878 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts @@ -11,7 +11,6 @@ import { Type, IField, IIntermediateType, Directive } from './schema-base'; * @option isRequired - is this attribute non-nullable * @option isRequiredList - is this attribute a non-nullable list * - * @experimental */ export interface BaseTypeOptions { /** @@ -48,7 +47,6 @@ export interface BaseTypeOptions { * @option isRequiredList - is this attribute a non-nullable list * @option objectType - the object type linked to this attribute * - * @experimental */ export interface GraphqlTypeOptions extends BaseTypeOptions { /** diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts index 5d87ba2e7f130..6236a00ce1803 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts @@ -12,7 +12,6 @@ import { BaseTypeOptions, GraphqlType, ResolvableFieldOptions, ResolvableField } * i.e. { string: GraphqlType, string: GraphqlType } * @param directives - the directives for this object type * - * @experimental */ export interface IntermediateTypeOptions { /** @@ -31,7 +30,6 @@ export interface IntermediateTypeOptions { * Interface Types are abstract types that includes a certain set of fields * that other types must include if they implement the interface. * - * @experimental */ export class InterfaceType implements IIntermediateType { /** @@ -122,7 +120,6 @@ export class InterfaceType implements IIntermediateType { * @param interfaceTypes - the interfaces that this object type implements * @param directives - the directives for this object type * - * @experimental */ export interface ObjectTypeOptions extends IntermediateTypeOptions { /** @@ -136,7 +133,6 @@ export interface ObjectTypeOptions extends IntermediateTypeOptions { /** * Object Types are types declared by you. * - * @experimental */ export class ObjectType extends InterfaceType implements IIntermediateType { /** @@ -233,7 +229,6 @@ export class ObjectType extends InterfaceType implements IIntermediateType { * Input Types are abstract types that define complex objects. * They are used in arguments to represent * - * @experimental */ export class InputType implements IIntermediateType { /** @@ -309,7 +304,6 @@ export class InputType implements IIntermediateType { /** * Properties for configuring an Union Type * - * @experimental */ export interface UnionTypeOptions { /** @@ -325,7 +319,6 @@ export interface UnionTypeOptions { * Note that fields of a union type need to be object types. In other words, * you can't create a union type out of interfaces, other unions, or inputs. * - * @experimental */ export class UnionType implements IIntermediateType { /** @@ -405,7 +398,6 @@ export class UnionType implements IIntermediateType { /** * Properties for configuring an Enum Type * - * @experimental */ export interface EnumTypeOptions { /** @@ -418,7 +410,6 @@ export interface EnumTypeOptions { * Enum Types are abstract types that includes a set of fields * that represent the strings this type can create. * - * @experimental */ export class EnumType implements IIntermediateType { /** diff --git a/packages/@aws-cdk/aws-appsync/lib/schema.ts b/packages/@aws-cdk/aws-appsync/lib/schema.ts index 1ae6964bda346..fb22204c34440 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema.ts @@ -98,7 +98,6 @@ export class Schema { * @param delimiter the delimiter between schema and addition * @default - '' * - * @experimental */ public addToSchema(addition: string, delimiter?: string): void { if (this.mode !== SchemaMode.CODE) { @@ -186,7 +185,6 @@ export class Schema { * * @param type the intermediate type to add to the schema * - * @experimental */ public addType(type: IIntermediateType): IIntermediateType { if (this.mode !== SchemaMode.CODE) { diff --git a/packages/@aws-cdk/aws-certificatemanager/jest.config.js b/packages/@aws-cdk/aws-certificatemanager/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-certificatemanager/jest.config.js +++ b/packages/@aws-cdk/aws-certificatemanager/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/lib/index.js b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/lib/index.js index 866e9405b049e..d34f7922f4e7e 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/lib/index.js +++ b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/lib/index.js @@ -2,7 +2,7 @@ const aws = require('aws-sdk'); -const defaultSleep = function(ms) { +const defaultSleep = function (ms) { return new Promise(resolve => setTimeout(resolve, ms)); }; @@ -24,7 +24,7 @@ let maxAttempts = 10; * @param {string} [reason] reason for failure, if any, to convey to the user * @returns {Promise} Promise that is resolved on success, or rejected on connection error or HTTP error response */ -let report = function(event, context, responseStatus, physicalResourceId, responseData, reason) { +let report = function (event, context, responseStatus, physicalResourceId, responseData, reason) { return new Promise((resolve, reject) => { const https = require('https'); const { URL } = require('url'); @@ -75,12 +75,13 @@ let report = function(event, context, responseStatus, physicalResourceId, respon * @param {string} requestId the CloudFormation request ID * @param {string} domainName the Common Name (CN) field for the requested certificate * @param {string} hostedZoneId the Route53 Hosted Zone ID + * @param {map} tags Tags to add to the requested certificate * @returns {string} Validated certificate ARN */ -const requestCertificate = async function(requestId, domainName, subjectAlternativeNames, hostedZoneId, region, route53Endpoint) { +const requestCertificate = async function (requestId, domainName, subjectAlternativeNames, hostedZoneId, region, route53Endpoint, tags) { const crypto = require('crypto'); const acm = new aws.ACM({ region }); - const route53 = route53Endpoint ? new aws.Route53({endpoint: route53Endpoint}) : new aws.Route53(); + const route53 = route53Endpoint ? new aws.Route53({ endpoint: route53Endpoint }) : new aws.Route53(); if (waiter) { // Used by the test suite, since waiters aren't mockable yet route53.waitFor = acm.waitFor = waiter; @@ -97,6 +98,16 @@ const requestCertificate = async function(requestId, domainName, subjectAlternat console.log(`Certificate ARN: ${reqCertResponse.CertificateArn}`); + + if (!!tags) { + const result = Array.from(Object.entries(tags)).map(([Key, Value]) => ({ Key, Value })) + + await acm.addTagsToCertificate({ + CertificateArn: reqCertResponse.CertificateArn, + Tags: result, + }).promise(); + } + console.log('Waiting for ACM to provide DNS records for validation...'); let records; @@ -129,6 +140,7 @@ const requestCertificate = async function(requestId, domainName, subjectAlternat throw new Error(`Response from describeCertificate did not contain DomainValidationOptions after ${maxAttempts} attempts.`) } + console.log(`Upserting ${records.length} DNS records into zone ${hostedZoneId}:`); const changeBatch = await route53.changeResourceRecordSets({ @@ -180,7 +192,7 @@ const requestCertificate = async function(requestId, domainName, subjectAlternat * * @param {string} arn The certificate ARN */ -const deleteCertificate = async function(arn, region) { +const deleteCertificate = async function (arn, region) { const acm = new aws.ACM({ region }); try { @@ -224,7 +236,7 @@ const deleteCertificate = async function(arn, region) { /** * Main handler, invoked by Lambda */ -exports.certificateRequestHandler = async function(event, context) { +exports.certificateRequestHandler = async function (event, context) { var responseData = {}; var physicalResourceId; var certificateArn; @@ -240,6 +252,7 @@ exports.certificateRequestHandler = async function(event, context) { event.ResourceProperties.HostedZoneId, event.ResourceProperties.Region, event.ResourceProperties.Route53Endpoint, + event.ResourceProperties.Tags, ); responseData.Arn = physicalResourceId = certificateArn; break; @@ -267,69 +280,69 @@ exports.certificateRequestHandler = async function(event, context) { /** * @private */ -exports.withReporter = function(reporter) { +exports.withReporter = function (reporter) { report = reporter; }; /** * @private */ -exports.withDefaultResponseURL = function(url) { +exports.withDefaultResponseURL = function (url) { defaultResponseURL = url; }; /** * @private */ -exports.withWaiter = function(w) { +exports.withWaiter = function (w) { waiter = w; }; /** * @private */ -exports.resetWaiter = function() { +exports.resetWaiter = function () { waiter = undefined; }; /** * @private */ -exports.withSleep = function(s) { +exports.withSleep = function (s) { sleep = s; } /** * @private */ -exports.resetSleep = function() { +exports.resetSleep = function () { sleep = defaultSleep; } /** * @private */ -exports.withRandom = function(r) { +exports.withRandom = function (r) { random = r; } /** * @private */ -exports.resetRandom = function() { +exports.resetRandom = function () { random = Math.random; } /** * @private */ -exports.withMaxAttempts = function(ma) { +exports.withMaxAttempts = function (ma) { maxAttempts = ma; } /** * @private */ -exports.resetMaxAttempts = function() { +exports.resetMaxAttempts = function () { maxAttempts = 10; } 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 fd561b065d25e..a73a32e430540 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json +++ b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json @@ -29,7 +29,7 @@ "devDependencies": { "aws-sdk": "^2.596.0", "aws-sdk-mock": "^5.1.0", - "eslint": "^7.23.0", + "eslint": "^7.24.0", "eslint-config-standard": "^14.1.1", "eslint-plugin-import": "^2.22.1", "eslint-plugin-node": "^11.1.0", diff --git a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/test/handler.test.js b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/test/handler.test.js index 3e93f09680a91..5b53bc0a46301 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/test/handler.test.js +++ b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/test/handler.test.js @@ -20,13 +20,15 @@ describe('DNS Validated Certificate Handler', () => { const testRRValue = '_x2.acm-validations.aws'; const testAltRRName = '_3639ac514e785e898d2646601fa951d5.foo.example.com'; const testAltRRValue = '_x3.acm-validations.aws'; - const spySleep = sinon.spy(function(ms) { + const testTags = { Tag1: 'Test1', Tag2: 'Test2' }; + const testTagsValue = [{ Key: 'Tag1', Value: 'Test1' }, { Key: 'Tag2', Value: 'Test2' }]; + const spySleep = sinon.spy(function (ms) { return Promise.resolve(); }); beforeEach(() => { handler.withDefaultResponseURL(ResponseURL); - handler.withWaiter(function() { + handler.withWaiter(function () { // Mock waiter is merely a self-fulfilling promise return { promise: () => { @@ -37,7 +39,7 @@ describe('DNS Validated Certificate Handler', () => { }; }); handler.withSleep(spySleep); - console.log = function() { }; + console.log = function () { }; }); afterEach(() => { // Restore waiters and logger @@ -99,6 +101,8 @@ describe('DNS Validated Certificate Handler', () => { } }); + const addTagsToCertificateFake = sinon.fake.resolves({}); + const changeResourceRecordSetsFake = sinon.fake.resolves({ ChangeInfo: { Id: 'bogus' @@ -108,6 +112,7 @@ describe('DNS Validated Certificate Handler', () => { AWS.mock('ACM', 'requestCertificate', requestCertificateFake); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); AWS.mock('Route53', 'changeResourceRecordSets', changeResourceRecordSetsFake); + AWS.mock('ACM', 'addTagsToCertificate', addTagsToCertificateFake); const request = nock(ResponseURL).put('/', body => { return body.Status === 'SUCCESS'; @@ -121,6 +126,7 @@ describe('DNS Validated Certificate Handler', () => { DomainName: testDomainName, HostedZoneId: testHostedZoneId, Region: 'us-east-1', + Tags: testTags } }) .expectResolve(() => { @@ -144,6 +150,10 @@ describe('DNS Validated Certificate Handler', () => { }, HostedZoneId: testHostedZoneId })); + sinon.assert.calledWith(addTagsToCertificateFake, sinon.match({ + "CertificateArn": testCertificateArn, + "Tags": testTagsValue, + })); expect(request.isDone()).toBe(true); }); }); @@ -182,14 +192,21 @@ describe('DNS Validated Certificate Handler', () => { } }); + const addTagsToCertificateFake = sinon.fake.resolves({ + Certificate: testCertificateArn, + Tags: testTags, + }); + const changeResourceRecordSetsFake = sinon.fake.resolves({ ChangeInfo: { Id: 'bogus' } }); + AWS.mock('ACM', 'requestCertificate', requestCertificateFake); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); + AWS.mock('ACM', 'addTagsToCertificate', addTagsToCertificateFake); AWS.mock('Route53', 'changeResourceRecordSets', changeResourceRecordSetsFake); const request = nock(ResponseURL).put('/', body => { @@ -205,6 +222,7 @@ describe('DNS Validated Certificate Handler', () => { SubjectAlternativeNames: [testSubjectAlternativeName], HostedZoneId: testHostedZoneId, Region: 'us-east-1', + Tags: testTags, } }) .expectResolve(() => { @@ -241,6 +259,10 @@ describe('DNS Validated Certificate Handler', () => { }, HostedZoneId: testHostedZoneId })); + sinon.assert.calledWith(addTagsToCertificateFake, sinon.match({ + "CertificateArn": testCertificateArn, + "Tags": testTagsValue, + })); expect(request.isDone()).toBe(true); }); }); @@ -286,6 +308,11 @@ describe('DNS Validated Certificate Handler', () => { } }); + const addTagsToCertificateFake = sinon.fake.resolves({ + Certificate: testCertificateArn, + Tags: testTags, + }); + const changeResourceRecordSetsFake = sinon.fake.resolves({ ChangeInfo: { Id: 'bogus' @@ -294,6 +321,7 @@ describe('DNS Validated Certificate Handler', () => { AWS.mock('ACM', 'requestCertificate', requestCertificateFake); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); + AWS.mock('ACM', 'addTagsToCertificate', addTagsToCertificateFake); AWS.mock('Route53', 'changeResourceRecordSets', changeResourceRecordSetsFake); const request = nock(ResponseURL).put('/', body => { @@ -308,6 +336,7 @@ describe('DNS Validated Certificate Handler', () => { DomainName: testDomainName, HostedZoneId: testHostedZoneId, Region: 'us-east-1', + Tags: testTags, } }) .expectResolve(() => { @@ -343,6 +372,10 @@ describe('DNS Validated Certificate Handler', () => { }, HostedZoneId: testHostedZoneId })); + sinon.assert.calledWith(addTagsToCertificateFake, sinon.match({ + "CertificateArn": testCertificateArn, + "Tags": testTagsValue, + })); expect(request.isDone()).toBe(true); }); }); @@ -453,6 +486,12 @@ describe('DNS Validated Certificate Handler', () => { }); AWS.mock('ACM', 'describeCertificate', describeCertificateFake); + const addTagsToCertificateFake = sinon.fake.resolves({ + Certificate: testCertificateArn, + Tags: testTags, + }); + AWS.mock('ACM', 'addTagsToCertificate', addTagsToCertificateFake); + const changeResourceRecordSetsFake = sinon.fake.resolves({ ChangeInfo: { Id: 'bogus' @@ -472,6 +511,7 @@ describe('DNS Validated Certificate Handler', () => { DomainName: testDomainName, HostedZoneId: testHostedZoneId, Region: 'us-east-1', + Tags: testTags, } }) .expectResolve(() => { @@ -479,6 +519,93 @@ describe('DNS Validated Certificate Handler', () => { sinon.assert.calledWith(describeCertificateFake, sinon.match({ CertificateArn: testCertificateArn, })); + sinon.assert.calledWith(addTagsToCertificateFake, sinon.match({ + "CertificateArn": testCertificateArn, + "Tags": testTagsValue, + })); + expect(request.isDone()).toBe(true); + }); + }); + + test('Create operation succeeds with no tags passed', () => { + const requestCertificateFake = sinon.fake.resolves({ + CertificateArn: testCertificateArn, + }); + + const describeCertificateFake = sinon.stub(); + describeCertificateFake.onFirstCall().resolves({ + Certificate: { + CertificateArn: testCertificateArn + } + }); + describeCertificateFake.resolves({ + Certificate: { + CertificateArn: testCertificateArn, + DomainValidationOptions: [{ + ValidationStatus: 'SUCCESS', + ResourceRecord: { + Name: testRRName, + Type: 'CNAME', + Value: testRRValue + } + }] + } + }); + + const changeResourceRecordSetsFake = sinon.fake.resolves({ + ChangeInfo: { + Id: 'bogus' + } + }); + + const addTagsToCertificateFake = sinon.fake.resolves({ + Certificate: testCertificateArn, + }); + + AWS.mock('ACM', 'requestCertificate', requestCertificateFake); + AWS.mock('ACM', 'describeCertificate', describeCertificateFake); + AWS.mock('Route53', 'changeResourceRecordSets', changeResourceRecordSetsFake); + AWS.mock('ACM', 'addTagsToCertificate', addTagsToCertificateFake); + + const request = nock(ResponseURL).put('/', body => { + return body.Status === 'SUCCESS'; + }).reply(200); + + return LambdaTester(handler.certificateRequestHandler) + .event({ + RequestType: 'Create', + RequestId: testRequestId, + ResourceProperties: { + DomainName: testDomainName, + HostedZoneId: testHostedZoneId, + Region: 'us-east-1' + } + }) + .expectResolve(() => { + sinon.assert.calledWith(requestCertificateFake, sinon.match({ + DomainName: testDomainName, + ValidationMethod: 'DNS' + })); + sinon.assert.calledWith(changeResourceRecordSetsFake, sinon.match({ + ChangeBatch: { + Changes: [{ + Action: 'UPSERT', + ResourceRecordSet: { + Name: testRRName, + Type: 'CNAME', + TTL: 60, + ResourceRecords: [{ + Value: testRRValue + }] + } + }] + }, + HostedZoneId: testHostedZoneId + })); + sinon.assert.neverCalledWith(addTagsToCertificateFake, sinon.match({ + "CertificateArn": testCertificateArn, + "Tags": testTagsValue, + })); expect(request.isDone()).toBe(true); }); }); diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts b/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts index 6bb389afea97e..2c47ad9c49d9e 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts @@ -9,7 +9,6 @@ import { CertificateProps, ICertificate } from './certificate'; /** * Properties to create a DNS validated certificate managed by AWS Certificate Manager * - * @experimental */ export interface DnsValidatedCertificateProps extends CertificateProps { /** @@ -46,6 +45,7 @@ export interface DnsValidatedCertificateProps extends CertificateProps { * @default - A new role will be created */ readonly customResourceRole?: iam.IRole; + } /** @@ -53,10 +53,16 @@ export interface DnsValidatedCertificateProps extends CertificateProps { * validated using DNS validation against the specified Route 53 hosted zone. * * @resource AWS::CertificateManager::Certificate - * @experimental */ -export class DnsValidatedCertificate extends cdk.Resource implements ICertificate { +export class DnsValidatedCertificate extends cdk.Resource implements ICertificate, cdk.ITaggable { public readonly certificateArn: string; + + /** + * Resource Tags. + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-certificatemanager-certificate.html#cfn-certificatemanager-certificate-tags + */ + + public readonly tags: cdk.TagManager; private normalizedZoneName: string; private hostedZoneId: string; private domainName: string; @@ -73,6 +79,7 @@ export class DnsValidatedCertificate extends cdk.Resource implements ICertificat // Remove any `/hostedzone/` prefix from the Hosted Zone ID this.hostedZoneId = props.hostedZone.hostedZoneId.replace(/^\/hostedzone\//, ''); + this.tags = new cdk.TagManager(cdk.TagType.MAP, 'AWS::CertificateManager::Certificate'); const requestorFunction = new lambda.Function(this, 'CertificateRequestorFunction', { code: lambda.Code.fromAsset(path.resolve(__dirname, '..', 'lambda-packages', 'dns_validated_certificate_handler', 'lib')), @@ -82,7 +89,7 @@ export class DnsValidatedCertificate extends cdk.Resource implements ICertificat role: props.customResourceRole, }); requestorFunction.addToRolePolicy(new iam.PolicyStatement({ - actions: ['acm:RequestCertificate', 'acm:DescribeCertificate', 'acm:DeleteCertificate'], + actions: ['acm:RequestCertificate', 'acm:DescribeCertificate', 'acm:DeleteCertificate', 'acm:AddTagsToCertificate'], resources: ['*'], })); requestorFunction.addToRolePolicy(new iam.PolicyStatement({ @@ -102,6 +109,7 @@ export class DnsValidatedCertificate extends cdk.Resource implements ICertificat HostedZoneId: this.hostedZoneId, Region: props.region, Route53Endpoint: props.route53Endpoint, + Tags: cdk.Lazy.list({ produce: () => this.tags.renderTags() }), }, }); @@ -112,8 +120,8 @@ export class DnsValidatedCertificate extends cdk.Resource implements ICertificat const errors: string[] = []; // Ensure the zone name is a parent zone of the certificate domain name if (!cdk.Token.isUnresolved(this.normalizedZoneName) && - this.domainName !== this.normalizedZoneName && - !this.domainName.endsWith('.' + this.normalizedZoneName)) { + this.domainName !== this.normalizedZoneName && + !this.domainName.endsWith('.' + this.normalizedZoneName)) { errors.push(`DNS zone ${this.normalizedZoneName} is not authoritative for certificate domain name ${this.domainName}`); } return errors; diff --git a/packages/@aws-cdk/aws-certificatemanager/test/dns-validated-certificate.test.ts b/packages/@aws-cdk/aws-certificatemanager/test/dns-validated-certificate.test.ts index 5074a69b3ac62..1b0a667b52738 100644 --- a/packages/@aws-cdk/aws-certificatemanager/test/dns-validated-certificate.test.ts +++ b/packages/@aws-cdk/aws-certificatemanager/test/dns-validated-certificate.test.ts @@ -2,7 +2,7 @@ import '@aws-cdk/assert-internal/jest'; import { SynthUtils } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import { HostedZone, PublicHostedZone } from '@aws-cdk/aws-route53'; -import { App, Stack, Token } from '@aws-cdk/core'; +import { App, Stack, Token, Tags } from '@aws-cdk/core'; import { DnsValidatedCertificate } from '../lib/dns-validated-certificate'; test('creates CloudFormation Custom Resource', () => { @@ -49,6 +49,7 @@ test('creates CloudFormation Custom Resource', () => { 'acm:RequestCertificate', 'acm:DescribeCertificate', 'acm:DeleteCertificate', + 'acm:AddTagsToCertificate', ], Effect: 'Allow', Resource: '*', @@ -136,6 +137,36 @@ test('test root certificate', () => { }); }); +test('test tags are passed to customresource', () => { + const stack = new Stack(); + Tags.of(stack).add('Key1', 'Value1'); + + const exampleDotComZone = new PublicHostedZone(stack, 'ExampleDotCom', { + zoneName: 'example.com', + }); + + new DnsValidatedCertificate(stack, 'Cert', { + domainName: 'example.com', + hostedZone: exampleDotComZone, + }); + + expect(stack).toHaveResource('AWS::CloudFormation::CustomResource', { + ServiceToken: { + 'Fn::GetAtt': [ + 'CertCertificateRequestorFunction98FDF273', + 'Arn', + ], + }, + DomainName: 'example.com', + HostedZoneId: { + Ref: 'ExampleDotCom4D1B83AA', + }, + Tags: { + Key1: 'Value1', + }, + }); +}); + test('works with imported zone', () => { // GIVEN const app = new App(); diff --git a/packages/@aws-cdk/aws-cloudformation/package.json b/packages/@aws-cdk/aws-cloudformation/package.json index c75595f815d31..c9d816c42a815 100644 --- a/packages/@aws-cdk/aws-cloudformation/package.json +++ b/packages/@aws-cdk/aws-cloudformation/package.json @@ -72,7 +72,7 @@ "@aws-cdk/aws-sns-subscriptions": "0.0.0", "@aws-cdk/aws-sqs": "0.0.0", "@aws-cdk/aws-ssm": "0.0.0", - "@types/aws-lambda": "^8.10.73", + "@types/aws-lambda": "^8.10.75", "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index 02e3d092295f3..3482d53e9624c 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -279,6 +279,12 @@ export class Distribution extends Resource implements IDistribution { this.certificate = props.certificate; this.errorResponses = props.errorResponses ?? []; + // Comments have an undocumented limit of 128 characters + const trimmedComment = + props.comment && props.comment.length > 128 + ? `${props.comment.substr(0, 128 - 3)}...` + : props.comment; + const distribution = new CfnDistribution(this, 'Resource', { distributionConfig: { enabled: props.enabled ?? true, @@ -287,7 +293,7 @@ export class Distribution extends Resource implements IDistribution { defaultCacheBehavior: this.defaultBehavior._renderBehavior(), aliases: props.domainNames, cacheBehaviors: Lazy.any({ produce: () => this.renderCacheBehaviors() }), - comment: props.comment, + comment: trimmedComment, customErrorResponses: this.renderErrorResponses(), defaultRootObject: props.defaultRootObject, httpVersion: props.httpVersion ?? HttpVersion.HTTP2, 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 ab8d94e79baa1..dee1f9cbbce50 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts @@ -15,7 +15,6 @@ import { Construct } from 'constructs'; /** * Properties for creating a Lambda@Edge function - * @experimental */ export interface EdgeFunctionProps extends lambda.FunctionProps { /** @@ -36,7 +35,6 @@ export interface EdgeFunctionProps extends lambda.FunctionProps { * See https://docs.aws.amazon.com/cdk/latest/guide/bootstrapping.html or 'cdk bootstrap --help' for options. * * @resource AWS::Lambda::Function - * @experimental */ export class EdgeFunction extends Resource implements lambda.IVersion { diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts index 87836434fa141..17aafe5e4f6fd 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts @@ -769,8 +769,14 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu constructor(scope: Construct, id: string, props: CloudFrontWebDistributionProps) { super(scope, id); + // Comments have an undocumented limit of 128 characters + const trimmedComment = + props.comment && props.comment.length > 128 + ? `${props.comment.substr(0, 128 - 3)}...` + : props.comment; + let distributionConfig: CfnDistribution.DistributionConfigProperty = { - comment: props.comment, + comment: trimmedComment, enabled: true, defaultRootObject: props.defaultRootObject ?? 'index.html', httpVersion: props.httpVersion || HttpVersion.HTTP2, diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts index 1f4fb64e01dc9..f6294ad1b6d08 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts @@ -107,6 +107,40 @@ test('exhaustive example of props renders correctly', () => { }); }); +test('ensure comment prop is not greater than max lenght', () => { + const origin = defaultOrigin(); + new Distribution(stack, 'MyDist', { + defaultBehavior: { origin }, + comment: `Adding a comment longer than 128 characters should be trimmed and added the +ellipsis so a user would know there was more to read and everything beyond this point should not show up`, + }); + + expect(stack).toHaveResource('AWS::CloudFront::Distribution', { + DistributionConfig: { + DefaultCacheBehavior: { + CachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6', + Compress: true, + TargetOriginId: 'StackMyDistOrigin1D6D5E535', + ViewerProtocolPolicy: 'allow-all', + }, + Comment: `Adding a comment longer than 128 characters should be trimmed and added the +ellipsis so a user would know there was more to ...`, + Enabled: true, + HttpVersion: 'http2', + IPV6Enabled: true, + Origins: [ + { + DomainName: 'www.example.com', + Id: 'StackMyDistOrigin1D6D5E535', + CustomOriginConfig: { + OriginProtocolPolicy: 'https-only', + }, + }, + ], + }, + }); +}); + describe('multiple behaviors', () => { test('a second behavior can\'t be specified with the catch-all path pattern', () => { 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 bb67af6535c10..6e10b54defc77 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts @@ -198,6 +198,78 @@ nodeunitShim({ test.done(); }, + 'ensure long comments will not break the distribution'(test: Test) { + const stack = new cdk.Stack(); + const sourceBucket = new s3.Bucket(stack, 'Bucket'); + + new CloudFrontWebDistribution(stack, 'AnAmazingWebsiteProbably', { + comment: `Adding a comment longer than 128 characters should be trimmed and +added the ellipsis so a user would know there was more to read and everything beyond this point should not show up`, + originConfigs: [ + { + s3OriginSource: { + s3BucketSource: sourceBucket, + }, + behaviors: [ + { + isDefaultBehavior: true, + }, + ], + }, + ], + }); + + expect(stack).toMatch({ + Resources: { + Bucket83908E77: { + Type: 'AWS::S3::Bucket', + DeletionPolicy: 'Retain', + UpdateReplacePolicy: 'Retain', + }, + AnAmazingWebsiteProbablyCFDistribution47E3983B: { + Type: 'AWS::CloudFront::Distribution', + Properties: { + DistributionConfig: { + DefaultRootObject: 'index.html', + Origins: [ + { + ConnectionAttempts: 3, + ConnectionTimeout: 10, + DomainName: { + 'Fn::GetAtt': ['Bucket83908E77', 'RegionalDomainName'], + }, + Id: 'origin1', + S3OriginConfig: {}, + }, + ], + ViewerCertificate: { + CloudFrontDefaultCertificate: true, + }, + PriceClass: 'PriceClass_100', + DefaultCacheBehavior: { + AllowedMethods: ['GET', 'HEAD'], + CachedMethods: ['GET', 'HEAD'], + TargetOriginId: 'origin1', + ViewerProtocolPolicy: 'redirect-to-https', + ForwardedValues: { + QueryString: false, + Cookies: { Forward: 'none' }, + }, + Compress: true, + }, + Comment: `Adding a comment longer than 128 characters should be trimmed and +added the ellipsis so a user would know there was more to ...`, + Enabled: true, + IPV6Enabled: true, + HttpVersion: 'http2', + }, + }, + }, + }, + }); + test.done(); + }, + 'distribution with bucket and OAI'(test: Test) { const stack = new cdk.Stack(); const s3BucketSource = new s3.Bucket(stack, 'Bucket'); diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index c0e82f9a6b708..2fb82c887ba39 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -648,3 +648,17 @@ new codebuild.Project(stack, 'MyProject', { queuedTimeout: Duration.minutes(30) }); ``` + +## Limiting concurrency + +By default if a new build is triggered it will be run even if there is a previous build already in progress. +It is possible to limit the maximum concurrent builds to value between 1 and the account specific maximum limit. +By default there is no explicit limit. + +```ts +import * as codebuild from '@aws-cdk/aws-codebuild'; + +new codebuild.Project(stack, 'MyProject', { + concurrentBuildLimit: 1 +}); +``` diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index abca536a2c354..14d435e8e8cbc 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -7,7 +7,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 { ArnComponents, Aws, Duration, IResource, Lazy, Names, PhysicalName, Resource, SecretValue, Stack, Token, Tokenization } from '@aws-cdk/core'; +import { ArnComponents, Aws, Duration, IResource, Lazy, Names, PhysicalName, Resource, SecretValue, Stack, Token, TokenComparison, Tokenization } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IArtifacts } from './artifacts'; import { BuildSpec } from './build-spec'; @@ -584,6 +584,13 @@ export interface CommonProjectProps { * @default - no queue timeout is set */ readonly queuedTimeout?: Duration + + /** + * Maximum number of concurrent builds. Minimum value is 1 and maximum is account build limit. + * + * @default - no explicit limit is set + */ + readonly concurrentBuildLimit?: number } export interface ProjectProps extends CommonProjectProps { @@ -714,6 +721,7 @@ export class Project extends ProjectBase { const ret = new Array(); const ssmIamResources = new Array(); const secretsManagerIamResources = new Array(); + const kmsIamResources = new Set(); for (const [name, envVariable] of Object.entries(environmentVariables)) { const envVariableValue = envVariable.value?.toString(); @@ -783,7 +791,7 @@ export class Project extends ProjectBase { // If we were given just a name, it must be partial, as CodeBuild doesn't support providing full names. // In this case, we need to accommodate for the generated suffix in the IAM resource name : `${secretName}-??????`; - secretsManagerIamResources.push(Stack.of(principal).formatArn({ + secretsManagerIamResources.push(stack.formatArn({ service: 'secretsmanager', resource: 'secret', resourceName: secretIamResourceName, @@ -793,6 +801,22 @@ export class Project extends ProjectBase { account: parsedArn?.account, region: parsedArn?.region, })); + // if secret comes from another account, SecretsManager will need to access + // KMS on the other account as well to be able to get the secret + if (parsedArn && parsedArn.account && Token.compareStrings(parsedArn.account, stack.account) === TokenComparison.DIFFERENT) { + kmsIamResources.add(stack.formatArn({ + service: 'kms', + resource: 'key', + // 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: '/', + // if we were given an ARN, we need to use the provided partition/account/region + partition: parsedArn.partition, + account: parsedArn.account, + region: parsedArn.region, + })); + } } } } @@ -810,6 +834,12 @@ export class Project extends ProjectBase { resources: secretsManagerIamResources, })); } + if (kmsIamResources.size !== 0) { + principal?.grantPrincipal.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['kms:Decrypt'], + resources: Array.from(kmsIamResources), + })); + } return ret; } @@ -917,6 +947,7 @@ export class Project extends ProjectBase { name: this.physicalName, timeoutInMinutes: props.timeout && props.timeout.toMinutes(), queuedTimeoutInMinutes: props.queuedTimeout && props.queuedTimeout.toMinutes(), + concurrentBuildLimit: props.concurrentBuildLimit, secondarySources: Lazy.any({ produce: () => this.renderSecondarySources() }), secondarySourceVersions: Lazy.any({ produce: () => this.renderSecondarySourceVersions() }), secondaryArtifacts: Lazy.any({ produce: () => this.renderSecondaryArtifacts() }), diff --git a/packages/@aws-cdk/aws-codebuild/test/test.project.ts b/packages/@aws-cdk/aws-codebuild/test/test.project.ts index 602db3508eabb..e1ecb633aa3ff 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.project.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.project.ts @@ -1057,6 +1057,17 @@ export = { }, })); + // THEN + expect(stack).to(not(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'kms:Decrypt', + 'Effect': 'Allow', + 'Resource': 'arn:aws:kms:us-west-2:123456789012:key/*', + }), + }, + }))); + test.done(); }, @@ -1098,6 +1109,83 @@ export = { }, })); + // THEN + expect(stack).to(not(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'kms:Decrypt', + 'Effect': 'Allow', + 'Resource': 'arn:aws:kms:us-west-2:123456789012:key/*', + }), + }, + }))); + + test.done(); + }, + + "when provided as a verbatim partial secret ARN from another account, adds permission to decrypt keys in the Secret's account"(test: Test) { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'ProjectStack', { + env: { account: '123456789012' }, + }); + + // WHEN + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: 'arn:aws:secretsmanager:us-west-2:901234567890:secret:mysecret', + }, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'kms:Decrypt', + 'Effect': 'Allow', + 'Resource': 'arn:aws:kms:us-west-2:901234567890:key/*', + }), + }, + })); + + test.done(); + }, + + 'when two secrets from another account are provided as verbatim partial secret ARNs, adds only one permission for decrypting'(test: Test) { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'ProjectStack', { + env: { account: '123456789012' }, + }); + + // WHEN + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: 'arn:aws:secretsmanager:us-west-2:901234567890:secret:mysecret', + }, + 'ENV_VAR2': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: 'arn:aws:secretsmanager:us-west-2:901234567890:secret:other-secret', + }, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'kms:Decrypt', + 'Effect': 'Allow', + 'Resource': 'arn:aws:kms:us-west-2:901234567890:key/*', + }), + }, + })); + test.done(); }, @@ -1409,6 +1497,29 @@ export = { }, }, + 'Maximum concurrency': { + 'can limit maximum concurrency'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.Project(stack, 'Project', { + source: codebuild.Source.s3({ + bucket: new s3.Bucket(stack, 'Bucket'), + path: 'path', + }), + concurrentBuildLimit: 1, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + ConcurrentBuildLimit: 1, + })); + + test.done(); + }, + }, + 'can be imported': { 'by ARN'(test: Test) { const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/README.md b/packages/@aws-cdk/aws-codepipeline-actions/README.md index 7be56cd3c8017..89a94d4918697 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/README.md +++ b/packages/@aws-cdk/aws-codepipeline-actions/README.md @@ -828,24 +828,16 @@ new codepipeline_actions.AlexaSkillDeployAction({ ### AWS Service Catalog -You can deploy a CloudFormation template to an existing Service Catalog product with the following action: +You can deploy a CloudFormation template to an existing Service Catalog product with the following Action: ```ts -new codepipeline.Pipeline(this, 'Pipeline', { - stages: [ - { - stageName: 'ServiceCatalogDeploy', - actions: [ - new codepipeline_actions.ServiceCatalogDeployAction({ - actionName: 'ServiceCatalogDeploy', - templatePath: cdkBuildOutput.atPath("Sample.template.json"), - productVersionName: "Version - " + Date.now.toString, - productType: "CLOUD_FORMATION_TEMPLATE", - productVersionDescription: "This is a version from the pipeline with a new description.", - productId: "prod-XXXXXXXX", - }), - }, - ], +const serviceCatalogDeployAction = new codepipeline_actions.ServiceCatalogDeployActionBeta1({ + actionName: 'ServiceCatalogDeploy', + templatePath: cdkBuildOutput.atPath("Sample.template.json"), + productVersionName: "Version - " + Date.now.toString, + productType: "CLOUD_FORMATION_TEMPLATE", + productVersionDescription: "This is a version from the pipeline with a new description.", + productId: "prod-XXXXXXXX", }); ``` diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/action.ts index c2f7ca5ca5305..f6e8a58d27329 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/action.ts @@ -1,125 +1,15 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; -import * as events from '@aws-cdk/aws-events'; -import { Lazy } 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'; /** * Low-level class for generic CodePipeline Actions. - * - * WARNING: this class should not be externally exposed, but is currently visible - * because of a limitation of jsii (https://github.com/aws/jsii/issues/524). - * - * This class will disappear in a future release and should not be used. - * - * @experimental + * If you're implementing your own IAction, + * prefer to use the Action class from the codepipeline module. */ -export abstract class Action implements codepipeline.IAction { - public readonly actionProperties: codepipeline.ActionProperties; - private _pipeline?: codepipeline.IPipeline; - private _stage?: codepipeline.IStage; - private _scope?: Construct; - private readonly customerProvidedNamespace?: string; - private readonly namespaceOrToken: string; - private actualNamespace?: string; - private variableReferenced = false; +export abstract class Action extends codepipeline.Action { + protected readonly providedActionProperties: codepipeline.ActionProperties; protected constructor(actionProperties: codepipeline.ActionProperties) { - this.customerProvidedNamespace = actionProperties.variablesNamespace; - this.namespaceOrToken = Lazy.string({ - produce: () => { - // make sure the action was bound (= added to a pipeline) - if (this.actualNamespace !== undefined) { - return this.customerProvidedNamespace !== undefined - // if a customer passed a namespace explicitly, always use that - ? this.customerProvidedNamespace - // otherwise, only return a namespace if any variable was referenced - : (this.variableReferenced ? this.actualNamespace : undefined); - } else { - throw new Error(`Cannot reference variables of action '${this.actionProperties.actionName}', ` + - 'as that action was never added to a pipeline'); - } - }, - }); - this.actionProperties = { - ...actionProperties, - variablesNamespace: this.namespaceOrToken, - }; - } - - public bind(scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): - codepipeline.ActionConfig { - this._pipeline = stage.pipeline; - this._stage = stage; - this._scope = scope; - - this.actualNamespace = this.customerProvidedNamespace === undefined - // default a namespace name, based on the stage and action names - ? `${stage.stageName}_${this.actionProperties.actionName}_NS` - : this.customerProvidedNamespace; - - return this.bound(scope, stage, options); - } - - public onStateChange(name: string, target?: events.IRuleTarget, options?: events.RuleProps) { - const rule = new events.Rule(this.scope, name, options); - rule.addTarget(target); - rule.addEventPattern({ - detailType: ['CodePipeline Action Execution State Change'], - source: ['aws.codepipeline'], - resources: [this.pipeline.pipelineArn], - detail: { - stage: [this.stage.stageName], - action: [this.actionProperties.actionName], - }, - }); - return rule; - } - - protected variableExpression(variableName: string): string { - this.variableReferenced = true; - return `#{${this.namespaceOrToken}.${variableName}}`; - } - - /** - * The method called when an Action is attached to a Pipeline. - * This method is guaranteed to be called only once for each Action instance. - * - * @param options an instance of the {@link ActionBindOptions} class, - * that contains the necessary information for the Action - * to configure itself, like a reference to the Role, etc. - */ - protected abstract bound(scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): - codepipeline.ActionConfig; - - private get pipeline(): codepipeline.IPipeline { - if (this._pipeline) { - return this._pipeline; - } else { - throw new Error('Action must be added to a stage that is part of a pipeline before using onStateChange'); - } - } - - private get stage(): codepipeline.IStage { - if (this._stage) { - return this._stage; - } else { - throw new Error('Action must be added to a stage that is part of a pipeline before using onStateChange'); - } - } - - /** - * Retrieves the Construct scope of this Action. - * Only available after the Action has been added to a Stage, - * and that Stage to a Pipeline. - */ - private get scope(): Construct { - if (this._scope) { - return this._scope; - } else { - throw new Error('Action must be added to a stage that is part of a pipeline first'); - } + super(); + this.providedActionProperties = actionProperties; } } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codestar-connections/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codestar-connections/source-action.ts index 9a883affe859d..ca47af5c29c8e 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codestar-connections/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codestar-connections/source-action.ts @@ -20,7 +20,7 @@ export interface CodeStarConnectionsSourceActionProps extends codepipeline.Commo /** * The ARN of the CodeStar Connection created in the AWS console - * that has permissions to access this BitBucket repository. + * that has permissions to access this GitHub or BitBucket repository. * * @example 'arn:aws:codestar-connections:us-east-1:123456789012:connection/12345678-abcd-12ab-34cdef5678gh' * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/connections-create.html diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts index 18e8ccf74a91b..254b2764f2684 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts @@ -16,5 +16,5 @@ export * from './manual-approval-action'; export * from './s3/deploy-action'; export * from './s3/source-action'; export * from './stepfunctions/invoke-action'; -export * from './servicecatalog/deploy-action'; -export * from './action'; // for some reason, JSII fails building the module without exporting this class +export * from './servicecatalog/deploy-action-beta1'; +export * from './action'; diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action-beta1.ts similarity index 86% rename from packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action.ts rename to packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action-beta1.ts index 9059af1ac0ba1..34b055c14735b 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action-beta1.ts @@ -8,13 +8,9 @@ import { Action } from '../action'; import { Construct } from '@aws-cdk/core'; /** - * Construction properties of the {@link ServiceCatalogDeployAction ServiceCatalog deploy CodePipeline Action}. - * - * **Note**: this API is still experimental, and may have breaking changes in the future! - * - * @experimental + * Construction properties of the {@link ServiceCatalogDeployActionBeta1 ServiceCatalog deploy CodePipeline Action}. */ -export interface ServiceCatalogDeployActionProps extends codepipeline.CommonAwsActionProps { +export interface ServiceCatalogDeployActionBeta1Props extends codepipeline.CommonAwsActionProps { /** * The path to the cloudformation artifact. */ @@ -39,19 +35,21 @@ export interface ServiceCatalogDeployActionProps extends codepipeline.CommonAwsA /** * CodePipeline action to connect to an existing ServiceCatalog product. +<<<<<<< HEAD:packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action.ts * * **Note**: this class is still experimental, and may have breaking changes in the future! * - * @experimental +======= +>>>>>>> master:packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action-beta1.ts */ -export class ServiceCatalogDeployAction extends Action { +export class ServiceCatalogDeployActionBeta1 extends Action { private readonly templatePath: string; private readonly productVersionName: string; private readonly productVersionDescription?: string; private readonly productId: string; private readonly productType: string; - constructor(props: ServiceCatalogDeployActionProps) { + constructor(props: ServiceCatalogDeployActionBeta1Props) { super({ ...props, provider: 'ServiceCatalog', diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package.json b/packages/@aws-cdk/aws-codepipeline-actions/package.json index b10affea070c5..990aae3431daa 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package.json @@ -90,7 +90,6 @@ "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", - "@aws-cdk/aws-servicecatalog": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sns-subscriptions": "0.0.0", "@aws-cdk/aws-stepfunctions": "0.0.0", @@ -113,7 +112,6 @@ "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", - "@aws-cdk/aws-servicecatalog": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sns-subscriptions": "0.0.0", "@aws-cdk/aws-stepfunctions": "0.0.0", @@ -148,7 +146,6 @@ "props-default-doc:@aws-cdk/aws-codepipeline-actions.CloudFormationCreateUpdateStackActionProps.extraInputs", "props-default-doc:@aws-cdk/aws-codepipeline-actions.CloudFormationDeleteStackActionProps.extraInputs", "props-default-doc:@aws-cdk/aws-codepipeline-actions.CodeBuildActionProps.extraInputs", - "docs-public-apis:@aws-cdk/aws-codepipeline-actions.Action.bind", "docs-public-apis:@aws-cdk/aws-codepipeline-actions.CodeCommitSourceActionProps.branch", "docs-public-apis:@aws-cdk/aws-codepipeline-actions.EcrSourceActionProps.output", "docs-public-apis:@aws-cdk/aws-codepipeline-actions.GitHubSourceActionProps.output", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/servicecatalog/servicecatalog-action.test.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/servicecatalog/servicecatalog-deploy-action-beta1.test.ts similarity index 99% rename from packages/@aws-cdk/aws-codepipeline-actions/test/servicecatalog/servicecatalog-action.test.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/servicecatalog/servicecatalog-deploy-action-beta1.test.ts index 5a8555a627799..ef96441ca585e 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/servicecatalog/servicecatalog-action.test.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/servicecatalog/servicecatalog-deploy-action-beta1.test.ts @@ -12,7 +12,7 @@ nodeunitShim({ // GIVEN const stack = new TestFixture(); // WHEN - stack.deployStage.addAction(new cpactions.ServiceCatalogDeployAction({ + stack.deployStage.addAction(new cpactions.ServiceCatalogDeployActionBeta1({ actionName: 'ServiceCatalogTest', templatePath: stack.sourceOutput.atPath('template.yaml'), productVersionDescription: 'This is a description of the version.', @@ -58,7 +58,7 @@ nodeunitShim({ // GIVEN const stack = new TestFixture(); // WHEN - stack.deployStage.addAction(new cpactions.ServiceCatalogDeployAction({ + stack.deployStage.addAction(new cpactions.ServiceCatalogDeployActionBeta1({ actionName: 'ServiceCatalogTest', templatePath: stack.sourceOutput.atPath('template.yaml'), productVersionName: 'VersionName', @@ -127,4 +127,4 @@ class TestFixture extends Stack { }); this.sourceStage.addAction(source); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codepipeline/lib/action.ts b/packages/@aws-cdk/aws-codepipeline/lib/action.ts index 4fcf28b25fc30..2bf32ca5ee330 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/action.ts @@ -1,7 +1,7 @@ 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 { IResource } from '@aws-cdk/core'; +import { IResource, Lazy } from '@aws-cdk/core'; import { Artifact } from './artifact'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main @@ -119,7 +119,10 @@ export interface ActionConfig { } /** - * A Pipeline Action + * A Pipeline Action. + * If you want to implement this interface, + * consider extending the {@link Action} class, + * which contains some common logic. */ export interface IAction { /** @@ -254,3 +257,125 @@ export interface CommonAwsActionProps extends CommonActionProps { */ readonly role?: iam.IRole; } + +/** + * Low-level class for generic CodePipeline Actions implementing the {@link IAction} interface. + * Contains some common logic that can be re-used by all {@link IAction} implementations. + * If you're writing your own Action class, + * feel free to extend this class. + */ +export abstract class Action implements IAction { + /** + * This is a renamed version of the {@link IAction.actionProperties} property. + */ + protected abstract readonly providedActionProperties: ActionProperties; + + private __actionProperties?: ActionProperties; + private __pipeline?: IPipeline; + private __stage?: IStage; + private __scope?: Construct; + private readonly _namespaceToken: string; + private _customerProvidedNamespace?: string; + private _actualNamespace?: string; + + private _variableReferenced = false; + + protected constructor() { + this._namespaceToken = Lazy.string({ + produce: () => { + // make sure the action was bound (= added to a pipeline) + if (this._actualNamespace === undefined) { + throw new Error(`Cannot reference variables of action '${this.actionProperties.actionName}', ` + + 'as that action was never added to a pipeline'); + } else { + return this._customerProvidedNamespace !== undefined + // if a customer passed a namespace explicitly, always use that + ? this._customerProvidedNamespace + // otherwise, only return a namespace if any variable was referenced + : (this._variableReferenced ? this._actualNamespace : undefined); + } + }, + }); + } + + public get actionProperties(): ActionProperties { + if (this.__actionProperties === undefined) { + const actionProperties = this.providedActionProperties; + this._customerProvidedNamespace = actionProperties.variablesNamespace; + this.__actionProperties = { + ...actionProperties, + variablesNamespace: this._customerProvidedNamespace === undefined + ? this._namespaceToken + : this._customerProvidedNamespace, + }; + } + return this.__actionProperties; + } + + public bind(scope: Construct, stage: IStage, options: ActionBindOptions): ActionConfig { + this.__pipeline = stage.pipeline; + this.__stage = stage; + this.__scope = scope; + + this._actualNamespace = this._customerProvidedNamespace === undefined + // default a namespace name, based on the stage and action names + ? `${stage.stageName}_${this.actionProperties.actionName}_NS` + : this._customerProvidedNamespace; + + return this.bound(scope, stage, options); + } + + public onStateChange(name: string, target?: events.IRuleTarget, options?: events.RuleProps) { + const rule = new events.Rule(this._scope, name, options); + rule.addTarget(target); + rule.addEventPattern({ + detailType: ['CodePipeline Action Execution State Change'], + source: ['aws.codepipeline'], + resources: [this._pipeline.pipelineArn], + detail: { + stage: [this._stage.stageName], + action: [this.actionProperties.actionName], + }, + }); + return rule; + } + + protected variableExpression(variableName: string): string { + this._variableReferenced = true; + return `#{${this._namespaceToken}.${variableName}}`; + } + + /** + * This is a renamed version of the {@link IAction.bind} method. + */ + protected abstract bound(scope: Construct, stage: IStage, options: ActionBindOptions): ActionConfig; + + private get _pipeline(): IPipeline { + if (this.__pipeline) { + return this.__pipeline; + } else { + throw new Error('Action must be added to a stage that is part of a pipeline before using onStateChange'); + } + } + + private get _stage(): IStage { + if (this.__stage) { + return this.__stage; + } else { + throw new Error('Action must be added to a stage that is part of a pipeline before using onStateChange'); + } + } + + /** + * Retrieves the Construct scope of this Action. + * Only available after the Action has been added to a Stage, + * and that Stage to a Pipeline. + */ + private get _scope(): Construct { + if (this.__scope) { + return this.__scope; + } else { + throw new Error('Action must be added to a stage that is part of a pipeline first'); + } + } +} diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index 8ef8cc3dede8e..1b3ca9e0e6adc 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -97,7 +97,6 @@ export interface PipelineProps { * the construct will automatically create a Stack containing an S3 Bucket in that region. * * @default - None. - * @experimental */ readonly crossRegionReplicationBuckets?: { [region: string]: s3.IBucket }; @@ -386,7 +385,6 @@ export class Pipeline extends PipelineBase { * Returns all of the {@link CrossRegionSupportStack}s that were generated automatically * when dealing with Actions that reside in a different region than the Pipeline itself. * - * @experimental */ public get crossRegionSupport(): { [region: string]: CrossRegionSupport } { const ret: { [region: string]: CrossRegionSupport } = {}; @@ -662,48 +660,68 @@ export class Pipeline extends PipelineBase { * @param action the Action to return the Stack for */ private getOtherStackIfActionIsCrossAccount(action: IAction): Stack | undefined { - const pipelineStack = Stack.of(this); + const targetAccount = action.actionProperties.resource + ? action.actionProperties.resource.env.account + : action.actionProperties.account; - if (action.actionProperties.resource) { - const resourceStack = Stack.of(action.actionProperties.resource); - // check if resource is from a different account - if (pipelineStack.account === resourceStack.account) { - return undefined; - } else { - this._crossAccountSupport[resourceStack.account] = resourceStack; - return resourceStack; - } - } - - if (!action.actionProperties.account) { + if (targetAccount === undefined) { + // if the account of the Action is not specified, + // then it defaults to the same account the pipeline itself is in return undefined; } - const targetAccount = action.actionProperties.account; - // check whether the account is a static string + // check whether the action's account is a static string if (Token.isUnresolved(targetAccount)) { - throw new Error(`The 'account' property must be a concrete value (action: '${action.actionProperties.actionName}')`); + if (Token.isUnresolved(this.env.account)) { + // the pipeline is also env-agnostic, so that's fine + return undefined; + } else { + throw new Error(`The 'account' property must be a concrete value (action: '${action.actionProperties.actionName}')`); + } } - // check whether the pipeline account is a static string - if (Token.isUnresolved(pipelineStack.account)) { + + // At this point, we know that the action's account is a static string. + // In this case, the pipeline's account must also be a static string. + if (Token.isUnresolved(this.env.account)) { throw new Error('Pipeline stack which uses cross-environment actions must have an explicitly set account'); } - if (pipelineStack.account === targetAccount) { + // at this point, we know that both the Pipeline's account, + // and the action-backing resource's account are static strings + + // if they are identical - nothing to do (the action is not cross-account) + if (this.env.account === targetAccount) { return undefined; } + // at this point, we know that the action is certainly cross-account, + // so we need to return a Stack in its account to create the helper Role in + + const candidateActionResourceStack = action.actionProperties.resource + ? Stack.of(action.actionProperties.resource) + : undefined; + if (candidateActionResourceStack?.account === targetAccount) { + // we always use the "latest" action-backing resource's Stack for this account, + // even if a different one was used earlier + this._crossAccountSupport[targetAccount] = candidateActionResourceStack; + return candidateActionResourceStack; + } + let targetAccountStack: Stack | undefined = this._crossAccountSupport[targetAccount]; if (!targetAccountStack) { const stackId = `cross-account-support-stack-${targetAccount}`; const app = this.requireApp(); targetAccountStack = app.node.tryFindChild(stackId) as Stack; if (!targetAccountStack) { + const actionRegion = action.actionProperties.resource + ? action.actionProperties.resource.env.region + : action.actionProperties.region; + const pipelineStack = Stack.of(this); targetAccountStack = new Stack(app, stackId, { stackName: `${pipelineStack.stackName}-support-${targetAccount}`, env: { account: targetAccount, - region: action.actionProperties.region ? action.actionProperties.region : pipelineStack.region, + region: actionRegion ?? pipelineStack.region, }, }); } @@ -916,7 +934,6 @@ export class Pipeline extends PipelineBase { * the cross-region capabilities of CodePipeline. * You get instances of this interface from the {@link Pipeline#crossRegionSupport} property. * - * @experimental */ export interface CrossRegionSupport { /** diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index 323b4659cacd5..5855723e0c7dc 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -154,9 +154,6 @@ "props-default-doc:@aws-cdk/aws-codepipeline.ActionProperties.runOrder", "docs-public-apis:@aws-cdk/aws-codepipeline.ActionProperties.version", "props-default-doc:@aws-cdk/aws-codepipeline.ActionProperties.version", - "docs-public-apis:@aws-cdk/aws-codepipeline.IAction.actionProperties", - "docs-public-apis:@aws-cdk/aws-codepipeline.IAction.bind", - "docs-public-apis:@aws-cdk/aws-codepipeline.IAction.onStateChange", "docs-public-apis:@aws-cdk/aws-codepipeline.IStage.pipeline", "docs-public-apis:@aws-cdk/aws-codepipeline.IStage.addAction", "docs-public-apis:@aws-cdk/aws-codepipeline.IStage.onStateChange", diff --git a/packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts b/packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts index 5a0d9a5e3b78b..f22c80e82cba1 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts @@ -138,4 +138,74 @@ describe.each(['legacy', 'modern'])('with %s synthesis', (synthesisStyle: string }); }); }); -}); \ No newline at end of file +}); + +describe('cross-environment CodePipeline', function () { + test('correctly detects that an Action is cross-account from the account of the resource backing the Action', () => { + const app = new App(); + + const pipelineStack = new Stack(app, 'PipelineStack', { + env: { account: '123', region: 'my-region' }, + }); + const sourceOutput = new codepipeline.Artifact(); + const pipeline = new codepipeline.Pipeline(pipelineStack, 'Pipeline', { + stages: [ + { + stageName: 'Source', + actions: [ + new FakeSourceAction({ + actionName: 'Source', + output: sourceOutput, + }), + ], + }, + ], + }); + + // Import a resource backing the FakeBuildAction into the pipeline's Stack, + // but specify a different account for it during the import. + // This should be correctly detected by the CodePipeline construct, + // and a correct support Stack should be created. + const deployBucket = s3.Bucket.fromBucketAttributes(pipelineStack, 'DeployBucket', { + bucketName: 'my-bucket', + account: '456', + }); + pipeline.addStage({ + stageName: 'Build', + actions: [ + new FakeBuildAction({ + actionName: 'Build', + input: sourceOutput, + resource: deployBucket, + }), + ], + }); + + const asm = app.synth(); + const supportStack = asm.getStackByName(`${pipelineStack.stackName}-support-456`); + expect(supportStack).toHaveResourceLike('AWS::IAM::Role', { + RoleName: 'pipelinestack-support-456dbuildactionrole91c6f1a469fd11d52dfe', + }); + + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + { Name: 'Source' }, + { + Name: 'Build', + Actions: [ + { + Name: 'Build', + RoleArn: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::456:role/pipelinestack-support-456dbuildactionrole91c6f1a469fd11d52dfe', + ]], + }, + }, + ], + }, + ], + }); + }); +}); diff --git a/packages/@aws-cdk/aws-codepipeline/test/fake-build-action.ts b/packages/@aws-cdk/aws-codepipeline/test/fake-build-action.ts index 53cec90d55267..9f2d41b7d5252 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/fake-build-action.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/fake-build-action.ts @@ -1,4 +1,3 @@ -import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import { IResource } from '@aws-cdk/core'; import { Construct } from 'constructs'; @@ -24,12 +23,13 @@ export interface FakeBuildActionProps extends codepipeline.CommonActionProps { resource?: IResource; } -export class FakeBuildAction implements codepipeline.IAction { - public readonly actionProperties: codepipeline.ActionProperties; +export class FakeBuildAction extends codepipeline.Action { + protected readonly providedActionProperties: codepipeline.ActionProperties; private readonly customConfigKey: string | undefined; constructor(props: FakeBuildActionProps) { - this.actionProperties = { + super(); + this.providedActionProperties = { ...props, category: codepipeline.ActionCategory.BUILD, provider: 'Fake', @@ -40,7 +40,7 @@ export class FakeBuildAction implements codepipeline.IAction { this.customConfigKey = props.customConfigKey; } - public bind(_scope: Construct, _stage: codepipeline.IStage, _options: codepipeline.ActionBindOptions): + public bound(_scope: Construct, _stage: codepipeline.IStage, _options: codepipeline.ActionBindOptions): codepipeline.ActionConfig { return { configuration: { @@ -48,8 +48,4 @@ export class FakeBuildAction implements codepipeline.IAction { }, }; } - - public onStateChange(_name: string, _target?: events.IRuleTarget, _options?: events.RuleProps): events.Rule { - throw new Error('onStateChange() is not available on FakeBuildAction'); - } } diff --git a/packages/@aws-cdk/aws-codepipeline/test/fake-source-action.ts b/packages/@aws-cdk/aws-codepipeline/test/fake-source-action.ts index 8e8c2652caa9c..13e4a21b0e77e 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/fake-source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/fake-source-action.ts @@ -1,4 +1,3 @@ -import * as events from '@aws-cdk/aws-events'; import { Lazy } from '@aws-cdk/core'; import { Construct } from 'constructs'; import * as codepipeline from '../lib'; @@ -15,15 +14,15 @@ export interface FakeSourceActionProps extends codepipeline.CommonActionProps { readonly region?: string; } -export class FakeSourceAction implements codepipeline.IAction { +export class FakeSourceAction extends codepipeline.Action { public readonly inputs?: codepipeline.Artifact[]; public readonly outputs?: codepipeline.Artifact[]; public readonly variables: IFakeSourceActionVariables; - - public readonly actionProperties: codepipeline.ActionProperties; + protected readonly providedActionProperties: codepipeline.ActionProperties; constructor(props: FakeSourceActionProps) { - this.actionProperties = { + super(); + this.providedActionProperties = { ...props, category: codepipeline.ActionCategory.SOURCE, provider: 'Fake', @@ -35,12 +34,8 @@ export class FakeSourceAction implements codepipeline.IAction { }; } - public bind(_scope: Construct, _stage: codepipeline.IStage, _options: codepipeline.ActionBindOptions): + public bound(_scope: Construct, _stage: codepipeline.IStage, _options: codepipeline.ActionBindOptions): codepipeline.ActionConfig { return {}; } - - public onStateChange(_name: string, _target?: events.IRuleTarget, _options?: events.RuleProps): events.Rule { - throw new Error('onStateChange() is not available on FakeSourceAction'); - } } 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 d67e31884cd8a..540b1f6ea76a7 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json @@ -29,7 +29,7 @@ "devDependencies": { "aws-sdk": "^2.596.0", "aws-sdk-mock": "^5.1.0", - "eslint": "^7.23.0", + "eslint": "^7.24.0", "eslint-config-standard": "^14.1.1", "eslint-plugin-import": "^2.22.1", "eslint-plugin-node": "^11.1.0", diff --git a/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts b/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts index c60ceb07c08c8..4a4c7596b4dfa 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts +++ b/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts @@ -62,7 +62,7 @@ export = { }, 'GlobalTable generated stacks inherit their account from the parent stack'(test: Test) { - const app = new App(); + const app = new App({ context: { '@aws-cdk/core:stackRelativeExports': true } }); const stack = new Stack(app, 'GlobalTableStack', { env: { account: '123456789012', region: 'us-east-1' } }); const globalTable = new GlobalTable(stack, CONSTRUCT_NAME, { @@ -81,7 +81,7 @@ export = { 'Outputs': { 'DynamoDbOutput': { 'Value': { - 'Fn::ImportValue': 'GlobalTableStackawscdkdynamodbglobalawscdkdynamodbglobaluseast19C1C8A14:awscdkdynamodbglobalawscdkdynamodbglobaluseast1ExportsOutputFnGetAttawscdkdynamodbglobalGlobalTableuseast1FC03DD69StreamArn28E90DB8', + 'Fn::ImportValue': 'GlobalTableStackawscdkdynamodbglobalawscdkdynamodbglobaluseast19C1C8A14:ExportsOutputFnGetAttawscdkdynamodbglobalGlobalTableuseast1FC03DD69StreamArn9CE585ED', }, }, }, diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index ed8f6a786d03c..776159b110e27 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -215,7 +215,6 @@ export interface TableOptions { * Regions where replica tables will be created * * @default - no replica tables are created - * @experimental */ readonly replicationRegions?: string[]; diff --git a/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts b/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts index 0656440ea3ff4..b0345f34b6b98 100644 --- a/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts +++ b/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts @@ -14,7 +14,7 @@ import { IVpc, SubnetSelection } from './vpc'; /** * Properties of the bastion host * - * @experimental + * */ export interface BastionHostLinuxProps { @@ -90,7 +90,7 @@ export interface BastionHostLinuxProps { * * You can also configure this bastion host to allow connections via SSH * - * @experimental + * * @resource AWS::EC2::Instance */ export class BastionHostLinux extends Resource implements IInstance { diff --git a/packages/@aws-cdk/aws-ec2/lib/nat.ts b/packages/@aws-cdk/aws-ec2/lib/nat.ts index bf65671b92287..75cd31142c0b6 100644 --- a/packages/@aws-cdk/aws-ec2/lib/nat.ts +++ b/packages/@aws-cdk/aws-ec2/lib/nat.ts @@ -49,7 +49,7 @@ export interface GatewayConfig { * Determines what type of NAT provider to create, either NAT gateways or NAT * instance. * - * @experimental + * */ export abstract class NatProvider { /** @@ -101,7 +101,7 @@ export abstract class NatProvider { /** * Options passed by the VPC when NAT needs to be configured * - * @experimental + * */ export interface ConfigureNatOptions { /** @@ -125,7 +125,7 @@ export interface ConfigureNatOptions { /** * Properties for a NAT instance * - * @experimental + * */ export interface NatInstanceProps { /** @@ -359,7 +359,7 @@ class PrefSet { /** * Machine image representing the latest NAT instance image * - * @experimental + * */ export class NatInstanceImage extends LookupMachineImage { constructor() { diff --git a/packages/@aws-cdk/aws-ec2/lib/network-acl-types.ts b/packages/@aws-cdk/aws-ec2/lib/network-acl-types.ts index deb5c25e43fba..2a4b25d765188 100644 --- a/packages/@aws-cdk/aws-ec2/lib/network-acl-types.ts +++ b/packages/@aws-cdk/aws-ec2/lib/network-acl-types.ts @@ -1,7 +1,7 @@ /** * Either an IPv4 or an IPv6 CIDR * - * @experimental + * */ export abstract class AclCidr { /** @@ -52,7 +52,7 @@ class AclCidrImpl extends AclCidr { /** * Acl Configuration for CIDR * - * @experimental + * */ export interface AclCidrConfig { /** @@ -69,7 +69,7 @@ export interface AclCidrConfig { /** * The traffic that is configured using a Network ACL entry * - * @experimental + * */ export abstract class AclTraffic { /** @@ -171,7 +171,7 @@ class AclTrafficImpl extends AclTraffic { /** * Acl Configuration for traffic * - * @experimental + * */ export interface AclTrafficConfig { /** @@ -210,7 +210,7 @@ export interface AclTrafficConfig { /** * Properties to create Icmp * - * @experimental + * */ export interface AclIcmp { /** @@ -230,7 +230,7 @@ export interface AclIcmp { /** * Properties to create PortRange * - * @experimental + * */ export interface AclPortRange { /** diff --git a/packages/@aws-cdk/aws-ec2/lib/network-acl.ts b/packages/@aws-cdk/aws-ec2/lib/network-acl.ts index 911948e8df39c..a957c3f46e92c 100644 --- a/packages/@aws-cdk/aws-ec2/lib/network-acl.ts +++ b/packages/@aws-cdk/aws-ec2/lib/network-acl.ts @@ -7,7 +7,7 @@ import { ISubnet, IVpc, SubnetSelection } from './vpc'; /** * A NetworkAcl * - * @experimental + * */ export interface INetworkAcl extends IResource { /** @@ -25,7 +25,7 @@ export interface INetworkAcl extends IResource { /** * A NetworkAclBase that is not created in this template * - * @experimental + * */ abstract class NetworkAclBase extends Resource implements INetworkAcl { public abstract readonly networkAclId: string; @@ -45,7 +45,7 @@ abstract class NetworkAclBase extends Resource implements INetworkAcl { /** * Properties to create NetworkAcl * - * @experimental + * */ export interface NetworkAclProps { /** @@ -80,7 +80,7 @@ export interface NetworkAclProps { * By default, will deny all inbound and outbound traffic unless entries are * added explicitly allowing it. * - * @experimental + * */ export class NetworkAcl extends NetworkAclBase { /** @@ -144,7 +144,7 @@ export class NetworkAcl extends NetworkAclBase { /** * What action to apply to traffic matching the ACL * - * @experimental + * */ export enum Action { /** @@ -161,7 +161,7 @@ export enum Action { /** * A NetworkAclEntry * - * @experimental + * */ export interface INetworkAclEntry extends IResource { /** @@ -174,7 +174,7 @@ export interface INetworkAclEntry extends IResource { /** * Base class for NetworkAclEntries * - * @experimental + * */ abstract class NetworkAclEntryBase extends Resource implements INetworkAclEntry { public abstract readonly networkAcl: INetworkAcl; @@ -183,7 +183,7 @@ abstract class NetworkAclEntryBase extends Resource implements INetworkAclEntry /** * Direction of traffic the AclEntry applies to * - * @experimental + * */ export enum TrafficDirection { /** @@ -200,7 +200,7 @@ export enum TrafficDirection { /** * Basic NetworkACL entry props * - * @experimental + * */ export interface CommonNetworkAclEntryOptions { /** @@ -250,7 +250,7 @@ export interface CommonNetworkAclEntryOptions { /** * Properties to create NetworkAclEntry * - * @experimental + * */ export interface NetworkAclEntryProps extends CommonNetworkAclEntryOptions { /** @@ -262,7 +262,7 @@ export interface NetworkAclEntryProps extends CommonNetworkAclEntryOptions { /** * Define an entry in a Network ACL table * - * @experimental + * */ export class NetworkAclEntry extends NetworkAclEntryBase { public readonly networkAcl: INetworkAcl; @@ -288,7 +288,7 @@ export class NetworkAclEntry extends NetworkAclEntryBase { /** * A SubnetNetworkAclAssociation * - * @experimental + * */ export interface ISubnetNetworkAclAssociation extends IResource { /** @@ -301,7 +301,7 @@ export interface ISubnetNetworkAclAssociation extends IResource { /** * Properties to create a SubnetNetworkAclAssociation * - * @experimental + * */ export interface SubnetNetworkAclAssociationProps { /** @@ -331,7 +331,7 @@ export interface SubnetNetworkAclAssociationProps { /** * Associate a network ACL with a subnet * - * @experimental + * */ abstract class SubnetNetworkAclAssociationBase extends Resource implements ISubnetNetworkAclAssociation { public abstract readonly subnetNetworkAclAssociationAssociationId: string; diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index 70bc1311bf8ab..c56a6b9dd34d1 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -260,7 +260,7 @@ export interface SecurityGroupImportOptions { * group. Be aware, this would undo any potential "all outbound traffic" * default. * - * @experimental + * * @default true */ readonly allowAllOutbound?: boolean; @@ -271,7 +271,7 @@ export interface SecurityGroupImportOptions { * Beware that making a SecurityGroup immutable might lead to issue * due to missing ingress/egress rules for new resources. * - * @experimental + * * @default true */ readonly mutable?: boolean; diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint-service.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint-service.ts index 8e9b597217a93..ffd90a32df56b 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint-service.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint-service.ts @@ -17,7 +17,7 @@ export interface IVpcEndpointServiceLoadBalancer { /** * A VPC endpoint service. - * @experimental + * */ export interface IVpcEndpointService extends IResource { /** @@ -40,7 +40,7 @@ export interface IVpcEndpointService extends IResource { /** * A VPC endpoint service * @resource AWS::EC2::VPCEndpointService - * @experimental + * */ export class VpcEndpointService extends Resource implements IVpcEndpointService { @@ -52,7 +52,7 @@ export class VpcEndpointService extends Resource implements IVpcEndpointService /** * Whether to require manual acceptance of new connections to the service. - * @experimental + * */ public readonly acceptanceRequired: boolean; @@ -64,7 +64,7 @@ export class VpcEndpointService extends Resource implements IVpcEndpointService /** * One or more Principal ARNs to allow inbound connections to. - * @experimental + * */ public readonly allowedPrincipals: ArnPrincipal[]; @@ -124,7 +124,7 @@ export class VpcEndpointService extends Resource implements IVpcEndpointService /** * Construction properties for a VpcEndpointService. - * @experimental + * */ export interface VpcEndpointServiceProps { @@ -137,7 +137,7 @@ export interface VpcEndpointServiceProps { /** * One or more load balancers to host the VPC Endpoint Service. - * @experimental + * */ readonly vpcEndpointServiceLoadBalancers: IVpcEndpointServiceLoadBalancer[]; @@ -145,7 +145,7 @@ export interface VpcEndpointServiceProps { * Whether requests from service consumers to connect to the service through * an endpoint must be accepted. * @default true - * @experimental + * */ readonly acceptanceRequired?: boolean; @@ -163,7 +163,7 @@ export interface VpcEndpointServiceProps { * These principals can connect to your service using VPC endpoints. Takes a * list of one or more ArnPrincipal. * @default - no principals - * @experimental + * */ readonly allowedPrincipals?: ArnPrincipal[]; } diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-flow-logs.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-flow-logs.ts index 498940fab878a..fdc4a06ad2227 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-flow-logs.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-flow-logs.ts @@ -13,7 +13,7 @@ import { Construct as CoreConstruct } from '@aws-cdk/core'; /** * A FlowLog * - * @experimental + * */ export interface IFlowLog extends IResource { /** @@ -27,7 +27,7 @@ export interface IFlowLog extends IResource { /** * The type of VPC traffic to log * - * @experimental + * */ export enum FlowLogTrafficType { /** @@ -48,7 +48,7 @@ export enum FlowLogTrafficType { /** * The available destination types for Flow Logs - * @experimental + * */ export enum FlowLogDestinationType { /** @@ -65,7 +65,7 @@ export enum FlowLogDestinationType { /** * The type of resource to create the flow log for * - * @experimental + * */ export abstract class FlowLogResourceType { /** @@ -112,7 +112,7 @@ export abstract class FlowLogResourceType { /** * The destination type for the flow log * - * @experimental + * */ export abstract class FlowLogDestination { /** @@ -146,7 +146,7 @@ export abstract class FlowLogDestination { /** * Flow Log Destination configuration * - * @experimental + * */ export interface FlowLogDestinationConfig { /** @@ -186,7 +186,7 @@ export interface FlowLogDestinationConfig { } /** - * @experimental + * */ class S3Destination extends FlowLogDestination { constructor(private readonly props: FlowLogDestinationConfig) { @@ -212,7 +212,7 @@ class S3Destination extends FlowLogDestination { } /** - * @experimental + * */ class CloudWatchLogsDestination extends FlowLogDestination { constructor(private readonly props: FlowLogDestinationConfig) { @@ -268,7 +268,7 @@ class CloudWatchLogsDestination extends FlowLogDestination { /** * Options to add a flow log to a VPC * - * @experimental + * */ export interface FlowLogOptions { /** @@ -290,7 +290,7 @@ export interface FlowLogOptions { /** * Properties of a VPC Flow Log * - * @experimental + * */ export interface FlowLogProps extends FlowLogOptions { /** @@ -312,7 +312,7 @@ export interface FlowLogProps extends FlowLogOptions { /** * The base class for a Flow Log * - * @experimental + * */ abstract class FlowLogBase extends Resource implements IFlowLog { /** @@ -327,7 +327,7 @@ abstract class FlowLogBase extends Resource implements IFlowLog { * A VPC flow log. * @resource AWS::EC2::FlowLog * - * @experimental + * */ export class FlowLog extends FlowLogBase { /** diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 1d9667d774a0c..1fe534df6c483 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -815,7 +815,7 @@ export interface VpcProps { * may not be available in all AWS regions. * * @default NatProvider.gateway() - * @experimental + * */ readonly natGatewayProvider?: NatProvider; diff --git a/packages/@aws-cdk/aws-ecr-assets/README.md b/packages/@aws-cdk/aws-ecr-assets/README.md index dc621f942b430..a08ed95a9d676 100644 --- a/packages/@aws-cdk/aws-ecr-assets/README.md +++ b/packages/@aws-cdk/aws-ecr-assets/README.md @@ -3,13 +3,7 @@ --- -![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) - -> The APIs of higher level constructs in this module are experimental and under active development. -> They are subject to non-backward compatible changes or removal in any future version. These are -> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be -> announced in the release notes. This means that while you may use them, you may need to update -> your source code when upgrading to a newer version of this package. +![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge) --- diff --git a/packages/@aws-cdk/aws-ecr-assets/package.json b/packages/@aws-cdk/aws-ecr-assets/package.json index 6dd3e6bb709eb..edd07d59c7fb2 100644 --- a/packages/@aws-cdk/aws-ecr-assets/package.json +++ b/packages/@aws-cdk/aws-ecr-assets/package.json @@ -102,8 +102,8 @@ "bundledDependencies": [ "minimatch" ], - "stability": "experimental", - "maturity": "experimental", + "stability": "stable", + "maturity": "stable", "awscdkio": { "announce": false }, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts index 2593853cd0350..2a2c8feb23d7a 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts @@ -422,13 +422,14 @@ export abstract class ApplicationLoadBalancedServiceBase extends CoreConstruct { this.targetGroup = this.listener.addTargets('ECS', targetProps); if (protocol === ApplicationProtocol.HTTPS) { - if (typeof props.domainName === 'undefined' || typeof props.domainZone === 'undefined') { - throw new Error('A domain name and zone is required when using the HTTPS protocol'); - } if (props.certificate !== undefined) { this.certificate = props.certificate; } else { + if (typeof props.domainName === 'undefined' || typeof props.domainZone === 'undefined') { + throw new Error('A domain name and zone is required when using the HTTPS protocol'); + } + this.certificate = new Certificate(this, 'Certificate', { domainName: props.domainName, validation: CertificateValidation.fromDns(props.domainZone), diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts index 8d204485eb125..a8c588d1187ef 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts @@ -1,4 +1,5 @@ import { expect, haveResource, haveResourceLike, SynthUtils } from '@aws-cdk/assert-internal'; +import { DnsValidatedCertificate } from '@aws-cdk/aws-certificatemanager'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import { ApplicationLoadBalancer, ApplicationProtocol, NetworkLoadBalancer } from '@aws-cdk/aws-elasticloadbalancingv2'; @@ -977,4 +978,38 @@ export = { test.done(); }, + 'domainName and domainZone not required for HTTPS listener with provided cert'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + const exampleDotComZone = new route53.PublicHostedZone(stack, 'ExampleDotCom', { + zoneName: 'example.com', + }); + const certificate = new DnsValidatedCertificate(stack, 'Certificate', { + domainName: 'test.example.com', + hostedZone: exampleDotComZone, + }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { + cluster, + protocol: ApplicationProtocol.HTTPS, + + taskImageOptions: { + containerPort: 2015, + image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), + }, + certificate: certificate, + }); + + // THEN + expect(stack).notTo(haveResourceLike('AWS::Route53::RecordSet', { + Name: 'test.domain.com.', + })); + + test.done(); + + }, + }; diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 5ad2855c8f642..4f19e2e1c915f 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -788,3 +788,39 @@ new ecs.FargateService(stack, 'FargateService', { app.synth(); ``` + +## Elastic Inference Accelerators + +Currently, this feature is only supported for services with EC2 launch types. + +To add elastic inference accelerators to your EC2 instance, first add +`inferenceAccelerator` field to the EC2TaskDefinition and set the `deviceName` +and `deviceType` properties. + +```ts +const inferenceAccelerators = [{ + deviceName: 'device1', + deviceType: 'eia2.medium', +}]; + +const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { + inferenceAccelerators, +}); +``` + +To enable using the inference accelerator in the containers, set the +`type` and `value` properties accordingly. The `value` should match the +`DeviceName` for an `InferenceAccelerator` specified in a task definition. + +```ts +const resourceRequirements = [{ + type: ecs.ResourceRequirementType.INFERENCEACCELERATOR, + value: 'device1', +}]; + +taskDefinition.addContainer('cont', { + image: ecs.ContainerImage.fromRegistry('test'), + memoryLimitMiB: 1024, + resourceRequirements, +}); +``` diff --git a/packages/@aws-cdk/aws-ecs/jest.config.js b/packages/@aws-cdk/aws-ecs/jest.config.js index f5d5c4c8ad18f..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-ecs/jest.config.js +++ b/packages/@aws-cdk/aws-ecs/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); -module.exports = baseConfig; \ No newline at end of file +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-ecs/lib/base/_imported-task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/base/_imported-task-definition.ts index 3c9c583b96de0..accffb9dac20b 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/_imported-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/_imported-task-definition.ts @@ -1,9 +1,9 @@ import { IRole } from '@aws-cdk/aws-iam'; +import { Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IEc2TaskDefinition } from '../ec2/ec2-task-definition'; import { IFargateTaskDefinition } from '../fargate/fargate-task-definition'; import { Compatibility, NetworkMode, isEc2Compatible, isFargateCompatible } from './task-definition'; -import { Resource } from '@aws-cdk/core'; /** * The properties of ImportedTaskDefinition 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 306b2bced3477..41fe61e299276 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -8,8 +8,8 @@ import * as cloudmap from '@aws-cdk/aws-servicediscovery'; import { Annotations, Duration, IResolvable, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { LoadBalancerTargetOptions, NetworkMode, TaskDefinition } from '../base/task-definition'; -import { ContainerDefinition, Protocol } from '../container-definition'; import { ICluster, CapacityProviderStrategy } from '../cluster'; +import { ContainerDefinition, Protocol } from '../container-definition'; import { CfnService } from '../ecs.generated'; import { ScalableTaskCount } from './scalable-task-count'; diff --git a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts index 7c3bb618142a5..3e7f82160d3fa 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -184,6 +184,15 @@ export interface TaskDefinitionProps extends CommonTaskDefinitionProps { * @default - PidMode used by the task is not specified */ readonly pidMode?: PidMode; + + /** + * The inference accelerators to use for the containers in the task. + * + * Not supported in Fargate. + * + * @default - No inference accelerators. + */ + readonly inferenceAccelerators?: InferenceAccelerator[]; } /** @@ -322,6 +331,11 @@ export class TaskDefinition extends TaskDefinitionBase { */ private readonly placementConstraints = new Array(); + /** + * Inference accelerators for task instances + */ + private readonly _inferenceAccelerators: InferenceAccelerator[] = []; + private _executionRole?: iam.IRole; private _referencesSecretJsonField?: boolean; @@ -354,12 +368,20 @@ export class TaskDefinition extends TaskDefinitionBase { throw new Error(`Fargate-compatible tasks require both CPU (${props.cpu}) and memory (${props.memoryMiB}) specifications`); } + if (props.inferenceAccelerators && props.inferenceAccelerators.length > 0 && this.isFargateCompatible) { + throw new Error('Cannot use inference accelerators on tasks that run on Fargate'); + } + this._executionRole = props.executionRole; this.taskRole = props.taskRole || new iam.Role(this, 'TaskRole', { assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), }); + if (props.inferenceAccelerators) { + props.inferenceAccelerators.forEach(ia => this.addInferenceAccelerator(ia)); + } + const taskDef = new CfnTaskDefinition(this, 'Resource', { containerDefinitions: Lazy.any({ produce: () => this.renderContainers() }, { omitEmptyArray: true }), volumes: Lazy.any({ produce: () => this.renderVolumes() }, { omitEmptyArray: true }), @@ -380,6 +402,10 @@ export class TaskDefinition extends TaskDefinitionBase { memory: props.memoryMiB, ipcMode: props.ipcMode, pidMode: props.pidMode, + inferenceAccelerators: Lazy.any({ + produce: () => + !isFargateCompatible(this.compatibility) ? this.renderInferenceAccelerators() : undefined, + }, { omitEmptyArray: true }), }); if (props.placementConstraints) { @@ -393,6 +419,13 @@ export class TaskDefinition extends TaskDefinitionBase { return this._executionRole; } + /** + * Public getter method to access list of inference accelerators attached to the instance. + */ + public get inferenceAccelerators(): InferenceAccelerator[] { + return this._inferenceAccelerators; + } + private renderVolumes(): CfnTaskDefinition.VolumeProperty[] { return this.volumes.map(renderVolume); @@ -419,6 +452,17 @@ export class TaskDefinition extends TaskDefinitionBase { } } + private renderInferenceAccelerators(): CfnTaskDefinition.InferenceAcceleratorProperty[] { + return this._inferenceAccelerators.map(renderInferenceAccelerator); + + function renderInferenceAccelerator(inferenceAccelerator: InferenceAccelerator) : CfnTaskDefinition.InferenceAcceleratorProperty { + return { + deviceName: inferenceAccelerator.deviceName, + deviceType: inferenceAccelerator.deviceType, + }; + } + } + /** * Validate the existence of the input target and set default values. * @@ -531,6 +575,16 @@ export class TaskDefinition extends TaskDefinitionBase { extension.extend(this); } + /** + * Adds an inference accelerator to the task definition. + */ + public addInferenceAccelerator(inferenceAccelerator: InferenceAccelerator) { + if (isFargateCompatible(this.compatibility)) { + throw new Error('Cannot use inference accelerators on tasks that run on Fargate'); + } + this._inferenceAccelerators.push(inferenceAccelerator); + } + /** * Creates the task execution IAM role if it doesn't already exist. */ @@ -683,6 +737,24 @@ export enum PidMode { TASK = 'task', } +/** + * Elastic Inference Accelerator. + * For more information, see [Elastic Inference Basics](https://docs.aws.amazon.com/elastic-inference/latest/developerguide/basics.html) + */ +export interface InferenceAccelerator { + /** + * The Elastic Inference accelerator device name. + * @default - empty + */ + readonly deviceName?: string; + + /** + * The Elastic Inference accelerator type to use. The allowed values are: eia2.medium, eia2.large and eia2.xlarge. + * @default - empty + */ + readonly deviceType?: string; +} + /** * A data volume used in a task definition. * diff --git a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts index dfe041afd2d99..8f57e84872de5 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts @@ -294,6 +294,12 @@ export interface ContainerDefinitionOptions { * @default - No ports are mapped. */ readonly portMappings?: PortMapping[]; + + /** + * The inference accelerators referenced by the container. + * @default - No inference accelerators assigned. + */ + readonly inferenceAcceleratorResources?: string[]; } /** @@ -386,6 +392,11 @@ export class ContainerDefinition extends CoreConstruct { */ public readonly referencesSecretJsonField?: boolean; + /** + * The inference accelerators referenced by this container. + */ + private readonly inferenceAcceleratorResources: string[] = []; + /** * The configured container links */ @@ -443,6 +454,10 @@ export class ContainerDefinition extends CoreConstruct { if (props.portMappings) { this.addPortMappings(...props.portMappings); } + + if (props.inferenceAcceleratorResources) { + this.addInferenceAcceleratorResource(...props.inferenceAcceleratorResources); + } } /** @@ -516,6 +531,20 @@ export class ContainerDefinition extends CoreConstruct { })); } + /** + * This method adds one or more resources to the container. + */ + public addInferenceAcceleratorResource(...inferenceAcceleratorResources: string[]) { + this.inferenceAcceleratorResources.push(...inferenceAcceleratorResources.map(resource => { + for (const inferenceAccelerator of this.taskDefinition.inferenceAccelerators) { + if (resource === inferenceAccelerator.deviceName) { + return resource; + } + } + throw new Error(`Resource value ${resource} in container definition doesn't match any inference accelerator device name in the task definition.`); + })); + } + /** * This method adds one or more ulimits to the container. */ @@ -631,7 +660,8 @@ export class ContainerDefinition extends CoreConstruct { healthCheck: this.props.healthCheck && renderHealthCheck(this.props.healthCheck), links: cdk.Lazy.list({ produce: () => this.links }, { omitEmpty: true }), linuxParameters: this.linuxParameters && this.linuxParameters.renderLinuxParameters(), - resourceRequirements: (this.props.gpuCount !== undefined) ? renderResourceRequirements(this.props.gpuCount) : undefined, + resourceRequirements: (!this.props.gpuCount && this.inferenceAcceleratorResources.length == 0 ) ? undefined : + renderResourceRequirements(this.props.gpuCount, this.inferenceAcceleratorResources), }; } } @@ -742,12 +772,22 @@ function getHealthCheckCommand(hc: HealthCheck): string[] { return hcCommand.concat(cmd); } -function renderResourceRequirements(gpuCount: number): CfnTaskDefinition.ResourceRequirementProperty[] | undefined { - if (gpuCount === 0) { return undefined; } - return [{ - type: 'GPU', - value: gpuCount.toString(), - }]; +function renderResourceRequirements(gpuCount: number = 0, inferenceAcceleratorResources: string[] = []): +CfnTaskDefinition.ResourceRequirementProperty[] | undefined { + const ret = []; + for (const resource of inferenceAcceleratorResources) { + ret.push({ + type: 'InferenceAccelerator', + value: resource, + }); + } + if (gpuCount > 0) { + ret.push({ + type: 'GPU', + value: gpuCount.toString(), + }); + } + return ret; } /** diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts index ff571c884b73e..3b65516ba7dfa 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts @@ -1,4 +1,5 @@ import { Construct } from 'constructs'; +import { ImportedTaskDefinition } from '../base/_imported-task-definition'; import { CommonTaskDefinitionAttributes, CommonTaskDefinitionProps, @@ -8,9 +9,9 @@ import { NetworkMode, PidMode, TaskDefinition, + InferenceAccelerator, } from '../base/task-definition'; import { PlacementConstraint } from '../placement'; -import { ImportedTaskDefinition } from '../base/_imported-task-definition'; /** * The properties for a task definition run on an EC2 cluster. @@ -51,6 +52,15 @@ export interface Ec2TaskDefinitionProps extends CommonTaskDefinitionProps { * @default - PidMode used by the task is not specified */ readonly pidMode?: PidMode; + + /** + * The inference accelerators to use for the containers in the task. + * + * Not supported in Fargate. + * + * @default - No inference accelerators. + */ + readonly inferenceAccelerators?: InferenceAccelerator[]; } /** @@ -109,6 +119,7 @@ export class Ec2TaskDefinition extends TaskDefinition implements IEc2TaskDefinit placementConstraints: props.placementConstraints, ipcMode: props.ipcMode, pidMode: props.pidMode, + inferenceAccelerators: props.inferenceAccelerators, }); } } diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts index 3d8d113886709..ccf3291859708 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts @@ -1,5 +1,6 @@ import { Tokenization } from '@aws-cdk/core'; import { Construct } from 'constructs'; +import { ImportedTaskDefinition } from '../base/_imported-task-definition'; import { CommonTaskDefinitionAttributes, CommonTaskDefinitionProps, @@ -8,7 +9,6 @@ import { NetworkMode, TaskDefinition, } from '../base/task-definition'; -import { ImportedTaskDefinition } from '../base/_imported-task-definition'; /** * The properties for a task definition. diff --git a/packages/@aws-cdk/aws-ecs/lib/images/repository.ts b/packages/@aws-cdk/aws-ecs/lib/images/repository.ts index bf014fb18cb68..4ef78bc3854c1 100644 --- a/packages/@aws-cdk/aws-ecs/lib/images/repository.ts +++ b/packages/@aws-cdk/aws-ecs/lib/images/repository.ts @@ -10,7 +10,6 @@ import { Construct as CoreConstruct } from '@aws-cdk/core'; /** * Regex pattern to check if it is an ECR image URL. * - * @experimental */ const ECR_IMAGE_REGEX = /(^[a-zA-Z0-9][a-zA-Z0-9-_]*).dkr.ecr.([a-zA-Z0-9][a-zA-Z0-9-_]*).amazonaws.com(.cn)?\/.*/; 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 e69abefd2036c..c5d6ece5776d3 100644 --- a/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts @@ -997,6 +997,176 @@ describe('container definition', () => { }); }); + describe('Given InferenceAccelerator resource parameter', () => { + test('correctly adds resource requirements to container definition using inference accelerator resource property', () => { + // GIVEN + const stack = new cdk.Stack(); + + const inferenceAccelerators = [{ + deviceName: 'device1', + deviceType: 'eia2.medium', + }]; + + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { + inferenceAccelerators, + }); + + const inferenceAcceleratorResources = ['device1']; + + // WHEN + taskDefinition.addContainer('cont', { + image: ecs.ContainerImage.fromRegistry('test'), + memoryLimitMiB: 1024, + inferenceAcceleratorResources, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::ECS::TaskDefinition', { + Family: 'Ec2TaskDef', + InferenceAccelerators: [{ + DeviceName: 'device1', + DeviceType: 'eia2.medium', + }], + ContainerDefinitions: [ + { + Image: 'test', + ResourceRequirements: [ + { + Type: 'InferenceAccelerator', + Value: 'device1', + }, + ], + }, + ], + }); + + + }); + test('correctly adds resource requirements to container definition using both props and addInferenceAcceleratorResource method', () => { + // GIVEN + const stack = new cdk.Stack(); + + const inferenceAccelerators = [{ + deviceName: 'device1', + deviceType: 'eia2.medium', + }, { + deviceName: 'device2', + deviceType: 'eia2.large', + }]; + + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { + inferenceAccelerators, + }); + + const inferenceAcceleratorResources = ['device1']; + + const container = taskDefinition.addContainer('cont', { + image: ecs.ContainerImage.fromRegistry('test'), + memoryLimitMiB: 1024, + inferenceAcceleratorResources, + }); + + // WHEN + container.addInferenceAcceleratorResource('device2'); + + // THEN + expect(stack).toHaveResourceLike('AWS::ECS::TaskDefinition', { + Family: 'Ec2TaskDef', + InferenceAccelerators: [{ + DeviceName: 'device1', + DeviceType: 'eia2.medium', + }, { + DeviceName: 'device2', + DeviceType: 'eia2.large', + }], + ContainerDefinitions: [ + { + Image: 'test', + ResourceRequirements: [ + { + Type: 'InferenceAccelerator', + Value: 'device1', + }, + { + Type: 'InferenceAccelerator', + Value: 'device2', + }, + ], + }, + ], + }); + + }); + test('throws when the value of inference accelerator resource does not match any inference accelerators defined in the Task Definition', () => { + // GIVEN + const stack = new cdk.Stack(); + + const inferenceAccelerators = [{ + deviceName: 'device1', + deviceType: 'eia2.medium', + }]; + + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { + inferenceAccelerators, + }); + + const inferenceAcceleratorResources = ['device2']; + + // THEN + expect(() => { + taskDefinition.addContainer('cont', { + image: ecs.ContainerImage.fromRegistry('test'), + memoryLimitMiB: 1024, + inferenceAcceleratorResources, + }); + }).toThrow(/Resource value device2 in container definition doesn't match any inference accelerator device name in the task definition./); + }); + }); + + test('adds resource requirements when both inference accelerator and gpu count are defined in the container definition', () => { + // GIVEN + const stack = new cdk.Stack(); + + const inferenceAccelerators = [{ + deviceName: 'device1', + deviceType: 'eia2.medium', + }]; + + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { + inferenceAccelerators, + }); + + const inferenceAcceleratorResources = ['device1']; + + taskDefinition.addContainer('cont', { + image: ecs.ContainerImage.fromRegistry('test'), + memoryLimitMiB: 1024, + gpuCount: 2, + inferenceAcceleratorResources, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::ECS::TaskDefinition', { + Family: 'Ec2TaskDef', + InferenceAccelerators: [{ + DeviceName: 'device1', + DeviceType: 'eia2.medium', + }], + ContainerDefinitions: [ + { + Image: 'test', + ResourceRequirements: [{ + Type: 'InferenceAccelerator', + Value: 'device1', + }, { + Type: 'GPU', + Value: '2', + }], + }, + ], + }); + }); + test('can add secret environment variables to the container definition', () => { // GIVEN const stack = new cdk.Stack(); 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 0d21804117863..06f891868feb8 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 @@ -1199,6 +1199,72 @@ describe('ec2 task definition', () => { }); }); + describe('setting inferenceAccelerators', () => { + test('correctly sets inferenceAccelerators using props', () => { + // GIVEN + const stack = new cdk.Stack(); + const inferenceAccelerators = [{ + deviceName: 'device1', + deviceType: 'eia2.medium', + }]; + + // WHEN + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { + inferenceAccelerators, + }); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::ECS::TaskDefinition', { + Family: 'Ec2TaskDef', + InferenceAccelerators: [{ + DeviceName: 'device1', + DeviceType: 'eia2.medium', + }], + }); + + }); + test('correctly sets inferenceAccelerators using props and addInferenceAccelerator method', () => { + // GIVEN + const stack = new cdk.Stack(); + const inferenceAccelerators = [{ + deviceName: 'device1', + deviceType: 'eia2.medium', + }]; + + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { + inferenceAccelerators, + }); + + // WHEN + taskDefinition.addInferenceAccelerator({ + deviceName: 'device2', + deviceType: 'eia2.large', + }); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::ECS::TaskDefinition', { + Family: 'Ec2TaskDef', + InferenceAccelerators: [{ + DeviceName: 'device1', + DeviceType: 'eia2.medium', + }, { + DeviceName: 'device2', + DeviceType: 'eia2.large', + }], + }); + }); + }); + describe('When importing from an existing Ec2 TaskDefinition', () => { test('can succeed using TaskDefinition Arn', () => { // GIVEN diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-task-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-task-definition.test.ts index e0fedafa2c3a6..9cd5d994c9555 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-task-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-task-definition.test.ts @@ -113,6 +113,24 @@ nodeunitShim({ test.done(); }, + + 'throws when adding inference accelerators'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + const inferenceAccelerator = { + deviceName: 'device1', + deviceType: 'eia2.medium', + }; + + // THEN + test.throws(() => { + taskDefinition.addInferenceAccelerator(inferenceAccelerator); + }, /Cannot use inference accelerators on tasks that run on Fargate/); + + test.done(); + }, }, 'When importing from an existing Fargate TaskDefinition': { diff --git a/packages/@aws-cdk/aws-ecs/test/task-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/task-definition.test.ts index 1e00e7722c3d9..6e578952a5765 100644 --- a/packages/@aws-cdk/aws-ecs/test/task-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/task-definition.test.ts @@ -1,8 +1,8 @@ import { expect, haveResource } from '@aws-cdk/assert-internal'; +import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; import * as ecs from '../lib'; -import * as iam from '@aws-cdk/aws-iam'; nodeunitShim({ 'When creating a new TaskDefinition': { diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index ef35cfc9ce7dc..bb64c62070a0e 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -202,7 +202,7 @@ cluster.addNodegroupCapacity('custom-node-group', { #### Spot Instances Support Use `capacityType` to create managed node groups comprised of spot instances. To maximize the availability of your applications while using -Spot Instances, we recommend that you configure a Spot managed node group to use multiple instance types with the `instanceTypes` property. +Spot Instances, we recommend that you configure a Spot managed node group to use multiple instance types with the `instanceTypes` property. > For more details visit [Managed node group capacity types](https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html#managed-node-group-capacity-types). @@ -395,7 +395,7 @@ const asg = new ec2.AutoScalingGroup(...) cluster.connectAutoScalingGroupCapacity(asg); ``` -This will add the necessary user-data and configure all connections, roles, and tags needed for the instances in the auto-scaling group to properly join the cluster. +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. #### Spot Instances diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 9feac854438cb..9e9a22d916e56 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -1264,7 +1264,7 @@ export class Cluster extends ClusterBase { if (bootstrapEnabled) { const userData = options.machineImageType === MachineImageType.BOTTLEROCKET ? renderBottlerocketUserData(this) : - renderAmazonLinuxUserData(this.clusterName, autoScalingGroup, options.bootstrapOptions); + renderAmazonLinuxUserData(this, autoScalingGroup, options.bootstrapOptions); autoScalingGroup.addUserData(...userData); } diff --git a/packages/@aws-cdk/aws-eks/lib/user-data.ts b/packages/@aws-cdk/aws-eks/lib/user-data.ts index c6f30d215a9c0..8add0f7cb5bbc 100644 --- a/packages/@aws-cdk/aws-eks/lib/user-data.ts +++ b/packages/@aws-cdk/aws-eks/lib/user-data.ts @@ -1,9 +1,9 @@ import * as autoscaling from '@aws-cdk/aws-autoscaling'; import { Stack } from '@aws-cdk/core'; -import { BootstrapOptions, ICluster } from './cluster'; +import { BootstrapOptions, ICluster, Cluster } from './cluster'; // eslint-disable-next-line max-len -export function renderAmazonLinuxUserData(clusterName: string, autoScalingGroup: autoscaling.AutoScalingGroup, options: BootstrapOptions = {}): string[] { +export function renderAmazonLinuxUserData(cluster: Cluster, autoScalingGroup: autoscaling.AutoScalingGroup, options: BootstrapOptions = {}): string[] { const stack = Stack.of(autoScalingGroup); @@ -13,6 +13,9 @@ export function renderAmazonLinuxUserData(clusterName: string, autoScalingGroup: const extraArgs = new Array(); + extraArgs.push(`--apiserver-endpoint '${cluster.clusterEndpoint}'`); + extraArgs.push(`--b64-cluster-ca '${cluster.clusterCertificateAuthorityData}'`); + extraArgs.push(`--use-max-pods ${options.useMaxPods ?? true}`); if (options.awsApiRetryAttempts) { @@ -45,7 +48,7 @@ export function renderAmazonLinuxUserData(clusterName: string, autoScalingGroup: return [ 'set -o xtrace', - `/etc/eks/bootstrap.sh ${clusterName} --kubelet-extra-args "${kubeletExtraArgs}" ${commandLineSuffix}`.trim(), + `/etc/eks/bootstrap.sh ${cluster.clusterName} --kubelet-extra-args "${kubeletExtraArgs}" ${commandLineSuffix}`.trim(), `/opt/aws/bin/cfn-signal --exit-code $? --stack ${stack.stackName} --resource ${asgLogicalId} --region ${stack.region}`, ]; } diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json index 1ea62568197d4..de8e6fe0bf8ae 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json @@ -1682,7 +1682,21 @@ { "Ref": "Cluster9EE0221C" }, - " --kubelet-extra-args \"--node-labels lifecycle=OnDemand\" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack aws-cdk-eks-cluster-test --resource ClusterNodesASGF172BD19 --region test-region" + " --kubelet-extra-args \"--node-labels lifecycle=OnDemand\" --apiserver-endpoint '", + { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "Endpoint" + ] + }, + "' --b64-cluster-ca '", + { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "CertificateAuthorityData" + ] + }, + "' --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack aws-cdk-eks-cluster-test --resource ClusterNodesASGF172BD19 --region test-region" ] ] } @@ -1993,7 +2007,21 @@ { "Ref": "Cluster9EE0221C" }, - " --kubelet-extra-args \"--node-labels lifecycle=OnDemand\" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack aws-cdk-eks-cluster-test --resource ClusterNodesArmASG40A593D0 --region test-region" + " --kubelet-extra-args \"--node-labels lifecycle=OnDemand\" --apiserver-endpoint '", + { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "Endpoint" + ] + }, + "' --b64-cluster-ca '", + { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "CertificateAuthorityData" + ] + }, + "' --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack aws-cdk-eks-cluster-test --resource ClusterNodesArmASG40A593D0 --region test-region" ] ] } @@ -2630,7 +2658,21 @@ { "Ref": "Cluster9EE0221C" }, - " --kubelet-extra-args \"--node-labels lifecycle=Ec2Spot --register-with-taints=spotInstance=true:PreferNoSchedule --node-labels foo=bar,goo=far\" --use-max-pods true --aws-api-retry-attempts 5\n/opt/aws/bin/cfn-signal --exit-code $? --stack aws-cdk-eks-cluster-test --resource ClusterspotASG857494B6 --region test-region" + " --kubelet-extra-args \"--node-labels lifecycle=Ec2Spot --register-with-taints=spotInstance=true:PreferNoSchedule --node-labels foo=bar,goo=far\" --apiserver-endpoint '", + { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "Endpoint" + ] + }, + "' --b64-cluster-ca '", + { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "CertificateAuthorityData" + ] + }, + "' --use-max-pods true --aws-api-retry-attempts 5\n/opt/aws/bin/cfn-signal --exit-code $? --stack aws-cdk-eks-cluster-test --resource ClusterspotASG857494B6 --region test-region" ] ] } @@ -2973,7 +3015,21 @@ { "Ref": "Cluster9EE0221C" }, - " --kubelet-extra-args \"--node-labels lifecycle=OnDemand\" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack aws-cdk-eks-cluster-test --resource ClusterInferenceInstancesASGE90717C7 --region test-region" + " --kubelet-extra-args \"--node-labels lifecycle=OnDemand\" --apiserver-endpoint '", + { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "Endpoint" + ] + }, + "' --b64-cluster-ca '", + { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "CertificateAuthorityData" + ] + }, + "' --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack aws-cdk-eks-cluster-test --resource ClusterInferenceInstancesASGE90717C7 --region test-region" ] ] } @@ -4146,7 +4202,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters952bd1c03e8201c4c1c67d6de0f3fdaaf88fda05f89a1232c3f6364343cd5344S3Bucket055DC235" + "Ref": "AssetParameters6afd8be511f58dbedd46c8a09c07db8b7340d99fd3527b6d3dfb729208060fc3S3BucketB7E1A9C0" }, "S3Key": { "Fn::Join": [ @@ -4159,7 +4215,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters952bd1c03e8201c4c1c67d6de0f3fdaaf88fda05f89a1232c3f6364343cd5344S3VersionKey2FFFA299" + "Ref": "AssetParameters6afd8be511f58dbedd46c8a09c07db8b7340d99fd3527b6d3dfb729208060fc3S3VersionKey542FDEBD" } ] } @@ -4172,7 +4228,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters952bd1c03e8201c4c1c67d6de0f3fdaaf88fda05f89a1232c3f6364343cd5344S3VersionKey2FFFA299" + "Ref": "AssetParameters6afd8be511f58dbedd46c8a09c07db8b7340d99fd3527b6d3dfb729208060fc3S3VersionKey542FDEBD" } ] } @@ -4674,17 +4730,17 @@ "Type": "String", "Description": "Artifact hash for asset \"b7d38dc0eeb2c5d024919020e09d2590b68559eab4a5264c3b1aa7a429d1edd4\"" }, - "AssetParameters952bd1c03e8201c4c1c67d6de0f3fdaaf88fda05f89a1232c3f6364343cd5344S3Bucket055DC235": { + "AssetParameters6afd8be511f58dbedd46c8a09c07db8b7340d99fd3527b6d3dfb729208060fc3S3BucketB7E1A9C0": { "Type": "String", - "Description": "S3 bucket for asset \"952bd1c03e8201c4c1c67d6de0f3fdaaf88fda05f89a1232c3f6364343cd5344\"" + "Description": "S3 bucket for asset \"6afd8be511f58dbedd46c8a09c07db8b7340d99fd3527b6d3dfb729208060fc3\"" }, - "AssetParameters952bd1c03e8201c4c1c67d6de0f3fdaaf88fda05f89a1232c3f6364343cd5344S3VersionKey2FFFA299": { + "AssetParameters6afd8be511f58dbedd46c8a09c07db8b7340d99fd3527b6d3dfb729208060fc3S3VersionKey542FDEBD": { "Type": "String", - "Description": "S3 key for asset version \"952bd1c03e8201c4c1c67d6de0f3fdaaf88fda05f89a1232c3f6364343cd5344\"" + "Description": "S3 key for asset version \"6afd8be511f58dbedd46c8a09c07db8b7340d99fd3527b6d3dfb729208060fc3\"" }, - "AssetParameters952bd1c03e8201c4c1c67d6de0f3fdaaf88fda05f89a1232c3f6364343cd5344ArtifactHash1AB042BC": { + "AssetParameters6afd8be511f58dbedd46c8a09c07db8b7340d99fd3527b6d3dfb729208060fc3ArtifactHash5E61FCA5": { "Type": "String", - "Description": "Artifact hash for asset \"952bd1c03e8201c4c1c67d6de0f3fdaaf88fda05f89a1232c3f6364343cd5344\"" + "Description": "Artifact hash for asset \"6afd8be511f58dbedd46c8a09c07db8b7340d99fd3527b6d3dfb729208060fc3\"" }, "AssetParameters5f49893093e1ad14831626016699156d48da5f0890f19eb930bc3c46cf5f636dS3BucketA6642550": { "Type": "String", diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index d82f74eb59602..3a1866d6f460b 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -1294,7 +1294,7 @@ export = { // THEN const template = app.synth().getStackByName(stack.stackName).template; const userData = template.Resources.ClusterMyCapcityLaunchConfig58583345.Properties.UserData; - test.deepEqual(userData, { 'Fn::Base64': { 'Fn::Join': ['', ['#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ', { Ref: 'Cluster9EE0221C' }, ' --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack Stack --resource ClusterMyCapcityASGD4CD8B97 --region us-east-1']] } }); + test.deepEqual(userData, { 'Fn::Base64': { 'Fn::Join': ['', ['#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ', { Ref: 'Cluster9EE0221C' }, ' --kubelet-extra-args "--node-labels lifecycle=OnDemand" --apiserver-endpoint \'', { 'Fn::GetAtt': ['Cluster9EE0221C', 'Endpoint'] }, '\' --b64-cluster-ca \'', { 'Fn::GetAtt': ['Cluster9EE0221C', 'CertificateAuthorityData'] }, '\' --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack Stack --resource ClusterMyCapcityASGD4CD8B97 --region us-east-1']] } }); test.done(); }, @@ -1333,7 +1333,7 @@ export = { // THEN const template = app.synth().getStackByName(stack.stackName).template; const userData = template.Resources.ClusterMyCapcityLaunchConfig58583345.Properties.UserData; - test.deepEqual(userData, { 'Fn::Base64': { 'Fn::Join': ['', ['#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ', { Ref: 'Cluster9EE0221C' }, ' --kubelet-extra-args "--node-labels lifecycle=OnDemand --node-labels FOO=42" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack Stack --resource ClusterMyCapcityASGD4CD8B97 --region us-east-1']] } }); + test.deepEqual(userData, { 'Fn::Base64': { 'Fn::Join': ['', ['#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ', { Ref: 'Cluster9EE0221C' }, ' --kubelet-extra-args "--node-labels lifecycle=OnDemand --node-labels FOO=42" --apiserver-endpoint \'', { 'Fn::GetAtt': ['Cluster9EE0221C', 'Endpoint'] }, '\' --b64-cluster-ca \'', { 'Fn::GetAtt': ['Cluster9EE0221C', 'CertificateAuthorityData'] }, '\' --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack Stack --resource ClusterMyCapcityASGD4CD8B97 --region us-east-1']] } }); test.done(); }, @@ -1353,7 +1353,7 @@ export = { // THEN const template = app.synth().getStackByName(stack.stackName).template; const userData = template.Resources.ClusterMyCapcityLaunchConfig58583345.Properties.UserData; - test.deepEqual(userData, { 'Fn::Base64': { 'Fn::Join': ['', ['#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ', { Ref: 'Cluster9EE0221C' }, ' --kubelet-extra-args "--node-labels lifecycle=Ec2Spot --register-with-taints=spotInstance=true:PreferNoSchedule" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack Stack --resource ClusterMyCapcityASGD4CD8B97 --region us-east-1']] } }); + test.deepEqual(userData, { 'Fn::Base64': { 'Fn::Join': ['', ['#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ', { Ref: 'Cluster9EE0221C' }, ' --kubelet-extra-args "--node-labels lifecycle=Ec2Spot --register-with-taints=spotInstance=true:PreferNoSchedule" --apiserver-endpoint \'', { 'Fn::GetAtt': ['Cluster9EE0221C', 'Endpoint'] }, '\' --b64-cluster-ca \'', { 'Fn::GetAtt': ['Cluster9EE0221C', 'CertificateAuthorityData'] }, '\' --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack Stack --resource ClusterMyCapcityASGD4CD8B97 --region us-east-1']] } }); test.done(); }, diff --git a/packages/@aws-cdk/aws-eks/test/test.user-data.ts b/packages/@aws-cdk/aws-eks/test/test.user-data.ts index e796e5fd19ee4..ce582a374a3fc 100644 --- a/packages/@aws-cdk/aws-eks/test/test.user-data.ts +++ b/packages/@aws-cdk/aws-eks/test/test.user-data.ts @@ -2,6 +2,7 @@ import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import { App, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; +import { Cluster, KubernetesVersion } from '../lib/cluster'; import { renderAmazonLinuxUserData } from '../lib/user-data'; /* eslint-disable max-len */ @@ -9,15 +10,30 @@ import { renderAmazonLinuxUserData } from '../lib/user-data'; export = { 'default user data'(test: Test) { // GIVEN - const { asg, stack } = newFixtures(); + const { asg, stack, cluster } = newFixtures(); // WHEN - const userData = stack.resolve(renderAmazonLinuxUserData('my-cluster-name', asg)); + const userData = stack.resolve(renderAmazonLinuxUserData(cluster, asg)); // THEN test.deepEqual(userData, [ 'set -o xtrace', - '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true', + { + 'Fn::Join': [ + '', + [ + '/etc/eks/bootstrap.sh ', + { Ref: 'clusterC5B25D0D' }, + ' --kubelet-extra-args "--node-labels lifecycle=OnDemand" --apiserver-endpoint \'', + { 'Fn::GetAtt': ['clusterC5B25D0D', 'Endpoint'] }, + "' --b64-cluster-ca '", + { + 'Fn::GetAtt': ['clusterC5B25D0D', 'CertificateAuthorityData'], + }, + "' --use-max-pods true", + ], + ], + }, '/opt/aws/bin/cfn-signal --exit-code $? --stack my-stack --resource ASG46ED3070 --region us-west-33', ]); @@ -26,141 +42,322 @@ export = { '--use-max-pods=true'(test: Test) { // GIVEN - const { asg, stack } = newFixtures(); + const { asg, stack, cluster } = newFixtures(); // WHEN - const userData = stack.resolve(renderAmazonLinuxUserData('my-cluster-name', asg, { + const userData = stack.resolve(renderAmazonLinuxUserData(cluster, asg, { useMaxPods: true, })); // THEN - test.deepEqual(userData[1], '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true'); + test.deepEqual( + userData[1], + { + 'Fn::Join': [ + '', + [ + '/etc/eks/bootstrap.sh ', + { Ref: 'clusterC5B25D0D' }, + ' --kubelet-extra-args "--node-labels lifecycle=OnDemand" --apiserver-endpoint \'', + { 'Fn::GetAtt': ['clusterC5B25D0D', 'Endpoint'] }, + "' --b64-cluster-ca '", + { + 'Fn::GetAtt': ['clusterC5B25D0D', 'CertificateAuthorityData'], + }, + "' --use-max-pods true", + ], + ], + }, + ); test.done(); }, '--use-max-pods=false'(test: Test) { // GIVEN - const { asg, stack } = newFixtures(); + const { asg, stack, cluster } = newFixtures(); // WHEN - const userData = stack.resolve(renderAmazonLinuxUserData('my-cluster-name', asg, { + const userData = stack.resolve(renderAmazonLinuxUserData(cluster, asg, { useMaxPods: false, })); // THEN - test.deepEqual(userData[1], '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods false'); + test.deepEqual( + userData[1], + { + 'Fn::Join': [ + '', + [ + '/etc/eks/bootstrap.sh ', + { Ref: 'clusterC5B25D0D' }, + ' --kubelet-extra-args "--node-labels lifecycle=OnDemand" --apiserver-endpoint \'', + { 'Fn::GetAtt': ['clusterC5B25D0D', 'Endpoint'] }, + "' --b64-cluster-ca '", + { + 'Fn::GetAtt': ['clusterC5B25D0D', 'CertificateAuthorityData'], + }, + "' --use-max-pods false", + ], + ], + }, + ); test.done(); }, '--aws-api-retry-attempts'(test: Test) { // GIVEN - const { asg, stack } = newFixtures(); + const { asg, stack, cluster } = newFixtures(); // WHEN - const userData = stack.resolve(renderAmazonLinuxUserData('my-cluster-name', asg, { + const userData = stack.resolve(renderAmazonLinuxUserData(cluster, asg, { awsApiRetryAttempts: 123, })); // THEN - test.deepEqual(userData[1], '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true --aws-api-retry-attempts 123'); + test.deepEqual( + userData[1], + { + 'Fn::Join': [ + '', + [ + '/etc/eks/bootstrap.sh ', + { Ref: 'clusterC5B25D0D' }, + ' --kubelet-extra-args "--node-labels lifecycle=OnDemand" --apiserver-endpoint \'', + { 'Fn::GetAtt': ['clusterC5B25D0D', 'Endpoint'] }, + "' --b64-cluster-ca '", + { + 'Fn::GetAtt': ['clusterC5B25D0D', 'CertificateAuthorityData'], + }, + "' --use-max-pods true --aws-api-retry-attempts 123", + ], + ], + }, + ); test.done(); }, '--dns-cluster-ip'(test: Test) { // GIVEN - const { asg, stack } = newFixtures(); + const { asg, stack, cluster } = newFixtures(); // WHEN - const userData = stack.resolve(renderAmazonLinuxUserData('my-cluster-name', asg, { + const userData = stack.resolve(renderAmazonLinuxUserData(cluster, asg, { dnsClusterIp: '192.0.2.53', })); // THEN - test.deepEqual(userData[1], '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true --dns-cluster-ip 192.0.2.53'); + test.deepEqual( + userData[1], + { + 'Fn::Join': [ + '', + [ + '/etc/eks/bootstrap.sh ', + { Ref: 'clusterC5B25D0D' }, + ' --kubelet-extra-args "--node-labels lifecycle=OnDemand" --apiserver-endpoint \'', + { 'Fn::GetAtt': ['clusterC5B25D0D', 'Endpoint'] }, + "' --b64-cluster-ca '", + { + 'Fn::GetAtt': ['clusterC5B25D0D', 'CertificateAuthorityData'], + }, + "' --use-max-pods true --dns-cluster-ip 192.0.2.53", + ], + ], + }, + ); test.done(); }, '--docker-config-json'(test: Test) { // GIVEN - const { asg } = newFixtures(); + const { asg, stack, cluster } = newFixtures(); // WHEN - const userData = renderAmazonLinuxUserData('my-cluster-name', asg, { + const userData = stack.resolve(renderAmazonLinuxUserData(cluster, asg, { dockerConfigJson: '{"docker":123}', - }); + })); // THEN - test.deepEqual(userData[1], '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true --docker-config-json \'{"docker":123}\''); + test.deepEqual( + userData[1], + { + 'Fn::Join': [ + '', + [ + '/etc/eks/bootstrap.sh ', + { Ref: 'clusterC5B25D0D' }, + ' --kubelet-extra-args "--node-labels lifecycle=OnDemand" --apiserver-endpoint \'', + { 'Fn::GetAtt': ['clusterC5B25D0D', 'Endpoint'] }, + "' --b64-cluster-ca '", + { + 'Fn::GetAtt': ['clusterC5B25D0D', 'CertificateAuthorityData'], + }, + '\' --use-max-pods true --docker-config-json \'{"docker":123}\'', + ], + ], + }, + ); test.done(); }, '--enable-docker-bridge=true'(test: Test) { // GIVEN - const { asg, stack } = newFixtures(); + const { asg, stack, cluster } = newFixtures(); // WHEN - const userData = stack.resolve(renderAmazonLinuxUserData('my-cluster-name', asg, { + const userData = stack.resolve(renderAmazonLinuxUserData(cluster, asg, { enableDockerBridge: true, })); // THEN - test.deepEqual(userData[1], '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true --enable-docker-bridge true'); + test.deepEqual( + userData[1], + { + 'Fn::Join': [ + '', + [ + '/etc/eks/bootstrap.sh ', + { Ref: 'clusterC5B25D0D' }, + ' --kubelet-extra-args "--node-labels lifecycle=OnDemand" --apiserver-endpoint \'', + { 'Fn::GetAtt': ['clusterC5B25D0D', 'Endpoint'] }, + "' --b64-cluster-ca '", + { + 'Fn::GetAtt': ['clusterC5B25D0D', 'CertificateAuthorityData'], + }, + "' --use-max-pods true --enable-docker-bridge true", + ], + ], + }, + ); test.done(); }, '--enable-docker-bridge=false'(test: Test) { // GIVEN - const { asg, stack } = newFixtures(); + const { asg, stack, cluster } = newFixtures(); // WHEN - const userData = stack.resolve(renderAmazonLinuxUserData('my-cluster-name', asg, { + const userData = stack.resolve(renderAmazonLinuxUserData(cluster, asg, { enableDockerBridge: false, })); // THEN - test.deepEqual(userData[1], '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true'); + test.deepEqual( + userData[1], + { + 'Fn::Join': [ + '', + [ + '/etc/eks/bootstrap.sh ', + { Ref: 'clusterC5B25D0D' }, + ' --kubelet-extra-args "--node-labels lifecycle=OnDemand" --apiserver-endpoint \'', + { 'Fn::GetAtt': ['clusterC5B25D0D', 'Endpoint'] }, + "' --b64-cluster-ca '", + { + 'Fn::GetAtt': ['clusterC5B25D0D', 'CertificateAuthorityData'], + }, + "' --use-max-pods true", + ], + ], + }, + ); test.done(); }, '--kubelet-extra-args'(test: Test) { // GIVEN - const { asg, stack } = newFixtures(); + const { asg, stack, cluster } = newFixtures(); // WHEN - const userData = stack.resolve(renderAmazonLinuxUserData('my-cluster-name', asg, { + const userData = stack.resolve(renderAmazonLinuxUserData(cluster, asg, { kubeletExtraArgs: '--extra-args-for --kubelet', })); // THEN - test.deepEqual(userData[1], '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand --extra-args-for --kubelet" --use-max-pods true'); + test.deepEqual( + userData[1], + { + 'Fn::Join': [ + '', + [ + '/etc/eks/bootstrap.sh ', + { Ref: 'clusterC5B25D0D' }, + ' --kubelet-extra-args "--node-labels lifecycle=OnDemand --extra-args-for --kubelet" --apiserver-endpoint \'', + { 'Fn::GetAtt': ['clusterC5B25D0D', 'Endpoint'] }, + "' --b64-cluster-ca '", + { + 'Fn::GetAtt': ['clusterC5B25D0D', 'CertificateAuthorityData'], + }, + "' --use-max-pods true", + ], + ], + }, + ); test.done(); }, 'arbitrary additional bootstrap arguments can be passed through "additionalArgs"'(test: Test) { // GIVEN - const { asg, stack } = newFixtures(); + const { asg, stack, cluster } = newFixtures(); // WHEN - const userData = stack.resolve(renderAmazonLinuxUserData('my-cluster-name', asg, { + const userData = stack.resolve(renderAmazonLinuxUserData(cluster, asg, { additionalArgs: '--apiserver-endpoint 1111 --foo-bar', })); // THEN - test.deepEqual(userData[1], '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true --apiserver-endpoint 1111 --foo-bar'); + // NB: duplicated --apiserver-endpoint is fine. Last wins. + test.deepEqual( + userData[1], + { + 'Fn::Join': [ + '', + [ + '/etc/eks/bootstrap.sh ', + { Ref: 'clusterC5B25D0D' }, + ' --kubelet-extra-args "--node-labels lifecycle=OnDemand" --apiserver-endpoint \'', + { 'Fn::GetAtt': ['clusterC5B25D0D', 'Endpoint'] }, + "' --b64-cluster-ca '", + { + 'Fn::GetAtt': ['clusterC5B25D0D', 'CertificateAuthorityData'], + }, + "' --use-max-pods true --apiserver-endpoint 1111 --foo-bar", + ], + ], + }, + ); test.done(); }, 'if asg has spot instances, the correct label and taint is used'(test: Test) { // GIVEN - const { asg, stack } = newFixtures(true); + const { asg, stack, cluster } = newFixtures(true); // WHEN - const userData = stack.resolve(renderAmazonLinuxUserData('my-cluster-name', asg, { + const userData = stack.resolve(renderAmazonLinuxUserData(cluster, asg, { kubeletExtraArgs: '--node-labels X=y', })); // THEN - test.deepEqual(userData[1], '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=Ec2Spot --register-with-taints=spotInstance=true:PreferNoSchedule --node-labels X=y" --use-max-pods true'); + test.deepEqual( + userData[1], + { + 'Fn::Join': [ + '', + [ + '/etc/eks/bootstrap.sh ', + { Ref: 'clusterC5B25D0D' }, + ' --kubelet-extra-args "--node-labels lifecycle=Ec2Spot --register-with-taints=spotInstance=true:PreferNoSchedule --node-labels X=y" --apiserver-endpoint \'', + { 'Fn::GetAtt': ['clusterC5B25D0D', 'Endpoint'] }, + "' --b64-cluster-ca '", + { + 'Fn::GetAtt': ['clusterC5B25D0D', 'CertificateAuthorityData'], + }, + "' --use-max-pods true", + ], + ], + }, + ); test.done(); }, }; @@ -169,6 +366,11 @@ function newFixtures(spot = false) { const app = new App(); const stack = new Stack(app, 'my-stack', { env: { region: 'us-west-33' } }); const vpc = new ec2.Vpc(stack, 'vpc'); + const cluster = new Cluster(stack, 'cluster', { + version: KubernetesVersion.V1_19, + clusterName: 'my-cluster-name', + vpc, + }); const asg = new autoscaling.AutoScalingGroup(stack, 'ASG', { instanceType: new ec2.InstanceType('m4.xlarge'), machineImage: new ec2.AmazonLinuxImage(), @@ -176,5 +378,5 @@ function newFixtures(spot = false) { vpc, }); - return { stack, vpc, asg }; + return { stack, vpc, cluster, asg }; } diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts index eaed2daee58d2..d60d91e0a9a51 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts @@ -187,10 +187,18 @@ export interface LoadBalancerListener { readonly policyNames?: string[]; /** - * ID of SSL certificate + * the ARN of the SSL certificate + * @deprecated - use sslCertificateArn instead */ readonly sslCertificateId?: string; + /** + * the ARN of the SSL certificate + * + * @default - none + */ + readonly sslCertificateArn?: string; + /** * Allow connections to the load balancer from the given set of connection peers * @@ -264,8 +272,12 @@ export class LoadBalancer extends Resource implements IConnectable { * @returns A ListenerPort object that controls connections to the listener port */ public addListener(listener: LoadBalancerListener): ListenerPort { + if (listener.sslCertificateArn && listener.sslCertificateId) { + throw new Error('"sslCertificateId" is deprecated, please use "sslCertificateArn" only.'); + } const protocol = ifUndefinedLazy(listener.externalProtocol, () => wellKnownProtocol(listener.externalPort)); const instancePort = listener.internalPort || listener.externalPort; + const sslCertificateArn = listener.sslCertificateArn || listener.sslCertificateId; const instanceProtocol = ifUndefined(listener.internalProtocol, ifUndefined(tryWellKnownProtocol(instancePort), isHttpProtocol(protocol) ? LoadBalancingProtocol.HTTP : LoadBalancingProtocol.TCP)); @@ -275,7 +287,7 @@ export class LoadBalancer extends Resource implements IConnectable { protocol, instancePort: instancePort.toString(), instanceProtocol, - sslCertificateId: listener.sslCertificateId, + sslCertificateId: sslCertificateArn, policyNames: listener.policyNames, }); diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts b/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts index 60bf1ee3632bb..9cac87e057e87 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts @@ -1,4 +1,4 @@ -import { expect, haveResource } from '@aws-cdk/assert-internal'; +import '@aws-cdk/assert-internal/jest'; import { Connections, Peer, SubnetType, Vpc } from '@aws-cdk/aws-ec2'; import { Duration, Stack } from '@aws-cdk/core'; import { ILoadBalancerTarget, LoadBalancer, LoadBalancingProtocol } from '../lib'; @@ -18,14 +18,14 @@ describe('tests', () => { internalPort: 8080, }); - expect(stack).to(haveResource('AWS::ElasticLoadBalancing::LoadBalancer', { + expect(stack).toHaveResource('AWS::ElasticLoadBalancing::LoadBalancer', { Listeners: [{ InstancePort: '8080', InstanceProtocol: 'http', LoadBalancerPort: '8080', Protocol: 'http', }], - })); + }); }); test('add a health check', () => { @@ -45,7 +45,7 @@ describe('tests', () => { }); // THEN - expect(stack).to(haveResource('AWS::ElasticLoadBalancing::LoadBalancer', { + expect(stack).toHaveResource('AWS::ElasticLoadBalancing::LoadBalancer', { HealthCheck: { HealthyThreshold: '2', Interval: '60', @@ -53,7 +53,7 @@ describe('tests', () => { Timeout: '5', UnhealthyThreshold: '5', }, - })); + }); }); test('add a listener and load balancing target', () => { @@ -75,7 +75,7 @@ describe('tests', () => { elb.addTarget(new FakeTarget()); // THEN: at the very least it added a security group rule for the backend - expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + expect(stack).toHaveResource('AWS::EC2::SecurityGroup', { SecurityGroupEgress: [ { Description: 'Port 8080 LB to fleet', @@ -85,7 +85,7 @@ describe('tests', () => { ToPort: 8080, }, ], - })); + }); }); test('enable cross zone load balancing', () => { @@ -100,9 +100,9 @@ describe('tests', () => { }); // THEN - expect(stack).to(haveResource('AWS::ElasticLoadBalancing::LoadBalancer', { + expect(stack).toHaveResource('AWS::ElasticLoadBalancing::LoadBalancer', { CrossZone: true, - })); + }); }); test('disable cross zone load balancing', () => { @@ -117,9 +117,9 @@ describe('tests', () => { }); // THEN - expect(stack).to(haveResource('AWS::ElasticLoadBalancing::LoadBalancer', { + expect(stack).toHaveResource('AWS::ElasticLoadBalancing::LoadBalancer', { CrossZone: false, - })); + }); }); test('cross zone load balancing enabled by default', () => { @@ -133,9 +133,9 @@ describe('tests', () => { }); // THEN - expect(stack).to(haveResource('AWS::ElasticLoadBalancing::LoadBalancer', { + expect(stack).toHaveResource('AWS::ElasticLoadBalancing::LoadBalancer', { CrossZone: true, - })); + }); }); test('use specified subnet', () => { @@ -170,11 +170,84 @@ describe('tests', () => { }); // THEN - expect(stack).to(haveResource('AWS::ElasticLoadBalancing::LoadBalancer', { + expect(stack).toHaveResource('AWS::ElasticLoadBalancing::LoadBalancer', { Subnets: vpc.selectSubnets({ subnetGroupName: 'private1', }).subnetIds.map((subnetId: string) => stack.resolve(subnetId)), - })); + }); + }); + + test('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(); + const vpc = new Vpc(stack, 'VCP'); + + // WHEN + const lb = new LoadBalancer(stack, 'LB', { vpc }); + + lb.addListener({ + externalPort: 80, + internalPort: 8080, + sslCertificateId: sslCertificateArn, + }); + + // THEN + expect(stack).toHaveResource('AWS::ElasticLoadBalancing::LoadBalancer', { + Listeners: [{ + InstancePort: '8080', + InstanceProtocol: 'http', + LoadBalancerPort: '80', + Protocol: 'http', + SSLCertificateId: sslCertificateArn, + }], + }); + }); + + test('does not fail when sslCertificateArn is used', () => { + // GIVEN + const sslCertificateArn = 'arn:aws:acm:us-east-1:12345:test/12345'; + const stack = new Stack(); + const vpc = new Vpc(stack, 'VCP'); + + // WHEN + const lb = new LoadBalancer(stack, 'LB', { vpc }); + + lb.addListener({ + externalPort: 80, + internalPort: 8080, + sslCertificateArn: sslCertificateArn, + }); + + // THEN + expect(stack).toHaveResource('AWS::ElasticLoadBalancing::LoadBalancer', { + Listeners: [{ + InstancePort: '8080', + InstanceProtocol: 'http', + LoadBalancerPort: '80', + Protocol: 'http', + SSLCertificateId: sslCertificateArn, + }], + }); + }); + + test('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(); + const vpc = new Vpc(stack, 'VCP'); + + // WHEN + const lb = new LoadBalancer(stack, 'LB', { vpc }); + + // THEN + expect(() => + lb.addListener({ + externalPort: 80, + internalPort: 8080, + sslCertificateArn: sslCertificateArn, + sslCertificateId: sslCertificateArn, + })).toThrow(/"sslCertificateId" is deprecated, please use "sslCertificateArn" only./); }); }); diff --git a/packages/@aws-cdk/aws-events-targets/lib/batch.ts b/packages/@aws-cdk/aws-events-targets/lib/batch.ts index 0d3e3c5b8409f..15a823549055c 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/batch.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/batch.ts @@ -1,12 +1,10 @@ -import * as batch from '@aws-cdk/aws-batch'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; -import { Names } from '@aws-cdk/core'; +import { Names, IConstruct } from '@aws-cdk/core'; import { singletonEventRole } from './util'; /** * Customize the Batch Job Event Target - * @experimental */ export interface BatchJobProps { /** @@ -44,12 +42,32 @@ export interface BatchJobProps { /** * Use an AWS Batch Job / Queue as an event rule target. - * @experimental + * Most likely the code will look something like this: + * `new BatchJob(jobQueue.jobQueueArn, jobQueue, jobDefinition.jobDefinitionArn, jobDefinition)` + * + * In the future this API will be improved to be fully typed */ export class BatchJob implements events.IRuleTarget { constructor( - private readonly jobQueue: batch.IJobQueue, - private readonly jobDefinition: batch.IJobDefinition, + /** + * The JobQueue arn + */ + private readonly jobQueueArn: string, + + /** + * The JobQueue Resource + */ + private readonly jobQueueScope: IConstruct, + + /** + * The jobDefinition arn + */ + private readonly jobDefinitionArn: string, + + /** + * The JobQueue Resource + */ + private readonly jobDefinitionScope: IConstruct, private readonly props: BatchJobProps = {}, ) { } @@ -59,27 +77,27 @@ export class BatchJob implements events.IRuleTarget { */ public bind(rule: events.IRule, _id?: string): events.RuleTargetConfig { const batchParameters: events.CfnRule.BatchParametersProperty = { - jobDefinition: this.jobDefinition.jobDefinitionArn, + jobDefinition: this.jobDefinitionArn, jobName: this.props.jobName ?? Names.nodeUniqueId(rule.node), arrayProperties: this.props.size ? { size: this.props.size } : undefined, retryStrategy: this.props.attempts ? { attempts: this.props.attempts } : undefined, }; return { - arn: this.jobQueue.jobQueueArn, + arn: this.jobQueueArn, // When scoping resource-level access for job submission, you must provide both job queue and job definition resource types. // https://docs.aws.amazon.com/batch/latest/userguide/ExamplePolicies_BATCH.html#iam-example-restrict-job-def - role: singletonEventRole(this.jobDefinition, [ + role: singletonEventRole(this.jobDefinitionScope, [ new iam.PolicyStatement({ actions: ['batch:SubmitJob'], resources: [ - this.jobDefinition.jobDefinitionArn, - this.jobQueue.jobQueueArn, + this.jobDefinitionArn, + this.jobQueueArn, ], }), ]), input: this.props.event, - targetResource: this.jobQueue, + targetResource: this.jobQueueScope, batchParameters, }; } diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index daa566296ac8d..0dd8a9b3bb892 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -73,6 +73,7 @@ "devDependencies": { "@aws-cdk/aws-codecommit": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-batch": "0.0.0", "aws-sdk": "^2.848.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", @@ -82,7 +83,6 @@ "@aws-cdk/assert-internal": "0.0.0" }, "dependencies": { - "@aws-cdk/aws-batch": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codepipeline": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", @@ -103,7 +103,6 @@ }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-batch": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codepipeline": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-events-targets/test/batch/batch.test.ts b/packages/@aws-cdk/aws-events-targets/test/batch/batch.test.ts index bfa08fe3f4f60..acdf2477832f5 100644 --- a/packages/@aws-cdk/aws-events-targets/test/batch/batch.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/batch/batch.test.ts @@ -28,7 +28,7 @@ test('use aws batch job as an eventrule target', () => { }); // WHEN - rule.addTarget(new targets.BatchJob(jobQueue, jobDefinition)); + rule.addTarget(new targets.BatchJob(jobQueue.jobQueueArn, jobQueue, jobDefinition.jobDefinitionArn, jobDefinition)); // THEN expect(stack).to(haveResource('AWS::Events::Rule', { diff --git a/packages/@aws-cdk/aws-events-targets/test/batch/integ.job-definition-events.ts b/packages/@aws-cdk/aws-events-targets/test/batch/integ.job-definition-events.ts index d09960c142971..01d1058eb4064 100644 --- a/packages/@aws-cdk/aws-events-targets/test/batch/integ.job-definition-events.ts +++ b/packages/@aws-cdk/aws-events-targets/test/batch/integ.job-definition-events.ts @@ -27,11 +27,11 @@ const job = new batch.JobDefinition(stack, 'MyJob', { const timer = new events.Rule(stack, 'Timer', { schedule: events.Schedule.rate(cdk.Duration.minutes(1)), }); -timer.addTarget(new targets.BatchJob(queue, job)); +timer.addTarget(new targets.BatchJob(queue.jobQueueArn, queue, job.jobDefinitionArn, job)); const timer2 = new events.Rule(stack, 'Timer2', { schedule: events.Schedule.rate(cdk.Duration.minutes(2)), }); -timer2.addTarget(new targets.BatchJob(queue, job)); +timer2.addTarget(new targets.BatchJob(queue.jobQueueArn, queue, job.jobDefinitionArn, job)); app.synth(); diff --git a/packages/@aws-cdk/aws-fsx/lib/lustre-file-system.ts b/packages/@aws-cdk/aws-fsx/lib/lustre-file-system.ts index 2206bb31dfad3..b88b3e99ac081 100644 --- a/packages/@aws-cdk/aws-fsx/lib/lustre-file-system.ts +++ b/packages/@aws-cdk/aws-fsx/lib/lustre-file-system.ts @@ -73,8 +73,8 @@ export interface LustreConfiguration { readonly perUnitStorageThroughput?: number; /** - * The preferred day and time to perform weekly maintenance. The first digit is the day of the week, starting at 0 - * for Sunday, then the following are hours and minutes in the UTC time zone, 24 hour clock. For example: '2:20:30' + * The preferred day and time to perform weekly maintenance. The first digit is the day of the week, starting at 1 + * for Monday, then the following are hours and minutes in the UTC time zone, 24 hour clock. For example: '2:20:30' * is Tuesdays at 20:30. * * @default - no preference diff --git a/packages/@aws-cdk/aws-fsx/lib/maintenance-time.ts b/packages/@aws-cdk/aws-fsx/lib/maintenance-time.ts index d9e06de3d0266..57c2da79379b4 100644 --- a/packages/@aws-cdk/aws-fsx/lib/maintenance-time.ts +++ b/packages/@aws-cdk/aws-fsx/lib/maintenance-time.ts @@ -2,10 +2,6 @@ * Enum for representing all the days of the week */ export enum Weekday { - /** - * Sunday - */ - SUNDAY = '0', /** * Monday */ @@ -29,7 +25,11 @@ export enum Weekday { /** * Saturday */ - SATURDAY = '6' + SATURDAY = '6', + /** + * Sunday + */ + SUNDAY = '7' } /** diff --git a/packages/@aws-cdk/aws-fsx/test/lustre-file-system.test.ts b/packages/@aws-cdk/aws-fsx/test/lustre-file-system.test.ts index 2236a15a3ea2d..ad4c4462d8c58 100644 --- a/packages/@aws-cdk/aws-fsx/test/lustre-file-system.test.ts +++ b/packages/@aws-cdk/aws-fsx/test/lustre-file-system.test.ts @@ -121,7 +121,7 @@ describe('FSx for Lustre File System', () => { expectCDK(stack).to(haveResource('AWS::FSx::FileSystem', { LustreConfiguration: { DeploymentType: 'SCRATCH_2', - WeeklyMaintenanceStartTime: '0:12:34', + WeeklyMaintenanceStartTime: '7:12:34', }, })); expectCDK(stack).to(haveResource('AWS::EC2::SecurityGroup')); diff --git a/packages/@aws-cdk/aws-fsx/test/maintenance-time.test.ts b/packages/@aws-cdk/aws-fsx/test/maintenance-time.test.ts index 8ea63ccacd050..9a92e6041085b 100644 --- a/packages/@aws-cdk/aws-fsx/test/maintenance-time.test.ts +++ b/packages/@aws-cdk/aws-fsx/test/maintenance-time.test.ts @@ -2,10 +2,10 @@ import { strictEqual } from 'assert'; import { LustreMaintenanceTime, Weekday } from '../lib'; test.each([ - [Weekday.SUNDAY, 0, 0, '0:00:00'], + [Weekday.SUNDAY, 0, 0, '7:00:00'], [Weekday.SATURDAY, 0, 0, '6:00:00'], - [Weekday.SUNDAY, 24, 0, '0:24:00'], - [Weekday.SUNDAY, 0, 59, '0:00:59'], + [Weekday.SUNDAY, 24, 0, '7:24:00'], + [Weekday.SUNDAY, 0, 59, '7:00:59'], ])('valid maintenance time %s:%d:%d returns %s', (day: Weekday, hour: number, minute: number, expected: string) => { strictEqual( new LustreMaintenanceTime({ day, hour, minute }).toTimestamp(), diff --git a/packages/@aws-cdk/aws-glue/jest.config.js b/packages/@aws-cdk/aws-glue/jest.config.js index cd664e1d069e5..54e28beb9798b 100644 --- a/packages/@aws-cdk/aws-glue/jest.config.js +++ b/packages/@aws-cdk/aws-glue/jest.config.js @@ -1,2 +1,2 @@ -const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +const baseConfig = require('cdk-build-tools/config/jest.config'); module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-iam/lib/grant.ts b/packages/@aws-cdk/aws-iam/lib/grant.ts index 6529db181c712..f648d6a4572c3 100644 --- a/packages/@aws-cdk/aws-iam/lib/grant.ts +++ b/packages/@aws-cdk/aws-iam/lib/grant.ts @@ -5,7 +5,6 @@ import { IGrantable, IPrincipal } from './principals'; /** * Basic options for a grant operation * - * @experimental */ export interface CommonGrantOptions { /** @@ -29,7 +28,6 @@ export interface CommonGrantOptions { /** * Options for a grant operation * - * @experimental */ export interface GrantWithResourceOptions extends CommonGrantOptions { /** @@ -53,7 +51,6 @@ export interface GrantWithResourceOptions extends CommonGrantOptions { /** * Options for a grant operation that only applies to principals * - * @experimental */ export interface GrantOnPrincipalOptions extends CommonGrantOptions { /** @@ -67,7 +64,6 @@ export interface GrantOnPrincipalOptions extends CommonGrantOptions { /** * Options for a grant operation to both identity and resource * - * @experimental */ export interface GrantOnPrincipalAndResourceOptions extends CommonGrantOptions { /** diff --git a/packages/@aws-cdk/aws-iam/lib/oidc-provider.ts b/packages/@aws-cdk/aws-iam/lib/oidc-provider.ts index ec70c6d152cbe..ab6d81dd30b90 100644 --- a/packages/@aws-cdk/aws-iam/lib/oidc-provider.ts +++ b/packages/@aws-cdk/aws-iam/lib/oidc-provider.ts @@ -15,7 +15,6 @@ const RESOURCE_TYPE = 'Custom::AWSCDKOpenIdConnectProvider'; /** * Represents an IAM OpenID Connect provider. * - * @experimental */ export interface IOpenIdConnectProvider extends IResource { /** @@ -31,7 +30,6 @@ export interface IOpenIdConnectProvider extends IResource { /** * Initialization properties for `OpenIdConnectProvider`. - * @experimental */ export interface OpenIdConnectProviderProps { /** @@ -101,7 +99,6 @@ export interface OpenIdConnectProviderProps { * @see http://openid.net/connect * @see https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_oidc.html * - * @experimental * @resource AWS::CloudFormation::CustomResource */ export class OpenIdConnectProvider extends Resource implements IOpenIdConnectProvider { diff --git a/packages/@aws-cdk/aws-iam/lib/principals.ts b/packages/@aws-cdk/aws-iam/lib/principals.ts index 6295b8fa966e9..02bde4cfb4cd2 100644 --- a/packages/@aws-cdk/aws-iam/lib/principals.ts +++ b/packages/@aws-cdk/aws-iam/lib/principals.ts @@ -75,7 +75,6 @@ export interface AddToPrincipalPolicyResult { /** * Whether the statement was added to the identity's policies. * - * @experimental */ readonly statementAdded: boolean; @@ -83,7 +82,6 @@ export interface AddToPrincipalPolicyResult { * Dependable which allows depending on the policy change being applied * * @default - Required if `statementAdded` is true. - * @experimental */ readonly policyDependable?: cdk.IDependable; } diff --git a/packages/@aws-cdk/aws-iam/lib/role.ts b/packages/@aws-cdk/aws-iam/lib/role.ts index 9b81e4f174152..756a0f23832ec 100644 --- a/packages/@aws-cdk/aws-iam/lib/role.ts +++ b/packages/@aws-cdk/aws-iam/lib/role.ts @@ -144,7 +144,6 @@ export interface FromRoleArnOptions { * * @default true * - * @experimental */ readonly mutable?: boolean; } diff --git a/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application.ts b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application.ts index 1bcfa0fc4cbbf..d5023494bf91d 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application.ts +++ b/packages/@aws-cdk/aws-kinesisanalytics-flink/lib/application.ts @@ -210,7 +210,6 @@ class Import extends ApplicationBase { * * @resource AWS::KinesisAnalyticsV2::Application * - * @experimental */ export class Application extends ApplicationBase { /** diff --git a/packages/@aws-cdk/aws-lambda-event-sources/README.md b/packages/@aws-cdk/aws-lambda-event-sources/README.md index 55cd7e864125a..d770e10943c22 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/README.md +++ b/packages/@aws-cdk/aws-lambda-event-sources/README.md @@ -225,9 +225,8 @@ import * as msk from '@aws-cdk/aws-lambda'; import { Secret } from '@aws-cdk/aws-secretmanager'; import { ManagedKafkaEventSource } from '@aws-cdk/aws-lambda-event-sources'; -// Your MSK cluster -const cluster = msk.Cluster.fromClusterArn(this, 'Cluster', - 'arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-4'); +// Your MSK cluster arn +const cluster = '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' @@ -237,7 +236,7 @@ const topic = 'some-cool-topic' const secret = new Secret(this, 'Secret', { secretName: 'AmazonMSK_KafkaSecret' }); myFunction.addEventSource(new ManagedKafkaEventSource({ - cluster: cluster, + clusterArn, topic: topic, secret: secret, batchSize: 100, // default 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 f0782964d93ce..e4892b41abf6b 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts @@ -2,7 +2,6 @@ import * as crypto from 'crypto'; 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 msk from '@aws-cdk/aws-msk'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import { Stack } from '@aws-cdk/core'; import { StreamEventSource, StreamEventSourceProps } from './stream'; @@ -32,7 +31,7 @@ export interface ManagedKafkaEventSourceProps extends KafkaEventSourceProps { /** * an MSK cluster construct */ - readonly cluster: msk.ICluster + readonly clusterArn: string; } /** @@ -103,9 +102,9 @@ export class ManagedKafkaEventSource extends StreamEventSource { public bind(target: lambda.IFunction) { target.addEventSourceMapping( - `KafkaEventSource:${this.innerProps.cluster.clusterArn}${this.innerProps.topic}`, + `KafkaEventSource:${this.innerProps.clusterArn}${this.innerProps.topic}`, this.enrichMappingOptions({ - eventSourceArn: this.innerProps.cluster.clusterArn, + eventSourceArn: this.innerProps.clusterArn, startingPosition: this.innerProps.startingPosition, // From https://docs.aws.amazon.com/msk/latest/developerguide/msk-password.html#msk-password-limitations, "Amazon MSK only supports SCRAM-SHA-512 authentication." sourceAccessConfigurations: [{ type: lambda.SourceAccessConfigurationType.SASL_SCRAM_512_AUTH, uri: this.innerProps.secret.secretArn }], @@ -118,7 +117,7 @@ export class ManagedKafkaEventSource extends StreamEventSource { target.addToRolePolicy(new iam.PolicyStatement( { actions: ['kafka:DescribeCluster', 'kafka:GetBootstrapBrokers', 'kafka:ListScramSecrets'], - resources: [this.innerProps.cluster.clusterArn], + resources: [this.innerProps.clusterArn], }, )); diff --git a/packages/@aws-cdk/aws-lambda-event-sources/package.json b/packages/@aws-cdk/aws-lambda-event-sources/package.json index 759608c348992..c58998830ee70 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/package.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/package.json @@ -77,7 +77,6 @@ "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kinesis": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", - "@aws-cdk/aws-msk": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-s3-notifications": "0.0.0", "@aws-cdk/aws-secretsmanager": "0.0.0", @@ -96,7 +95,6 @@ "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kinesis": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", - "@aws-cdk/aws-msk": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-s3-notifications": "0.0.0", "@aws-cdk/aws-secretsmanager": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.kafka.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.kafka.ts index 452794014b6e4..be63ce81245e6 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.kafka.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.kafka.ts @@ -1,7 +1,6 @@ import { arrayWith, expect, haveResource } from '@aws-cdk/assert-internal'; import { SecurityGroup, SubnetType, Vpc } from '@aws-cdk/aws-ec2'; import * as lambda from '@aws-cdk/aws-lambda'; -import * as msk from '@aws-cdk/aws-msk'; import { Secret } from '@aws-cdk/aws-secretsmanager'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; @@ -14,14 +13,14 @@ export = { // GIVEN const stack = new cdk.Stack(); const fn = new TestFunction(stack, 'Fn'); - const cluster = msk.Cluster.fromClusterArn(stack, 'Cluster', 'some-arn'); + const clusterArn = 'some-arn'; const kafkaTopic = 'some-topic'; const secret = new Secret(stack, 'Secret', { secretName: 'AmazonMSK_KafkaSecret' }); // WHEN fn.addEventSource(new sources.ManagedKafkaEventSource( { - cluster: cluster, + clusterArn, topic: kafkaTopic, secret: secret, startingPosition: lambda.StartingPosition.TRIM_HORIZON, @@ -48,7 +47,7 @@ export = { 'kafka:ListScramSecrets', ], Effect: 'Allow', - Resource: cluster.clusterArn, + Resource: clusterArn, }, ], Version: '2012-10-17', @@ -62,7 +61,7 @@ export = { })); expect(stack).to(haveResource('AWS::Lambda::EventSourceMapping', { - EventSourceArn: cluster.clusterArn, + EventSourceArn: clusterArn, FunctionName: { Ref: 'Fn9270CBC0', }, diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index 42257f7df5225..57da4b190cca6 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -139,6 +139,8 @@ new lambda.NodejsFunction(this, 'my-handler', { }, define: { // Replace strings during build time 'process.env.API_KEY': JSON.stringify('xxx-xxxx-xxx'), + 'process.env.PRODUCTION': JSON.stringify(true), + 'process.env.NUMBER': JSON.stringify(123), }, logLevel: LogLevel.SILENT, // defaults to LogLevel.WARNING keepNames: true, // defaults to false diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 8d4ce3e7261d6..8624dce8c1359 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -148,7 +148,7 @@ export class Bundling implements cdk.BundlingOptions { ...this.props.sourceMap ? ['--sourcemap'] : [], ...this.externals.map(external => `--external:${external}`), ...loaders.map(([ext, name]) => `--loader:${ext}=${name}`), - ...defines.map(([key, value]) => `--define:${key}=${value}`), + ...defines.map(([key, value]) => `--define:${key}=${JSON.stringify(value)}`), ...this.props.logLevel ? [`--log-level=${this.props.logLevel}`] : [], ...this.props.keepNames ? ['--keep-names'] : [], ...this.relativeTsconfigPath ? [`--tsconfig=${pathJoin(inputDir, this.relativeTsconfigPath)}`] : [], diff --git a/packages/@aws-cdk/aws-lambda-nodejs/package.json b/packages/@aws-cdk/aws-lambda-nodejs/package.json index 7e7fead742103..40e0e6c480185 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/package.json +++ b/packages/@aws-cdk/aws-lambda-nodejs/package.json @@ -66,7 +66,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "delay": "5.0.0", - "esbuild": "^0.11.6", + "esbuild": "^0.11.10", "pkglint": "0.0.0", "@aws-cdk/assert-internal": "0.0.0" }, diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/__snapshots__/bundling.test.js.snap b/packages/@aws-cdk/aws-lambda-nodejs/test/__snapshots__/bundling.test.js.snap new file mode 100644 index 0000000000000..13ae6a72da036 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/__snapshots__/bundling.test.js.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`esbuild bundling with esbuild options 1`] = ` +"(() => { + // test/integ-handlers/define.ts + function handler() { + return [ + \\"VALUE\\", + true, + 7777, + 'this is a \\"test\\"' + ]; + } +})(); +" +`; diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index f6c373831526c..064c8458dbf97 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -169,12 +169,15 @@ test('esbuild bundling with esbuild options', () => { footer: '/* comments */', forceDockerBundling: true, define: { - 'DEBUG': 'true', 'process.env.KEY': JSON.stringify('VALUE'), + 'process.env.BOOL': 'true', + 'process.env.NUMBER': '7777', + 'process.env.STRING': JSON.stringify('this is a "test"'), }, }); // Correctly bundles with esbuild + const defineInstructions = '--define:process.env.KEY="\\"VALUE\\"" --define:process.env.BOOL="true" --define:process.env.NUMBER="7777" --define:process.env.STRING="\\"this is a \\\\\\"test\\\\\\"\\""'; expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(depsLockFilePath), { assetHashType: AssetHashType.OUTPUT, bundling: expect.objectContaining({ @@ -184,13 +187,17 @@ test('esbuild bundling with esbuild options', () => { 'npx esbuild --bundle "/asset-input/lib/handler.ts"', '--target=es2020 --platform=node --outfile="/asset-output/index.js"', '--minify --sourcemap --external:aws-sdk --loader:.png=dataurl', - '--define:DEBUG=true --define:process.env.KEY="VALUE"', + defineInstructions, '--log-level=silent --keep-names --tsconfig=/asset-input/lib/custom-tsconfig.ts', '--metafile=/asset-output/index.meta.json --banner=\'/* comments */\' --footer=\'/* comments */\'', ].join(' '), ], }), }); + + // Make sure that the define instructions are working as expected with the esbuild CLI + const bundleProcess = util.exec('bash', ['-c', `npx esbuild --bundle ${`${__dirname}/integ-handlers/define.ts`} ${defineInstructions}`]); + expect(bundleProcess.stdout.toString()).toMatchSnapshot(); }); test('Detects yarn.lock', () => { diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/define.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/define.ts new file mode 100644 index 0000000000000..bd66dd3920bab --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/define.ts @@ -0,0 +1,8 @@ +export function handler() { + return [ + process.env.KEY, + process.env.BOOL, + process.env.NUMBER, + process.env.STRING, + ]; +} diff --git a/packages/@aws-cdk/aws-lambda-python/lib/layer.ts b/packages/@aws-cdk/aws-lambda-python/lib/layer.ts index 3299781bb413c..1a9684e224580 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/layer.ts +++ b/packages/@aws-cdk/aws-lambda-python/lib/layer.ts @@ -26,7 +26,6 @@ export interface PythonLayerVersionProps extends lambda.LayerVersionOptions { /** * A lambda layer version. * - * @experimental */ export class PythonLayerVersion extends lambda.LayerVersion { constructor(scope: Construct, id: string, props: PythonLayerVersionProps) { diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index ae21d6ea9216e..ca67608a5e19f 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -451,19 +451,19 @@ Example with Python: new lambda.Function(this, 'Function', { code: lambda.Code.fromAsset(path.join(__dirname, 'my-python-handler'), { bundling: { - image: lambda.Runtime.PYTHON_3_6.bundlingDockerImage, + image: lambda.Runtime.PYTHON_3_8.bundlingImage, command: [ 'bash', '-c', 'pip install -r requirements.txt -t /asset-output && cp -au . /asset-output' ], }, }), - runtime: lambda.Runtime.PYTHON_3_6, + runtime: lambda.Runtime.PYTHON_3_8, handler: 'index.handler', }); ``` -Runtimes expose a `bundlingDockerImage` property that points to the [AWS SAM](https://github.com/awslabs/aws-sam-cli) build image. +Runtimes expose a `bundlingImage` property that points to the [AWS SAM](https://github.com/awslabs/aws-sam-cli) build image. Use `cdk.DockerImage.fromRegistry(image)` to use an existing image or `cdk.DockerImage.fromBuild(path)` to build a specific image: diff --git a/packages/@aws-cdk/aws-lambda/lib/filesystem.ts b/packages/@aws-cdk/aws-lambda/lib/filesystem.ts index 388db50e045ec..e27e643cbd6d7 100644 --- a/packages/@aws-cdk/aws-lambda/lib/filesystem.ts +++ b/packages/@aws-cdk/aws-lambda/lib/filesystem.ts @@ -5,7 +5,6 @@ import { IDependable, Stack } from '@aws-cdk/core'; /** * FileSystem configurations for the Lambda function - * @experimental */ export interface FileSystemConfig { /** @@ -42,7 +41,6 @@ export interface FileSystemConfig { /** * Represents the filesystem for the Lambda function - * @experimental */ export class FileSystem { /** diff --git a/packages/@aws-cdk/aws-lambda/lib/image-function.ts b/packages/@aws-cdk/aws-lambda/lib/image-function.ts index b53b6216894bf..a53f303c88b78 100644 --- a/packages/@aws-cdk/aws-lambda/lib/image-function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/image-function.ts @@ -25,7 +25,6 @@ export abstract class DockerImageCode { * Use an existing ECR image as the Lambda code. * @param repository the ECR repository that the image is in * @param props properties to further configure the selected image - * @experimental */ public static fromEcr(repository: ecr.IRepository, props?: EcrImageCodeProps): DockerImageCode { return { @@ -39,7 +38,6 @@ export abstract class DockerImageCode { * Create an ECR image from the specified asset and bind it as the Lambda code. * @param directory the directory from which the asset must be created * @param props properties to further configure the selected image - * @experimental */ public static fromImageAsset(directory: string, props: AssetImageCodeProps = {}): DockerImageCode { return { diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index 94ae195c4bc1b..d8ae716e37fff 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -75,7 +75,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/aws-lambda": "^8.10.73", + "@types/aws-lambda": "^8.10.75", "@types/lodash": "^4.14.168", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index ee0598b3d30dd..e4ac71c25c8d1 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -8,8 +8,8 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; -import * as sqs from '@aws-cdk/aws-sqs'; import * as signer from '@aws-cdk/aws-signer'; +import * as sqs from '@aws-cdk/aws-sqs'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; import * as _ from 'lodash'; diff --git a/packages/@aws-cdk/aws-lambda/test/integ.bundling.ts b/packages/@aws-cdk/aws-lambda/test/integ.bundling.ts index 6dfd3be463caf..ba662242262c3 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.bundling.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.bundling.ts @@ -18,7 +18,7 @@ class TestStack extends Stack { const fn = new lambda.Function(this, 'Function', { code: lambda.Code.fromAsset(assetPath, { bundling: { - image: lambda.Runtime.PYTHON_3_6.bundlingDockerImage, + image: lambda.Runtime.PYTHON_3_6.bundlingImage, command: [ 'bash', '-c', [ 'cp -au . /asset-output', diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts index 7e75162761a33..96346bcb9e03f 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster-engine.ts @@ -332,6 +332,8 @@ export class AuroraMysqlEngineVersion { public static readonly VER_2_09_0 = AuroraMysqlEngineVersion.builtIn_5_7('2.09.0'); /** Version "5.7.mysql_aurora.2.09.1". */ public static readonly VER_2_09_1 = AuroraMysqlEngineVersion.builtIn_5_7('2.09.1'); + /** Version "5.7.mysql_aurora.2.09.2". */ + public static readonly VER_2_09_2 = AuroraMysqlEngineVersion.builtIn_5_7('2.09.2'); /** * 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 3b35ad1c87ce1..bf8d41f9fb434 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance-engine.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance-engine.ts @@ -664,6 +664,11 @@ export class PostgresEngineVersion { /** Version "12.5". */ public static readonly VER_12_5 = PostgresEngineVersion.of('12.5', '12', { s3Import: true }); + /** Version "13" (only a major version, without a specific minor version). */ + public static readonly VER_13 = PostgresEngineVersion.of('13', '13', { s3Import: true }); + /** Version "13.1". */ + public static readonly VER_13_1 = PostgresEngineVersion.of('13.1', '13', { s3Import: true }); + /** * Create a new PostgresEngineVersion with an arbitrary version. * diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 5ce8eca7bdf81..632e3cfec431b 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -5,16 +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 { - Duration, - FeatureFlags, - IResource, - Lazy, - RemovalPolicy, - Resource, - Stack, - Token, -} from '@aws-cdk/core'; +import { ArnComponents, Duration, FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import { DatabaseSecret } from './database-secret'; @@ -196,12 +187,19 @@ export abstract class DatabaseInstanceBase extends Resource implements IDatabase * The instance arn. */ public get instanceArn(): string { - return Stack.of(this).formatArn({ + const commonAnComponents: ArnComponents = { service: 'rds', resource: 'db', sep: ':', + }; + const localArn = Stack.of(this).formatArn({ + ...commonAnComponents, resourceName: this.instanceIdentifier, }); + return this.getResourceArnAttribute(localArn, { + ...commonAnComponents, + resourceName: this.physicalName, + }); } /** @@ -640,7 +638,15 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData protected enableIamAuthentication?: boolean; constructor(scope: Construct, id: string, props: DatabaseInstanceNewProps) { - super(scope, id); + // RDS always lower-cases the ID of the database, so use that for the physical name + // (which is the name used for cross-environment access, so it needs to be correct, + // regardless of the feature flag that changes it in the template for the L1) + const instancePhysicalName = Token.isUnresolved(props.instanceIdentifier) + ? props.instanceIdentifier + : props.instanceIdentifier?.toLowerCase(); + super(scope, id, { + physicalName: instancePhysicalName, + }); this.vpc = props.vpc; if (props.vpcSubnets && props.vpcPlacement) { @@ -697,7 +703,7 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData }); } - const instanceIdentifier = FeatureFlags.of(this).isEnabled(cxapi.RDS_LOWERCASE_DB_IDENTIFIER) + const maybeLowercasedInstanceId = FeatureFlags.of(this).isEnabled(cxapi.RDS_LOWERCASE_DB_IDENTIFIER) ? props.instanceIdentifier?.toLowerCase() : props.instanceIdentifier; @@ -707,7 +713,13 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData backupRetentionPeriod: props.backupRetention?.toDays(), copyTagsToSnapshot: props.copyTagsToSnapshot ?? true, dbInstanceClass: Lazy.string({ produce: () => `db.${this.instanceType}` }), - dbInstanceIdentifier: instanceIdentifier, + dbInstanceIdentifier: Token.isUnresolved(props.instanceIdentifier) + // if the passed identifier is a Token, + // we need to use the physicalName of the database + // (we cannot change its case anyway), + // as it might be used in a cross-environment fashion + ? this.physicalName + : maybeLowercasedInstanceId, dbSubnetGroupName: subnetGroup.subnetGroupName, deleteAutomatedBackups: props.deleteAutomatedBackups, deletionProtection: defaultDeletionProtection(props.deletionProtection, props.removalPolicy), @@ -982,7 +994,7 @@ export class DatabaseInstance extends DatabaseInstanceSource implements IDatabas storageEncrypted: props.storageEncryptionKey ? true : props.storageEncrypted, }); - this.instanceIdentifier = instance.ref; + this.instanceIdentifier = this.getResourceNameAttribute(instance.ref); this.dbInstanceEndpointAddress = instance.attrEndpointAddress; this.dbInstanceEndpointPort = instance.attrEndpointPort; diff --git a/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts b/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts index 5d3868a5bd90c..8375948441f4c 100644 --- a/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/serverless-cluster.ts @@ -17,7 +17,6 @@ import { ISubnetGroup, SubnetGroup } from './subnet-group'; /** * Interface representing a serverless database cluster. * - * @experimental */ export interface IServerlessCluster extends IResource, ec2.IConnectable, secretsmanager.ISecretAttachmentTarget { /** @@ -52,7 +51,6 @@ export interface IServerlessCluster extends IResource, ec2.IConnectable, secrets /** * Properties to configure an Aurora Serverless Cluster * - * @experimental */ export interface ServerlessClusterProps { /** @@ -167,7 +165,6 @@ export interface ServerlessClusterProps { /** * Properties that describe an existing cluster instance * - * @experimental */ export interface ServerlessClusterAttributes { /** @@ -218,7 +215,6 @@ export interface ServerlessClusterAttributes { * @see https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless.setting-capacity.html * @see https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless.how-it-works.html#aurora-serverless.architecture * - * @experimental */ export enum AuroraCapacityUnit { /** 1 Aurora Capacity Unit */ @@ -248,7 +244,6 @@ export enum AuroraCapacityUnit { /** * Options for configuring scaling on an Aurora Serverless cluster * - * @experimental */ export interface ServerlessScalingOptions { /** @@ -361,7 +356,6 @@ abstract class ServerlessClusterBase extends Resource implements IServerlessClus * * @resource AWS::RDS::DBCluster * - * @experimental */ export class ServerlessCluster extends ServerlessClusterBase { diff --git a/packages/@aws-cdk/aws-rds/lib/subnet-group.ts b/packages/@aws-cdk/aws-rds/lib/subnet-group.ts index be33fb337fe75..373129702b2cc 100644 --- a/packages/@aws-cdk/aws-rds/lib/subnet-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/subnet-group.ts @@ -1,5 +1,5 @@ import * as ec2 from '@aws-cdk/aws-ec2'; -import { IResource, RemovalPolicy, Resource } from '@aws-cdk/core'; +import { IResource, RemovalPolicy, Resource, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnDBSubnetGroup } from './rds.generated'; @@ -77,7 +77,12 @@ export class SubnetGroup extends Resource implements ISubnetGroup { // Using 'Default' as the resource id for historical reasons (usage from `Instance` and `Cluster`). const subnetGroup = new CfnDBSubnetGroup(this, 'Default', { dbSubnetGroupDescription: props.description, - dbSubnetGroupName: props.subnetGroupName, + // names are actually stored by RDS changed to lowercase on the server side, + // and not lowercasing them in CloudFormation means things like { Ref } + // do not work correctly + dbSubnetGroupName: Token.isUnresolved(props.subnetGroupName) + ? props.subnetGroupName + : props.subnetGroupName?.toLowerCase(), subnetIds, }); diff --git a/packages/@aws-cdk/aws-rds/test/cluster.test.ts b/packages/@aws-cdk/aws-rds/test/cluster.test.ts index 4903869eba371..50376175b568c 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-rds/test/cluster.test.ts @@ -1885,7 +1885,7 @@ describe('cluster', () => { test('does not changes the case of the cluster identifier if the lowercaseDbIdentifier feature flag is disabled', () => { // GIVEN - const app = new cdk.App(); + const app = new cdk.App({ context: { '@aws-cdk/aws-rds:lowercaseDbIdentifier': false } }); const stack = testStack(app); const vpc = new ec2.Vpc(stack, 'VPC'); diff --git a/packages/@aws-cdk/aws-rds/test/instance.test.ts b/packages/@aws-cdk/aws-rds/test/instance.test.ts index 4dcbb84d1703e..883f10b93d2db 100644 --- a/packages/@aws-cdk/aws-rds/test/instance.test.ts +++ b/packages/@aws-cdk/aws-rds/test/instance.test.ts @@ -9,7 +9,7 @@ import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { testFutureBehavior, testLegacyBehavior } from 'cdk-build-tools/lib/feature-flag'; +import { testFutureBehavior } from 'cdk-build-tools/lib/feature-flag'; import * as rds from '../lib'; let stack: cdk.Stack; @@ -1280,33 +1280,34 @@ describe('instance', () => { }); }); - testFutureBehavior( - 'changes the case of the cluster identifier if the lowercaseDbIdentifier feature flag is enabled', - { [cxapi.RDS_LOWERCASE_DB_IDENTIFIER]: true }, cdk.App, (app, - ) => { - // GIVEN - stack = new cdk.Stack( app ); - vpc = new ec2.Vpc( stack, 'VPC' ); - - // WHEN - const instanceIdentifier = 'TestInstanceIdentifier'; - new rds.DatabaseInstance( stack, 'DB', { - engine: rds.DatabaseInstanceEngine.mysql({ - version: rds.MysqlEngineVersion.VER_8_0_19, - }), - vpc, - instanceIdentifier, - } ); + test('changes the case of the cluster identifier if the lowercaseDbIdentifier feature flag is enabled', () => { + // GIVEN + const app = new cdk.App({ + context: { [cxapi.RDS_LOWERCASE_DB_IDENTIFIER]: true }, + }); + stack = new cdk.Stack( app ); + vpc = new ec2.Vpc( stack, 'VPC' ); - // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { - DBInstanceIdentifier: instanceIdentifier.toLowerCase(), - }); + // WHEN + const instanceIdentifier = 'TestInstanceIdentifier'; + new rds.DatabaseInstance( stack, 'DB', { + engine: rds.DatabaseInstanceEngine.mysql({ + version: rds.MysqlEngineVersion.VER_8_0_19, + }), + vpc, + instanceIdentifier, + } ); + + // THEN + expect(stack).toHaveResource('AWS::RDS::DBInstance', { + DBInstanceIdentifier: instanceIdentifier.toLowerCase(), }); + }); - testLegacyBehavior( 'does not changes the case of the cluster identifier if the lowercaseDbIdentifier feature flag is disabled', cdk.App, (app ) => { + test( 'does not changes the case of the cluster identifier if the lowercaseDbIdentifier feature flag is disabled', () => { // GIVEN - stack = new cdk.Stack( app ); + const app = new cdk.App({ context: { '@aws-cdk/aws-rds:lowercaseDbIdentifier': false } }); + stack = new cdk.Stack(app); vpc = new ec2.Vpc( stack, 'VPC' ); // WHEN @@ -1357,3 +1358,48 @@ test.each([ UpdateReplacePolicy: subnetValue, }, ResourcePart.CompleteDefinition); }); + +describe('cross-account instance', () => { + test.each([ + ['MyInstance', 'MyInstance', 'myinstance'], + ['PhysicalName.GENERATE_IF_NEEDED', cdk.PhysicalName.GENERATE_IF_NEEDED, 'instancestackncestackinstancec830ba83756a6dfc7154'], + ])("with database identifier '%s' can be referenced from a Stack in a different account", (_, providedInstanceId, expectedInstanceId) => { + const app = new cdk.App(); + const instanceStack = new cdk.Stack(app, 'InstanceStack', { + env: { account: '123', region: 'my-region' }, + }); + const instance = new rds.DatabaseInstance(instanceStack, 'Instance', { + vpc: new ec2.Vpc(instanceStack, 'Vpc'), + engine: rds.DatabaseInstanceEngine.mariaDb({ version: rds.MariaDbEngineVersion.VER_10_5 }), + // physical name set + instanceIdentifier: providedInstanceId, + }); + + const outputStack = new cdk.Stack(app, 'OutputStack', { + env: { account: '456', region: 'my-region' }, + }); + new cdk.CfnOutput(outputStack, 'DatabaseInstanceArn', { + value: instance.instanceArn, + }); + new cdk.CfnOutput(outputStack, 'DatabaseInstanceName', { + value: instance.instanceIdentifier, + }); + + expect(outputStack).toMatchTemplate({ + Outputs: { + DatabaseInstanceArn: { + Value: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + `:rds:my-region:123:db:${expectedInstanceId}`, + ]], + }, + }, + DatabaseInstanceName: { + Value: expectedInstanceId, + }, + }, + }); + }); +}); diff --git a/packages/@aws-cdk/aws-rds/test/integ.serverless-cluster.expected.json b/packages/@aws-cdk/aws-rds/test/integ.serverless-cluster.expected.json index 752eae1b22894..bf73e45641f66 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.serverless-cluster.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.serverless-cluster.expected.json @@ -355,10 +355,10 @@ } } }, - "ServerlessDatabaseSubnetsB39719DC": { + "SubnetGroup": { "Type": "AWS::RDS::DBSubnetGroup", "Properties": { - "DBSubnetGroupDescription": "Subnets for Serverless Database database", + "DBSubnetGroupDescription": "My Subnet Group", "SubnetIds": [ { "Ref": "VPCPublicSubnet1SubnetB4246D30" @@ -366,7 +366,8 @@ { "Ref": "VPCPublicSubnet2Subnet74179F39" } - ] + ], + "DBSubnetGroupName": "mynotlowercasesubnetgroupname" } }, "ServerlessDatabaseSecurityGroup1E143FBB": { @@ -417,7 +418,7 @@ "Engine": "aurora-mysql", "DBClusterParameterGroupName": "default.aurora-mysql5.7", "DBSubnetGroupName": { - "Ref": "ServerlessDatabaseSubnetsB39719DC" + "Ref": "SubnetGroup" }, "EngineMode": "serverless", "MasterUsername": "admin", @@ -432,8 +433,8 @@ } ] }, - "UpdateReplacePolicy": "Snapshot", - "DeletionPolicy": "Snapshot" + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/integ.serverless-cluster.ts b/packages/@aws-cdk/aws-rds/test/integ.serverless-cluster.ts index d6ae6b8dad980..cb268f4d08667 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.serverless-cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.serverless-cluster.ts @@ -1,6 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; -import { DatabaseClusterEngine, ServerlessCluster } from '../lib'; +import * as rds from '../lib'; const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-rds-integ'); @@ -8,17 +8,24 @@ const stack = new cdk.Stack(app, 'aws-cdk-rds-integ'); const vpc = new ec2.Vpc(stack, 'VPC', { maxAzs: 2, }); +const subnetGroup = new rds.SubnetGroup(stack, 'SubnetGroup', { + vpc, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, + description: 'My Subnet Group', + subnetGroupName: 'MyNotLowerCaseSubnetGroupName', +}); -const cluster = new ServerlessCluster(stack, 'Serverless Database', { - engine: DatabaseClusterEngine.AURORA_MYSQL, +const cluster = new rds.ServerlessCluster(stack, 'Serverless Database', { + engine: rds.DatabaseClusterEngine.AURORA_MYSQL, credentials: { username: 'admin', password: cdk.SecretValue.plainText('7959866cacc02c2d243ecfe177464fe6'), }, vpc, vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, + subnetGroup, + removalPolicy: cdk.RemovalPolicy.DESTROY, }); - cluster.connections.allowDefaultPortFromAnyIpv4('Open to the world'); app.synth(); diff --git a/packages/@aws-cdk/aws-rds/test/serverless-cluster.test.ts b/packages/@aws-cdk/aws-rds/test/serverless-cluster.test.ts index 2c84a20489f32..ba157ef9aa6b1 100644 --- a/packages/@aws-cdk/aws-rds/test/serverless-cluster.test.ts +++ b/packages/@aws-cdk/aws-rds/test/serverless-cluster.test.ts @@ -846,7 +846,7 @@ nodeunitShim({ 'does not change the case of the cluster identifier if the lowercaseDbIdentifier feature flag is disabled'(test: Test) { // GIVEN - const app = new cdk.App(); + const app = new cdk.App({ context: { '@aws-cdk/aws-rds:lowercaseDbIdentifier': false } }); const stack = testStack(app); const clusterIdentifier = 'TestClusterIdentifier'; const vpc = ec2.Vpc.fromLookup(stack, 'VPC', { isDefault: true }); diff --git a/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts b/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts index ef8338efac75d..565c10c6b6d92 100644 --- a/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts +++ b/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts @@ -1,4 +1,4 @@ -import { expect, haveResource } from '@aws-cdk/assert-internal'; +import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert-internal'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; @@ -41,7 +41,7 @@ nodeunitShim({ expect(stack).to(haveResource('AWS::RDS::DBSubnetGroup', { DBSubnetGroupDescription: 'My Shared Group', - DBSubnetGroupName: 'SharedGroup', + DBSubnetGroupName: 'sharedgroup', SubnetIds: [ { Ref: 'VPCPrivateSubnet1Subnet8BCA10E0' }, { Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A' }, @@ -51,6 +51,24 @@ nodeunitShim({ test.done(); }, + 'correctly creates a subnet group with a deploy-time value for its name'(test: Test) { + const parameter = new cdk.CfnParameter(stack, 'Parameter'); + new rds.SubnetGroup(stack, 'Group', { + description: 'My Shared Group', + subnetGroupName: parameter.valueAsString, + vpc, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE }, + }); + + expect(stack).to(haveResourceLike('AWS::RDS::DBSubnetGroup', { + DBSubnetGroupName: { + Ref: 'Parameter', + }, + })); + + test.done(); + }, + 'subnet selection': { 'defaults to private subnets'(test: Test) { new rds.SubnetGroup(stack, 'Group', { diff --git a/packages/@aws-cdk/aws-route53-targets/README.md b/packages/@aws-cdk/aws-route53-targets/README.md index ba9f1ea7e4831..6ab9c8822cae9 100644 --- a/packages/@aws-cdk/aws-route53-targets/README.md +++ b/packages/@aws-cdk/aws-route53-targets/README.md @@ -24,9 +24,10 @@ This library contains Route53 Alias Record targets for: * API Gateway V2 custom domains ```ts + new route53.ARecord(this, 'AliasRecord', { zone, - target: route53.RecordTarget.fromAlias(new alias.ApiGatewayv2Domain(domainName)), + target: route53.RecordTarget.fromAlias(new alias.ApiGatewayv2DomainProperties(domainName.regionalDomainName, domainName.regionalHostedZoneId)), }); ``` diff --git a/packages/@aws-cdk/aws-route53-targets/lib/api-gatewayv2-domain-name.ts b/packages/@aws-cdk/aws-route53-targets/lib/api-gatewayv2-domain-name.ts index b78078fca525a..dab8032cd3d1b 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/api-gatewayv2-domain-name.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/api-gatewayv2-domain-name.ts @@ -1,16 +1,19 @@ -import * as apigv2 from '@aws-cdk/aws-apigatewayv2'; import * as route53 from '@aws-cdk/aws-route53'; /** * Defines an API Gateway V2 domain name as the alias target. */ -export class ApiGatewayv2Domain implements route53.IAliasRecordTarget { - constructor(private readonly domainName: apigv2.IDomainName) { } +export class ApiGatewayv2DomainProperties implements route53.IAliasRecordTarget { + /** + * @param regionalDomainName the region-specific Amazon Route 53 Hosted Zone ID of the regional endpoint. + * @param regionalHostedZoneId the domain name associated with the regional endpoint for this custom domain name. + */ + constructor(private readonly regionalDomainName: string, private readonly regionalHostedZoneId: string) { } public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { return { - dnsName: this.domainName.regionalDomainName, - hostedZoneId: this.domainName.regionalHostedZoneId, + dnsName: this.regionalDomainName, + hostedZoneId: this.regionalHostedZoneId, }; } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53-targets/package.json b/packages/@aws-cdk/aws-route53-targets/package.json index aa93fb6d5955a..47ea2969a1e93 100644 --- a/packages/@aws-cdk/aws-route53-targets/package.json +++ b/packages/@aws-cdk/aws-route53-targets/package.json @@ -64,6 +64,7 @@ "devDependencies": { "@aws-cdk/aws-certificatemanager": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-apigatewayv2": "0.0.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", @@ -73,7 +74,6 @@ }, "dependencies": { "@aws-cdk/aws-apigateway": "0.0.0", - "@aws-cdk/aws-apigatewayv2": "0.0.0", "@aws-cdk/aws-cloudfront": "0.0.0", "@aws-cdk/aws-cognito": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", @@ -90,7 +90,6 @@ "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { "@aws-cdk/aws-apigateway": "0.0.0", - "@aws-cdk/aws-apigatewayv2": "0.0.0", "@aws-cdk/aws-cloudfront": "0.0.0", "@aws-cdk/aws-cognito": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", diff --git a/packages/@aws-cdk/aws-route53-targets/test/apigatewayv2-target.test.ts b/packages/@aws-cdk/aws-route53-targets/test/apigatewayv2-target.test.ts index 0e1f7304a6860..d2a66e41bde26 100644 --- a/packages/@aws-cdk/aws-route53-targets/test/apigatewayv2-target.test.ts +++ b/packages/@aws-cdk/aws-route53-targets/test/apigatewayv2-target.test.ts @@ -8,10 +8,10 @@ import * as targets from '../lib'; test('targets.ApiGatewayv2Domain can be used to directly reference a domain', () => { // GIVEN const stack = new Stack(); - const domainName = 'example.com'; - const cert = new acm.Certificate(stack, 'cert', { domainName }); - const dn = new apigwv2.DomainName(stack, 'DN', { - domainName, + const name = 'example.com'; + const cert = new acm.Certificate(stack, 'cert', { domainName: name }); + const domainName = new apigwv2.DomainName(stack, 'DN', { + domainName: name, certificate: cert, }); const zone = new route53.HostedZone(stack, 'zone', { @@ -21,7 +21,7 @@ test('targets.ApiGatewayv2Domain can be used to directly reference a domain', () // WHEN new route53.ARecord(stack, 'A', { zone, - target: route53.RecordTarget.fromAlias(new targets.ApiGatewayv2Domain(dn)), + target: route53.RecordTarget.fromAlias(new targets.ApiGatewayv2DomainProperties(domainName.regionalDomainName, domainName.regionalHostedZoneId)), }); // THEN diff --git a/packages/@aws-cdk/aws-route53/lib/record-set.ts b/packages/@aws-cdk/aws-route53/lib/record-set.ts index 566aea6d97a70..b880100ea60a4 100644 --- a/packages/@aws-cdk/aws-route53/lib/record-set.ts +++ b/packages/@aws-cdk/aws-route53/lib/record-set.ts @@ -212,7 +212,7 @@ export class RecordSet extends Resource implements IRecordSet { constructor(scope: Construct, id: string, props: RecordSetProps) { super(scope, id); - const ttl = props.target.aliasTarget ? undefined : ((props.ttl && props.ttl.toSeconds()) || 1800).toString(); + const ttl = props.target.aliasTarget ? undefined : ((props.ttl && props.ttl.toSeconds()) ?? 1800).toString(); const recordSet = new CfnRecordSet(this, 'Resource', { hostedZoneId: props.zone.hostedZoneId, diff --git a/packages/@aws-cdk/aws-route53/test/record-set.test.ts b/packages/@aws-cdk/aws-route53/test/record-set.test.ts index 682e2f327851e..136f88a27d426 100644 --- a/packages/@aws-cdk/aws-route53/test/record-set.test.ts +++ b/packages/@aws-cdk/aws-route53/test/record-set.test.ts @@ -68,6 +68,30 @@ nodeunitShim({ test.done(); }, + 'with ttl of 0'(test: Test) { + // GIVEN + const stack = new Stack(); + + const zone = new route53.HostedZone(stack, 'HostedZone', { + zoneName: 'myzone', + }); + + // WHEN + new route53.RecordSet(stack, 'Basic', { + zone, + recordName: 'aa', + recordType: route53.RecordType.CNAME, + target: route53.RecordTarget.fromValues('bbb'), + ttl: Duration.seconds(0), + }); + + // THEN + expect(stack).to(haveResource('AWS::Route53::RecordSet', { + TTL: '0', + })); + test.done(); + }, + 'defaults to zone root'(test: Test) { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-s3-assets/README.md b/packages/@aws-cdk/aws-s3-assets/README.md index 8dae008aa5444..a73cbf0919642 100644 --- a/packages/@aws-cdk/aws-s3-assets/README.md +++ b/packages/@aws-cdk/aws-s3-assets/README.md @@ -3,13 +3,7 @@ --- -![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) - -> The APIs of higher level constructs in this module are experimental and under active development. -> They are subject to non-backward compatible changes or removal in any future version. These are -> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be -> announced in the release notes. This means that while you may use them, you may need to update -> your source code when upgrading to a newer version of this package. +![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge) --- diff --git a/packages/@aws-cdk/aws-s3-assets/package.json b/packages/@aws-cdk/aws-s3-assets/package.json index 203dd790bf998..25d39d64b5a2e 100644 --- a/packages/@aws-cdk/aws-s3-assets/package.json +++ b/packages/@aws-cdk/aws-s3-assets/package.json @@ -97,8 +97,8 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, - "stability": "experimental", - "maturity": "experimental", + "stability": "stable", + "maturity": "stable", "awslint": { "exclude": [ "docs-public-apis:@aws-cdk/aws-s3-assets.AssetOptions", diff --git a/packages/@aws-cdk/aws-secretsmanager/README.md b/packages/@aws-cdk/aws-secretsmanager/README.md index 959b73e12a669..0c0f45828e9ef 100644 --- a/packages/@aws-cdk/aws-secretsmanager/README.md +++ b/packages/@aws-cdk/aws-secretsmanager/README.md @@ -180,3 +180,28 @@ const mySecretFromAttrs = secretsmanager.Secret.fromSecretAttributes(stack, 'Sec encryptionKey, }); ``` + +## Replicating secrets + +Secrets can be replicated to multiple regions by specifying `replicaRegions`: + +```ts +new secretsmanager.Secret(this, 'Secret', { + replicaRegions: [ + { + region: 'eu-west-1', + }, + { + region: 'eu-central-1', + encryptionKey: myKey, + } + ] +}); +``` + +Alternatively, use `addReplicaRegion()`: + +```ts +const secret = new secretsmanager.Secret(this, 'Secret'); +secret.addReplicaRegion('eu-west-1'); +``` diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index 92ca9cad73ee2..f438c5fd21e14 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, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/core'; +import { 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'; @@ -134,6 +134,30 @@ export interface SecretProps { * @default - Not set. */ readonly removalPolicy?: RemovalPolicy; + + /** + * A list of regions where to replicate this secret. + * + * @default - Secret is not replicated + */ + readonly replicaRegions?: ReplicaRegion[]; +} + +/** + * Secret replica region + */ +export interface ReplicaRegion { + /** + * The name of the region + */ + readonly region: string; + + /** + * The customer-managed encryption key to use for encrypting the secret value. + * + * @default - A default KMS key for the account and region is used. + */ + readonly encryptionKey?: kms.IKey; } /** @@ -408,6 +432,8 @@ export class Secret extends SecretBase { public readonly secretArn: string; public readonly secretName: string; + private replicaRegions: secretsmanager.CfnSecret.ReplicaRegionProperty[] = []; + protected readonly autoCreatePolicy = true; constructor(scope: Construct, id: string, props: SecretProps = {}) { @@ -426,6 +452,7 @@ export class Secret extends SecretBase { kmsKeyId: props.encryptionKey && props.encryptionKey.keyArn, generateSecretString: props.generateSecretString || {}, name: this.physicalName, + replicaRegions: Lazy.any({ produce: () => this.replicaRegions }, { omitEmptyArray: true }), }); if (props.removalPolicy) { @@ -450,6 +477,10 @@ export class Secret extends SecretBase { new kms.ViaServicePrincipal(`secretsmanager.${Stack.of(this).region}.amazonaws.com`, new iam.AccountPrincipal(Stack.of(this).account)); this.encryptionKey?.grantEncryptDecrypt(principal); this.encryptionKey?.grant(principal, 'kms:CreateGrant', 'kms:DescribeKey'); + + for (const replica of props.replicaRegions ?? []) { + this.addReplicaRegion(replica.region, replica.encryptionKey); + } } /** @@ -465,6 +496,24 @@ export class Secret extends SecretBase { ...options, }); } + + /** + * Adds a replica region for the secret + * + * @param region The name of the region + * @param encryptionKey The customer-managed encryption key to use for encrypting the secret value. + */ + public addReplicaRegion(region: string, encryptionKey?: kms.IKey): void { + const stack = Stack.of(this); + if (!Token.isUnresolved(stack.region) && !Token.isUnresolved(region) && region === stack.region) { + throw new Error('Cannot add the region where this stack is deployed as a replica region.'); + } + + this.replicaRegions.push({ + region, + kmsKeyId: encryptionKey?.keyArn, + }); + } } /** diff --git a/packages/@aws-cdk/aws-secretsmanager/test/integ.replica.expected.json b/packages/@aws-cdk/aws-secretsmanager/test/integ.replica.expected.json new file mode 100644 index 0000000000000..99fd65c02e2b4 --- /dev/null +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.replica.expected.json @@ -0,0 +1,15 @@ +{ + "Resources": { + "SecretA720EF05": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "GenerateSecretString": {}, + "ReplicaRegions": [ + { + "Region": "eu-central-1" + } + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-secretsmanager/test/integ.replica.ts b/packages/@aws-cdk/aws-secretsmanager/test/integ.replica.ts new file mode 100644 index 0000000000000..b172a46eac567 --- /dev/null +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.replica.ts @@ -0,0 +1,15 @@ +import * as cdk from '@aws-cdk/core'; +import * as secretsmanager from '../lib'; + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string) { + super(scope, id); + + const secret = new secretsmanager.Secret(this, 'Secret'); + secret.addReplicaRegion('eu-central-1'); + } +} + +const app = new cdk.App(); +new TestStack(app, 'cdk-integ-secrets-replica'); +app.synth(); diff --git a/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts index 325385c4757c6..c2ee7200d05bd 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts @@ -1071,3 +1071,28 @@ test('fails if secret policy has no IAM principals', () => { // THEN expect(() => app.synth()).toThrow(/A PolicyStatement used in a resource-based policy must specify at least one IAM principal/); }); + +test('with replication regions', () => { + // WHEN + const secret = new secretsmanager.Secret(stack, 'Secret', { + replicaRegions: [ + { + region: 'eu-west-1', + }, + ], + }); + secret.addReplicaRegion('eu-central-1', kms.Key.fromKeyArn(stack, 'Key', 'arn:aws:kms:eu-central-1:123456789012:key/my-key-id')); + + // THEN + expect(stack).toHaveResource('AWS::SecretsManager::Secret', { + ReplicaRegions: [ + { + Region: 'eu-west-1', + }, + { + KmsKeyId: 'arn:aws:kms:eu-central-1:123456789012:key/my-key-id', + Region: 'eu-central-1', + }, + ], + }); +}); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md index 8d024db58e552..0888563679d47 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md @@ -21,59 +21,61 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw ## Table Of Contents -- [Task](#task) -- [Paths](#paths) - - [InputPath](#inputpath) - - [OutputPath](#outputpath) - - [ResultPath](#resultpath) -- [Parameters](#task-parameters-from-the-state-json) -- [Evaluate Expression](#evaluate-expression) -- [API Gateway](#api-gateway) - - [Call REST API Endpoint](#call-rest-api-endpoint) - - [Call HTTP API Endpoint](#call-http-api-endpoint) -- [Athena](#athena) - - [StartQueryExecution](#startQueryExecution) - - [GetQueryExecution](#getQueryExecution) - - [GetQueryResults](#getQueryResults) - - [StopQueryExecution](#stopQueryExecution) -- [Batch](#batch) - - [SubmitJob](#submitjob) -- [CodeBuild](#codebuild) - - [StartBuild](#startbuild) -- [DynamoDB](#dynamodb) - - [GetItem](#getitem) - - [PutItem](#putitem) - - [DeleteItem](#deleteitem) - - [UpdateItem](#updateitem) -- [ECS](#ecs) - - [RunTask](#runtask) - - [EC2](#ec2) - - [Fargate](#fargate) -- [EMR](#emr) - - [Create Cluster](#create-cluster) - - [Termination Protection](#termination-protection) - - [Terminate Cluster](#terminate-cluster) - - [Add Step](#add-step) - - [Cancel Step](#cancel-step) - - [Modify Instance Fleet](#modify-instance-fleet) - - [Modify Instance Group](#modify-instance-group) -- [EKS](#eks) - - [Call](#call) -- [Glue](#glue) -- [Glue DataBrew](#glue-databrew) -- [Lambda](#lambda) -- [SageMaker](#sagemaker) - - [Create Training Job](#create-training-job) - - [Create Transform Job](#create-transform-job) - - [Create Endpoint](#create-endpoint) - - [Create Endpoint Config](#create-endpoint-config) - - [Create Model](#create-model) - - [Update Endpoint](#update-endpoint) -- [SNS](#sns) -- [Step Functions](#step-functions) - - [Start Execution](#start-execution) - - [Invoke Activity Worker](#invoke-activity) -- [SQS](#sqs) +- [Tasks for AWS Step Functions](#tasks-for-aws-step-functions) + - [Table Of Contents](#table-of-contents) + - [Task](#task) + - [Paths](#paths) + - [InputPath](#inputpath) + - [OutputPath](#outputpath) + - [ResultPath](#resultpath) + - [Task parameters from the state JSON](#task-parameters-from-the-state-json) + - [Evaluate Expression](#evaluate-expression) + - [API Gateway](#api-gateway) + - [Call REST API Endpoint](#call-rest-api-endpoint) + - [Call HTTP API Endpoint](#call-http-api-endpoint) + - [Athena](#athena) + - [StartQueryExecution](#startqueryexecution) + - [GetQueryExecution](#getqueryexecution) + - [GetQueryResults](#getqueryresults) + - [StopQueryExecution](#stopqueryexecution) + - [Batch](#batch) + - [SubmitJob](#submitjob) + - [CodeBuild](#codebuild) + - [StartBuild](#startbuild) + - [DynamoDB](#dynamodb) + - [GetItem](#getitem) + - [PutItem](#putitem) + - [DeleteItem](#deleteitem) + - [UpdateItem](#updateitem) + - [ECS](#ecs) + - [RunTask](#runtask) + - [EC2](#ec2) + - [Fargate](#fargate) + - [EMR](#emr) + - [Create Cluster](#create-cluster) + - [Termination Protection](#termination-protection) + - [Terminate Cluster](#terminate-cluster) + - [Add Step](#add-step) + - [Cancel Step](#cancel-step) + - [Modify Instance Fleet](#modify-instance-fleet) + - [Modify Instance Group](#modify-instance-group) + - [EKS](#eks) + - [Call](#call) + - [Glue](#glue) + - [Glue DataBrew](#glue-databrew) + - [Lambda](#lambda) + - [SageMaker](#sagemaker) + - [Create Training Job](#create-training-job) + - [Create Transform Job](#create-transform-job) + - [Create Endpoint](#create-endpoint) + - [Create Endpoint Config](#create-endpoint-config) + - [Create Model](#create-model) + - [Update Endpoint](#update-endpoint) + - [SNS](#sns) + - [Step Functions](#step-functions) + - [Start Execution](#start-execution) + - [Invoke Activity](#invoke-activity) + - [SQS](#sqs) ## Task @@ -256,7 +258,8 @@ import * as tasks from `@aws-cdk/aws-stepfunctions-tasks`; const httpApi = new apigatewayv2.HttpApi(stack, 'MyHttpApi'); const invokeTask = new tasks.CallApiGatewayHttpApiEndpoint(stack, 'Call HTTP API', { - api: httpApi, + apiId: httpApi.apiId, + apiStack: cdk.Stack.of(httpApi), method: HttpMethod.GET, }); ``` @@ -327,9 +330,9 @@ The [SubmitJob](https://docs.aws.amazon.com/batch/latest/APIReference/API_Submit ```ts fixture=with-batch-job const task = new tasks.BatchSubmitJob(this, 'Submit Job', { - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinitionArn, jobName: 'MyJob', - jobQueue: batchQueue, + jobQueueArn: batchQueueArn, }); ``` 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 e06e46c2580b0..4624bcba049a0 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 @@ -1,4 +1,3 @@ -import * as apigatewayv2 from '@aws-cdk/aws-apigatewayv2'; import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; @@ -11,9 +10,14 @@ import { CallApiGatewayEndpointBaseProps } from './base-types'; */ export interface CallApiGatewayHttpApiEndpointProps extends CallApiGatewayEndpointBaseProps { /** - * API to call + * The Id of the API to call */ - readonly api: apigatewayv2.IHttpApi; + readonly apiId: string; + + /** + * The Stack in which the API is defined + */ + readonly apiStack: cdk.Stack; /** * Name of the stage where the API is deployed to in API Gateway @@ -45,16 +49,16 @@ export class CallApiGatewayHttpApiEndpoint extends CallApiGatewayEndpointBase { } private getApiEndpoint(): string { - const apiStack = cdk.Stack.of(this.props.api); - return `${this.props.api.apiId}.execute-api.${apiStack.region}.${apiStack.urlSuffix}`; + const apiStack = this.props.apiStack; + return `${this.props.apiId}.execute-api.${apiStack.region}.${apiStack.urlSuffix}`; } private getArnForExecuteApi(): string { - const { api, stageName, method, apiPath } = this.props; + const { apiId, stageName, method, apiPath } = this.props; - return cdk.Stack.of(api).formatArn({ + return this.props.apiStack.formatArn({ service: 'execute-api', - resource: api.apiId, + resource: apiId, sep: '/', 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 555a42f107cd8..6a9eaab73eb4b 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 @@ -5,7 +5,6 @@ import { integrationResourceArn, validatePatternSupported } from '../private/tas /** * Properties for getting a Query Execution - * @experimental */ export interface AthenaGetQueryExecutionProps extends sfn.TaskStateBaseProps { /** @@ -20,7 +19,6 @@ export interface AthenaGetQueryExecutionProps extends sfn.TaskStateBaseProps { * Get an Athena Query Execution as a Task * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-athena.html - * @experimental */ export class AthenaGetQueryExecution extends sfn.TaskStateBase { 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 4adc8cd4ce379..07ec38efa97e1 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 @@ -5,7 +5,6 @@ import { integrationResourceArn, validatePatternSupported } from '../private/tas /** * Properties for getting a Query Results - * @experimental */ export interface AthenaGetQueryResultsProps extends sfn.TaskStateBaseProps { /** @@ -34,7 +33,6 @@ export interface AthenaGetQueryResultsProps extends sfn.TaskStateBaseProps { * Get an Athena Query Results as a Task * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-athena.html - * @experimental */ export class AthenaGetQueryResults extends sfn.TaskStateBase { 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 893bb55af6b02..146f8c7d1cd48 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 @@ -8,7 +8,6 @@ import { integrationResourceArn, validatePatternSupported } from '../private/tas /** * Properties for starting a Query Execution - * @experimental */ export interface AthenaStartQueryExecutionProps extends sfn.TaskStateBaseProps { /** @@ -49,7 +48,6 @@ export interface AthenaStartQueryExecutionProps extends sfn.TaskStateBaseProps { * Start an Athena Query as a Task * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-athena.html - * @experimental */ export class AthenaStartQueryExecution extends sfn.TaskStateBase { @@ -226,7 +224,6 @@ export class AthenaStartQueryExecution extends sfn.TaskStateBase { * Location of query result along with S3 bucket configuration * * @see https://docs.aws.amazon.com/athena/latest/APIReference/API_ResultConfiguration.html - * @experimental */ export interface ResultConfiguration { @@ -250,7 +247,6 @@ export interface ResultConfiguration { * Encryption Configuration of the S3 bucket * * @see https://docs.aws.amazon.com/athena/latest/APIReference/API_EncryptionConfiguration.html - * @experimental */ export interface EncryptionConfiguration { @@ -273,7 +269,6 @@ export interface EncryptionConfiguration { * Encryption Options of the S3 bucket * * @see https://docs.aws.amazon.com/athena/latest/APIReference/API_EncryptionConfiguration.html#athena-Type-EncryptionConfiguration-EncryptionOption - * @experimental */ export enum EncryptionOption { /** @@ -302,7 +297,6 @@ export enum EncryptionOption { * Database and data catalog context in which the query execution occurs * * @see https://docs.aws.amazon.com/athena/latest/APIReference/API_QueryExecutionContext.html - * @experimental */ export interface QueryExecutionContext { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/stop-query-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/stop-query-execution.ts index 7a51559a79631..a7c44cebfb4a0 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/stop-query-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/stop-query-execution.ts @@ -5,7 +5,6 @@ import { integrationResourceArn, validatePatternSupported } from '../private/tas /** * Properties for stoping a Query Execution - * @experimental */ export interface AthenaStopQueryExecutionProps extends sfn.TaskStateBaseProps { /** @@ -18,7 +17,6 @@ export interface AthenaStopQueryExecutionProps extends sfn.TaskStateBaseProps { * Stop an Athena Query Execution as a Task * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-athena.html - * @experimental */ export class AthenaStopQueryExecution extends sfn.TaskStateBase { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/run-batch-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/run-batch-job.ts index 80c4eb28140be..9ee1cae598095 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/run-batch-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/run-batch-job.ts @@ -1,4 +1,3 @@ -import * as batch from '@aws-cdk/aws-batch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; @@ -88,9 +87,9 @@ export interface JobDependency { */ export interface RunBatchJobProps { /** - * The job definition used by this job. + * The arn of the job definition used by this job. */ - readonly jobDefinition: batch.IJobDefinition; + readonly jobDefinitionArn: string; /** * The name of the job. @@ -100,9 +99,9 @@ export interface RunBatchJobProps { readonly jobName: string; /** - * The job queue into which the job is submitted. + * The arn of the job queue into which the job is submitted. */ - readonly jobQueue: batch.IJobQueue; + readonly jobQueueArn: string; /** * The array size can be between 2 and 10,000. @@ -242,9 +241,9 @@ export class RunBatchJob implements sfn.IStepFunctionsTask { ), policyStatements: this.configurePolicyStatements(_task), parameters: { - JobDefinition: this.props.jobDefinition.jobDefinitionArn, + JobDefinition: this.props.jobDefinitionArn, JobName: this.props.jobName, - JobQueue: this.props.jobQueue.jobQueueArn, + JobQueue: this.props.jobQueueArn, Parameters: this.props.payload, ArrayProperties: @@ -287,7 +286,7 @@ export class RunBatchJob implements sfn.IStepFunctionsTask { resource: 'job-definition', resourceName: '*', }), - this.props.jobQueue.jobQueueArn, + this.props.jobQueueArn, ], actions: ['batch:SubmitJob'], }), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/submit-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/submit-job.ts index 8064c5e0fe712..0fe43f42e21ac 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/submit-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/batch/submit-job.ts @@ -1,4 +1,3 @@ -import * as batch from '@aws-cdk/aws-batch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as sfn from '@aws-cdk/aws-stepfunctions'; @@ -87,9 +86,9 @@ export interface BatchJobDependency { */ export interface BatchSubmitJobProps extends sfn.TaskStateBaseProps { /** - * The job definition used by this job. + * The arn of the job definition used by this job. */ - readonly jobDefinition: batch.IJobDefinition; + readonly jobDefinitionArn: string; /** * The name of the job. @@ -99,9 +98,9 @@ export interface BatchSubmitJobProps extends sfn.TaskStateBaseProps { readonly jobName: string; /** - * The job queue into which the job is submitted. + * The arn of the job queue into which the job is submitted. */ - readonly jobQueue: batch.IJobQueue; + readonly jobQueueArn: string; /** * The array size can be between 2 and 10,000. @@ -220,9 +219,9 @@ export class BatchSubmitJob extends sfn.TaskStateBase { return { Resource: integrationResourceArn('batch', 'submitJob', this.integrationPattern), Parameters: sfn.FieldUtils.renderObject({ - JobDefinition: this.props.jobDefinition.jobDefinitionArn, + JobDefinition: this.props.jobDefinitionArn, JobName: this.props.jobName, - JobQueue: this.props.jobQueue.jobQueueArn, + JobQueue: this.props.jobQueueArn, Parameters: this.props.payload?.value, ArrayProperties: this.props.arraySize !== undefined @@ -265,7 +264,7 @@ export class BatchSubmitJob extends sfn.TaskStateBase { resource: 'job-definition', resourceName: '*', }), - this.props.jobQueue.jobQueueArn, + this.props.jobQueueArn, ], actions: ['batch:SubmitJob'], }), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/databrew/start-job-run.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/databrew/start-job-run.ts index 75b6abcb6edda..538e0392c842e 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/databrew/start-job-run.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/databrew/start-job-run.ts @@ -6,7 +6,6 @@ import { integrationResourceArn, validatePatternSupported } from '../private/tas /** * Properties for starting a job run with StartJobRun - * @experimental */ export interface GlueDataBrewStartJobRunProps extends sfn.TaskStateBaseProps { @@ -20,7 +19,6 @@ export interface GlueDataBrewStartJobRunProps extends sfn.TaskStateBaseProps { * Start a Job run as a Task * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-databrew.html - * @experimental */ export class GlueDataBrewStartJobRun extends sfn.TaskStateBase { @@ -35,7 +33,6 @@ export class GlueDataBrewStartJobRun extends sfn.TaskStateBase { private readonly integrationPattern: sfn.IntegrationPattern; /** - * @experimental */ constructor(scope: Construct, id: string, private readonly props: GlueDataBrewStartJobRunProps) { super(scope, id, props); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eks/call.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eks/call.ts index 8cbf3fcca5886..5e4bcda8a9b64 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eks/call.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/eks/call.ts @@ -6,7 +6,6 @@ import { integrationResourceArn, validatePatternSupported } from '../private/tas /** * Properties for calling a EKS endpoint with EksCall - * @experimental */ export interface EksCallProps extends sfn.TaskStateBaseProps { @@ -43,7 +42,6 @@ export interface EksCallProps extends sfn.TaskStateBaseProps { * Call a EKS endpoint as a Task * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-eks.html - * @experimental */ export class EksCall extends sfn.TaskStateBase { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-add-step.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-add-step.ts index 83fe924866e95..6e966ea738b2e 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-add-step.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-add-step.ts @@ -12,7 +12,6 @@ import { integrationResourceArn, validatePatternSupported } from '../private/tas * * @default CONTINUE * - * @experimental */ export enum ActionOnFailure { /** @@ -34,7 +33,6 @@ export enum ActionOnFailure { /** * Properties for EmrAddStep * - * @experimental */ export interface EmrAddStepProps extends sfn.TaskStateBaseProps { /** @@ -100,7 +98,6 @@ export interface EmrAddStepProps extends sfn.TaskStateBaseProps { * * OUTPUT: the StepId * - * @experimental */ export class EmrAddStep extends sfn.TaskStateBase { private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-cancel-step.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-cancel-step.ts index ee15fb4f44e68..cec4772436b7c 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-cancel-step.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-cancel-step.ts @@ -7,7 +7,6 @@ import { integrationResourceArn } from '../private/task-utils'; /** * Properties for EmrCancelStep * - * @experimental */ export interface EmrCancelStepProps extends sfn.TaskStateBaseProps { /** @@ -24,7 +23,6 @@ export interface EmrCancelStepProps extends sfn.TaskStateBaseProps { /** * A Step Functions Task to to cancel a Step on an EMR Cluster. * - * @experimental */ export class EmrCancelStep extends sfn.TaskStateBase { 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 d59cc8693cdb9..c38bd9d8d5039 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 @@ -18,7 +18,6 @@ import { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_RunJobFlow.html * - * @experimental */ export interface EmrCreateClusterProps extends sfn.TaskStateBaseProps { /** @@ -153,7 +152,6 @@ export interface EmrCreateClusterProps extends sfn.TaskStateBaseProps { * * OUTPUT: the ClusterId. * - * @experimental */ export class EmrCreateCluster extends sfn.TaskStateBase { private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ @@ -364,7 +362,6 @@ export namespace EmrCreateCluster { /** * Valid valus for the Cluster ScaleDownBehavior * - * @experimental */ export enum EmrClusterScaleDownBehavior { /** @@ -383,7 +380,6 @@ export namespace EmrCreateCluster { /** * Instance Role Types * - * @experimental */ export enum InstanceRoleType { /** @@ -403,7 +399,6 @@ export namespace EmrCreateCluster { /** * EBS Volume Types * - * @experimental */ export enum EbsBlockDeviceVolumeType { /** @@ -426,7 +421,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_VolumeSpecification.html * - * @experimental */ export interface VolumeSpecificationProperty { /** @@ -454,7 +448,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_EbsBlockDeviceConfig.html * - * @experimental */ export interface EbsBlockDeviceConfigProperty { /** @@ -476,7 +469,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_EbsConfiguration.html * - * @experimental */ export interface EbsConfigurationProperty { /** @@ -500,7 +492,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_InstanceTypeConfig.html * - * @experimental */ export interface InstanceTypeConfigProperty { /** @@ -549,7 +540,6 @@ export namespace EmrCreateCluster { /** * Spot Timeout Actions * - * @experimental */ export enum SpotTimeoutAction { /**\ @@ -567,7 +557,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_SpotProvisioningSpecification.html * - * @experimental */ export interface SpotProvisioningSpecificationProperty { /** @@ -593,7 +582,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_InstanceFleetProvisioningSpecifications.html * - * @experimental */ export interface InstanceFleetProvisioningSpecificationsProperty { /** @@ -607,7 +595,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_InstanceFleetConfig.html * - * @experimental */ export interface InstanceFleetConfigProperty { /** @@ -654,7 +641,6 @@ export namespace EmrCreateCluster { /** * CloudWatch Alarm Comparison Operators * - * @experimental */ export enum CloudWatchAlarmComparisonOperator { /** @@ -678,7 +664,6 @@ export namespace EmrCreateCluster { /** * CloudWatch Alarm Statistics * - * @experimental */ export enum CloudWatchAlarmStatistic { /** @@ -706,7 +691,6 @@ export namespace EmrCreateCluster { /** * CloudWatch Alarm Units * - * @experimental */ export enum CloudWatchAlarmUnit { /** @@ -826,7 +810,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_MetricDimension.html * - * @experimental */ export interface MetricDimensionProperty { /** @@ -846,7 +829,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_CloudWatchAlarmDefinition.html * - * @experimental */ export interface CloudWatchAlarmDefinitionProperty { /** @@ -916,7 +898,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_ScalingTrigger.html * - * @experimental */ export interface ScalingTriggerProperty { /** @@ -929,7 +910,6 @@ export namespace EmrCreateCluster { /** * EC2 Instance Market * - * @experimental */ export enum InstanceMarket { /** @@ -945,7 +925,6 @@ export namespace EmrCreateCluster { /** * AutoScaling Adjustment Type * - * @experimental */ export enum ScalingAdjustmentType { /** @@ -968,7 +947,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_SimpleScalingPolicyConfiguration.html * - * @experimental */ export interface SimpleScalingPolicyConfigurationProperty { /** @@ -1001,7 +979,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_ScalingAction.html * - * @experimental */ export interface ScalingActionProperty { /** @@ -1023,7 +1000,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_ScalingRule.html * - * @experimental */ export interface ScalingRuleProperty { /** @@ -1055,7 +1031,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_ScalingConstraints.html * - * @experimental */ export interface ScalingConstraintsProperty { /** @@ -1076,7 +1051,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_AutoScalingPolicy.html * - * @experimental */ export interface AutoScalingPolicyProperty { /** @@ -1096,7 +1070,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_InstanceGroupConfig.html * - * @experimental */ export interface InstanceGroupConfigProperty { /** @@ -1162,7 +1135,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_PlacementType.html * - * @experimental */ export interface PlacementTypeProperty { /** @@ -1189,7 +1161,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_JobFlowInstancesConfig.html * - * @experimental */ export interface InstancesConfigProperty { /** @@ -1319,7 +1290,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_Application.html * - * @experimental */ export interface ApplicationConfigProperty { /** @@ -1355,7 +1325,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_ScriptBootstrapActionConfig.html * - * @experimental */ export interface ScriptBootstrapActionConfigProperty { /** @@ -1378,7 +1347,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_BootstrapActionConfig.html * - * @experimental */ export interface BootstrapActionConfigProperty { /** @@ -1400,7 +1368,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_Configuration.html * - * @experimental */ export interface ConfigurationProperty { /** @@ -1432,7 +1399,6 @@ export namespace EmrCreateCluster { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_KerberosAttributes.html * - * @experimental */ export interface KerberosAttributesProperty { /** diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-fleet-by-name.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-fleet-by-name.ts index 9c0300abef845..3fed76b790a94 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-fleet-by-name.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-fleet-by-name.ts @@ -7,7 +7,6 @@ import { integrationResourceArn } from '../private/task-utils'; /** * Properties for EmrModifyInstanceFleetByName * - * @experimental */ export interface EmrModifyInstanceFleetByNameProps extends sfn.TaskStateBaseProps { /** @@ -42,7 +41,6 @@ export interface EmrModifyInstanceFleetByNameProps extends sfn.TaskStateBaseProp /** * A Step Functions Task to to modify an InstanceFleet on an EMR Cluster. * - * @experimental */ export class EmrModifyInstanceFleetByName extends sfn.TaskStateBase { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-group-by-name.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-group-by-name.ts index a131f8b2e7f19..5b32e5efbc16a 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-group-by-name.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-modify-instance-group-by-name.ts @@ -9,7 +9,6 @@ import { InstanceGroupModifyConfigPropertyToJson } from './private/cluster-utils /** * Properties for EmrModifyInstanceGroupByName * - * @experimental */ export interface EmrModifyInstanceGroupByNameProps extends sfn.TaskStateBaseProps { /** @@ -35,7 +34,6 @@ export interface EmrModifyInstanceGroupByNameProps extends sfn.TaskStateBaseProp /** * A Step Functions Task to to modify an InstanceGroup on an EMR Cluster. * - * @experimental */ export class EmrModifyInstanceGroupByName extends sfn.TaskStateBase { protected readonly taskPolicies?: iam.PolicyStatement[]; @@ -81,7 +79,6 @@ export namespace EmrModifyInstanceGroupByName { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_InstanceResizePolicy.html * - * @experimental */ export interface InstanceResizePolicyProperty { /** @@ -111,7 +108,6 @@ export namespace EmrModifyInstanceGroupByName { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_ShrinkPolicy.html * - * @experimental */ export interface ShrinkPolicyProperty { /** @@ -134,7 +130,6 @@ export namespace EmrModifyInstanceGroupByName { * * @see https://docs.aws.amazon.com/emr/latest/APIReference/API_InstanceGroupModifyConfig.html * - * @experimental */ export interface InstanceGroupModifyConfigProperty { /** diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-set-cluster-termination-protection.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-set-cluster-termination-protection.ts index 78a5f305b54d2..962bf710477c0 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-set-cluster-termination-protection.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-set-cluster-termination-protection.ts @@ -7,7 +7,6 @@ import { integrationResourceArn } from '../private/task-utils'; /** * Properties for EmrSetClusterTerminationProtection * - * @experimental */ export interface EmrSetClusterTerminationProtectionProps extends sfn.TaskStateBaseProps { /** @@ -24,7 +23,6 @@ export interface EmrSetClusterTerminationProtectionProps extends sfn.TaskStateBa /** * A Step Functions Task to to set Termination Protection on an EMR Cluster. * - * @experimental */ export class EmrSetClusterTerminationProtection extends sfn.TaskStateBase { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-terminate-cluster.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-terminate-cluster.ts index 9783da1a3c9d3..04896ea40e2ff 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-terminate-cluster.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-terminate-cluster.ts @@ -7,7 +7,6 @@ import { integrationResourceArn, validatePatternSupported } from '../private/tas /** * Properties for EmrTerminateCluster * - * @experimental */ export interface EmrTerminateClusterProps extends sfn.TaskStateBaseProps { /** @@ -19,7 +18,6 @@ export interface EmrTerminateClusterProps extends sfn.TaskStateBaseProps { /** * A Step Functions Task to terminate an EMR Cluster. * - * @experimental */ export class EmrTerminateCluster extends sfn.TaskStateBase { private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ 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 cf36dc806ebfb..7fb010119373b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/evaluate-expression.ts @@ -7,7 +7,6 @@ import { Construct } from 'constructs'; /** * Properties for EvaluateExpression * - * @experimental */ export interface EvaluateExpressionProps extends sfn.TaskStateBaseProps { /** @@ -47,7 +46,6 @@ export interface Event { * * OUTPUT: the output of this task is the evaluated expression. * - * @experimental */ export class EvaluateExpression extends sfn.TaskStateBase { protected readonly taskMetrics?: sfn.TaskMetricsConfig; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts index 96d1a4ef38234..b9b5c20d5683f 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts @@ -10,13 +10,11 @@ import { Construct } from 'constructs'; /** * Task to train a machine learning model using Amazon SageMaker - * @experimental */ export interface ISageMakerTask extends iam.IGrantable {} /** * Specify the training algorithm and algorithm-specific metadata - * @experimental */ export interface AlgorithmSpecification { @@ -54,7 +52,6 @@ export interface AlgorithmSpecification { /** * Describes the training, validation or test dataset and the Amazon S3 location where it is stored. * - * @experimental */ export interface Channel { @@ -109,7 +106,6 @@ export interface Channel { /** * Configuration for a shuffle option for input data in a channel. * - * @experimental */ export interface ShuffleConfig { /** @@ -121,7 +117,6 @@ export interface ShuffleConfig { /** * Location of the channel data. * - * @experimental */ export interface DataSource { /** @@ -135,7 +130,6 @@ export interface DataSource { * * @see https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_S3DataSource.html * - * @experimental */ export interface S3DataSource { /** @@ -167,7 +161,6 @@ export interface S3DataSource { /** * Configures the S3 bucket where SageMaker will save the result of model training - * @experimental */ export interface OutputDataConfig { /** @@ -187,7 +180,6 @@ export interface OutputDataConfig { * Specifies a limit to how long a model training job can run. * When the job reaches the time limit, Amazon SageMaker ends the training job. * - * @experimental */ export interface StoppingCondition { /** @@ -201,7 +193,6 @@ export interface StoppingCondition { /** * Specifies the resources, ML compute instances, and ML storage volumes to deploy for model training. * - * @experimental */ export interface ResourceConfig { @@ -237,7 +228,6 @@ export interface ResourceConfig { /** * Specifies the VPC that you want your Amazon SageMaker training job to connect to. * - * @experimental */ export interface VpcConfig { /** @@ -256,7 +246,6 @@ export interface VpcConfig { /** * Specifies the metric name and regular expressions used to parse algorithm logs. * - * @experimental */ export interface MetricDefinition { @@ -274,7 +263,6 @@ export interface MetricDefinition { /** * Stores information about the location of an object in Amazon S3 * - * @experimental */ export interface S3LocationConfig { @@ -287,7 +275,6 @@ export interface S3LocationConfig { /** * Constructs `IS3Location` objects. * - * @experimental */ export abstract class S3Location { /** @@ -321,7 +308,6 @@ export abstract class S3Location { /** * Options for binding an S3 Location. * - * @experimental */ export interface S3LocationBindOptions { /** @@ -342,7 +328,6 @@ export interface S3LocationBindOptions { /** * Configuration for a using Docker image. * - * @experimental */ export interface DockerImageConfig { /** @@ -354,7 +339,6 @@ export interface DockerImageConfig { /** * Creates `IDockerImage` instances. * - * @experimental */ export abstract class DockerImage { /** @@ -409,7 +393,6 @@ export abstract class DockerImage { /** * S3 Data Type. * - * @experimental */ export enum S3DataType { /** @@ -431,7 +414,6 @@ export enum S3DataType { /** * S3 Data Distribution Type. * - * @experimental */ export enum S3DataDistributionType { /** @@ -448,7 +430,6 @@ export enum S3DataDistributionType { /** * Define the format of the input data. * - * @experimental */ export enum RecordWrapperType { /** @@ -465,7 +446,6 @@ export enum RecordWrapperType { /** * Input mode that the algorithm supports. * - * @experimental */ export enum InputMode { /** @@ -482,7 +462,6 @@ export enum InputMode { /** * Compression type of the data. * - * @experimental */ export enum CompressionType { /** @@ -503,7 +482,6 @@ export enum CompressionType { /** * Configures the timeout and maximum number of retries for processing a transform job invocation. * - * @experimental */ export interface ModelClientOptions { @@ -525,7 +503,6 @@ export interface ModelClientOptions { /** * Dataset to be transformed and the Amazon S3 location where it is stored. * - * @experimental */ export interface TransformInput { @@ -559,7 +536,6 @@ export interface TransformInput { /** * S3 location of the input data that the model can consume. * - * @experimental */ export interface TransformDataSource { @@ -572,7 +548,6 @@ export interface TransformDataSource { /** * Location of the channel data. * - * @experimental */ export interface TransformS3DataSource { @@ -592,7 +567,6 @@ export interface TransformS3DataSource { /** * S3 location where you want Amazon SageMaker to save the results from the transform job. * - * @experimental */ export interface TransformOutput { @@ -626,7 +600,6 @@ export interface TransformOutput { /** * ML compute instances for the transform job. * - * @experimental */ export interface TransformResources { @@ -652,7 +625,6 @@ export interface TransformResources { * Properties to define a ContainerDefinition * * @see https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ContainerDefinition.html - * @experimental */ export interface ContainerDefinitionOptions { /** @@ -701,7 +673,6 @@ export interface ContainerDefinitionOptions { * Describes the container, as part of model definition. * * @see https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ContainerDefinition.html - * @experimental */ export class ContainerDefinition implements IContainerDefinition { @@ -728,7 +699,6 @@ export class ContainerDefinition implements IContainerDefinition { * Configuration of the container used to host the model * * @see https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ContainerDefinition.html - * @experimental */ export interface IContainerDefinition { /** @@ -752,7 +722,6 @@ export interface ContainerDefinitionConfig { /** * Specifies how many models the container hosts * - * @experimental */ export enum Mode { /** @@ -771,7 +740,6 @@ export enum Mode { * Identifies a model that you want to host and the resources to deploy for hosting it. * * @see https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ProductionVariant.html - * @experimental */ export interface ProductionVariant { /** @@ -810,7 +778,6 @@ export interface ProductionVariant { * The generation of Elastic Inference (EI) instance * * @see https://docs.aws.amazon.com/sagemaker/latest/dg/ei.html - * @experimental */ export class AcceleratorClass { /** @@ -837,7 +804,6 @@ export class AcceleratorClass { * EI instances provide on-demand GPU computing for inference * * @see https://docs.aws.amazon.com/sagemaker/latest/dg/ei.html - * @experimental */ export class AcceleratorType { /** @@ -863,7 +829,6 @@ export class AcceleratorType { /** * Specifies the number of records to include in a mini-batch for an HTTP inference request. * - * @experimental */ export enum BatchStrategy { @@ -881,7 +846,6 @@ export enum BatchStrategy { /** * Method to use to split the transform job's data files into smaller batches. * - * @experimental */ export enum SplitType { @@ -909,7 +873,6 @@ export enum SplitType { /** * How to assemble the results of the transform job as a single S3 object. * - * @experimental */ export enum AssembleWith { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint-config.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint-config.ts index a7ff0134e1827..9d615caab886d 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint-config.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint-config.ts @@ -10,7 +10,6 @@ import { ProductionVariant } from './base-types'; * Properties for creating an Amazon SageMaker endpoint configuration * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html - * @experimental */ export interface SageMakerCreateEndpointConfigProps extends sfn.TaskStateBaseProps { /** @@ -44,7 +43,6 @@ export interface SageMakerCreateEndpointConfigProps extends sfn.TaskStateBasePro * A Step Functions Task to create a SageMaker endpoint configuration * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html - * @experimental */ export class SageMakerCreateEndpointConfig extends sfn.TaskStateBase { private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint.ts index d831a8dcbd682..6e46a7c129772 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-endpoint.ts @@ -8,7 +8,6 @@ import { integrationResourceArn, validatePatternSupported } from '../private/tas * Properties for creating an Amazon SageMaker endpoint * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html - * @experimental */ export interface SageMakerCreateEndpointProps extends sfn.TaskStateBaseProps { /** @@ -31,7 +30,6 @@ export interface SageMakerCreateEndpointProps extends sfn.TaskStateBaseProps { * A Step Functions Task to create a SageMaker endpoint * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html - * @experimental */ export class SageMakerCreateEndpoint extends sfn.TaskStateBase { private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-model.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-model.ts index ee82cc6f69818..1416c157ff69c 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-model.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-model.ts @@ -10,7 +10,6 @@ import { IContainerDefinition } from './base-types'; * Properties for creating an Amazon SageMaker model * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html - * @experimental */ export interface SageMakerCreateModelProps extends sfn.TaskStateBaseProps { /** @@ -69,7 +68,6 @@ export interface SageMakerCreateModelProps extends sfn.TaskStateBaseProps { * A Step Functions Task to create a SageMaker model * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html - * @experimental */ export class SageMakerCreateModel extends sfn.TaskStateBase implements iam.IGrantable, ec2.IConnectable { private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts index e063378bf3eb2..1d0b41bfc76d9 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts @@ -10,7 +10,6 @@ import { renderTags } from './private/utils'; /** * Properties for creating an Amazon SageMaker training job * - * @experimental */ export interface SageMakerCreateTrainingJobProps extends sfn.TaskStateBaseProps { /** @@ -84,7 +83,6 @@ export interface SageMakerCreateTrainingJobProps extends sfn.TaskStateBaseProps /** * Class representing the SageMaker Create Training Job task. * - * @experimental */ export class SageMakerCreateTrainingJob extends sfn.TaskStateBase implements iam.IGrantable, ec2.IConnectable { private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-transform-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-transform-job.ts index 4525ef489e3f2..e1b5aabfc61b6 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-transform-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-transform-job.ts @@ -10,7 +10,6 @@ import { renderTags } from './private/utils'; /** * Properties for creating an Amazon SageMaker transform job task * - * @experimental */ export interface SageMakerCreateTransformJobProps extends sfn.TaskStateBaseProps { /** @@ -94,7 +93,6 @@ export interface SageMakerCreateTransformJobProps extends sfn.TaskStateBaseProps /** * Class representing the SageMaker Create Transform Job task. * - * @experimental */ export class SageMakerCreateTransformJob extends sfn.TaskStateBase { private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/update-endpoint.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/update-endpoint.ts index 4ff5bf4d19bb3..7d42a24c80714 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/update-endpoint.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/update-endpoint.ts @@ -8,7 +8,6 @@ import { integrationResourceArn, validatePatternSupported } from '../private/tas * Properties for updating Amazon SageMaker endpoint * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html - * @experimental */ export interface SageMakerUpdateEndpointProps extends sfn.TaskStateBaseProps { /** @@ -25,7 +24,6 @@ export interface SageMakerUpdateEndpointProps extends sfn.TaskStateBaseProps { * A Step Functions Task to update a SageMaker endpoint * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-sagemaker.html - * @experimental */ export class SageMakerUpdateEndpoint extends sfn.TaskStateBase { private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json index 23724cc057666..8e4c71ef3e10b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json @@ -49,6 +49,12 @@ "compat": "cdk-compat", "rosetta:extract": "yarn --silent jsii-rosetta extract" }, + "awslint": { + "exclude": [ + "props-no-arn-refs:@aws-cdk/aws-stepfunctions-tasks.BatchSubmitJobProps.jobDefinitionArn", + "props-no-arn-refs:@aws-cdk/aws-stepfunctions-tasks.BatchSubmitJobProps.jobQueueArn" + ] + }, "keywords": [ "aws", "cdk", @@ -68,15 +74,15 @@ "@aws-cdk/aws-glue": "0.0.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", + "@aws-cdk/aws-apigatewayv2": "0.0.0", + "@aws-cdk/aws-apigatewayv2-integrations": "0.0.0", + "@aws-cdk/aws-batch": "0.0.0", "jest": "^26.6.3", "pkglint": "0.0.0", "@aws-cdk/assert-internal": "0.0.0" }, "dependencies": { "@aws-cdk/aws-apigateway": "0.0.0", - "@aws-cdk/aws-apigatewayv2": "0.0.0", - "@aws-cdk/aws-apigatewayv2-integrations": "0.0.0", - "@aws-cdk/aws-batch": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-databrew": "0.0.0", @@ -99,9 +105,6 @@ "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { "@aws-cdk/aws-apigateway": "0.0.0", - "@aws-cdk/aws-apigatewayv2": "0.0.0", - "@aws-cdk/aws-apigatewayv2-integrations": "0.0.0", - "@aws-cdk/aws-batch": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-databrew": "0.0.0", diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/call-http-api.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/call-http-api.test.ts index 0e7a2cf616b9a..f72272d13558e 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/call-http-api.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/call-http-api.test.ts @@ -11,7 +11,8 @@ describe('CallApiGatewayHttpApiEndpoint', () => { // WHEN const task = new CallApiGatewayHttpApiEndpoint(stack, 'Call', { - api: httpApi, + apiId: httpApi.apiId, + apiStack: cdk.Stack.of(httpApi), method: HttpMethod.GET, }); @@ -63,7 +64,8 @@ describe('CallApiGatewayHttpApiEndpoint', () => { // WHEN const task = new CallApiGatewayHttpApiEndpoint(stack, 'Call', { - api: httpApi, + apiId: httpApi.apiId, + apiStack: cdk.Stack.of(httpApi), method: HttpMethod.GET, integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, headers: sfn.TaskInput.fromObject({ TaskToken: sfn.JsonPath.taskToken }), @@ -121,7 +123,8 @@ describe('CallApiGatewayHttpApiEndpoint', () => { // THEN expect(() => { new CallApiGatewayHttpApiEndpoint(stack, 'Call', { - api: httpApi, + apiId: httpApi.apiId, + apiStack: cdk.Stack.of(httpApi), method: HttpMethod.GET, integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, }); @@ -136,7 +139,8 @@ describe('CallApiGatewayHttpApiEndpoint', () => { // THEN expect(() => { new CallApiGatewayHttpApiEndpoint(stack, 'Call', { - api: httpApi, + apiId: httpApi.apiId, + apiStack: cdk.Stack.of(httpApi), method: HttpMethod.GET, integrationPattern: sfn.IntegrationPattern.RUN_JOB, }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-http-api.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-http-api.ts index 4eb1f3b896e92..214f2e5a25ba9 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-http-api.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/apigateway/integ.call-http-api.ts @@ -30,7 +30,8 @@ httpApi.addRoutes({ }); const callEndpointJob = new CallApiGatewayHttpApiEndpoint(stack, 'Call APIGW', { - api: httpApi, + apiId: httpApi.apiId, + apiStack: cdk.Stack.of(httpApi), method: HttpMethod.GET, authType: AuthType.IAM_ROLE, outputPath: sfn.JsonPath.stringAt('$.ResponseBody'), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.ts index ff679d770040b..c31aa18cc5fbf 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.run-batch-job.ts @@ -42,9 +42,9 @@ class RunBatchStack extends cdk.Stack { const submitJob = new sfn.Task(this, 'Submit Job', { task: new tasks.RunBatchJob({ - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'MyJob', - jobQueue: batchQueue, + jobQueueArn: batchQueue.jobQueueArn, containerOverrides: { environment: { key: 'value' }, memory: 256, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.ts index 2e8f603b9d809..93c20cc790c36 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/integ.submit-job.ts @@ -41,9 +41,9 @@ class RunBatchStack extends cdk.Stack { }); const submitJob = new BatchSubmitJob(this, 'Submit Job', { - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobQueueArn: batchQueue.jobQueueArn, jobName: 'MyJob', - jobQueue: batchQueue, containerOverrides: { environment: { key: 'value' }, memory: cdk.Size.mebibytes(256), 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 ce05c1021e22d..1d290e8bba8eb 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 @@ -38,9 +38,9 @@ test('Task with only the required parameters', () => { // WHEN const task = new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, }), }); @@ -72,9 +72,9 @@ test('Task with all the parameters', () => { // WHEN const task = new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, arraySize: 15, containerOverrides: { command: ['sudo', 'rm'], @@ -135,9 +135,9 @@ test('supports tokens', () => { // WHEN const task = new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobQueueArn: batchJobQueue.jobQueueArn, jobName: sfn.JsonPath.stringAt('$.jobName'), - jobQueue: batchJobQueue, arraySize: sfn.JsonPath.numberAt('$.arraySize'), timeout: cdk.Duration.seconds(sfn.JsonPath.numberAt('$.timeout')), attempts: sfn.JsonPath.numberAt('$.attempts'), @@ -181,9 +181,9 @@ test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration patt expect(() => { new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, }), }); @@ -196,9 +196,9 @@ test('Task throws if environment in containerOverrides contain env with name sta expect(() => { new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, containerOverrides: { environment: { AWS_BATCH_MY_NAME: 'MY_VALUE' }, }, @@ -213,9 +213,9 @@ test('Task throws if arraySize is out of limits 2-10000', () => { expect(() => { new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, arraySize: 1, }), }); @@ -226,9 +226,9 @@ test('Task throws if arraySize is out of limits 2-10000', () => { expect(() => { new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, arraySize: 10001, }), }); @@ -241,9 +241,9 @@ test('Task throws if dependencies exceeds 20', () => { expect(() => { new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, dependsOn: [...Array(21).keys()].map(i => ({ jobId: `${i}`, type: `some_type-${i}`, @@ -259,9 +259,9 @@ test('Task throws if attempts is out of limits 1-10', () => { expect(() => { new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, attempts: 0, }), }); @@ -272,9 +272,9 @@ test('Task throws if attempts is out of limits 1-10', () => { expect(() => { new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, attempts: 11, }), }); @@ -287,9 +287,9 @@ test('Task throws if timeout is less than 60 sec', () => { expect(() => { new sfn.Task(stack, 'Task', { task: new tasks.RunBatchJob({ - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, timeout: cdk.Duration.seconds(59), }), }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/submit-job.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/submit-job.test.ts index d9d0f1f797ee7..ade6e2983106f 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/submit-job.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/batch/submit-job.test.ts @@ -37,9 +37,9 @@ beforeEach(() => { test('Task with only the required parameters', () => { // WHEN const task = new BatchSubmitJob(stack, 'Task', { - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, }); // THEN @@ -69,9 +69,9 @@ test('Task with only the required parameters', () => { test('Task with all the parameters', () => { // WHEN const task = new BatchSubmitJob(stack, 'Task', { - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, arraySize: 15, containerOverrides: { command: ['sudo', 'rm'], @@ -130,9 +130,9 @@ test('Task with all the parameters', () => { test('supports tokens', () => { // WHEN const task = new BatchSubmitJob(stack, 'Task', { - jobDefinition: batchJobDefinition, jobName: sfn.JsonPath.stringAt('$.jobName'), - jobQueue: batchJobQueue, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobQueueArn: batchJobQueue.jobQueueArn, arraySize: sfn.JsonPath.numberAt('$.arraySize'), timeout: cdk.Duration.seconds(sfn.JsonPath.numberAt('$.timeout')), attempts: sfn.JsonPath.numberAt('$.attempts'), @@ -174,9 +174,9 @@ test('supports tokens', () => { test('supports passing task input into payload', () => { // WHEN const task = new BatchSubmitJob(stack, 'Task', { - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, + jobQueueArn: batchJobQueue.jobQueueArn, jobName: sfn.JsonPath.stringAt('$.jobName'), - jobQueue: batchJobQueue, payload: sfn.TaskInput.fromDataAt('$.foo'), }); @@ -208,9 +208,9 @@ test('supports passing task input into payload', () => { test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { expect(() => { new BatchSubmitJob(stack, 'Task', { - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, }); }).toThrow( @@ -221,9 +221,9 @@ test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration patt test('Task throws if environment in containerOverrides contain env with name starting with AWS_BATCH', () => { expect(() => { new BatchSubmitJob(stack, 'Task', { - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, containerOverrides: { environment: { AWS_BATCH_MY_NAME: 'MY_VALUE' }, }, @@ -236,9 +236,9 @@ test('Task throws if environment in containerOverrides contain env with name sta test('Task throws if arraySize is out of limits 2-10000', () => { expect(() => { new BatchSubmitJob(stack, 'Task', { - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, arraySize: 1, }); }).toThrow( @@ -247,9 +247,9 @@ test('Task throws if arraySize is out of limits 2-10000', () => { expect(() => { new BatchSubmitJob(stack, 'Task2', { - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, arraySize: 10001, }); }).toThrow( @@ -260,9 +260,9 @@ test('Task throws if arraySize is out of limits 2-10000', () => { test('Task throws if dependencies exceeds 20', () => { expect(() => { new BatchSubmitJob(stack, 'Task', { - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, dependsOn: [...Array(21).keys()].map(i => ({ jobId: `${i}`, type: `some_type-${i}`, @@ -276,9 +276,9 @@ test('Task throws if dependencies exceeds 20', () => { test('Task throws if attempts is out of limits 1-10', () => { expect(() => { new BatchSubmitJob(stack, 'Task', { - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, attempts: 0, }); }).toThrow( @@ -287,9 +287,9 @@ test('Task throws if attempts is out of limits 1-10', () => { expect(() => { new BatchSubmitJob(stack, 'Task2', { - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, attempts: 11, }); }).toThrow( @@ -300,9 +300,9 @@ test('Task throws if attempts is out of limits 1-10', () => { test('Task throws if attempt duration is less than 60 sec', () => { expect(() => { new BatchSubmitJob(stack, 'Task', { - jobDefinition: batchJobDefinition, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'JobName', - jobQueue: batchJobQueue, + jobQueueArn: batchJobQueue.jobQueueArn, timeout: cdk.Duration.seconds(59), }); }).toThrow( diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/custom-state.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/custom-state.ts index eccfad6e9bcc1..20d46a14510c0 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/custom-state.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/custom-state.ts @@ -18,7 +18,6 @@ export interface CustomStateProps { /** * State defined by supplying Amazon States Language (ASL) in the state machine. * - * @experimental */ export class CustomState extends State implements IChainable, INextable { public readonly endStates: INextable[]; diff --git a/packages/@aws-cdk/cloudformation-diff/package.json b/packages/@aws-cdk/cloudformation-diff/package.json index 5b4afe8d0dcc4..7f76e1847c701 100644 --- a/packages/@aws-cdk/cloudformation-diff/package.json +++ b/packages/@aws-cdk/cloudformation-diff/package.json @@ -26,7 +26,7 @@ "diff": "^5.0.0", "fast-deep-equal": "^3.1.3", "string-width": "^4.2.2", - "table": "^6.0.9" + "table": "^6.1.0" }, "devDependencies": { "@types/jest": "^26.0.22", diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/fn-sub-map-empty.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/fn-sub-map-empty.json new file mode 100644 index 0000000000000..07def731df41f --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/fn-sub-map-empty.json @@ -0,0 +1,12 @@ +{ + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::Sub": ["my-bucket", {}] + } + } + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts index 3c211c5d476da..0741b7f9d23df 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -273,6 +273,14 @@ describe('CDK Include', () => { ); }); + test('preserves an empty map passed to Fn::Sub', () => { + includeTestTemplate(stack, 'fn-sub-map-empty.json'); + + expect(stack).toMatchTemplate( + loadTestFileToJsObject('fn-sub-map-empty.json'), + ); + }); + test('can ingest a template with Fn::Sub shadowing a logical ID from the template and output it unchanged', () => { includeTestTemplate(stack, 'fn-sub-shadow.json'); @@ -326,6 +334,44 @@ describe('CDK Include', () => { ); }); + test('when a parameter in an Fn::Sub expression is substituted with a deploy-time value, it adds a new key to the Fn::Sub map', () => { + const parameter = new core.CfnParameter(stack, 'AnotherParam'); + includeTestTemplate(stack, 'fn-sub-parameters.json', { + parameters: { + 'MyParam': `it's_a_${parameter.valueAsString}_concatenation`, + }, + }); + + expect(stack).toMatchTemplate({ + "Parameters": { + "AnotherParam": { + "Type": "String", + }, + }, + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::Sub": [ + "${MyParam}", + { + "MyParam": { + "Fn::Join": ["", [ + "it's_a_", + { "Ref": "AnotherParam" }, + "_concatenation", + ]], + }, + }, + ], + }, + }, + }, + }, + }); + }); + test('can ingest a template with a Ref expression for an array value, and output it unchanged', () => { includeTestTemplate(stack, 'ref-array-property.json'); diff --git a/packages/@aws-cdk/core/lib/asset-staging.ts b/packages/@aws-cdk/core/lib/asset-staging.ts index 6a34bd9b4b1ac..09820293b63ec 100644 --- a/packages/@aws-cdk/core/lib/asset-staging.ts +++ b/packages/@aws-cdk/core/lib/asset-staging.ts @@ -75,13 +75,11 @@ export interface AssetStagingProps extends FingerprintOptions, AssetOptions { export class AssetStaging extends CoreConstruct { /** * The directory inside the bundling container into which the asset sources will be mounted. - * @experimental */ public static readonly BUNDLING_INPUT_DIR = '/asset-input'; /** * The directory inside the bundling container into which the bundled output should be written. - * @experimental */ public static readonly BUNDLING_OUTPUT_DIR = '/asset-output'; diff --git a/packages/@aws-cdk/core/lib/assets.ts b/packages/@aws-cdk/core/lib/assets.ts index 72cba9719e784..d9bd02d4d91a9 100644 --- a/packages/@aws-cdk/core/lib/assets.ts +++ b/packages/@aws-cdk/core/lib/assets.ts @@ -53,7 +53,7 @@ export interface AssetOptions { * @default - uploaded as-is to S3 if the asset is a regular file or a .zip file, * archived into a .zip file and uploaded to S3 otherwise * - * @experimental + * */ readonly bundling?: BundlingOptions; } diff --git a/packages/@aws-cdk/core/lib/bundling.ts b/packages/@aws-cdk/core/lib/bundling.ts index bd9299a5a990d..24af4f1b3139f 100644 --- a/packages/@aws-cdk/core/lib/bundling.ts +++ b/packages/@aws-cdk/core/lib/bundling.ts @@ -6,7 +6,6 @@ import { FileSystem } from './fs'; /** * Bundling options * - * @experimental */ export interface BundlingOptions { /** @@ -77,7 +76,6 @@ export interface BundlingOptions { * * @default - bundling will only be performed in a Docker container * - * @experimental */ readonly local?: ILocalBundling; @@ -86,7 +84,6 @@ export interface BundlingOptions { * * @default BundlingOutput.AUTO_DISCOVER * - * @experimental */ readonly outputType?: BundlingOutput; } @@ -94,7 +91,6 @@ export interface BundlingOptions { /** * The type of output that a bundling operation is producing. * - * @experimental */ export enum BundlingOutput { /** @@ -120,7 +116,6 @@ export enum BundlingOutput { /** * Local bundling * - * @experimental */ export interface ILocalBundling { /** diff --git a/packages/@aws-cdk/core/lib/cfn-parse.ts b/packages/@aws-cdk/core/lib/cfn-parse.ts index 8f5d820584d98..be8f3560210d9 100644 --- a/packages/@aws-cdk/core/lib/cfn-parse.ts +++ b/packages/@aws-cdk/core/lib/cfn-parse.ts @@ -79,7 +79,6 @@ export class FromCloudFormationPropertyObject> ext * (to not make it part of the public API), * it is directly referenced in the generated L1 code. * - * @experimental */ export class FromCloudFormation { // nothing to for any but return it @@ -340,7 +339,6 @@ export interface ParseCfnOptions { * it is directly referenced in the generated L1 code, * so any renames of it need to be reflected in cfn2ts/codegen.ts as well. * - * @experimental */ export class CfnParser { private readonly options: ParseCfnOptions; @@ -648,7 +646,7 @@ export class CfnParser { map = value[1]; } - return Fn.sub(this.parseFnSubString(fnSubString, map), map); + return this.parseFnSubString(fnSubString, map); } case 'Condition': { // a reference to a Condition from another Condition @@ -682,48 +680,72 @@ export class CfnParser { : undefined; } - private parseFnSubString(value: string, map: { [key: string]: any } = {}): string { - const leftBrace = value.indexOf('${'); - const rightBrace = value.indexOf('}') + 1; - // don't include left and right braces when searching for the target of the reference - if (leftBrace === -1 || leftBrace >= rightBrace) { - return value; - } - - const leftHalf = value.substring(0, leftBrace); - const rightHalf = value.substring(rightBrace); - const refTarget = value.substring(leftBrace + 2, rightBrace - 1).trim(); - if (refTarget[0] === '!') { - return value.substring(0, rightBrace) + this.parseFnSubString(rightHalf, map); - } - - // lookup in map - if (refTarget in map) { - return leftHalf + '${' + refTarget + '}' + this.parseFnSubString(rightHalf, map); - } - - // since it's not in the map, check if it's a pseudo parameter - const specialRef = this.specialCaseSubRefs(refTarget); - if (specialRef !== undefined) { - return leftHalf + specialRef + this.parseFnSubString(rightHalf, map); - } + private parseFnSubString(templateString: string, expressionMap: { [key: string]: any } | undefined): string { + const map = expressionMap ?? {}; + const self = this; + return Fn.sub(go(templateString), Object.keys(map).length === 0 ? expressionMap : map); + + function go(value: string): string { + const leftBrace = value.indexOf('${'); + const rightBrace = value.indexOf('}') + 1; + // don't include left and right braces when searching for the target of the reference + if (leftBrace === -1 || leftBrace >= rightBrace) { + return value; + } + + const leftHalf = value.substring(0, leftBrace); + const rightHalf = value.substring(rightBrace); + const refTarget = value.substring(leftBrace + 2, rightBrace - 1).trim(); + if (refTarget[0] === '!') { + return value.substring(0, rightBrace) + go(rightHalf); + } + + // lookup in map + if (refTarget in map) { + return leftHalf + '${' + refTarget + '}' + go(rightHalf); + } + + // since it's not in the map, check if it's a pseudo-parameter + // (or a value to be substituted for a Parameter, provided by the customer) + const specialRef = self.specialCaseSubRefs(refTarget); + if (specialRef !== undefined) { + if (Token.isUnresolved(specialRef)) { + // specialRef can only be a Token if the value passed by the customer + // for substituting a Parameter was a Token. + // This is actually bad here, + // because the Token can potentially be something that doesn't render + // well inside an Fn::Sub template string, like a { Ref } object. + // To handle this case, + // instead of substituting the Parameter directly with the token in the template string, + // add a new entry to the Fn::Sub map, + // with key refTarget, and the token as the value. + // This is safe, because this sort of shadowing is legal in CloudFormation, + // and also because we're certain the Fn::Sub map doesn't contain an entry for refTarget + // (as we check that condition in the code right above this). + map[refTarget] = specialRef; + return leftHalf + '${' + refTarget + '}' + go(rightHalf); + } else { + return leftHalf + specialRef + go(rightHalf); + } + } - const dotIndex = refTarget.indexOf('.'); - const isRef = dotIndex === -1; - if (isRef) { - const refElement = this.finder.findRefTarget(refTarget); - if (!refElement) { - throw new Error(`Element referenced in Fn::Sub expression with logical ID: '${refTarget}' was not found in the template`); - } - return leftHalf + CfnReference.for(refElement, 'Ref', ReferenceRendering.FN_SUB).toString() + this.parseFnSubString(rightHalf, map); - } else { - const targetId = refTarget.substring(0, dotIndex); - const refResource = this.finder.findResource(targetId); - if (!refResource) { - throw new Error(`Resource referenced in Fn::Sub expression with logical ID: '${targetId}' was not found in the template`); - } - const attribute = refTarget.substring(dotIndex + 1); - return leftHalf + CfnReference.for(refResource, attribute, ReferenceRendering.FN_SUB).toString() + this.parseFnSubString(rightHalf, map); + const dotIndex = refTarget.indexOf('.'); + const isRef = dotIndex === -1; + if (isRef) { + const refElement = self.finder.findRefTarget(refTarget); + if (!refElement) { + throw new Error(`Element referenced in Fn::Sub expression with logical ID: '${refTarget}' was not found in the template`); + } + return leftHalf + CfnReference.for(refElement, 'Ref', ReferenceRendering.FN_SUB).toString() + go(rightHalf); + } else { + const targetId = refTarget.substring(0, dotIndex); + const refResource = self.finder.findResource(targetId); + if (!refResource) { + throw new Error(`Resource referenced in Fn::Sub expression with logical ID: '${targetId}' was not found in the template`); + } + const attribute = refTarget.substring(dotIndex + 1); + return leftHalf + CfnReference.for(refResource, attribute, ReferenceRendering.FN_SUB).toString() + go(rightHalf); + } } } diff --git a/packages/@aws-cdk/core/lib/construct-compat.ts b/packages/@aws-cdk/core/lib/construct-compat.ts index 09a6fbd929496..d468ce234c4f3 100644 --- a/packages/@aws-cdk/core/lib/construct-compat.ts +++ b/packages/@aws-cdk/core/lib/construct-compat.ts @@ -522,7 +522,6 @@ export class ConstructNode { * Remove the child with the given name, if present. * * @returns Whether a child with the given name was deleted. - * @experimental */ public tryRemoveChild(childName: string): boolean { return this._actualNode.tryRemoveChild(childName); } } diff --git a/packages/@aws-cdk/core/lib/context-provider.ts b/packages/@aws-cdk/core/lib/context-provider.ts index d0ebaa34705ab..1eb210623558f 100644 --- a/packages/@aws-cdk/core/lib/context-provider.ts +++ b/packages/@aws-cdk/core/lib/context-provider.ts @@ -6,7 +6,6 @@ import { Stack } from './stack'; import { Token } from './token'; /** - * @experimental */ export interface GetContextKeyOptions { /** @@ -21,7 +20,6 @@ export interface GetContextKeyOptions { } /** - * @experimental */ export interface GetContextValueOptions extends GetContextKeyOptions { /** @@ -33,7 +31,6 @@ export interface GetContextValueOptions extends GetContextKeyOptions { } /** - * @experimental */ export interface GetContextKeyResult { readonly key: string; @@ -41,7 +38,6 @@ export interface GetContextKeyResult { } /** - * @experimental */ export interface GetContextValueResult { readonly value?: any; @@ -56,7 +52,6 @@ export interface GetContextValueResult { * * ContextProvider needs access to a Construct to hook into the context mechanism. * - * @experimental */ export class ContextProvider { /** 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 c7f3776339907..06f257fa18ff3 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 @@ -19,7 +19,6 @@ import { Construct as CoreConstruct } from '../construct-compat'; /** * Initialization properties for `CustomResourceProvider`. * - * @experimental */ export interface CustomResourceProviderProps { /** @@ -79,7 +78,6 @@ export interface CustomResourceProviderProps { /** * The lambda runtime to use for the resource provider. This also indicates * which language is used for the handler. - * @experimental */ export enum CustomResourceProviderRuntime { /** @@ -103,7 +101,6 @@ export enum CustomResourceProviderRuntime { /** * An AWS-Lambda backed custom resource provider. * - * @experimental */ export class CustomResourceProvider extends CoreConstruct { /** diff --git a/packages/@aws-cdk/core/lib/dependency.ts b/packages/@aws-cdk/core/lib/dependency.ts index 382d5acae0a61..781a1ef281bed 100644 --- a/packages/@aws-cdk/core/lib/dependency.ts +++ b/packages/@aws-cdk/core/lib/dependency.ts @@ -21,7 +21,6 @@ export interface IDependable { * This class can be used when a set of constructs which are disjoint in the * construct tree needs to be combined to be used as a single dependable. * - * @experimental */ export class ConcreteDependable implements IDependable { private readonly _dependencyRoots = new Array(); @@ -66,7 +65,6 @@ const DEPENDABLE_SYMBOL = Symbol.for('@aws-cdk/core.DependableTrait'); * } * DependableTrait.implement(construct, new TraitImplementation()); * - * @experimental */ export abstract class DependableTrait { /** diff --git a/packages/@aws-cdk/core/lib/nested-stack.ts b/packages/@aws-cdk/core/lib/nested-stack.ts index 648211aab22f0..fd8c47f6a0731 100644 --- a/packages/@aws-cdk/core/lib/nested-stack.ts +++ b/packages/@aws-cdk/core/lib/nested-stack.ts @@ -23,7 +23,6 @@ const NESTED_STACK_SYMBOL = Symbol.for('@aws-cdk/core.NestedStack'); /** * Initialization props for the `NestedStack` construct. * - * @experimental */ export interface NestedStackProps { /** @@ -90,7 +89,6 @@ export interface NestedStackProps { * nested stack will automatically be translated to stack parameters and * outputs. * - * @experimental */ export class NestedStack extends Stack { diff --git a/packages/@aws-cdk/core/lib/private/cloudformation-lang.ts b/packages/@aws-cdk/core/lib/private/cloudformation-lang.ts index c2be580474426..049ceb207f92f 100644 --- a/packages/@aws-cdk/core/lib/private/cloudformation-lang.ts +++ b/packages/@aws-cdk/core/lib/private/cloudformation-lang.ts @@ -233,7 +233,15 @@ function tokenAwareStringify(root: any, space: number, ctx: IResolveContext) { // // Therefore, if we encounter lists we need to defer to a custom resource to handle // them properly at deploy time. - pushIntrinsic(CfnUtils.stringify(Stack.of(ctx.scope), `CdkJsonStringify${stringifyCounter++}`, intrinsic)); + const stack = Stack.of(ctx.scope); + + // Because this will be called twice (once during `prepare`, once during `resolve`), + // we need to make sure to be idempotent, so use a cache. + const stringifyResponse = stringifyCache.obtain(stack, JSON.stringify(intrinsic), () => + CfnUtils.stringify(stack, `CdkJsonStringify${stringifyCounter++}`, intrinsic), + ); + + pushIntrinsic(stringifyResponse); return; case ResolutionTypeHint.NUMBER: @@ -418,4 +426,28 @@ function quoteString(s: string) { return s.substring(1, s.length - 1); } -let stringifyCounter = 1; \ No newline at end of file +let stringifyCounter = 1; + +/** + * A cache scoped to object instances, that's maintained externally to the object instances + */ +class ScopedCache { + private cache = new WeakMap>(); + + public obtain(object: O, key: K, init: () => V): V { + let kvMap = this.cache.get(object); + if (!kvMap) { + kvMap = new Map(); + this.cache.set(object, kvMap); + } + + let ret = kvMap.get(key); + if (ret === undefined) { + ret = init(); + kvMap.set(key, ret); + } + return ret; + } +} + +const stringifyCache = new ScopedCache(); \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/private/intrinsic.ts b/packages/@aws-cdk/core/lib/private/intrinsic.ts index 88cbe6ee02af7..cb814a6363594 100644 --- a/packages/@aws-cdk/core/lib/private/intrinsic.ts +++ b/packages/@aws-cdk/core/lib/private/intrinsic.ts @@ -5,7 +5,6 @@ import { Token } from '../token'; /** * Customization properties for an Intrinsic token * - * @experimental */ export interface IntrinsicProps { /** @@ -24,7 +23,6 @@ export interface IntrinsicProps { * * This class will disappear in a future release and should not be used. * - * @experimental */ export class Intrinsic implements IResolvable { /** diff --git a/packages/@aws-cdk/core/lib/private/runtime-info.ts b/packages/@aws-cdk/core/lib/private/runtime-info.ts index da4fbdcbe99d8..e92bbc1c986c9 100644 --- a/packages/@aws-cdk/core/lib/private/runtime-info.ts +++ b/packages/@aws-cdk/core/lib/private/runtime-info.ts @@ -88,5 +88,11 @@ function getJsiiAgentVersion() { jsiiAgent = `node.js/${process.version}`; } + // Sanitize the agent to remove characters which might mess with the downstream + // prefix encoding & decoding. In particular the .NET jsii agent takes a form like: + // DotNet/5.0.3/.NETCoreApp,Version=v3.1/1.0.0.0 + // The `,` in the above messes with the prefix decoding when reporting the analytics. + jsiiAgent = jsiiAgent.replace(/[^a-z0-9.-/=_]/gi, '-'); + return jsiiAgent; } diff --git a/packages/@aws-cdk/core/lib/private/synthesis.ts b/packages/@aws-cdk/core/lib/private/synthesis.ts index 586f472469abd..c8a54267823a8 100644 --- a/packages/@aws-cdk/core/lib/private/synthesis.ts +++ b/packages/@aws-cdk/core/lib/private/synthesis.ts @@ -205,7 +205,6 @@ function visit(root: IConstruct, order: 'pre' | 'post', cb: (x: IProtectedConstr /** * Interface which provides access to special methods of Construct * - * @experimental */ interface IProtectedConstructMethods extends IConstruct { /** diff --git a/packages/@aws-cdk/core/lib/private/tree-metadata.ts b/packages/@aws-cdk/core/lib/private/tree-metadata.ts index 97fe514bb4d87..8421fd3933aea 100644 --- a/packages/@aws-cdk/core/lib/private/tree-metadata.ts +++ b/packages/@aws-cdk/core/lib/private/tree-metadata.ts @@ -15,7 +15,6 @@ const FILE_PATH = 'tree.json'; * This generates, as part of synthesis, a file containing the construct tree and the metadata for each node in the tree. * The output is in a tree format so as to preserve the construct hierarchy. * - * @experimental */ export class TreeMetadata extends Construct { constructor(scope: Construct) { diff --git a/packages/@aws-cdk/core/lib/resolvable.ts b/packages/@aws-cdk/core/lib/resolvable.ts index 9004cd111bb33..bde3d670c5419 100644 --- a/packages/@aws-cdk/core/lib/resolvable.ts +++ b/packages/@aws-cdk/core/lib/resolvable.ts @@ -105,7 +105,6 @@ export interface ITokenResolver { * * Interface so it could potentially be exposed over jsii. * - * @experimental */ export interface IFragmentConcatenator { /** @@ -130,7 +129,6 @@ export class StringConcat implements IFragmentConcatenator { /** * Default resolver implementation * - * @experimental */ export class DefaultTokenResolver implements ITokenResolver { constructor(private readonly concat: IFragmentConcatenator) { diff --git a/packages/@aws-cdk/core/lib/resource.ts b/packages/@aws-cdk/core/lib/resource.ts index 79d5542acc56e..65fcacd55bf25 100644 --- a/packages/@aws-cdk/core/lib/resource.ts +++ b/packages/@aws-cdk/core/lib/resource.ts @@ -128,7 +128,6 @@ export abstract class Resource extends CoreConstruct implements IResource { * - a concrete name generated automatically during synthesis, in * cross-environment scenarios. * - * @experimental */ protected readonly physicalName: string; @@ -229,7 +228,6 @@ export abstract class Resource extends CoreConstruct implements IResource { * * @param nameAttr The CFN attribute which resolves to the resource's name. * Commonly this is the resource's `ref`. - * @experimental */ protected getResourceNameAttribute(nameAttr: string) { return mimicReference(nameAttr, { @@ -262,7 +260,6 @@ export abstract class Resource extends CoreConstruct implements IResource { * reference `this.physicalName` somewhere within the ARN in order for * cross-environment references to work. * - * @experimental */ protected getResourceArnAttribute(arnAttr: string, arnComponents: ArnComponents) { return mimicReference(arnAttr, { diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index bd6f77c0da2f9..e256f73231714 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -273,7 +273,6 @@ export class Stack extends CoreConstruct implements ITaggable { * If this is a nested stack, this represents its `AWS::CloudFormation::Stack` * resource. `undefined` for top-level (non-nested) stacks. * - * @experimental */ public readonly nestedStackResource?: CfnResource; @@ -293,7 +292,6 @@ export class Stack extends CoreConstruct implements ITaggable { /** * Synthesis method for this stack * - * @experimental */ public readonly synthesizer: IStackSynthesizer; diff --git a/packages/@aws-cdk/core/lib/stage.ts b/packages/@aws-cdk/core/lib/stage.ts index fd93f4251bf7f..ba0fc21d4248d 100644 --- a/packages/@aws-cdk/core/lib/stage.ts +++ b/packages/@aws-cdk/core/lib/stage.ts @@ -75,7 +75,6 @@ export class Stage extends CoreConstruct { * Return the stage this construct is contained with, if available. If called * on a nested stage, returns its parent. * - * @experimental */ public static of(construct: IConstruct): Stage | undefined { return Node.of(construct).scopes.reverse().slice(1).find(Stage.isStage); @@ -84,7 +83,6 @@ export class Stage extends CoreConstruct { /** * Test whether the given construct is a stage. * - * @experimental */ public static isStage(x: any ): x is Stage { return x !== null && typeof(x) === 'object' && STAGE_SYMBOL in x; @@ -93,21 +91,18 @@ export class Stage extends CoreConstruct { /** * The default region for all resources defined within this stage. * - * @experimental */ public readonly region?: string; /** * The default account for all resources defined within this stage. * - * @experimental */ public readonly account?: string; /** * The cloud assembly builder that is being used for this App * - * @experimental * @internal */ public readonly _assemblyBuilder: cxapi.CloudAssemblyBuilder; @@ -116,14 +111,12 @@ export class Stage extends CoreConstruct { * The name of the stage. Based on names of the parent stages separated by * hypens. * - * @experimental */ public readonly stageName: string; /** * The parent stage or `undefined` if this is the app. * * - * @experimental */ public readonly parentStage?: Stage; @@ -170,7 +163,6 @@ export class Stage extends CoreConstruct { * * Derived from the construct path. * - * @experimental */ public get artifactId() { if (!this.node.path) { return ''; } diff --git a/packages/@aws-cdk/core/test/cloudformation-json.test.ts b/packages/@aws-cdk/core/test/cloudformation-json.test.ts index e9a0957a86269..977795fc35d9d 100644 --- a/packages/@aws-cdk/core/test/cloudformation-json.test.ts +++ b/packages/@aws-cdk/core/test/cloudformation-json.test.ts @@ -1,4 +1,4 @@ -import { App, Aws, CfnOutput, Fn, IPostProcessor, IResolvable, IResolveContext, Lazy, Stack, Token } from '../lib'; +import { App, Aws, CfnOutput, CfnResource, Fn, IPostProcessor, IResolvable, IResolveContext, Lazy, Stack, Token } from '../lib'; import { Intrinsic } from '../lib/private/intrinsic'; import { evaluateCFN } from './evaluate-cfn'; @@ -102,10 +102,22 @@ describe('tokens that return literals', () => { const someList = Token.asList(new Intrinsic({ Ref: 'Thing' })); // WHEN - expect(stack.resolve(stack.toJsonString({ someList }))).toEqual({ + new CfnResource(stack, 'Resource', { + type: 'AWS::Banana', + properties: { + someJson: stack.toJsonString({ someList }), + }, + }); + + const asm = app.synth(); + const template = asm.getStackByName(stack.stackName).template; + const stringifyLogicalId = Object.keys(template.Resources).filter(id => id.startsWith('CdkJsonStringify'))[0]; + expect(stringifyLogicalId).toBeDefined(); + + expect(template.Resources.Resource.Properties.someJson).toEqual({ 'Fn::Join': ['', [ '{"someList":', - { 'Fn::GetAtt': [expect.stringContaining('CdkJsonStringify'), 'Value'] }, + { 'Fn::GetAtt': [stringifyLogicalId, 'Value'] }, '}', ]], }); diff --git a/packages/@aws-cdk/core/test/runtime-info.test.ts b/packages/@aws-cdk/core/test/runtime-info.test.ts index 421c504036e33..b637bb2ba9b50 100644 --- a/packages/@aws-cdk/core/test/runtime-info.test.ts +++ b/packages/@aws-cdk/core/test/runtime-info.test.ts @@ -84,6 +84,16 @@ describeTscSafe('constructInfoForStack', () => { expect(jsiiInfo?.version).toMatch(/node.js/); }); + test('sanitizes jsii runtime info to remove unwanted characters', () => { + process.env.JSII_AGENT = 'DotNet/5.0.3/.NETCore^App,Version=v3.1!/1.0.0_0'; + const constructInfos = constructInfoFromStack(stack); + const jsiiInfo = constructInfos.find(i => i.fqn === 'jsii-runtime.Runtime'); + + expect(jsiiInfo?.version).toEqual('DotNet/5.0.3/.NETCore-App-Version=v3.1-/1.0.0_0'); + + delete process.env.JSII_AGENT; + }); + test('returns info for constructs added to the stack', () => { new TestConstruct(stack, 'TestConstruct'); diff --git a/packages/@aws-cdk/core/test/stack.test.ts b/packages/@aws-cdk/core/test/stack.test.ts index 849ad10369147..11a4a01796031 100644 --- a/packages/@aws-cdk/core/test/stack.test.ts +++ b/packages/@aws-cdk/core/test/stack.test.ts @@ -489,7 +489,7 @@ describe('stack', () => { test('cross stack references and dependencies work within child stacks (non-nested)', () => { // GIVEN - const app = new App(); + const app = new App({ context: { '@aws-cdk/core:stackRelativeExports': true } }); const parent = new Stack(app, 'Parent'); const child1 = new Stack(parent, 'Child1'); const child2 = new Stack(parent, 'Child2'); @@ -520,7 +520,7 @@ describe('stack', () => { Outputs: { ExportsOutputRefResourceA461B4EF9: { Value: { Ref: 'ResourceA' }, - Export: { Name: 'ParentChild18FAEF419:Child1ExportsOutputRefResourceA7BF20B37' }, + Export: { Name: 'ParentChild18FAEF419:ExportsOutputRefResourceA461B4EF9' }, }, }, }); @@ -529,7 +529,7 @@ describe('stack', () => { Resource1: { Type: 'R2', Properties: { - RefToResource1: { 'Fn::ImportValue': 'ParentChild18FAEF419:Child1ExportsOutputRefResourceA7BF20B37' }, + RefToResource1: { 'Fn::ImportValue': 'ParentChild18FAEF419:ExportsOutputRefResourceA461B4EF9' }, }, }, }, @@ -541,7 +541,7 @@ describe('stack', () => { test('automatic cross-stack references and manual exports look the same', () => { // GIVEN: automatic - const appA = new App(); + const appA = new App({ context: { '@aws-cdk/core:stackRelativeExports': true } }); const producerA = new Stack(appA, 'Producer'); const consumerA = new Stack(appA, 'Consumer'); const resourceA = new CfnResource(producerA, 'Resource', { type: 'AWS::Resource' }); @@ -704,7 +704,7 @@ describe('stack', () => { test('cross-stack reference (parent stack references substack)', () => { // GIVEN - const app = new App(); + const app = new App({ context: { '@aws-cdk/core:stackRelativeExports': true } }); const parentStack = new Stack(app, 'parent'); const childStack = new Stack(parentStack, 'child'); @@ -724,7 +724,7 @@ describe('stack', () => { MyParentResource: { Type: 'Resource::Parent', Properties: { - ParentProp: { 'Fn::ImportValue': 'parentchild13F9359B:childExportsOutputFnGetAttMyChildResourceAttributeOfChildResource420052FC' }, + ParentProp: { 'Fn::ImportValue': 'parentchild13F9359B:ExportsOutputFnGetAttMyChildResourceAttributeOfChildResource52813264' }, }, }, }, @@ -735,7 +735,7 @@ describe('stack', () => { Outputs: { ExportsOutputFnGetAttMyChildResourceAttributeOfChildResource52813264: { Value: { 'Fn::GetAtt': ['MyChildResource', 'AttributeOfChildResource'] }, - Export: { Name: 'parentchild13F9359B:childExportsOutputFnGetAttMyChildResourceAttributeOfChildResource420052FC' }, + Export: { Name: 'parentchild13F9359B:ExportsOutputFnGetAttMyChildResourceAttributeOfChildResource52813264' }, }, }, }); @@ -1181,4 +1181,4 @@ class StackWithPostProcessor extends Stack { return template; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index 3f3a64a859998..26299f67a8f16 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -74,7 +74,7 @@ "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-ssm": "0.0.0", - "@types/aws-lambda": "^8.10.73", + "@types/aws-lambda": "^8.10.75", "@types/fs-extra": "^8.1.1", "@types/sinon": "^9.0.11", "aws-sdk": "^2.848.0", diff --git a/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts b/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts index 415f49f703e26..0e0dc6e7f6215 100644 --- a/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts +++ b/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts @@ -7,10 +7,11 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; -import { cloudAssemblyBuildSpecDir } from '../private/construct-internals'; import { toPosixPath } from '../private/fs'; import { copyEnvironmentVariables, filterEmpty } from './_util'; +const DEFAULT_OUTPUT_DIR = 'cdk.out'; + // 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'; @@ -378,7 +379,7 @@ export class SimpleSynthAction implements codepipeline.IAction, iam.IGrantable { // using secondary artifacts or not. const cloudAsmArtifactSpec = { - 'base-directory': toPosixPath(path.join(self.props.subdirectory ?? '.', cloudAssemblyBuildSpecDir(scope))), + 'base-directory': toPosixPath(path.join(self.props.subdirectory ?? '.', DEFAULT_OUTPUT_DIR)), 'files': '**/*', }; diff --git a/packages/@aws-cdk/pipelines/test/builds.test.ts b/packages/@aws-cdk/pipelines/test/builds.test.ts index 0fa86802390d7..dcff691e62002 100644 --- a/packages/@aws-cdk/pipelines/test/builds.test.ts +++ b/packages/@aws-cdk/pipelines/test/builds.test.ts @@ -85,7 +85,7 @@ test.each([['npm'], ['yarn']])('%s build automatically determines artifact base- Source: { BuildSpec: encodedJson(deepObjectLike({ artifacts: { - 'base-directory': 'testcdk.out', + 'base-directory': 'cdk.out', }, })), }, @@ -117,7 +117,7 @@ test.each([['npm'], ['yarn']])('%s build respects subdirectory', (npmYarn) => { }, }, artifacts: { - 'base-directory': 'subdir/testcdk.out', + 'base-directory': 'subdir/cdk.out', }, })), }, @@ -301,7 +301,7 @@ test('Standard (NPM) synth can output additional artifacts', () => { artifacts: { 'secondary-artifacts': { CloudAsm: { - 'base-directory': 'testcdk.out', + 'base-directory': 'cdk.out', 'files': '**/*', }, IntegTest: { diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json index 248e3b79911c9..0bb2f5eecd442 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json @@ -293,7 +293,7 @@ "ProjectName": { "Ref": "PipelineBuildSynthCdkBuildProject6BEFA8E6" }, - "EnvironmentVariables": "[{\"name\":\"_PROJECT_CONFIG_HASH\",\"type\":\"PLAINTEXT\",\"value\":\"ea30b07764cb215963f4b0c292b5a06993fc3dd94621fbdc1c618e0f1e0dae10\"}]" + "EnvironmentVariables": "[{\"name\":\"_PROJECT_CONFIG_HASH\",\"type\":\"PLAINTEXT\",\"value\":\"08799ba84511ab1fb89dabc96d52ac2e7b45933dc1d624ce9524dc5c9d75bb26\"}]" }, "InputArtifacts": [ { @@ -777,7 +777,7 @@ ] }, "Source": { - "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"pre_build\": {\n \"commands\": [\n \"npm ci\"\n ]\n },\n \"build\": {\n \"commands\": [\n \"npx cdk synth\"\n ]\n }\n },\n \"artifacts\": {\n \"secondary-artifacts\": {\n \"CloudAsm\": {\n \"base-directory\": \"cdk-integ.out\",\n \"files\": \"**/*\"\n },\n \"IntegTests\": {\n \"base-directory\": \"test\",\n \"files\": \"**/*\"\n }\n }\n }\n}", + "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"pre_build\": {\n \"commands\": [\n \"npm ci\"\n ]\n },\n \"build\": {\n \"commands\": [\n \"npx cdk synth\"\n ]\n }\n },\n \"artifacts\": {\n \"secondary-artifacts\": {\n \"CloudAsm\": {\n \"base-directory\": \"cdk.out\",\n \"files\": \"**/*\"\n },\n \"IntegTests\": {\n \"base-directory\": \"test\",\n \"files\": \"**/*\"\n }\n }\n }\n}", "Type": "CODEPIPELINE" }, "EncryptionKey": { diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json index 5d1569fbadc84..845f23f44b07e 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json @@ -283,7 +283,7 @@ "ProjectName": { "Ref": "PipelineBuildSynthCdkBuildProject6BEFA8E6" }, - "EnvironmentVariables": "[{\"name\":\"_PROJECT_CONFIG_HASH\",\"type\":\"PLAINTEXT\",\"value\":\"ea30b07764cb215963f4b0c292b5a06993fc3dd94621fbdc1c618e0f1e0dae10\"}]" + "EnvironmentVariables": "[{\"name\":\"_PROJECT_CONFIG_HASH\",\"type\":\"PLAINTEXT\",\"value\":\"08799ba84511ab1fb89dabc96d52ac2e7b45933dc1d624ce9524dc5c9d75bb26\"}]" }, "InputArtifacts": [ { @@ -710,7 +710,7 @@ ] }, "Source": { - "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"pre_build\": {\n \"commands\": [\n \"npm ci\"\n ]\n },\n \"build\": {\n \"commands\": [\n \"npx cdk synth\"\n ]\n }\n },\n \"artifacts\": {\n \"secondary-artifacts\": {\n \"CloudAsm\": {\n \"base-directory\": \"cdk-integ.out\",\n \"files\": \"**/*\"\n },\n \"IntegTests\": {\n \"base-directory\": \"test\",\n \"files\": \"**/*\"\n }\n }\n }\n}", + "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"pre_build\": {\n \"commands\": [\n \"npm ci\"\n ]\n },\n \"build\": {\n \"commands\": [\n \"npx cdk synth\"\n ]\n }\n },\n \"artifacts\": {\n \"secondary-artifacts\": {\n \"CloudAsm\": {\n \"base-directory\": \"cdk.out\",\n \"files\": \"**/*\"\n },\n \"IntegTests\": {\n \"base-directory\": \"test\",\n \"files\": \"**/*\"\n }\n }\n }\n}", "Type": "CODEPIPELINE" }, "EncryptionKey": { diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index 4317245d34d13..abc3c2aabaf5e 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -33,7 +33,11 @@ "cdk-build": { "eslint": { "disable": true - } + }, + "stripDeprecated": true + }, + "cdk-package": { + "post": "node ./scripts/verify-stripped-exp.js" }, "pkglint": { "exclude": [ @@ -52,15 +56,13 @@ "dotnet": { "namespace": "Amazon.CDK", "packageId": "Amazon.CDK.Lib", - "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png", - "versionSuffix": "-devpreview" + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "java": { - "package": "software.amazon.awscdk.core", + "package": "software.amazon.awscdk", "maven": { "groupId": "software.amazon.awscdk", - "artifactId": "aws-cdk-lib", - "versionSuffix": ".DEVPREVIEW" + "artifactId": "aws-cdk-lib" } }, "python": { @@ -314,6 +316,7 @@ "announce": false }, "ubergen": { - "exclude": true + "exclude": true, + "excludeExperimentalModules": true } } diff --git a/packages/aws-cdk-lib/scripts/verify-stripped-exp.ts b/packages/aws-cdk-lib/scripts/verify-stripped-exp.ts new file mode 100644 index 0000000000000..10e3da07b7f4d --- /dev/null +++ b/packages/aws-cdk-lib/scripts/verify-stripped-exp.ts @@ -0,0 +1,158 @@ +// +------------------------------------------------------------------------------------------------ +// | this script is executed post packaging to verify that experimental modules in aws-cdk-lib includes **only** L1 autogenerated files. +// | The purpose is to avoid publishing L2 of experimental modules with aws-cdk-lib +// | +import { spawnSync } from 'child_process'; +import * as console from 'console'; +import * as os from 'os'; +import * as path from 'path'; +import * as fs from 'fs-extra'; + +async function main(tempDir: string) { + console.log('🧐 Verifying all experimental modules includes only L1s files...'); + const cwd = process.cwd(); + const awsCdkModulesRepoPath = path.join(findWorkspacePath(), 'packages', '@aws-cdk'); + // eslint-disable-next-line @typescript-eslint/no-require-imports + const version = require('./../package.json').version; + const tarFullPath = path.join(cwd, 'dist', 'js', `aws-cdk-lib@${version}.jsii.tgz`); + + const invalidCfnModules = new Map>(); + const invalidModules = new Array(); + + // install the tarball in a temp directory + console.log(`installing aws-cdk-lib from dist/js into ${tempDir}`); + exec('npm', ['install', '--prefix', tempDir, tarFullPath]); + const installedAwsCdkLibPath = path.join(tempDir, 'node_modules', 'aws-cdk-lib', 'lib'); + + for (const module of fs.readdirSync(awsCdkModulesRepoPath)) { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const pkgJson = require(path.join(awsCdkModulesRepoPath, module, 'package.json')); + if (pkgJson.stability !== 'experimental') { + continue; + } + if (pkgJson['cdk-build'].cloudformation) { + // if a cfn module, verify only the allowed files exists + const files = await listAllFiles(path.join(installedAwsCdkLibPath, module)); + const invalidFiles = new Array(); + files.forEach(file => { + if (!isAllowedFile(file)) { + invalidFiles.push(file); + } + }); + if (invalidFiles.length > 0) { + invalidCfnModules.set(module, invalidFiles); + } + } else { + // not a cfn module, verify it was entirely removed + if (fs.existsSync(path.join(installedAwsCdkLibPath, module))) { + invalidModules.push(module); + } + } + } + + if (invalidCfnModules.size > 0 || invalidModules.length > 0) { + if (invalidCfnModules.size > 0 ) { + console.log('cfn module with invalid files:'); + for (let [module, files] of invalidCfnModules.entries()) { + console.log(`${module}:`); + files.forEach(file => console.log(`\t ${file}`)); + } + } + console.log('---------------------------------------------'); + if (invalidModules.length > 0) { + console.log('non-cfn experimental modules:'); + invalidModules.forEach(m => console.log(`\t ${m}`)); + } + throw new Error('Verification Error'); + } +} + +const tempDir = fs.mkdtempSync(os.tmpdir()); + +main(tempDir).then( + () => { + fs.removeSync(tempDir); + console.log('✅ All experimental modules includes only L1s files!'); + process.exit(0); + }, + (err) => { + process.stderr.write(`${err}\n`); + process.stderr.write(`❌ Verification failed, Some experimental modules includes non L1 files, see details above. Inspect working directory: '${tempDir}'`); + process.exit(1); + }, +); + + +/** + * Spawn sync with error handling + */ +function exec(cmd: string, args: string[]) { + const proc = spawnSync(cmd, args); + + if (proc.error) { + throw proc.error; + } + + if (proc.status !== 0) { + if (proc.stdout || proc.stderr) { + throw new Error(`${cmd} exited with status ${proc.status}; stdout: ${proc.stdout?.toString().trim()}\n\n\nstderr: ${proc.stderr?.toString().trim()}`); + } + throw new Error(`${cmd} exited with status ${proc.status}`); + } + + return proc; +} + +const GENERATED_SUFFIX_REGEX = new RegExp(/generated\.(js|d\.ts)$/); +const ALLOWED_FILES = ['.jsiirc.json', 'index.ts', 'index.js', 'index.d.ts']; + +/** + * Recursively collect all files in dir + */ +async function listAllFiles(dir: string) { + const ret = new Array(); + + async function recurse(part: string) { + const files = await fs.readdir(part); + for (const file of files) { + const fullPath = path.join(part, file); + if ((await fs.stat(fullPath)).isDirectory()) { + await recurse(fullPath); + } else { + ret.push(file); + } + } + } + await recurse(dir); + return ret; +} + +/** + * Find the workspace root path. Walk up the directory tree until you find lerna.json + */ +function findWorkspacePath() { + + return _findRootPath(process.cwd()); + + function _findRootPath(part: string): string { + if (part === path.resolve(part, '..')) { + throw new Error('couldn\'t find a \'lerna.json\' file when walking up the directory tree, are you in a aws-cdk project?'); + } + + if (fs.existsSync(path.resolve(part, 'lerna.json'))) { + return part; + } + return _findRootPath(path.resolve(part, '..')); + } +} + +/** + * @param file + * @returns true if the file allowed in an L1 only modules, otherwise false + */ +function isAllowedFile(file: string) { + if (GENERATED_SUFFIX_REGEX.test(file)) { + return true; + } + return ALLOWED_FILES.includes(file); +} \ No newline at end of file diff --git a/packages/aws-cdk-migration/.no-packagejson-validator b/packages/aws-cdk-migration/.no-packagejson-validator new file mode 100644 index 0000000000000..171d30544e368 --- /dev/null +++ b/packages/aws-cdk-migration/.no-packagejson-validator @@ -0,0 +1 @@ +disable pkglint diff --git a/packages/aws-cdk-migration/README.md b/packages/aws-cdk-migration/README.md index b93c0bd78bc03..ba37d609c28db 100644 --- a/packages/aws-cdk-migration/README.md +++ b/packages/aws-cdk-migration/README.md @@ -1,19 +1,4 @@ -# @aws-cdk-lib/rewrite-imports - - ---- - -![cdk-constructs: Developer Preview](https://img.shields.io/badge/cdk--constructs-developer--preview-informational.svg?style=for-the-badge) - -> The APIs of higher level constructs in this module are in **developer preview** before they -> become stable. We will only make breaking changes to address unforeseen API issues. Therefore, -> these APIs are not subject to [Semantic Versioning](https://semver.org/), and breaking changes -> will be announced in release notes. This means that while you may use them, you may need to -> update your source code when upgrading to a newer version of this package. - ---- - - +# aws-cdk-migration Migrate TypeScript `import` statements from modular CDK (i.e. `@aws-cdk/aws-s3`) to aws-cdk-lib (i.e. `aws-cdk-lib`); diff --git a/packages/aws-cdk-migration/package.json b/packages/aws-cdk-migration/package.json index b1ce33926dd9f..26b5c61d98bd7 100644 --- a/packages/aws-cdk-migration/package.json +++ b/packages/aws-cdk-migration/package.json @@ -49,7 +49,6 @@ }, "homepage": "https://github.com/aws/aws-cdk", "stability": "experimental", - "maturity": "developer-preview", "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 9dc3a71799457..85ebcc00e821c 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -256,24 +256,7 @@ async function initCommandLine() { }); case 'bootstrap': - // Use new bootstrapping if it's requested via environment variable, or if - // new style stack synthesis has been configured in `cdk.json`. - // - // In code it's optimistically called "default" bootstrapping but that is in - // anticipation of flipping the switch, in user messaging we still call it - // "new" bootstrapping. - let source: BootstrapSource = { source: 'legacy' }; - const newStyleStackSynthesis = isFeatureEnabled(configuration, cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT); - if (args.template) { - print(`Using bootstrapping template from ${args.template}`); - source = { source: 'custom', templateFile: args.template }; - } else if (process.env.CDK_NEW_BOOTSTRAP) { - print('CDK_NEW_BOOTSTRAP set, using new-style bootstrapping'); - source = { source: 'default' }; - } else if (newStyleStackSynthesis) { - print(`'${cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT}' context set, using new-style bootstrapping`); - source = { source: 'default' }; - } + const source: BootstrapSource = determineBootsrapVersion(args, configuration); const bootstrapper = new Bootstrapper(source); @@ -361,6 +344,48 @@ async function initCommandLine() { } } +/** + * Determine which version of bootstrapping + * (legacy, or "new") should be used. + */ +function determineBootsrapVersion(args: { template?: string }, configuration: Configuration): BootstrapSource { + const isV1 = version.DISPLAY_VERSION.startsWith('1.'); + return isV1 ? determineV1BootstrapSource(args, configuration) : determineV2BootstrapSource(args); +} + +function determineV1BootstrapSource(args: { template?: string }, configuration: Configuration): BootstrapSource { + let source: BootstrapSource; + if (args.template) { + print(`Using bootstrapping template from ${args.template}`); + source = { source: 'custom', templateFile: args.template }; + } else if (process.env.CDK_NEW_BOOTSTRAP) { + print('CDK_NEW_BOOTSTRAP set, using new-style bootstrapping'); + source = { source: 'default' }; + } else if (isFeatureEnabled(configuration, cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT)) { + print(`'${cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT}' context set, using new-style bootstrapping`); + source = { source: 'default' }; + } else { + // in V1, the "legacy" bootstrapping is the default + source = { source: 'legacy' }; + } + return source; +} + +function determineV2BootstrapSource(args: { template?: string }): BootstrapSource { + let source: BootstrapSource; + if (args.template) { + print(`Using bootstrapping template from ${args.template}`); + source = { source: 'custom', templateFile: args.template }; + } else if (process.env.CDK_LEGACY_BOOTSTRAP) { + print('CDK_LEGACY_BOOTSTRAP set, using legacy-style bootstrapping'); + source = { source: 'legacy' }; + } else { + // in V2, the "new" bootstrapping is the default + source = { source: 'default' }; + } + return source; +} + function isFeatureEnabled(configuration: Configuration, featureFlag: string) { return configuration.context.get(featureFlag) ?? cxapi.futureFlagDefault(featureFlag); } diff --git a/packages/aws-cdk/lib/api/aws-auth/credentials.ts b/packages/aws-cdk/lib/api/aws-auth/credentials.ts index 00f75ba57908c..8643f3d76eac3 100644 --- a/packages/aws-cdk/lib/api/aws-auth/credentials.ts +++ b/packages/aws-cdk/lib/api/aws-auth/credentials.ts @@ -6,7 +6,6 @@ export enum Mode { } /** - * @experimental */ export interface CredentialProviderSource { name: string; diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk.ts b/packages/aws-cdk/lib/api/aws-auth/sdk.ts index 891ced65451b8..d5787a258275d 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk.ts @@ -5,7 +5,6 @@ import { cached } from '../../util/functions'; import { AccountAccessKeyCache } from './account-cache'; import { Account } from './sdk-provider'; -/** @experimental */ export interface ISDK { /** * The region this SDK has been instantiated for diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts index a5443517ad984..5350524b0dae3 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts @@ -40,7 +40,6 @@ export class Bootstrapper { /** * Deploy legacy bootstrap stack * - * @experimental */ private async legacyBootstrap(environment: cxapi.Environment, sdkProvider: SdkProvider, options: BootstrapEnvironmentOptions = {}): Promise { const params = options.parameters ?? {}; @@ -68,7 +67,6 @@ export class Bootstrapper { /** * Deploy CI/CD-ready bootstrap stack from template * - * @experimental */ private async modernBootstrap( environment: cxapi.Environment, diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts index 010e5603f1400..dd36739d4381d 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts @@ -1,14 +1,9 @@ import { Tag } from '../../cdk-toolkit'; -/** @experimental */ export const BUCKET_NAME_OUTPUT = 'BucketName'; -/** @experimental */ export const REPOSITORY_NAME_OUTPUT = 'RepositoryName'; -/** @experimental */ export const BUCKET_DOMAIN_NAME_OUTPUT = 'BucketDomainName'; -/** @experimental */ export const BOOTSTRAP_VERSION_OUTPUT = 'BootstrapVersion'; -/** @experimental */ export const BOOTSTRAP_VERSION_RESOURCE = 'CdkBootstrapVersion'; /** diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index 177df82d4fcbf..df811e39587be 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -32,7 +32,6 @@ type TemplateBodyParameter = { TemplateURL?: string }; -/** @experimental */ export interface DeployStackResult { readonly noOp: boolean; readonly outputs: { [name: string]: string }; @@ -40,7 +39,6 @@ export interface DeployStackResult { readonly stackArtifact: cxapi.CloudFormationStackArtifact; } -/** @experimental */ export interface DeployStackOptions { /** * The stack to be deployed @@ -181,7 +179,6 @@ export interface DeployStackOptions { const LARGE_TEMPLATE_SIZE_KB = 50; -/** @experimental */ export async function deployStack(options: DeployStackOptions): Promise { const stackArtifact = options.stack; @@ -366,7 +363,6 @@ async function makeBodyParameter( return { TemplateURL: templateURL }; } -/** @experimental */ export interface DestroyStackOptions { /** * The stack to be destroyed @@ -379,7 +375,6 @@ export interface DestroyStackOptions { quiet?: boolean; } -/** @experimental */ export async function destroyStack(options: DestroyStackOptions) { const deployName = options.deployName || options.stack.stackName; const cfn = options.sdk.cloudFormation(); diff --git a/packages/aws-cdk/lib/api/toolkit-info.ts b/packages/aws-cdk/lib/api/toolkit-info.ts index 014a08c670619..5cd62fb402313 100644 --- a/packages/aws-cdk/lib/api/toolkit-info.ts +++ b/packages/aws-cdk/lib/api/toolkit-info.ts @@ -34,14 +34,12 @@ const BOOTSTRAP_TEMPLATE_VERSION_INTRODUCING_GETPARAMETER = 5; * * Called "ToolkitInfo" for historical reasons. * - * @experimental */ export abstract class ToolkitInfo { public static determineName(overrideName?: string) { return overrideName ?? DEFAULT_TOOLKIT_STACK_NAME; } - /** @experimental */ public static async lookup(environment: cxapi.Environment, sdk: ISDK, stackName: string | undefined): Promise { const cfn = sdk.cloudFormation(); const stack = await stabilizeStack(cfn, stackName ?? DEFAULT_TOOLKIT_STACK_NAME); @@ -185,7 +183,6 @@ class ExistingToolkitInfo extends ToolkitInfo { /** * Prepare an ECR repository for uploading to using Docker * - * @experimental */ public async prepareEcrRepository(repositoryName: string): Promise { if (!this.sdk) { @@ -292,12 +289,10 @@ class BootstrapStackNotFoundInfo extends ToolkitInfo { } } -/** @experimental */ export interface EcrRepositoryInfo { repositoryUri: string; } -/** @experimental */ export interface EcrCredentials { username: string; password: string; diff --git a/packages/aws-cdk/lib/init-templates/LICENSE b/packages/aws-cdk/lib/init-templates/LICENSE new file mode 100644 index 0000000000000..a2f04c7c3bdc9 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/LICENSE @@ -0,0 +1,16 @@ +MIT No Attribution + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +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. + +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/lib/init-templates/v1/app/python/setup.template.py b/packages/aws-cdk/lib/init-templates/v1/app/python/setup.template.py index 3303427f63d0e..113bf29e54755 100644 --- a/packages/aws-cdk/lib/init-templates/v1/app/python/setup.template.py +++ b/packages/aws-cdk/lib/init-templates/v1/app/python/setup.template.py @@ -29,8 +29,6 @@ "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", - "Programming Language :: JavaScript", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.6", diff --git a/packages/aws-cdk/lib/init-templates/v1/sample-app/python/setup.template.py b/packages/aws-cdk/lib/init-templates/v1/sample-app/python/setup.template.py index 9e9b137c1014c..d79611021c860 100644 --- a/packages/aws-cdk/lib/init-templates/v1/sample-app/python/setup.template.py +++ b/packages/aws-cdk/lib/init-templates/v1/sample-app/python/setup.template.py @@ -34,8 +34,6 @@ "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", - "Programming Language :: JavaScript", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.6", diff --git a/packages/aws-cdk/lib/init-templates/v2/app/csharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.cs b/packages/aws-cdk/lib/init-templates/v2/app/csharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.cs index 998db3c5335cd..2e8c5e654324a 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/csharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.cs +++ b/packages/aws-cdk/lib/init-templates/v2/app/csharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.cs @@ -1,4 +1,5 @@ -using Amazon.CDK.Lib; +using Amazon.CDK; +using Constructs; namespace %name.PascalCased% { diff --git a/packages/aws-cdk/lib/init-templates/v2/app/csharp/src/%name.PascalCased%/Program.template.cs b/packages/aws-cdk/lib/init-templates/v2/app/csharp/src/%name.PascalCased%/Program.template.cs index b514172b49459..0a35f06be9217 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/csharp/src/%name.PascalCased%/Program.template.cs +++ b/packages/aws-cdk/lib/init-templates/v2/app/csharp/src/%name.PascalCased%/Program.template.cs @@ -1,4 +1,4 @@ -using Amazon.CDK.Lib; +using Amazon.CDK; using System; using System.Collections.Generic; using System.Linq; diff --git a/packages/aws-cdk/lib/init-templates/v2/app/fsharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.fs b/packages/aws-cdk/lib/init-templates/v2/app/fsharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.fs index 40f90dc43d77e..5e4d9ef94e748 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/fsharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.fs +++ b/packages/aws-cdk/lib/init-templates/v2/app/fsharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.fs @@ -1,6 +1,6 @@ namespace %name.PascalCased% -open Amazon.CDK.Lib +open Amazon.CDK type %name.PascalCased%Stack(scope, id, props) as this = inherit Stack(scope, id, props) diff --git a/packages/aws-cdk/lib/init-templates/v2/app/fsharp/src/%name.PascalCased%/Program.template.fs b/packages/aws-cdk/lib/init-templates/v2/app/fsharp/src/%name.PascalCased%/Program.template.fs index 4b28d1e03ba9a..dc618af3fbbf1 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/fsharp/src/%name.PascalCased%/Program.template.fs +++ b/packages/aws-cdk/lib/init-templates/v2/app/fsharp/src/%name.PascalCased%/Program.template.fs @@ -1,4 +1,4 @@ -open Amazon.CDK.Lib +open Amazon.CDK open %name.PascalCased% [] diff --git a/packages/aws-cdk/lib/init-templates/v2/app/java/pom.template.xml b/packages/aws-cdk/lib/init-templates/v2/app/java/pom.template.xml index 5defab0b3a0b6..922c04ccb0943 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/java/pom.template.xml +++ b/packages/aws-cdk/lib/init-templates/v2/app/java/pom.template.xml @@ -40,7 +40,7 @@ software.amazon.awscdk - lib + aws-cdk-lib ${cdk.version} diff --git a/packages/aws-cdk/lib/init-templates/v2/app/java/src/main/java/com/myorg/%name.PascalCased%App.template.java b/packages/aws-cdk/lib/init-templates/v2/app/java/src/main/java/com/myorg/%name.PascalCased%App.template.java index 08bccaddc2695..6499cbc9d2870 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/java/src/main/java/com/myorg/%name.PascalCased%App.template.java +++ b/packages/aws-cdk/lib/init-templates/v2/app/java/src/main/java/com/myorg/%name.PascalCased%App.template.java @@ -1,8 +1,8 @@ package com.myorg; -import software.amazon.awscdk.lib.App; -import software.amazon.awscdk.lib.Environment; -import software.amazon.awscdk.lib.StackProps; +import software.amazon.awscdk.App; +import software.amazon.awscdk.Environment; +import software.amazon.awscdk.StackProps; import java.util.Arrays; diff --git a/packages/aws-cdk/lib/init-templates/v2/app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py b/packages/aws-cdk/lib/init-templates/v2/app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py index 40bad7e3b032d..34f669bde7832 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py +++ b/packages/aws-cdk/lib/init-templates/v2/app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py @@ -1,4 +1,5 @@ -import aws_cdk_lib as core +from aws_cdk import Stack +from constructs import Construct class %name.PascalCased%Stack(core.Stack): diff --git a/packages/aws-cdk/lib/init-templates/v2/app/python/app.template.py b/packages/aws-cdk/lib/init-templates/v2/app/python/app.template.py index a258f798a7e6f..65cd03f7253da 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/python/app.template.py +++ b/packages/aws-cdk/lib/init-templates/v2/app/python/app.template.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 import os -import aws_cdk_lib as core +import aws_cdk as cdk from %name.PythonModule%.%name.PythonModule%_stack import %name.PascalCased%Stack -app = core.App() +app = cdk.App() %name.PascalCased%Stack(app, "%name.PascalCased%Stack", # If you don't specify 'env', this stack will be environment-agnostic. # Account/Region-dependent features and context lookups will not work, @@ -15,12 +15,12 @@ # Uncomment the next line to specialize this stack for the AWS Account # and Region that are implied by the current CLI configuration. - #env=core.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION')), + #env=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION')), # Uncomment the next line if you know exactly what Account and Region you # want to deploy the stack to. */ - #env=core.Environment(account='123456789012', region='us-east-1'), + #env=cdk.Environment(account='123456789012', region='us-east-1'), # For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html ) diff --git a/packages/aws-cdk/lib/init-templates/v2/app/python/setup.template.py b/packages/aws-cdk/lib/init-templates/v2/app/python/setup.template.py index 4aadde6ecede7..de40700458a80 100644 --- a/packages/aws-cdk/lib/init-templates/v2/app/python/setup.template.py +++ b/packages/aws-cdk/lib/init-templates/v2/app/python/setup.template.py @@ -29,8 +29,6 @@ "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", - "Programming Language :: JavaScript", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.6", diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.cs b/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.cs index db2baa2ca3924..365330ed65925 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.cs +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.cs @@ -1,4 +1,4 @@ -using Amazon.CDK.Lib; +using Amazon.CDK; using Amazon.CDK.AWS.SNS; using Amazon.CDK.AWS.SNS.Subscriptions; using Amazon.CDK.AWS.SQS; diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/src/%name.PascalCased%/Program.template.cs b/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/src/%name.PascalCased%/Program.template.cs index ce88608d08cfe..20d5337f09888 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/src/%name.PascalCased%/Program.template.cs +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/csharp/src/%name.PascalCased%/Program.template.cs @@ -1,4 +1,4 @@ -using Amazon.CDK.Lib; +using Amazon.CDK; namespace %name.PascalCased% { diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.fs b/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.fs index fb527366dfb7b..1582e37f4faaf 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.fs +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/src/%name.PascalCased%/%name.PascalCased%Stack.template.fs @@ -1,6 +1,6 @@ namespace %name.PascalCased% -open Amazon.CDK.Lib +open Amazon.CDK open Amazon.CDK.AWS.SNS open Amazon.CDK.AWS.SNS.Subscriptions open Amazon.CDK.AWS.SQS diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/src/%name.PascalCased%/Program.template.fs b/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/src/%name.PascalCased%/Program.template.fs index 4b28d1e03ba9a..dc618af3fbbf1 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/src/%name.PascalCased%/Program.template.fs +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/fsharp/src/%name.PascalCased%/Program.template.fs @@ -1,4 +1,4 @@ -open Amazon.CDK.Lib +open Amazon.CDK open %name.PascalCased% [] diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/java/pom.template.xml b/packages/aws-cdk/lib/init-templates/v2/sample-app/java/pom.template.xml index 7f10c89a54dd4..c6628e62b18a2 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/java/pom.template.xml +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/java/pom.template.xml @@ -35,7 +35,7 @@ software.amazon.awscdk - lib + aws-cdk-lib ${cdk.version} diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py index 7629e4361788b..b74e7d6438146 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py @@ -1,5 +1,7 @@ -import aws_cdk_lib as core -from aws_cdk_lib import ( +from constructs import Construct +from aws_cdk import ( + Duration, + Stack, aws_iam as iam, aws_sqs as sqs, aws_sns as sns, diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/app.template.py b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/app.template.py index f53ecf105b8ca..1f64e6a67bc56 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/app.template.py +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/app.template.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 -import aws_cdk_lib as core +import aws_cdk as cdk from %name.PythonModule%.%name.PythonModule%_stack import %name.PascalCased%Stack -app = core.App() +app = cdk.App() %name.PascalCased%Stack(app, "%name.StackName%", env={'region': 'us-west-2'}) app.synth() diff --git a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/setup.template.py b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/setup.template.py index 585578354a190..3bcffe32b2e8f 100644 --- a/packages/aws-cdk/lib/init-templates/v2/sample-app/python/setup.template.py +++ b/packages/aws-cdk/lib/init-templates/v2/sample-app/python/setup.template.py @@ -29,8 +29,6 @@ "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", - "Programming Language :: JavaScript", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.6", diff --git a/packages/aws-cdk/lib/plugin.ts b/packages/aws-cdk/lib/plugin.ts index 4b4ad7956edc3..705fa3c3d859d 100644 --- a/packages/aws-cdk/lib/plugin.ts +++ b/packages/aws-cdk/lib/plugin.ts @@ -17,7 +17,6 @@ import { error } from './logging'; * } * } * - * @experimental */ export interface Plugin { /** @@ -38,7 +37,6 @@ export interface Plugin { /** * A utility to manage plug-ins. * - * @experimental */ export class PluginHost { public static instance = new PluginHost(); diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 35a834805ff94..58431ed31c084 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -85,7 +85,7 @@ "proxy-agent": "^4.0.1", "semver": "^7.3.5", "source-map-support": "^0.5.19", - "table": "^6.0.9", + "table": "^6.1.0", "uuid": "^8.3.2", "wrap-ansi": "^7.0.0", "yaml": "1.10.2", diff --git a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts index c6ba2e53ec2d8..d1d122156d11a 100644 --- a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts @@ -8,9 +8,10 @@ jest.setTimeout(600_000); integTest('can bootstrap without execution', withDefaultFixture(async (fixture) => { const bootstrapStackName = fixture.fullStackName('bootstrap-stack'); - await fixture.cdk(['bootstrap', - '--toolkit-stack-name', bootstrapStackName, - '--no-execute']); + await fixture.cdkBootstrapLegacy({ + toolkitStackName: bootstrapStackName, + noExecute: true, + }); const resp = await fixture.aws.cloudFormation('describeStacks', { StackName: bootstrapStackName, @@ -28,9 +29,10 @@ integTest('upgrade legacy bootstrap stack to new bootstrap stack while in use', fixture.rememberToDeleteBucket(newBootstrapBucketName); // This one shouldn't leak if the test succeeds, but let's be safe in case it doesn't // Legacy bootstrap - await fixture.cdk(['bootstrap', - '--toolkit-stack-name', bootstrapStackName, - '--bootstrap-bucket-name', legacyBootstrapBucketName]); + await fixture.cdkBootstrapLegacy({ + toolkitStackName: bootstrapStackName, + bootstrapBucketName: legacyBootstrapBucketName, + }); // Deploy stack that uses file assets await fixture.cdkDeploy('lambda', { @@ -38,14 +40,10 @@ integTest('upgrade legacy bootstrap stack to new bootstrap stack while in use', }); // Upgrade bootstrap stack to "new" style - await fixture.cdk(['bootstrap', - '--toolkit-stack-name', bootstrapStackName, - '--bootstrap-bucket-name', newBootstrapBucketName, - '--qualifier', fixture.qualifier, - '--cloudformation-execution-policies', 'arn:aws:iam::aws:policy/AdministratorAccess'], { - modEnv: { - CDK_NEW_BOOTSTRAP: '1', - }, + await fixture.cdkBootstrapModern({ + toolkitStackName: bootstrapStackName, + bootstrapBucketName: newBootstrapBucketName, + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', }); // (Force) deploy stack again @@ -61,12 +59,8 @@ integTest('upgrade legacy bootstrap stack to new bootstrap stack while in use', integTest('can and deploy if omitting execution policies', withDefaultFixture(async (fixture) => { const bootstrapStackName = fixture.fullStackName('bootstrap-stack'); - await fixture.cdk(['bootstrap', - '--toolkit-stack-name', bootstrapStackName, - '--qualifier', fixture.qualifier], { - modEnv: { - CDK_NEW_BOOTSTRAP: '1', - }, + await fixture.cdkBootstrapModern({ + toolkitStackName: bootstrapStackName, }); // Deploy stack that uses file assets @@ -82,13 +76,9 @@ integTest('can and deploy if omitting execution policies', withDefaultFixture(as integTest('deploy new style synthesis to new style bootstrap', withDefaultFixture(async (fixture) => { const bootstrapStackName = fixture.fullStackName('bootstrap-stack'); - await fixture.cdk(['bootstrap', - '--toolkit-stack-name', bootstrapStackName, - '--qualifier', fixture.qualifier, - '--cloudformation-execution-policies', 'arn:aws:iam::aws:policy/AdministratorAccess'], { - modEnv: { - CDK_NEW_BOOTSTRAP: '1', - }, + await fixture.cdkBootstrapModern({ + toolkitStackName: bootstrapStackName, + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', }); // Deploy stack that uses file assets @@ -104,13 +94,9 @@ integTest('deploy new style synthesis to new style bootstrap', withDefaultFixtur integTest('deploy new style synthesis to new style bootstrap (with docker image)', withDefaultFixture(async (fixture) => { const bootstrapStackName = fixture.fullStackName('bootstrap-stack'); - await fixture.cdk(['bootstrap', - '--toolkit-stack-name', bootstrapStackName, - '--qualifier', fixture.qualifier, - '--cloudformation-execution-policies', 'arn:aws:iam::aws:policy/AdministratorAccess'], { - modEnv: { - CDK_NEW_BOOTSTRAP: '1', - }, + await fixture.cdkBootstrapModern({ + toolkitStackName: bootstrapStackName, + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', }); // Deploy stack that uses file assets @@ -126,13 +112,9 @@ integTest('deploy new style synthesis to new style bootstrap (with docker image) integTest('deploy old style synthesis to new style bootstrap', withDefaultFixture(async (fixture) => { const bootstrapStackName = fixture.fullStackName('bootstrap-stack'); - await fixture.cdk(['bootstrap', - '--toolkit-stack-name', bootstrapStackName, - '--qualifier', fixture.qualifier, - '--cloudformation-execution-policies', 'arn:aws:iam::aws:policy/AdministratorAccess'], { - modEnv: { - CDK_NEW_BOOTSTRAP: '1', - }, + await fixture.cdkBootstrapModern({ + toolkitStackName: bootstrapStackName, + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', }); // Deploy stack that uses file assets @@ -146,7 +128,9 @@ integTest('deploy old style synthesis to new style bootstrap', withDefaultFixtur integTest('deploying new style synthesis to old style bootstrap fails', withDefaultFixture(async (fixture) => { const bootstrapStackName = fixture.fullStackName('bootstrap-stack'); - await fixture.cdk(['bootstrap', '--toolkit-stack-name', bootstrapStackName]); + await fixture.cdkBootstrapLegacy({ + toolkitStackName: bootstrapStackName, + }); // Deploy stack that uses file assets, this fails because the bootstrap stack // is version checked. @@ -161,7 +145,12 @@ integTest('deploying new style synthesis to old style bootstrap fails', withDefa integTest('can create a legacy bootstrap stack with --public-access-block-configuration=false', withDefaultFixture(async (fixture) => { const bootstrapStackName = fixture.fullStackName('bootstrap-stack-1'); - await fixture.cdk(['bootstrap', '-v', '--toolkit-stack-name', bootstrapStackName, '--public-access-block-configuration', 'false', '--tags', 'Foo=Bar']); + await fixture.cdkBootstrapLegacy({ + verbose: true, + toolkitStackName: bootstrapStackName, + publicAccessBlockConfiguration: false, + tags: 'Foo=Bar', + }); const response = await fixture.aws.cloudFormation('describeStacks', { StackName: bootstrapStackName }); expect(response.Stacks?.[0].Tags).toEqual([ @@ -175,8 +164,15 @@ integTest('can create multiple legacy bootstrap stacks', withDefaultFixture(asyn // deploy two toolkit stacks into the same environment (see #1416) // one with tags - await fixture.cdk(['bootstrap', '-v', '--toolkit-stack-name', bootstrapStackName1, '--tags', 'Foo=Bar']); - await fixture.cdk(['bootstrap', '-v', '--toolkit-stack-name', bootstrapStackName2]); + await fixture.cdkBootstrapLegacy({ + verbose: true, + toolkitStackName: bootstrapStackName1, + tags: 'Foo=Bar', + }); + await fixture.cdkBootstrapLegacy({ + verbose: true, + toolkitStackName: bootstrapStackName2, + }); const response = await fixture.aws.cloudFormation('describeStacks', { StackName: bootstrapStackName1 }); expect(response.Stacks?.[0].Tags).toEqual([ @@ -185,10 +181,12 @@ integTest('can create multiple legacy bootstrap stacks', withDefaultFixture(asyn })); integTest('can dump the template, modify and use it to deploy a custom bootstrap stack', withDefaultFixture(async (fixture) => { - let template = await fixture.cdk(['bootstrap', '--show-template'], { - captureStderr: false, - modEnv: { - CDK_NEW_BOOTSTRAP: '1', + let template = await fixture.cdkBootstrapModern({ + // toolkitStackName doesn't matter for this particular invocation + toolkitStackName: fixture.fullStackName('bootstrap-stack'), + showTemplate: true, + cliOptions: { + captureStderr: false, }, }); @@ -201,27 +199,27 @@ integTest('can dump the template, modify and use it to deploy a custom bootstrap const filename = path.join(fixture.integTestDir, `${fixture.qualifier}-template.yaml`); fs.writeFileSync(filename, template, { encoding: 'utf-8' }); - await fixture.cdk(['bootstrap', - '--toolkit-stack-name', fixture.fullStackName('bootstrap-stack'), - '--qualifier', fixture.qualifier, - '--template', filename, - '--cloudformation-execution-policies', 'arn:aws:iam::aws:policy/AdministratorAccess'], { - modEnv: { - CDK_NEW_BOOTSTRAP: '1', - }, + await fixture.cdkBootstrapModern({ + toolkitStackName: fixture.fullStackName('bootstrap-stack'), + template: filename, + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', }); })); integTest('switch on termination protection, switch is left alone on re-bootstrap', withDefaultFixture(async (fixture) => { const bootstrapStackName = fixture.fullStackName('bootstrap-stack'); - await fixture.cdk(['bootstrap', '-v', '--toolkit-stack-name', bootstrapStackName, - '--termination-protection', 'true', - '--qualifier', fixture.qualifier, - '--cloudformation-execution-policies', 'arn:aws:iam::aws:policy/AdministratorAccess'], { - modEnv: { CDK_NEW_BOOTSTRAP: '1' }, + await fixture.cdkBootstrapModern({ + verbose: true, + toolkitStackName: bootstrapStackName, + terminationProtection: true, + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', + }); + await fixture.cdkBootstrapModern({ + verbose: true, + toolkitStackName: bootstrapStackName, + force: true, }); - await fixture.cdk(['bootstrap', '-v', '--toolkit-stack-name', bootstrapStackName, '--force'], { modEnv: { CDK_NEW_BOOTSTRAP: '1' } }); const response = await fixture.aws.cloudFormation('describeStacks', { StackName: bootstrapStackName }); expect(response.Stacks?.[0].EnableTerminationProtection).toEqual(true); @@ -230,13 +228,17 @@ integTest('switch on termination protection, switch is left alone on re-bootstra integTest('add tags, left alone on re-bootstrap', withDefaultFixture(async (fixture) => { const bootstrapStackName = fixture.fullStackName('bootstrap-stack'); - await fixture.cdk(['bootstrap', '-v', '--toolkit-stack-name', bootstrapStackName, - '--tags', 'Foo=Bar', - '--qualifier', fixture.qualifier, - '--cloudformation-execution-policies', 'arn:aws:iam::aws:policy/AdministratorAccess'], { - modEnv: { CDK_NEW_BOOTSTRAP: '1' }, + await fixture.cdkBootstrapModern({ + verbose: true, + toolkitStackName: bootstrapStackName, + tags: 'Foo=Bar', + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', + }); + await fixture.cdkBootstrapModern({ + verbose: true, + toolkitStackName: bootstrapStackName, + force: true, }); - await fixture.cdk(['bootstrap', '-v', '--toolkit-stack-name', bootstrapStackName, '--force'], { modEnv: { CDK_NEW_BOOTSTRAP: '1' } }); const response = await fixture.aws.cloudFormation('describeStacks', { StackName: bootstrapStackName }); expect(response.Stacks?.[0].Tags).toEqual([ @@ -247,13 +249,9 @@ integTest('add tags, left alone on re-bootstrap', withDefaultFixture(async (fixt integTest('can deploy modern-synthesized stack even if bootstrap stack name is unknown', withDefaultFixture(async (fixture) => { const bootstrapStackName = fixture.fullStackName('bootstrap-stack'); - await fixture.cdk(['bootstrap', - '--toolkit-stack-name', bootstrapStackName, - '--qualifier', fixture.qualifier, - '--cloudformation-execution-policies', 'arn:aws:iam::aws:policy/AdministratorAccess'], { - modEnv: { - CDK_NEW_BOOTSTRAP: '1', - }, + await fixture.cdkBootstrapModern({ + toolkitStackName: bootstrapStackName, + cfnExecutionPolicy: 'arn:aws:iam::aws:policy/AdministratorAccess', }); // Deploy stack that uses file assets @@ -266,4 +264,4 @@ integTest('can deploy modern-synthesized stack even if bootstrap stack name is u '--context', '@aws-cdk/core:newStyleStackSynthesis=1', ], }); -})); \ No newline at end of file +})); diff --git a/packages/aws-cdk/test/integ/helpers/cdk.ts b/packages/aws-cdk/test/integ/helpers/cdk.ts index 3758998a63206..13e0cbf6bf88c 100644 --- a/packages/aws-cdk/test/integ/helpers/cdk.ts +++ b/packages/aws-cdk/test/integ/helpers/cdk.ts @@ -189,6 +189,63 @@ export async function cloneDirectory(source: string, target: string, output?: No await shell(['cp', '-R', source + '/*', target], { output }); } +interface CommonCdkBootstrapCommandOptions { + readonly toolkitStackName: string; + + /** + * @default false + */ + readonly verbose?: boolean; + + /** + * @default - auto-generated CloudFormation name + */ + readonly bootstrapBucketName?: string; + + readonly cliOptions?: CdkCliOptions; + + /** + * @default - none + */ + readonly tags?: string; +} + +export interface CdkLegacyBootstrapCommandOptions extends CommonCdkBootstrapCommandOptions { + /** + * @default false + */ + readonly noExecute?: boolean; + + /** + * @default true + */ + readonly publicAccessBlockConfiguration?: boolean; +} + +export interface CdkModernBootstrapCommandOptions extends CommonCdkBootstrapCommandOptions { + /** + * @default false + */ + readonly force?: boolean; + + /** + * @default - none + */ + readonly cfnExecutionPolicy?: string; + + /** + * @default false + */ + readonly showTemplate?: boolean; + + readonly template?: string; + + /** + * @default false + */ + readonly terminationProtection?: boolean; +} + export class TestFixture { public readonly qualifier = randomString().substr(0, 10); private readonly bucketsToDelete = new Array(); @@ -239,6 +296,78 @@ export class TestFixture { ...this.fullStackName(stackNames)], options); } + public async cdkBootstrapLegacy(options: CdkLegacyBootstrapCommandOptions): Promise { + const args = ['bootstrap']; + + if (options.verbose) { + args.push('-v'); + } + args.push('--toolkit-stack-name', options.toolkitStackName); + if (options.bootstrapBucketName) { + args.push('--bootstrap-bucket-name', options.bootstrapBucketName); + } + if (options.noExecute) { + args.push('--no-execute'); + } + if (options.publicAccessBlockConfiguration !== undefined) { + args.push('--public-access-block-configuration', options.publicAccessBlockConfiguration.toString()); + } + if (options.tags) { + args.push('--tags', options.tags); + } + + return this.cdk(args, { + ...options.cliOptions, + modEnv: { + ...options.cliOptions?.modEnv, + // so that this works for V2, + // where the "new" bootstrap is the default + CDK_LEGACY_BOOTSTRAP: '1', + }, + }); + } + + public async cdkBootstrapModern(options: CdkModernBootstrapCommandOptions): Promise { + const args = ['bootstrap']; + + if (options.verbose) { + args.push('-v'); + } + if (options.showTemplate) { + args.push('--show-template'); + } + if (options.template) { + args.push('--template', options.template); + } + args.push('--toolkit-stack-name', options.toolkitStackName); + if (options.bootstrapBucketName) { + args.push('--bootstrap-bucket-name', options.bootstrapBucketName); + } + args.push('--qualifier', this.qualifier); + if (options.cfnExecutionPolicy) { + args.push('--cloudformation-execution-policies', options.cfnExecutionPolicy); + } + if (options.terminationProtection !== undefined) { + args.push('--termination-protection', options.terminationProtection.toString()); + } + if (options.force) { + args.push('--force'); + } + if (options.tags) { + args.push('--tags', options.tags); + } + + return this.cdk(args, { + ...options.cliOptions, + modEnv: { + ...options.cliOptions?.modEnv, + // so that this works for V1, + // where the "old" bootstrap is the default + CDK_NEW_BOOTSTRAP: '1', + }, + }); + } + public async cdk(args: string[], options: CdkCliOptions = {}) { const verbose = options.verbose ?? true; @@ -365,8 +494,9 @@ let sanityChecked: boolean | undefined; * by hand so let's just mass-automate it. */ async function ensureBootstrapped(fixture: TestFixture) { - // Old-style bootstrap stack with default name + // Use the default name for the bootstrap stack if (await fixture.aws.stackStatus('CDKToolkit') === undefined) { + // use whatever version of bootstrap is the default for this particular version of the CLI await fixture.cdk(['bootstrap', `aws://${await fixture.aws.account()}/${fixture.aws.region}`]); } } @@ -511,4 +641,4 @@ const installNpm7 = memoize0(async (): Promise => { 'npm@7']); return path.join(installDir, 'node_modules', '.bin', 'npm'); -}); \ No newline at end of file +}); diff --git a/packages/aws-cdk/test/integ/run-against-dist.bash b/packages/aws-cdk/test/integ/run-against-dist.bash index d3d349b9302f3..0d5dc245df5e7 100644 --- a/packages/aws-cdk/test/integ/run-against-dist.bash +++ b/packages/aws-cdk/test/integ/run-against-dist.bash @@ -4,8 +4,6 @@ npmws=/tmp/cdk-rundist rm -rf $npmws mkdir -p $npmws -set -x - # This script must create 1 or 2 traps, and the 'trap' command will replace # the previous trap, so get some 'dynamic traps' mechanism in place TRAPS=() @@ -43,6 +41,11 @@ function serve_npm_packages() { tarballs_glob="$dist_root/js/*.tgz" + if [[ -f package.json ]]; then + echo "Do not run this script in a directory with a package.json! It will most likely break!" >&2 + # Cowardly not running 'exit 1' because I'm not sure I won't mess up the build/canaries by doing so + fi + # When using '--daemon', 'npm install' first so the files are permanent, or # 'npx' will remove them too soon. npm install serve-npm-tarballs diff --git a/packages/aws-cdk/test/version.test.ts b/packages/aws-cdk/test/version.test.ts index 49ddb4b12aa8d..01b120c463a6b 100644 --- a/packages/aws-cdk/test/version.test.ts +++ b/packages/aws-cdk/test/version.test.ts @@ -6,6 +6,8 @@ import * as sinon from 'sinon'; import * as logging from '../lib/logging'; import { latestVersionIfHigher, VersionCheckTTL, displayVersionMessage } from '../lib/version'; +jest.setTimeout(10_000); + const setTimeout = promisify(_setTimeout); function tmpfile(): string { diff --git a/packages/awslint/.eslintrc.js b/packages/awslint/.eslintrc.js index 61dd8dd001f63..941ceb58503ad 100644 --- a/packages/awslint/.eslintrc.js +++ b/packages/awslint/.eslintrc.js @@ -1,3 +1,202 @@ -const baseConfig = require('cdk-build-tools/config/eslintrc'); -baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; -module.exports = baseConfig; +// This cannot reference the build rules from cdk-build-tools as this +// package is itself used by cdk-build-tools. +module.exports = { + env: { + jest: true, + node: true, + }, + plugins: [ + '@typescript-eslint', + 'import', + 'cdk', + 'jest', + ], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: '2018', + sourceType: 'module', + project: './tsconfig.json', + }, + extends: [ + 'plugin:import/typescript', + 'plugin:jest/recommended', + ], + settings: { + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + 'import/resolver': { + node: {}, + typescript: { + directory: './tsconfig.json', + }, + }, + }, + ignorePatterns: ['*.js', '*.d.ts', 'node_modules/', '*.generated.ts'], + rules: { + 'cdk/construct-import-order': [ 'error' ], + 'cdk/no-core-construct': [ 'error' ], + 'cdk/no-qualified-construct': [ 'error' ], + // Require use of the `import { foo } from 'bar';` form instead of `import foo = require('bar');` + '@typescript-eslint/no-require-imports': ['error'], + '@typescript-eslint/indent': ['error', 2], + + // Style + 'quotes': ['error', 'single', { avoidEscape: true }], + 'comma-dangle': ['error', 'always-multiline'], // ensures clean diffs, see https://medium.com/@nikgraf/why-you-should-enforce-dangling-commas-for-multiline-statements-d034c98e36f8 + 'comma-spacing': ['error', { before: false, after: true }], // space after, no space before + 'no-multi-spaces': ['error', { ignoreEOLComments: false }], // no multi spaces + 'array-bracket-spacing': ['error', 'never'], // [1, 2, 3] + 'array-bracket-newline': ['error', 'consistent'], // enforce consistent line breaks between brackets + 'object-curly-spacing': ['error', 'always'], // { key: 'value' } + 'object-curly-newline': ['error', { multiline: true, consistent: true }], // enforce consistent line breaks between braces + 'object-property-newline': ['error', { allowAllPropertiesOnSameLine: true }], // enforce "same line" or "multiple line" on object properties + 'keyword-spacing': ['error'], // require a space before & after keywords + 'brace-style': ['error', '1tbs', { allowSingleLine: true }], // enforce one true brace style + 'space-before-blocks': 'error', // require space before blocks + 'curly': ['error', 'multi-line', 'consistent'], // require curly braces for multiline control statements + + // Require all imported dependencies are actually declared in package.json + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ // Only allow importing devDependencies from: + '**/build-tools/**', // --> Build tools + '**/test/**', // --> Unit tests + ], + optionalDependencies: false, // Disallow importing optional dependencies (those shouldn't be in use in the project) + peerDependencies: false, // Disallow importing peer dependencies (that aren't also direct dependencies) + }, + ], + + // Require all imported libraries actually resolve (!!required for import/no-extraneous-dependencies to work!!) + 'import/no-unresolved': ['error'], + + // Require an ordering on all imports -- unfortunately a different ordering than TSLint used to + // enforce, but there are no compatible ESLint rules as far as I can tell :( + // + // WARNING for now, otherwise this will mess up all open PRs. Make it into an error after a transitionary period. + 'import/order': ['warn', { + groups: ['builtin', 'external'], + alphabetize: { order: 'asc', caseInsensitive: true }, + }], + + // disallow import of deprecated punycode package + 'no-restricted-imports': [ + 'error', { + paths: [ + { + name: 'punycode', + message: `Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation`, + }, + ], + patterns: ['!punycode/'], + }, + ], + + // Cannot import from the same module twice + 'no-duplicate-imports': ['error'], + + // Cannot shadow names + 'no-shadow': ['off'], + '@typescript-eslint/no-shadow': ['error'], + + // Required spacing in property declarations (copied from TSLint, defaults are good) + 'key-spacing': ['error'], + + // Require semicolons + 'semi': ['error', 'always'], + + // Don't unnecessarily quote properties + 'quote-props': ['error', 'consistent-as-needed'], + + // No multiple empty lines + 'no-multiple-empty-lines': ['error'], + + // Max line lengths + 'max-len': ['error', { + code: 150, + ignoreUrls: true, // Most common reason to disable it + ignoreStrings: true, // These are not fantastic but necessary for error messages + ignoreTemplateLiterals: true, + ignoreComments: true, + ignoreRegExpLiterals: true, + }], + + // One of the easiest mistakes to make + '@typescript-eslint/no-floating-promises': ['error'], + + // Make sure that inside try/catch blocks, promises are 'return await'ed + // (must disable the base rule as it can report incorrect errors) + 'no-return-await': 'off', + '@typescript-eslint/return-await': 'error', + + // Don't leave log statements littering the premises! + 'no-console': ['error'], + + // Useless diff results + 'no-trailing-spaces': ['error'], + + // Must use foo.bar instead of foo['bar'] if possible + 'dot-notation': ['error'], + + // Must use 'import' statements (disabled because it doesn't add a lot over no-require-imports) + // '@typescript-eslint/no-var-requires': ['error'], + + // Are you sure | is not a typo for || ? + 'no-bitwise': ['error'], + + // Oh ho ho naming. Everyone's favorite topic! + // FIXME: there's no way to do this properly. The proposed tslint replacement + // works very differently, also checking names in object literals, which we use all over the + // place for configs, mockfs, nodeunit tests, etc. + // + // The maintainer does not want to change behavior. + // https://github.com/typescript-eslint/typescript-eslint/issues/1483 + // + // There is no good replacement for tslint's name checking, currently. We will have to make do + // with jsii's validation. + /* + '@typescript-eslint/naming-convention': ['error', + + // We could maybe be more specific in a number of these but I didn't want to + // spend too much effort. Knock yourself out if you feel like it. + { selector: 'enumMember', format: ['PascalCase', 'UPPER_CASE'] }, + { selector: 'variableLike', format: ['camelCase', 'UPPER_CASE'], leadingUnderscore: 'allow' }, + { selector: 'typeLike', format: ['PascalCase'], leadingUnderscore: 'allow' }, + { selector: 'memberLike', format: ['camelCase', 'PascalCase', 'UPPER_CASE'], leadingUnderscore: 'allow' }, + + // FIXME: there's no way to disable name checking in object literals. Maintainer won't have it + // https://github.com/typescript-eslint/typescript-eslint/issues/1483 + ], + */ + + // Member ordering + '@typescript-eslint/member-ordering': ['error', { + default: [ + 'public-static-field', + 'public-static-method', + 'protected-static-field', + 'protected-static-method', + 'private-static-field', + 'private-static-method', + + 'field', + + // Constructors + 'constructor', // = ["public-constructor", "protected-constructor", "private-constructor"] + + // Methods + 'method', + ], + }], + + // Overrides for plugin:jest/recommended + "jest/expect-expect": "off", + "jest/no-conditional-expect": "off", + "jest/no-done-callback": "off", // Far too many of these in the codebase. + "jest/no-standalone-expect": "off", // nodeunitShim confuses this check. + "jest/valid-expect": "off", // expect from '@aws-cdk/assert' can take a second argument + "jest/valid-title": "off", // A little over-zealous with test('test foo') being an error. + }, +}; diff --git a/packages/awslint/package.json b/packages/awslint/package.json index 0160949c63e04..aa25d4f71a420 100644 --- a/packages/awslint/package.json +++ b/packages/awslint/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "description": "Enforces the AWS Construct Library guidelines", "scripts": { - "build": "tsc -b && npm run lint && chmod +x bin/awslint", + "build": "tsc -b && eslint . --ext=.ts && pkglint && chmod +x bin/awslint", "lint": "eslint . --ext=.ts && pkglint", "test": "echo ok", "watch": "tsc -b -w", @@ -16,18 +16,28 @@ "awslint": "bin/awslint" }, "dependencies": { - "@jsii/spec": "^1.27.0", + "@jsii/spec": "^1.28.0", "camelcase": "^6.2.0", "colors": "^1.4.0", "fs-extra": "^9.1.0", - "jsii-reflect": "^1.27.0", + "jsii-reflect": "^1.28.0", "yargs": "^16.2.0" }, "devDependencies": { "@types/fs-extra": "^8.1.1", + "@types/jest": "^26.0.22", "@types/yargs": "^15.0.13", "pkglint": "0.0.0", - "typescript": "~3.9.9" + "typescript": "~3.9.9", + "@typescript-eslint/eslint-plugin": "^4.21.0", + "@typescript-eslint/parser": "^4.21.0", + "eslint": "^7.23.0", + "eslint-import-resolver-node": "^0.3.4", + "eslint-import-resolver-typescript": "^2.4.0", + "eslint-plugin-cdk": "0.0.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jest": "^24.3.4", + "jest": "^26.6.3" }, "repository": { "type": "git", @@ -50,6 +60,9 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, + "nozem": { + "ostools": ["chmod", "npm"] + }, "publishConfig": { "tag": "latest" } diff --git a/packages/cdk-dasm/package.json b/packages/cdk-dasm/package.json index 6ca3c52326c49..ebd3ef93c0693 100644 --- a/packages/cdk-dasm/package.json +++ b/packages/cdk-dasm/package.json @@ -26,7 +26,7 @@ }, "license": "Apache-2.0", "dependencies": { - "codemaker": "^1.27.0", + "codemaker": "^1.28.0", "yaml": "1.10.2" }, "devDependencies": { diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 0704c80dc12e6..9fbc5c03117e9 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -213,7 +213,7 @@ "@aws-cdk/region-info": "0.0.0", "constructs": "^3.3.69", "fs-extra": "^9.1.0", - "jsii-reflect": "^1.27.0", + "jsii-reflect": "^1.28.0", "jsonschema": "^1.4.0", "yaml": "1.10.2", "yargs": "^16.2.0" @@ -224,7 +224,7 @@ "@types/yaml": "1.9.7", "@types/yargs": "^15.0.13", "jest": "^26.6.3", - "jsii": "^1.27.0" + "jsii": "^1.28.0" }, "keywords": [ "aws", diff --git a/patches/@lerna+package-graph+4.0.0.patch b/patches/@lerna+package-graph+4.0.0.patch new file mode 100644 index 0000000000000..e1677faa27c88 --- /dev/null +++ b/patches/@lerna+package-graph+4.0.0.patch @@ -0,0 +1,23 @@ +diff --git a/node_modules/@lerna/package-graph/index.js b/node_modules/@lerna/package-graph/index.js +index 804aae0..72c22b9 100644 +--- a/node_modules/@lerna/package-graph/index.js ++++ b/node_modules/@lerna/package-graph/index.js +@@ -218,6 +218,8 @@ class PackageGraph extends Map { + /** @type {(PackageGraphNode | CyclicPackageGraphNode)[]} */ + const walkStack = []; + ++ const seen = new Set(); ++ + function visits(baseNode, dependentNode) { + if (nodeToCycle.has(baseNode)) { + return; +@@ -228,6 +230,9 @@ class PackageGraph extends Map { + topLevelDependent = nodeToCycle.get(topLevelDependent); + } + ++ if (seen.has(topLevelDependent)) { return; } ++ seen.add(topLevelDependent); ++ + if ( + topLevelDependent === baseNode || + (topLevelDependent.isCycle && topLevelDependent.has(baseNode.name)) diff --git a/patches/README.md b/patches/README.md new file mode 100644 index 0000000000000..46c0f0415dc2f --- /dev/null +++ b/patches/README.md @@ -0,0 +1,29 @@ +# Vendored modules + +Only for build tools. + +## @lerna/package-graph + +A patched version of the module that drastically improves the time it takes to +check for cycles in the package dependency graph (5 minutes down to 1 second). + +Uses a cache to avoid checking the same node twice. Submitted upstream +as: https://github.com/lerna/lerna/pull/2874 + +Before: + +``` +$ time node_modules/.bin/lerna exec pwd +... +lerna success exec Executed command in 217 packages: "pwd" + 273.24 real 272.27 user 2.04 sys +``` + +After: + +``` +$ time node_modules/.bin/lerna exec pwd +... +lerna success exec Executed command in 219 packages: "pwd" + 1.11 real 0.99 user 0.82 sys +``` diff --git a/tools/cdk-build-tools/bin/cdk-package.ts b/tools/cdk-build-tools/bin/cdk-package.ts index fd9a9aac2aa58..145d7967acb6d 100644 --- a/tools/cdk-build-tools/bin/cdk-package.ts +++ b/tools/cdk-build-tools/bin/cdk-package.ts @@ -3,6 +3,7 @@ import * as fs from 'fs-extra'; import * as yargs from 'yargs'; import * as yarnCling from 'yarn-cling'; import { shell } from '../lib/os'; +import { cdkPackageOptions, isJsii, isPrivate } from '../lib/package-info'; import { Timers } from '../lib/timer'; const timers = new Timers(); @@ -22,26 +23,26 @@ async function main() { }) .argv; - // if this is a jsii package, use jsii-packmak + const options = cdkPackageOptions(); + const outdir = 'dist'; - const pkg = await fs.readJson('package.json'); // if this is a private module, don't package - if (pkg.private) { + if (isPrivate()) { process.stdout.write('No packaging for private modules.\n'); return; } // If we need to shrinkwrap this, do so now. - const packageOptions = pkg['cdk-package'] ?? {}; - if (packageOptions.shrinkWrap) { + if (options.shrinkWrap) { await yarnCling.generateShrinkwrap({ packageJsonFile: 'package.json', outputFile: 'npm-shrinkwrap.json', }); } - if (pkg.jsii) { + // if this is a jsii package, use jsii-packmak + if (isJsii()) { const command = [args['jsii-pacmak'], args.verbose ? '-vvv' : '-v', ...args.targets ? flatMap(args.targets, (target: string) => ['-t', target]) : [], @@ -55,8 +56,13 @@ async function main() { await fs.mkdirp(target); await fs.move(tarball, path.join(target, path.basename(tarball))); } + + if (options.post) { + await shell(options.post, { timers }); + } } + main().then(() => { buildTimer.end(); process.stdout.write(`Package complete. ${timers.display()}\n`); diff --git a/tools/cdk-build-tools/lib/package-info.ts b/tools/cdk-build-tools/lib/package-info.ts index b9d5b7614eb30..2ed8abff6171b 100644 --- a/tools/cdk-build-tools/lib/package-info.ts +++ b/tools/cdk-build-tools/lib/package-info.ts @@ -24,6 +24,13 @@ export function cdkBuildOptions(): CDKBuildOptions { return currentPackageJson()['cdk-build'] || {}; } +/** + * Return the cdk-package options + */ +export function cdkPackageOptions(): CDKPackageOptions { + return currentPackageJson()['cdk-package'] || {}; +} + /** * Whether this is a jsii package */ @@ -31,6 +38,13 @@ export function isJsii(): boolean { return currentPackageJson().jsii !== undefined; } +/** + * Whether this is a private package + */ +export function isPrivate(): boolean { + return currentPackageJson().private !== undefined; +} + export interface File { filename: string; path: string; @@ -160,6 +174,18 @@ export interface CDKBuildOptions { stripDeprecated?: boolean; } +export interface CDKPackageOptions { + /** + * Should this package be shrinkwrap + */ + shrinkWrap?: boolean; + + /* + * An optional command (formatted as a list of strings) to run after packaging + */ + post?: string[]; +} + /** * Return a full path to the config file in this package * diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index dd6523d47e2df..711dcde8a7542 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -39,20 +39,20 @@ "pkglint": "0.0.0" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^4.21.0", - "@typescript-eslint/parser": "^4.21.0", + "@typescript-eslint/eslint-plugin": "^4.22.0", + "@typescript-eslint/parser": "^4.22.0", "awslint": "0.0.0", "colors": "^1.4.0", - "eslint": "^7.23.0", + "eslint": "^7.24.0", "eslint-import-resolver-node": "^0.3.4", "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-cdk": "0.0.0", "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jest": "^24.3.4", + "eslint-plugin-jest": "^24.3.5", "fs-extra": "^9.1.0", "jest": "^26.6.3", - "jsii": "^1.27.0", - "jsii-pacmak": "^1.27.0", + "jsii": "^1.28.0", + "jsii-pacmak": "^1.28.0", "markdownlint-cli": "^0.27.1", "nodeunit": "^0.11.3", "nyc": "^15.1.0", diff --git a/tools/cdk-integ-tools/bin/cdk-integ-assert.ts b/tools/cdk-integ-tools/bin/cdk-integ-assert.ts index 2fd262a933792..3c5cb049cf0d0 100644 --- a/tools/cdk-integ-tools/bin/cdk-integ-assert.ts +++ b/tools/cdk-integ-tools/bin/cdk-integ-assert.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node // Verify that all integration tests still match their expected output -import { canonicalizeTemplate } from '@aws-cdk/assert'; +import { canonicalizeTemplate } from '@aws-cdk/assert-internal'; import { diffTemplate, formatDifferences } from '@aws-cdk/cloudformation-diff'; import { DEFAULT_SYNTH_OPTIONS, IntegrationTests } from '../lib/integ-helpers'; diff --git a/tools/cdk-integ-tools/lib/integ-helpers.ts b/tools/cdk-integ-tools/lib/integ-helpers.ts index bf65e8cbb8b9a..5aabc99210663 100644 --- a/tools/cdk-integ-tools/lib/integ-helpers.ts +++ b/tools/cdk-integ-tools/lib/integ-helpers.ts @@ -1,9 +1,8 @@ // Helper functions for integration tests import { spawnSync } from 'child_process'; import * as path from 'path'; -import { FUTURE_FLAGS } from '@aws-cdk/cx-api'; +import { AVAILABILITY_ZONE_FALLBACK_CONTEXT_KEY, FUTURE_FLAGS } from '@aws-cdk/cx-api'; import * as fs from 'fs-extra'; -import { AVAILABILITY_ZONE_FALLBACK_CONTEXT_KEY } from '../../../packages/@aws-cdk/cx-api/lib'; const CDK_OUTDIR = 'cdk-integ.out'; diff --git a/tools/cdk-integ-tools/package.json b/tools/cdk-integ-tools/package.json index bb9fbacb970b0..4526dcb65e6b2 100644 --- a/tools/cdk-integ-tools/package.json +++ b/tools/cdk-integ-tools/package.json @@ -37,7 +37,7 @@ "dependencies": { "@aws-cdk/cloudformation-diff": "0.0.0", "@aws-cdk/cx-api": "0.0.0", - "@aws-cdk/assert": "0.0.0", + "@aws-cdk/assert-internal": "0.0.0", "aws-cdk": "0.0.0", "fs-extra": "^9.1.0", "yargs": "^16.2.0" @@ -51,7 +51,7 @@ "node": ">= 10.13.0 <13 || >=13.7.0" }, "peerDependencies": { - "@aws-cdk/assert": "0.0.0" + "@aws-cdk/assert-internal": "0.0.0" }, "ubergen": { "exclude": true diff --git a/tools/cfn2ts/package.json b/tools/cfn2ts/package.json index 3a799b44659c6..82e397b74ce62 100644 --- a/tools/cfn2ts/package.json +++ b/tools/cfn2ts/package.json @@ -30,7 +30,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-cdk/cfnspec": "0.0.0", - "codemaker": "^1.27.0", + "codemaker": "^1.28.0", "fast-json-patch": "^3.0.0-1", "fs-extra": "^9.1.0", "yargs": "^16.2.0" diff --git a/tools/eslint-plugin-cdk/package.json b/tools/eslint-plugin-cdk/package.json index 83f238eb4f1f2..6392ecdae440f 100644 --- a/tools/eslint-plugin-cdk/package.json +++ b/tools/eslint-plugin-cdk/package.json @@ -12,7 +12,7 @@ "build+test": "npm run build && npm test" }, "devDependencies": { - "@types/eslint": "^7.2.8", + "@types/eslint": "^7.2.9", "@types/fs-extra": "^8.1.1", "@types/jest": "^26.0.22", "@types/node": "^10.17.56", @@ -21,8 +21,8 @@ "typescript": "~3.9.9" }, "dependencies": { - "@typescript-eslint/parser": "^4.21.0", - "eslint": "^7.23.0", + "@typescript-eslint/parser": "^4.22.0", + "eslint": "^7.24.0", "fs-extra": "^9.1.0" }, "jest": { diff --git a/tools/pkglint/.eslintrc.js b/tools/pkglint/.eslintrc.js index 61dd8dd001f63..941ceb58503ad 100644 --- a/tools/pkglint/.eslintrc.js +++ b/tools/pkglint/.eslintrc.js @@ -1,3 +1,202 @@ -const baseConfig = require('cdk-build-tools/config/eslintrc'); -baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; -module.exports = baseConfig; +// This cannot reference the build rules from cdk-build-tools as this +// package is itself used by cdk-build-tools. +module.exports = { + env: { + jest: true, + node: true, + }, + plugins: [ + '@typescript-eslint', + 'import', + 'cdk', + 'jest', + ], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: '2018', + sourceType: 'module', + project: './tsconfig.json', + }, + extends: [ + 'plugin:import/typescript', + 'plugin:jest/recommended', + ], + settings: { + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + 'import/resolver': { + node: {}, + typescript: { + directory: './tsconfig.json', + }, + }, + }, + ignorePatterns: ['*.js', '*.d.ts', 'node_modules/', '*.generated.ts'], + rules: { + 'cdk/construct-import-order': [ 'error' ], + 'cdk/no-core-construct': [ 'error' ], + 'cdk/no-qualified-construct': [ 'error' ], + // Require use of the `import { foo } from 'bar';` form instead of `import foo = require('bar');` + '@typescript-eslint/no-require-imports': ['error'], + '@typescript-eslint/indent': ['error', 2], + + // Style + 'quotes': ['error', 'single', { avoidEscape: true }], + 'comma-dangle': ['error', 'always-multiline'], // ensures clean diffs, see https://medium.com/@nikgraf/why-you-should-enforce-dangling-commas-for-multiline-statements-d034c98e36f8 + 'comma-spacing': ['error', { before: false, after: true }], // space after, no space before + 'no-multi-spaces': ['error', { ignoreEOLComments: false }], // no multi spaces + 'array-bracket-spacing': ['error', 'never'], // [1, 2, 3] + 'array-bracket-newline': ['error', 'consistent'], // enforce consistent line breaks between brackets + 'object-curly-spacing': ['error', 'always'], // { key: 'value' } + 'object-curly-newline': ['error', { multiline: true, consistent: true }], // enforce consistent line breaks between braces + 'object-property-newline': ['error', { allowAllPropertiesOnSameLine: true }], // enforce "same line" or "multiple line" on object properties + 'keyword-spacing': ['error'], // require a space before & after keywords + 'brace-style': ['error', '1tbs', { allowSingleLine: true }], // enforce one true brace style + 'space-before-blocks': 'error', // require space before blocks + 'curly': ['error', 'multi-line', 'consistent'], // require curly braces for multiline control statements + + // Require all imported dependencies are actually declared in package.json + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ // Only allow importing devDependencies from: + '**/build-tools/**', // --> Build tools + '**/test/**', // --> Unit tests + ], + optionalDependencies: false, // Disallow importing optional dependencies (those shouldn't be in use in the project) + peerDependencies: false, // Disallow importing peer dependencies (that aren't also direct dependencies) + }, + ], + + // Require all imported libraries actually resolve (!!required for import/no-extraneous-dependencies to work!!) + 'import/no-unresolved': ['error'], + + // Require an ordering on all imports -- unfortunately a different ordering than TSLint used to + // enforce, but there are no compatible ESLint rules as far as I can tell :( + // + // WARNING for now, otherwise this will mess up all open PRs. Make it into an error after a transitionary period. + 'import/order': ['warn', { + groups: ['builtin', 'external'], + alphabetize: { order: 'asc', caseInsensitive: true }, + }], + + // disallow import of deprecated punycode package + 'no-restricted-imports': [ + 'error', { + paths: [ + { + name: 'punycode', + message: `Package 'punycode' has to be imported with trailing slash, see warning in https://github.com/bestiejs/punycode.js#installation`, + }, + ], + patterns: ['!punycode/'], + }, + ], + + // Cannot import from the same module twice + 'no-duplicate-imports': ['error'], + + // Cannot shadow names + 'no-shadow': ['off'], + '@typescript-eslint/no-shadow': ['error'], + + // Required spacing in property declarations (copied from TSLint, defaults are good) + 'key-spacing': ['error'], + + // Require semicolons + 'semi': ['error', 'always'], + + // Don't unnecessarily quote properties + 'quote-props': ['error', 'consistent-as-needed'], + + // No multiple empty lines + 'no-multiple-empty-lines': ['error'], + + // Max line lengths + 'max-len': ['error', { + code: 150, + ignoreUrls: true, // Most common reason to disable it + ignoreStrings: true, // These are not fantastic but necessary for error messages + ignoreTemplateLiterals: true, + ignoreComments: true, + ignoreRegExpLiterals: true, + }], + + // One of the easiest mistakes to make + '@typescript-eslint/no-floating-promises': ['error'], + + // Make sure that inside try/catch blocks, promises are 'return await'ed + // (must disable the base rule as it can report incorrect errors) + 'no-return-await': 'off', + '@typescript-eslint/return-await': 'error', + + // Don't leave log statements littering the premises! + 'no-console': ['error'], + + // Useless diff results + 'no-trailing-spaces': ['error'], + + // Must use foo.bar instead of foo['bar'] if possible + 'dot-notation': ['error'], + + // Must use 'import' statements (disabled because it doesn't add a lot over no-require-imports) + // '@typescript-eslint/no-var-requires': ['error'], + + // Are you sure | is not a typo for || ? + 'no-bitwise': ['error'], + + // Oh ho ho naming. Everyone's favorite topic! + // FIXME: there's no way to do this properly. The proposed tslint replacement + // works very differently, also checking names in object literals, which we use all over the + // place for configs, mockfs, nodeunit tests, etc. + // + // The maintainer does not want to change behavior. + // https://github.com/typescript-eslint/typescript-eslint/issues/1483 + // + // There is no good replacement for tslint's name checking, currently. We will have to make do + // with jsii's validation. + /* + '@typescript-eslint/naming-convention': ['error', + + // We could maybe be more specific in a number of these but I didn't want to + // spend too much effort. Knock yourself out if you feel like it. + { selector: 'enumMember', format: ['PascalCase', 'UPPER_CASE'] }, + { selector: 'variableLike', format: ['camelCase', 'UPPER_CASE'], leadingUnderscore: 'allow' }, + { selector: 'typeLike', format: ['PascalCase'], leadingUnderscore: 'allow' }, + { selector: 'memberLike', format: ['camelCase', 'PascalCase', 'UPPER_CASE'], leadingUnderscore: 'allow' }, + + // FIXME: there's no way to disable name checking in object literals. Maintainer won't have it + // https://github.com/typescript-eslint/typescript-eslint/issues/1483 + ], + */ + + // Member ordering + '@typescript-eslint/member-ordering': ['error', { + default: [ + 'public-static-field', + 'public-static-method', + 'protected-static-field', + 'protected-static-method', + 'private-static-field', + 'private-static-method', + + 'field', + + // Constructors + 'constructor', // = ["public-constructor", "protected-constructor", "private-constructor"] + + // Methods + 'method', + ], + }], + + // Overrides for plugin:jest/recommended + "jest/expect-expect": "off", + "jest/no-conditional-expect": "off", + "jest/no-done-callback": "off", // Far too many of these in the codebase. + "jest/no-standalone-expect": "off", // nodeunitShim confuses this check. + "jest/valid-expect": "off", // expect from '@aws-cdk/assert' can take a second argument + "jest/valid-title": "off", // A little over-zealous with test('test foo') being an error. + }, +}; diff --git a/tools/pkglint/package.json b/tools/pkglint/package.json index 2924381d124c0..75130d0ea29f5 100644 --- a/tools/pkglint/package.json +++ b/tools/pkglint/package.json @@ -36,12 +36,24 @@ "license": "Apache-2.0", "devDependencies": { "@types/fs-extra": "^8.1.1", + "@types/glob": "^7.1.3", + "@types/jest": "^26.0.22", "@types/semver": "^7.3.4", "@types/yargs": "^15.0.13", + "@typescript-eslint/eslint-plugin": "^4.21.0", + "@typescript-eslint/parser": "^4.21.0", + "eslint": "^7.23.0", + "eslint-import-resolver-node": "^0.3.4", + "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-cdk": "0.0.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jest": "^24.3.4", "jest": "^26.6.3", "typescript": "~3.9.9" }, + "nozem": { + "ostools": ["chmod"] + }, "dependencies": { "case": "^1.6.3", "colors": "^1.4.0", diff --git a/tools/ubergen/bin/ubergen.ts b/tools/ubergen/bin/ubergen.ts index 62d1bd2a0bb9b..487ac4c7340e2 100644 --- a/tools/ubergen/bin/ubergen.ts +++ b/tools/ubergen/bin/ubergen.ts @@ -1,7 +1,7 @@ import * as console from 'console'; -import * as os from 'os'; import * as path from 'path'; import * as process from 'process'; +import cfn2ts from 'cfn2ts'; import * as fs from 'fs-extra'; import * as ts from 'typescript'; @@ -59,9 +59,14 @@ interface PackageJson { readonly name: string; readonly types: string; readonly version: string; + readonly stability: string; readonly [key: string]: unknown; + readonly 'cdk-build': { + readonly cloudformation: string[] | string; + }; readonly ubergen?: { readonly deprecatedPackages?: readonly string[]; + readonly excludeExperimentalModules?: boolean; }; } @@ -73,7 +78,7 @@ function findWorkspacePath(): string { return _findRootPath(process.cwd()); function _findRootPath(part: string): string { - if (process.cwd() === os.homedir()) { + if (part === path.resolve(part, '..')) { throw new Error('couldn\'t find a \'lerna.json\' file when walking up the directory tree, are you in a aws-cdk project?'); } @@ -214,13 +219,20 @@ async function verifyDependencies(packageJson: any, libraries: readonly LibraryR async function prepareSourceFiles(libraries: readonly LibraryReference[], packageJson: PackageJson) { console.log('📝 Preparing source files...'); + if (packageJson.ubergen?.excludeExperimentalModules) { + console.log('\t 👩🏻‍🔬 \'excludeExperimentalModules\' enabled. Regenerating all experimental modules as L1s using cfn2ts...'); + } + await fs.remove(LIB_ROOT); const indexStatements = new Array(); for (const library of libraries) { const libDir = path.join(LIB_ROOT, library.shortName); - await transformPackage(library, packageJson, libDir, libraries); + const copied = await transformPackage(library, packageJson, libDir, libraries); + if (!copied) { + continue; + } if (library.shortName === 'core') { indexStatements.push(`export * from './${library.shortName}';`); } else { @@ -260,7 +272,28 @@ async function transformPackage( ) { await fs.mkdirp(destination); - await copyOrTransformFiles(library.root, destination, allLibraries, uberPackageJson); + if (uberPackageJson.ubergen?.excludeExperimentalModules && library.packageJson.stability === 'experimental') { + // when stripExperimental is enabled, we only want to add the L1s of experimental modules. + let cfnScopes = library.packageJson['cdk-build'].cloudformation; + + if (cfnScopes === undefined) { + return false; + } + cfnScopes = Array.isArray(cfnScopes) ? cfnScopes : [cfnScopes]; + + const destinationLib = path.join(destination, 'lib'); + await fs.mkdirp(destinationLib); + await cfn2ts(cfnScopes, destinationLib); + // create a lib/index.ts which only exports the generated files + fs.writeFileSync(path.join(destinationLib, 'index.ts'), + /// logic copied from `create-missing-libraries.ts` + cfnScopes.map(s => (s === 'AWS::Serverless' ? 'AWS::SAM' : s).split('::')[1].toLocaleLowerCase()) + .map(s => `export * from './${s}.generated';`) + .join('\n')); + await copyOrTransformFiles(destination, destination, allLibraries, uberPackageJson); + } else { + await copyOrTransformFiles(library.root, destination, allLibraries, uberPackageJson); + } await fs.writeFile( path.join(destination, 'index.ts'), @@ -284,6 +317,7 @@ async function transformPackage( { encoding: 'utf8' }, ); } + return true; } function transformTargets(monoConfig: PackageJson['jsii']['targets'], targets: PackageJson['jsii']['targets']): PackageJson['jsii']['targets'] { @@ -360,6 +394,12 @@ async function copyOrTransformFiles(from: string, to: string, libraries: readonl : fqn.replace('@aws-cdk', uberPackageJson.name); } await fs.writeJson(destination, cfnTypes2Classes, { spaces: 2 }); + } else if (name === 'README.md') { + return fs.writeFile( + destination, + await rewriteReadmeImports(source), + { encoding: 'utf8' }, + ); } else { return fs.copyFile(source, destination); } @@ -368,6 +408,37 @@ async function copyOrTransformFiles(from: string, to: string, libraries: readonl await Promise.all(promises); } +/** + * Rewrites the imports in README.md from v1 ('@aws-cdk/...') to v2 ('aws-cdk-lib'). + * 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 { + const readmeOriginal = await fs.readFile(fromFile, { encoding: 'utf8' }); + return readmeOriginal + // import * as s3 from '@aws-cdk/aws-s3' + .replace(/^(\s*)import \* as (.*) from (?:'|")@aws-cdk\/(.*)(?:'|");(\s*)$/gm, rewriteCdkImports) + // import s3 = require('@aws-cdk/aws-s3') + .replace(/^(\s*)import (.*) = require\((?:'|")@aws-cdk\/(.*)(?:'|")\);(\s*)$/gm, rewriteCdkImports) + // import { Bucket } from '@aws-cdk/aws-s3' + .replace(/^(\s*)import ({.*}) from (?:'|")@aws-cdk\/(.*)(?:'|");(\s*)$/gm, rewriteCdkTypeImports); + + 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}`; + } else { + return `${prefix}import { ${module.replace(/-/g, '_')} as ${alias} } from 'aws-cdk-lib';${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}`; + } else { + return `${prefix}import ${types} from 'aws-cdk-lib/${module}';${suffix}`; + } + } +} + async function rewriteImports(fromFile: string, targetDir: string, libraries: readonly LibraryReference[]): Promise { const sourceFile = ts.createSourceFile( fromFile, diff --git a/tools/ubergen/package.json b/tools/ubergen/package.json index 65c88331540a8..93afb53f2bd28 100644 --- a/tools/ubergen/package.json +++ b/tools/ubergen/package.json @@ -33,7 +33,8 @@ }, "dependencies": { "fs-extra": "^9.1.0", - "typescript": "~3.9.9" + "typescript": "~3.9.9", + "cfn2ts": "0.0.0" }, "keywords": [ "aws", diff --git a/version.v1.json b/version.v1.json index 3a294301784a6..f27bd5db89e89 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.97.0" + "version": "1.100.0" } diff --git a/yarn.lock b/yarn.lock index 81510b4448e3d..535425bd3644f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -513,10 +513,10 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@jsii/spec@^1.27.0": - version "1.27.0" - resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.27.0.tgz#fd3a9c3c5074a79f91e80317c0d99d8f0db67a21" - integrity sha512-mdfSlcYY9qI3kI0rK1dAN13BkHtOffhFXzOwtuZvxjhz2+8hx6DpW5nqHAWCrq+ZQuPAPxiMOVXBsA58PZ9Ycg== +"@jsii/spec@^1.28.0": + version "1.28.0" + resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.28.0.tgz#47db1102fc0291dbffffb3adb7f8ee0671e15ef3" + integrity sha512-5mcupuCCXyhZwNmX/RDBn3WUYtd0oPXEDa3E+qOSjT30vaO8u9ZQ+mxwl4qsecx3m51LhXKnR1C9U9t4VlAmqA== dependencies: jsonschema "^1.4.0" @@ -1449,10 +1449,10 @@ dependencies: "@types/glob" "*" -"@types/aws-lambda@^8.10.73": - version "8.10.73" - resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.73.tgz#77773c9accb2cec26fcb7c6b510a555805604a53" - integrity sha512-P+a6TRQbRnVQOIjWkmw6F23wiJcF+4Uniasbzx7NAXjLQCVGx/Z4VoMfit81/pxlmcXNxAMGuYPugn6CrJLilQ== +"@types/aws-lambda@^8.10.75": + version "8.10.75" + resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.75.tgz#93b4e688db8a45755018561a3212e7766c0fef57" + integrity sha512-orOKSsIVUMsAbKgbSX2ST3FwQt9pxinHVCAIAVl4SmmTxmki2Gu+cGqobMD3eYwDV5FV0YNtaXyxnvE9pLrKTw== "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": version "7.1.13" @@ -1487,10 +1487,10 @@ dependencies: "@babel/types" "^7.3.0" -"@types/eslint@^7.2.8": - version "7.2.8" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.8.tgz#45cd802380fcc352e5680e1781d43c50916f12ee" - integrity sha512-RTKvBsfz0T8CKOGZMfuluDNyMFHnu5lvNr4hWEsQeHXH6FcmIDIozOyWMh36nLGMwVd5UFNXC2xztA8lln22MQ== +"@types/eslint@^7.2.9": + version "7.2.9" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.9.tgz#5d26eadbb6d04a225967176399a18eff622da982" + integrity sha512-SdAAXZNvWfhtf3X3y1cbbCZhP3xyPh7mfTvzV6CgfWc/ZhiHpyr9bVroe2/RCHIf7gczaNcprhaBLsx0CCJHQA== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -1727,13 +1727,13 @@ resolved "https://registry.yarnpkg.com/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.4.tgz#445251eb00bd9c1e751f82c7c6bf4f714edfd464" integrity sha512-/emrKCfQMQmFCqRqqBJ0JueHBT06jBRM3e8OgnvDUcvuExONujIk2hFA5dNsN9Nt41ljGVDdChvCydATZ+KOZw== -"@typescript-eslint/eslint-plugin@^4.21.0": - version "4.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.21.0.tgz#3fce2bfa76d95c00ac4f33dff369cb593aab8878" - integrity sha512-FPUyCPKZbVGexmbCFI3EQHzCZdy2/5f+jv6k2EDljGdXSRc0cKvbndd2nHZkSLqCNOPk0jB6lGzwIkglXcYVsQ== +"@typescript-eslint/eslint-plugin@^4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.22.0.tgz#3d5f29bb59e61a9dba1513d491b059e536e16dbc" + integrity sha512-U8SP9VOs275iDXaL08Ln1Fa/wLXfj5aTr/1c0t0j6CdbOnxh+TruXu1p4I0NAvdPBQgoPjHsgKn28mOi0FzfoA== dependencies: - "@typescript-eslint/experimental-utils" "4.21.0" - "@typescript-eslint/scope-manager" "4.21.0" + "@typescript-eslint/experimental-utils" "4.22.0" + "@typescript-eslint/scope-manager" "4.22.0" debug "^4.1.1" functional-red-black-tree "^1.0.1" lodash "^4.17.15" @@ -1741,15 +1741,15 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@4.21.0": - version "4.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.21.0.tgz#0b0bb7c15d379140a660c003bdbafa71ae9134b6" - integrity sha512-cEbgosW/tUFvKmkg3cU7LBoZhvUs+ZPVM9alb25XvR0dal4qHL3SiUqHNrzoWSxaXA9gsifrYrS1xdDV6w/gIA== +"@typescript-eslint/experimental-utils@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.22.0.tgz#68765167cca531178e7b650a53456e6e0bef3b1f" + integrity sha512-xJXHHl6TuAxB5AWiVrGhvbGL8/hbiCQ8FiWwObO3r0fnvBdrbWEDy1hlvGQOAWc6qsCWuWMKdVWlLAEMpxnddg== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.21.0" - "@typescript-eslint/types" "4.21.0" - "@typescript-eslint/typescript-estree" "4.21.0" + "@typescript-eslint/scope-manager" "4.22.0" + "@typescript-eslint/types" "4.22.0" + "@typescript-eslint/typescript-estree" "4.22.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" @@ -1765,14 +1765,14 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^4.21.0": - version "4.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.21.0.tgz#a227fc2af4001668c3e3f7415d4feee5093894c1" - integrity sha512-eyNf7QmE5O/l1smaQgN0Lj2M/1jOuNg2NrBm1dqqQN0sVngTLyw8tdCbih96ixlhbF1oINoN8fDCyEH9SjLeIA== +"@typescript-eslint/parser@^4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.22.0.tgz#e1637327fcf796c641fe55f73530e90b16ac8fe8" + integrity sha512-z/bGdBJJZJN76nvAY9DkJANYgK3nlRstRRi74WHm3jjgf2I8AglrSY+6l7ogxOmn55YJ6oKZCLLy+6PW70z15Q== dependencies: - "@typescript-eslint/scope-manager" "4.21.0" - "@typescript-eslint/types" "4.21.0" - "@typescript-eslint/typescript-estree" "4.21.0" + "@typescript-eslint/scope-manager" "4.22.0" + "@typescript-eslint/types" "4.22.0" + "@typescript-eslint/typescript-estree" "4.22.0" debug "^4.1.1" "@typescript-eslint/scope-manager@4.18.0": @@ -1783,23 +1783,23 @@ "@typescript-eslint/types" "4.18.0" "@typescript-eslint/visitor-keys" "4.18.0" -"@typescript-eslint/scope-manager@4.21.0": - version "4.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.21.0.tgz#c81b661c4b8af1ec0c010d847a8f9ab76ab95b4d" - integrity sha512-kfOjF0w1Ix7+a5T1knOw00f7uAP9Gx44+OEsNQi0PvvTPLYeXJlsCJ4tYnDj5PQEYfpcgOH5yBlw7K+UEI9Agw== +"@typescript-eslint/scope-manager@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.22.0.tgz#ed411545e61161a8d702e703a4b7d96ec065b09a" + integrity sha512-OcCO7LTdk6ukawUM40wo61WdeoA7NM/zaoq1/2cs13M7GyiF+T4rxuA4xM+6LeHWjWbss7hkGXjFDRcKD4O04Q== dependencies: - "@typescript-eslint/types" "4.21.0" - "@typescript-eslint/visitor-keys" "4.21.0" + "@typescript-eslint/types" "4.22.0" + "@typescript-eslint/visitor-keys" "4.22.0" "@typescript-eslint/types@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.18.0.tgz#bebe323f81f2a7e2e320fac9415e60856267584a" integrity sha512-/BRociARpj5E+9yQ7cwCF/SNOWwXJ3qhjurMuK2hIFUbr9vTuDeu476Zpu+ptxY2kSxUHDGLLKy+qGq2sOg37A== -"@typescript-eslint/types@4.21.0": - version "4.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.21.0.tgz#abdc3463bda5d31156984fa5bc316789c960edef" - integrity sha512-+OQaupjGVVc8iXbt6M1oZMwyKQNehAfLYJJ3SdvnofK2qcjfor9pEM62rVjBknhowTkh+2HF+/KdRAc/wGBN2w== +"@typescript-eslint/types@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.22.0.tgz#0ca6fde5b68daf6dba133f30959cc0688c8dd0b6" + integrity sha512-sW/BiXmmyMqDPO2kpOhSy2Py5w6KvRRsKZnV0c4+0nr4GIcedJwXAq+RHNK4lLVEZAJYFltnnk1tJSlbeS9lYA== "@typescript-eslint/typescript-estree@4.18.0": version "4.18.0" @@ -1814,13 +1814,13 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@4.21.0": - version "4.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.21.0.tgz#3817bd91857beeaeff90f69f1f112ea58d350b0a" - integrity sha512-ZD3M7yLaVGVYLw4nkkoGKumb7Rog7QID9YOWobFDMQKNl+vPxqVIW/uDk+MDeGc+OHcoG2nJ2HphwiPNajKw3w== +"@typescript-eslint/typescript-estree@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.22.0.tgz#b5d95d6d366ff3b72f5168c75775a3e46250d05c" + integrity sha512-TkIFeu5JEeSs5ze/4NID+PIcVjgoU3cUQUIZnH3Sb1cEn1lBo7StSV5bwPuJQuoxKXlzAObjYTilOEKRuhR5yg== dependencies: - "@typescript-eslint/types" "4.21.0" - "@typescript-eslint/visitor-keys" "4.21.0" + "@typescript-eslint/types" "4.22.0" + "@typescript-eslint/visitor-keys" "4.22.0" debug "^4.1.1" globby "^11.0.1" is-glob "^4.0.1" @@ -1835,12 +1835,12 @@ "@typescript-eslint/types" "4.18.0" eslint-visitor-keys "^2.0.0" -"@typescript-eslint/visitor-keys@4.21.0": - version "4.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.21.0.tgz#990a9acdc124331f5863c2cf21c88ba65233cd8d" - integrity sha512-dH22dROWGi5Z6p+Igc8bLVLmwy7vEe8r+8c+raPQU0LxgogPUrRAtRGtvBWmlr9waTu3n+QLt/qrS/hWzk1x5w== +"@typescript-eslint/visitor-keys@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.22.0.tgz#169dae26d3c122935da7528c839f42a8a42f6e47" + integrity sha512-nnMu4F+s4o0sll6cBSsTeVsT4cwxB7zECK3dFxzEjPBii9xLpq4yqqsy/FU5zMfan6G60DKZSCXAa3sHJZrcYw== dependencies: - "@typescript-eslint/types" "4.21.0" + "@typescript-eslint/types" "4.22.0" eslint-visitor-keys "^2.0.0" "@yarnpkg/lockfile@^1.1.0": @@ -2798,10 +2798,10 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= -codemaker@^1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/codemaker/-/codemaker-1.27.0.tgz#1bbe2b05ddc9d927f5fb8f286611885600d9198e" - integrity sha512-W5r3XLxBG2a33M3g3Sg9mOU5wPbw6hz14GfmeQsKlWoSCx8Y3CCxY8ogbh77/K34epqYh43ydybI8e7UVgD/tQ== +codemaker@^1.28.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/codemaker/-/codemaker-1.28.0.tgz#21237f9240ab05ecca6c65da48141b8b752539b8" + integrity sha512-TlpvV3q/68cZk7aljYW6b/5EvyB4uw523xJISTATrCrQu/UTA79/mxpA2ug8uhPcJoGYcfWXH4BHVVLNIuEtrg== dependencies: camelcase "^6.2.0" decamelize "^5.0.0" @@ -3211,7 +3211,7 @@ cross-spawn@^4: lru-cache "^4.0.1" which "^1.2.9" -cross-spawn@^6.0.0: +cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -3742,10 +3742,10 @@ es6-error@^4.0.1: resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -esbuild@^0.11.6: - version "0.11.6" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.11.6.tgz#20961309c4cfed00b71027e18806150358d0cbb0" - integrity sha512-L+nKW9ftVS/N2CVJMR9YmXHbkm+vHzlNYuo09rzipQhF7dYNvRLfWoEPSDRTl10and4owFBV9rJ2CTFNtLIOiw== +esbuild@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.11.10.tgz#f5d39e4d9cc130b78d751664fef1b663240f5545" + integrity sha512-XvGbf+UreVFA24Tlk6sNOqNcvF2z49XAZt4E7A4H80+yqn944QOLTTxaU0lkdYNtZKFiITNea+VxmtrfjvnLPA== escalade@^3.1.1: version "3.1.1" @@ -3838,10 +3838,10 @@ eslint-plugin-import@^2.22.1: resolve "^1.17.0" tsconfig-paths "^3.9.0" -eslint-plugin-jest@^24.3.4: - version "24.3.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.3.4.tgz#6d90c3554de0302e879603dd6405474c98849f19" - integrity sha512-3n5oY1+fictanuFkTWPwSlehugBTAgwLnYLFsCllzE3Pl1BwywHl5fL0HFxmMjoQY8xhUDk8uAWc3S4JOHGh3A== +eslint-plugin-jest@^24.3.5: + version "24.3.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.3.5.tgz#71f0b580f87915695c286c3f0eb88cf23664d044" + integrity sha512-XG4rtxYDuJykuqhsOqokYIR84/C8pRihRtEpVskYLbIIKGwPNW2ySxdctuVzETZE+MbF/e7wmsnbNVpzM0rDug== dependencies: "@typescript-eslint/experimental-utils" "^4.0.1" @@ -3897,10 +3897,10 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== -eslint@^7.23.0: - version "7.23.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.23.0.tgz#8d029d252f6e8cf45894b4bee08f5493f8e94325" - integrity sha512-kqvNVbdkjzpFy0XOszNwjkKzZ+6TcwCQ/h+ozlcIWwaimBBuhlQ4nN6kbiM2L+OjDcznkTJxzYfRFH92sx4a0Q== +eslint@^7.24.0: + version "7.24.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.24.0.tgz#2e44fa62d93892bfdb100521f17345ba54b8513a" + integrity sha512-k9gaHeHiFmGCDQ2rEfvULlSLruz6tgfA8DEn+rY9/oYPFFTlz55mM/Q/Rij1b2Y42jwZiK3lXvNTw6w6TXzcKQ== dependencies: "@babel/code-frame" "7.12.11" "@eslint/eslintrc" "^0.4.0" @@ -4309,6 +4309,13 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" +find-yarn-workspace-root@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== + dependencies: + micromatch "^4.0.2" + flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -4401,6 +4408,15 @@ fs-exists-cached@^1.0.0: resolved "https://registry.yarnpkg.com/fs-exists-cached/-/fs-exists-cached-1.0.0.tgz#cf25554ca050dc49ae6656b41de42258989dcbce" integrity sha1-zyVVTKBQ3EmuZla0HeQiWJidy84= +fs-extra@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -5355,7 +5371,7 @@ is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-wsl@^2.2.0: +is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -5967,65 +5983,65 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -jsii-diff@^1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/jsii-diff/-/jsii-diff-1.27.0.tgz#48da3594e784a6badb26e01ae3be017883653465" - integrity sha512-CBg3ZwT63iPLdfy8nNY++tc8xuBm+Zs1dDjnK2/0z3yApOAmYIfi/5BplPOoSfzCNAejtvIxLEgScl9BZktXXA== +jsii-diff@^1.28.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/jsii-diff/-/jsii-diff-1.28.0.tgz#16f889559f3d2679154f93eae004704db1e6c62d" + integrity sha512-SJUzVY7sXg3esBeuvj3tQGzeRYEkpmrqbC1lIHd8VdXpPybYWU958z3hlJJkvaM1AomQpiMyXK6Ev+2XOp741g== dependencies: - "@jsii/spec" "^1.27.0" + "@jsii/spec" "^1.28.0" fs-extra "^9.1.0" - jsii-reflect "^1.27.0" + jsii-reflect "^1.28.0" log4js "^6.3.0" typescript "~3.9.9" yargs "^16.2.0" -jsii-pacmak@^1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/jsii-pacmak/-/jsii-pacmak-1.27.0.tgz#d6d28f5e9208d7ca338edfeba95ba25d8f552bde" - integrity sha512-K19kyUvFKpg6l5VaTkwFz4pgnrOR/vH69iqE6YWSJVY1i3S7dTA2mhG+dVbeB96MMnx7IUno0iKT3br/aWCtew== +jsii-pacmak@^1.28.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/jsii-pacmak/-/jsii-pacmak-1.28.0.tgz#3d20c27a91266cf740a1ff229b29270785d2bcfc" + integrity sha512-QAW8rq7M9rA/QSXwaJKMVpttkNW/BJgE9GT6i9UahobQMkmp+zsXCJUENeRg2mndLqX0DDyxO1in/fuIeCeR3A== dependencies: - "@jsii/spec" "^1.27.0" + "@jsii/spec" "^1.28.0" clone "^2.1.2" - codemaker "^1.27.0" + codemaker "^1.28.0" commonmark "^0.29.3" escape-string-regexp "^4.0.0" fs-extra "^9.1.0" - jsii-reflect "^1.27.0" - jsii-rosetta "^1.27.0" + jsii-reflect "^1.28.0" + jsii-rosetta "^1.28.0" semver "^7.3.5" spdx-license-list "^6.4.0" xmlbuilder "^15.1.1" yargs "^16.2.0" -jsii-reflect@^1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/jsii-reflect/-/jsii-reflect-1.27.0.tgz#481edae2cbc497d4125207c37b964ba64c290dbc" - integrity sha512-+E2VhlDxvEcsBj8LdBaJ0OFS6+mDaWbDNmUSZ7UPIJxDPQzRFMGlMUyymz8J0f3Y2UWKiXgLvBhzEvF9UA4fCQ== +jsii-reflect@^1.28.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/jsii-reflect/-/jsii-reflect-1.28.0.tgz#d03276499702115ff0582f82ede7bd40f1a4b6b9" + integrity sha512-jFu9dUy5D0PrxVnaDilb50agbSr0wZRya6StwHyw8Wly3ruzS8uuSB1aWmEwN371m5ewDD4m9nPEQ9zMmKFvMQ== dependencies: - "@jsii/spec" "^1.27.0" + "@jsii/spec" "^1.28.0" colors "^1.4.0" fs-extra "^9.1.0" - oo-ascii-tree "^1.27.0" + oo-ascii-tree "^1.28.0" yargs "^16.2.0" -jsii-rosetta@^1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/jsii-rosetta/-/jsii-rosetta-1.27.0.tgz#13ff711cff331b1d62081d8015ea24f730d6bbf7" - integrity sha512-swQz1lsB5k2v2euJfxYOtRy+SHnYS9WJ2XRkstY8/j0xMFOLNNXoWwSDrK97h3qis+yUuCHZlI6DNQzQh1NutA== +jsii-rosetta@^1.28.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/jsii-rosetta/-/jsii-rosetta-1.28.0.tgz#89625e817d4bf50fe51924177eb78bcb08908193" + integrity sha512-lttDhXiBuWaN0DwsWakD5o7GxyVP8yMCRvpmpXOqz1eK+MMlZp654R6o39M7RksXhhxipCNwfbIY3T7Y7N85qQ== dependencies: - "@jsii/spec" "^1.27.0" + "@jsii/spec" "^1.28.0" commonmark "^0.29.3" fs-extra "^9.1.0" typescript "~3.9.9" xmldom "^0.5.0" yargs "^16.2.0" -jsii@^1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/jsii/-/jsii-1.27.0.tgz#7dc6716ad5c68d6a0c0ff913ff656cf2bbe56ec7" - integrity sha512-EP1NIeheeUw4WpGESkOK7Kb/bT9bBlOunlQuQb+KSMKYq+Zh8uWuFxzTYbt3pg/UdaVis5YD0jsdVgQFVU7ufA== +jsii@^1.28.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/jsii/-/jsii-1.28.0.tgz#c453f2a81d001dd2c3c3991e6e36d4bbb1d03f42" + integrity sha512-B6CbHi60fabeQZJYNea8wSUsrILJzN7ng+yx69GmMJ4C6NtCVt7Oc/CITfhY/cYTwdhN3FAJf01e5/v8qj6bUA== dependencies: - "@jsii/spec" "^1.27.0" + "@jsii/spec" "^1.28.0" case "^1.6.3" colors "^1.4.0" deep-equal "^2.0.5" @@ -6188,6 +6204,13 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +klaw-sync@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== + dependencies: + graceful-fs "^4.1.11" + kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -7400,10 +7423,18 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -oo-ascii-tree@^1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/oo-ascii-tree/-/oo-ascii-tree-1.27.0.tgz#d806ed771026abc3a113f823408914486e48401a" - integrity sha512-3hqwUDNTJC2YLzSRye8Fh35AC4fSHl2FZhFF/hyQtO8C9lV1PEXIPWGIRZ0zwQSHFutnriEvK8AHJgbbMrLxqg== +oo-ascii-tree@^1.28.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/oo-ascii-tree/-/oo-ascii-tree-1.28.0.tgz#2bafc084f7725b118b5a8511944a8dd4ebef14df" + integrity sha512-lCeBgtQutG2+K7BOJDurYNfCepvckj7jWtq2VVP1kseLry/VbLzE/oLiXEeK6iWUXJbBE2IzmxwGuUwee293yw== + +open@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" opener@^1.5.1: version "1.5.2" @@ -7725,6 +7756,25 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= +patch-package@^6.4.7: + version "6.4.7" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" + integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + chalk "^2.4.2" + cross-spawn "^6.0.5" + find-yarn-workspace-root "^2.0.0" + fs-extra "^7.0.1" + is-ci "^2.0.0" + klaw-sync "^6.0.0" + minimist "^1.2.0" + open "^7.4.2" + rimraf "^2.6.3" + semver "^5.6.0" + slash "^2.0.0" + tmp "^0.0.33" + path-exists@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" @@ -8678,6 +8728,11 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -9206,10 +9261,10 @@ table@^6.0.4: slice-ansi "^4.0.0" string-width "^4.2.0" -table@^6.0.9: - version "6.0.9" - resolved "https://registry.yarnpkg.com/table/-/table-6.0.9.tgz#790a12bf1e09b87b30e60419bafd6a1fd85536fb" - integrity sha512-F3cLs9a3hL1Z7N4+EkSscsel3z55XT950AvB05bwayrNg5T1/gykXtigioTAjbltvbMSJvvhFCbnf6mX+ntnJQ== +table@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/table/-/table-6.1.0.tgz#676a0cfb206008b59e783fcd94ef8ba7d67d966c" + integrity sha512-T4G5KMmqIk6X87gLKWyU5exPpTjLjY5KyrFWaIjv3SvgaIUGXV7UEzGEnZJdTA38/yUS6f9PlKezQ0bYXG3iIQ== dependencies: ajv "^8.0.1" is-boolean-object "^1.1.0"