diff --git a/CHANGELOG.md b/CHANGELOG.md index 55650f8a69ff0..c7330acf70017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,54 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.96.0](https://github.com/aws/aws-cdk/compare/v1.95.2...v1.96.0) (2021-04-01) + + +### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES + +* **globalaccelerator:** automatic naming algorithm has been changed: if you have existing Accelerators you will need to pass an +explicit name to prevent them from being replaced. All endpoints are now added by calling `addEndpoint()` with a +target-specific class that can be found in `@aws-cdk/aws-globalaccelerator-endpoints`. The generated Security Group +is now looked up by calling `endpointGroup.connectionsPeer()`. +* **docdb:** `DatabaseClusterProps.instanceProps` was hoisted and all its properties are now available one level up directly in `DatabaseClusterProps`. +* **docdb**: `DatabaseInstanceProps.instanceClass` renamed to `DatabaseInstanceProps.instanceType`. +* **core:** The type of the `image` property in `BundlingOptions` +is changed from `BundlingDockerImage` to `DockerImage`. +* **core:** The return type of the `DockerImage.fromBuild()` API is +changed from `BundlingDockerImage` to `DockerImage`. + +### Features + +* **autoscaling-common:** graduate to stable 🚀 ([#13862](https://github.com/aws/aws-cdk/issues/13862)) ([2d623d0](https://github.com/aws/aws-cdk/commit/2d623d08d8d5d8c356d871ccd69a8cdac9c4170e)) +* **chatbot:** graduate to stable 🚀 ([#13863](https://github.com/aws/aws-cdk/issues/13863)) ([2384cdd](https://github.com/aws/aws-cdk/commit/2384cdd39bae1639bf3e6bfdeb7a08edc6306cac)) +* **cli:** app init template for golang ([#13840](https://github.com/aws/aws-cdk/issues/13840)) ([41fd42b](https://github.com/aws/aws-cdk/commit/41fd42b89f6f9a95c6e736c17bd404d80c4756a7)), closes [aws/jsii#2678](https://github.com/aws/jsii/issues/2678) +* **cloudformation-diff:** graduate to stable 🚀 ([#13857](https://github.com/aws/aws-cdk/issues/13857)) ([294f546](https://github.com/aws/aws-cdk/commit/294f54692c609eaf20257caba0b53ceb9882ff35)) +* **docdb:** graduate to stable 🚀 ([#13875](https://github.com/aws/aws-cdk/issues/13875)) ([169c2fc](https://github.com/aws/aws-cdk/commit/169c2fc55c3de2426380d0a1151d1d33cbcc2190)) +* **ec2:** allow disabling inline security group rules ([#13613](https://github.com/aws/aws-cdk/issues/13613)) ([793230c](https://github.com/aws/aws-cdk/commit/793230c7a6dcaf93408206e680bd26159ece1b7d)) +* **elasticloadbalancingv2:** graduate to stable 🚀 ([#13861](https://github.com/aws/aws-cdk/issues/13861)) ([08fa5ed](https://github.com/aws/aws-cdk/commit/08fa5ede1721f5165fad5fcf402a83fc2496bc46)) +* **fsx:** graduate to stable 🚀 ([#13860](https://github.com/aws/aws-cdk/issues/13860)) ([b2322aa](https://github.com/aws/aws-cdk/commit/b2322aac00dbbf5b171d5887fef2a3c8f3267c73)) +* **globalaccelerator:** graduate to stable 🚀 ([#13843](https://github.com/aws/aws-cdk/issues/13843)) ([8571008](https://github.com/aws/aws-cdk/commit/8571008884df8e048754fc4e0cfdf06ab20f0149)) +* **lambda:** switch bundling images from DockerHub to ECR public gallery ([#13473](https://github.com/aws/aws-cdk/issues/13473)) ([e2e008b](https://github.com/aws/aws-cdk/commit/e2e008bd19c3ff1b08ccb093dba576551ec73240)), closes [#11296](https://github.com/aws/aws-cdk/issues/11296) +* **lambda-event-sources:** support for batching window to sqs event source ([#13406](https://github.com/aws/aws-cdk/issues/13406)) ([6743e3b](https://github.com/aws/aws-cdk/commit/6743e3bb79a8281a4be5677fff018d702c85038d)), closes [#11722](https://github.com/aws/aws-cdk/issues/11722) [#11724](https://github.com/aws/aws-cdk/issues/11724) [#13770](https://github.com/aws/aws-cdk/issues/13770) +* **lambda-event-sources:** tumbling window ([#13412](https://github.com/aws/aws-cdk/issues/13412)) ([e9f2773](https://github.com/aws/aws-cdk/commit/e9f2773aedeb7f01ebf2a05face719be9bb8b0d7)), closes [#13411](https://github.com/aws/aws-cdk/issues/13411) +* **lambda-nodejs:** graduate to stable 🚀 ([#13844](https://github.com/aws/aws-cdk/issues/13844)) ([37a5502](https://github.com/aws/aws-cdk/commit/37a5502ced1bf1b451ac4bd921752746277461bf)) + + +### Bug Fixes + +* **aws-ecs:** broken splunk-logging `tag`-option in fargate platform version 1.4 ([#13882](https://github.com/aws/aws-cdk/issues/13882)) ([e9d9299](https://github.com/aws/aws-cdk/commit/e9d9299b6bcdab489d94c974074e8c796bce00f3)), closes [#13881](https://github.com/aws/aws-cdk/issues/13881) +* **cloudfront:** auto-generated cache policy name might conflict cross-region ([#13737](https://github.com/aws/aws-cdk/issues/13737)) ([4f067cb](https://github.com/aws/aws-cdk/commit/4f067cb90d43d04659f68dec6b866ba77f10642c)), closes [#13629](https://github.com/aws/aws-cdk/issues/13629) +* **cloudfront:** Origin Request Policy headers enforce soft limit of 10 ([#13907](https://github.com/aws/aws-cdk/issues/13907)) ([9b0a6cf](https://github.com/aws/aws-cdk/commit/9b0a6cf0d349ef3ce1c941b25bbe8e630e09c639)), closes [#13410](https://github.com/aws/aws-cdk/issues/13410) [#13903](https://github.com/aws/aws-cdk/issues/13903) +* **codebuild:** allow passing the ARN of the Secret in environment variables ([#13706](https://github.com/aws/aws-cdk/issues/13706)) ([6f6e079](https://github.com/aws/aws-cdk/commit/6f6e079569fcdb7e0631717fbe269e94f8f7b127)), closes [#12703](https://github.com/aws/aws-cdk/issues/12703) +* **codebuild:** take the account & region of an imported Project from its ARN ([#13708](https://github.com/aws/aws-cdk/issues/13708)) ([fb65123](https://github.com/aws/aws-cdk/commit/fb6512314db1b11fc608cd62753090684ad0d3c4)), closes [#13694](https://github.com/aws/aws-cdk/issues/13694) +* **codedeploy:** script installing CodeDeploy agent fails ([#13758](https://github.com/aws/aws-cdk/issues/13758)) ([25e8d04](https://github.com/aws/aws-cdk/commit/25e8d04d7266a2642f11154750bef49a31b1892e)), closes [#13755](https://github.com/aws/aws-cdk/issues/13755) +* **cognito:** imported userpool not retaining environment from arn ([#13715](https://github.com/aws/aws-cdk/issues/13715)) ([aa9fd9c](https://github.com/aws/aws-cdk/commit/aa9fd9cd9bbaea4149927e08d57d29e547933f49)), closes [#13691](https://github.com/aws/aws-cdk/issues/13691) +* **core:** BundlingDockerImage.fromAsset() does not return a BundlingDockerImage ([#13846](https://github.com/aws/aws-cdk/issues/13846)) ([7176a5d](https://github.com/aws/aws-cdk/commit/7176a5d5208da7d727bbf5112bc12533983380ea)) +* **dynamodb:** table with replicas fails to deploy with "Unresolved resource dependencies" error ([#13889](https://github.com/aws/aws-cdk/issues/13889)) ([5c99d0d](https://github.com/aws/aws-cdk/commit/5c99d0d0e0fde00582e469b667265ebc9f5ef330)) +* **iam:** Role import doesn't fail when forgetting the region in the ARN ([#13821](https://github.com/aws/aws-cdk/issues/13821)) ([560a853](https://github.com/aws/aws-cdk/commit/560a8536ffc31f74fe2366b1365681c1e56e33da)), closes [#13812](https://github.com/aws/aws-cdk/issues/13812) +* **rds:** fail with a descriptive error if Cluster's instance count is a deploy-time value ([#13765](https://github.com/aws/aws-cdk/issues/13765)) ([dd22e8f](https://github.com/aws/aws-cdk/commit/dd22e8fc29f1fc33d391d1bb9ae93963bfd82563)), closes [#13558](https://github.com/aws/aws-cdk/issues/13558) +* **yaml-cfn:** do not deserialize year-month-date as strings ([#13745](https://github.com/aws/aws-cdk/issues/13745)) ([ffea818](https://github.com/aws/aws-cdk/commit/ffea818f26a383e7f314dac3505c46f3b4b4348d)), closes [#13709](https://github.com/aws/aws-cdk/issues/13709) + ## [1.95.2](https://github.com/aws/aws-cdk/compare/v1.95.1...v1.95.2) (2021-04-01) * Upgrade a downstream dependency([pac-resolver](https://github.com/TooTallNate/node-pac-resolver)) of the aws-cdk (the CDK CLI), to mitigate [CVE-2021-28918](https://github.com/advisories/GHSA-pch5-whg9-qr2r) ([13914](https://github.com/aws/aws-cdk/pull/13914)) ([794c951](https://github.com/aws/aws-cdk/commit/794c951b5da900fd30827e6f7b0b631bf21df979)) diff --git a/packages/@aws-cdk/assert/package.json b/packages/@aws-cdk/assert/package.json index def55461299b0..40ba792dc1f74 100644 --- a/packages/@aws-cdk/assert/package.json +++ b/packages/@aws-cdk/assert/package.json @@ -21,7 +21,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^26.0.21", + "@types/jest": "^26.0.22", "cdk-build-tools": "0.0.0", "jest": "^26.6.3", "pkglint": "0.0.0", diff --git a/packages/@aws-cdk/assets/lib/fs/options.ts b/packages/@aws-cdk/assets/lib/fs/options.ts index 3ccc107d3700d..548fa4bda42ee 100644 --- a/packages/@aws-cdk/assets/lib/fs/options.ts +++ b/packages/@aws-cdk/assets/lib/fs/options.ts @@ -10,6 +10,7 @@ export interface CopyOptions { * A strategy for how to handle symlinks. * * @default Never + * @deprecated use `followSymlinks` instead */ readonly follow?: FollowMode; diff --git a/packages/@aws-cdk/aws-autoscaling-common/README.md b/packages/@aws-cdk/aws-autoscaling-common/README.md index 83b483af43573..fdea0531620e1 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/README.md +++ b/packages/@aws-cdk/aws-autoscaling-common/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-autoscaling-common/package.json b/packages/@aws-cdk/aws-autoscaling-common/package.json index ffc31b5241a02..6378bdc86c35d 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/package.json +++ b/packages/@aws-cdk/aws-autoscaling-common/package.json @@ -105,8 +105,8 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, - "stability": "experimental", - "maturity": "experimental", + "stability": "stable", + "maturity": "stable", "awscdkio": { "announce": false }, 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 0ccfbcc561479..fd561b065d25e 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.22.0", + "eslint": "^7.23.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-chatbot/README.md b/packages/@aws-cdk/aws-chatbot/README.md index 2c4cee1196900..857e2524ed784 100644 --- a/packages/@aws-cdk/aws-chatbot/README.md +++ b/packages/@aws-cdk/aws-chatbot/README.md @@ -5,17 +5,7 @@ ![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) -> All classes with the `Cfn` prefix in this module ([CFN Resources]) are always stable and safe to use. -> -> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib - -![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-chatbot/package.json b/packages/@aws-cdk/aws-chatbot/package.json index 285a2637720dd..6873e8860eb2e 100644 --- a/packages/@aws-cdk/aws-chatbot/package.json +++ b/packages/@aws-cdk/aws-chatbot/package.json @@ -98,8 +98,8 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, - "stability": "experimental", - "maturity": "experimental", + "stability": "stable", + "maturity": "stable", "awscdkio": { "announce": false }, diff --git a/packages/@aws-cdk/aws-cloudformation/package.json b/packages/@aws-cdk/aws-cloudformation/package.json index b7afa856758b4..ee1f4477ed421 100644 --- a/packages/@aws-cdk/aws-cloudformation/package.json +++ b/packages/@aws-cdk/aws-cloudformation/package.json @@ -73,7 +73,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.72", + "@types/aws-lambda": "^8.10.73", "@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/cache-policy.ts b/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts index e9dbf46901eda..a71569e89f02d 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts @@ -1,4 +1,4 @@ -import { Duration, Names, Resource, Token } from '@aws-cdk/core'; +import { Duration, Names, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnCachePolicy } from './cloudfront.generated'; @@ -125,7 +125,7 @@ export class CachePolicy extends Resource implements ICachePolicy { physicalName: props.cachePolicyName, }); - const cachePolicyName = props.cachePolicyName ?? Names.uniqueId(this); + const cachePolicyName = props.cachePolicyName ?? `${Names.uniqueId(this)}-${Stack.of(this).region}`; if (!Token.isUnresolved(cachePolicyName) && !cachePolicyName.match(/^[\w-]+$/i)) { throw new Error(`'cachePolicyName' can only include '-', '_', and alphanumeric characters, got: '${props.cachePolicyName}'`); } diff --git a/packages/@aws-cdk/aws-cloudfront/lib/origin-request-policy.ts b/packages/@aws-cdk/aws-cloudfront/lib/origin-request-policy.ts index 17e7894e6e84e..1bd4fde321080 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/origin-request-policy.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/origin-request-policy.ts @@ -184,9 +184,6 @@ export class OriginRequestHeaderBehavior { if (headers.length === 0) { throw new Error('At least one header to allow must be provided'); } - if (headers.length > 10) { - throw new Error(`Maximum allowed headers in Origin Request Policy is 10; got ${headers.length}.`); - } if (/Authorization/i.test(headers.join('|')) || /Accept-Encoding/i.test(headers.join('|'))) { throw new Error('you cannot pass `Authorization` or `Accept-Encoding` as header values; use a CachePolicy to forward these headers instead'); } diff --git a/packages/@aws-cdk/aws-cloudfront/test/cache-policy.test.ts b/packages/@aws-cdk/aws-cloudfront/test/cache-policy.test.ts index 4f6d618c51621..07d2752de4e2c 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/cache-policy.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/cache-policy.test.ts @@ -24,7 +24,7 @@ describe('CachePolicy', () => { expect(stack).toHaveResource('AWS::CloudFront::CachePolicy', { CachePolicyConfig: { - Name: 'StackCachePolicy0D6FCBC0', + Name: 'StackCachePolicy0D6FCBC0-testregion', MinTTL: 0, DefaultTTL: 86400, MaxTTL: 31536000, diff --git a/packages/@aws-cdk/aws-cloudfront/test/origin-request-policy.test.ts b/packages/@aws-cdk/aws-cloudfront/test/origin-request-policy.test.ts index b342ac434e48e..f2f65107c180a 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/origin-request-policy.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/origin-request-policy.test.ts @@ -89,17 +89,6 @@ describe('OriginRequestPolicy', () => { expect(() => new OriginRequestPolicy(stack, 'OriginRequestPolicy7', { headerBehavior: OriginRequestHeaderBehavior.allowList('Foo', 'Bar') })).not.toThrow(); }); - test('throws if more than 10 OriginRequestHeaderBehavior headers are being passed', () => { - const errorMessage = /Maximum allowed headers in Origin Request Policy is 10; got (.*?)/; - expect(() => new OriginRequestPolicy(stack, 'OriginRequestPolicy1', { - headerBehavior: OriginRequestHeaderBehavior.allowList('Lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing', 'elit', 'sed', 'do', 'eiusmod'), - })).toThrow(errorMessage); - - expect(() => new OriginRequestPolicy(stack, 'OriginRequestPolicy2', { - headerBehavior: OriginRequestHeaderBehavior.allowList('Lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing', 'elit', 'sed', 'do'), - })).not.toThrow(); - }); - test('does not throw if originRequestPolicyName is a token', () => { expect(() => new OriginRequestPolicy(stack, 'CachePolicy', { originRequestPolicyName: Aws.STACK_NAME, diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 718f7263ada13..abca536a2c354 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 { Aws, Duration, IResource, Lazy, Names, PhysicalName, Resource, SecretValue, Stack, Tokenization } from '@aws-cdk/core'; +import { ArnComponents, Aws, Duration, IResource, Lazy, Names, PhysicalName, Resource, SecretValue, Stack, Token, Tokenization } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IArtifacts } from './artifacts'; import { BuildSpec } from './build-spec'; @@ -639,14 +639,19 @@ export interface BindToCodePipelineOptions { export class Project extends ProjectBase { public static fromProjectArn(scope: Construct, id: string, projectArn: string): IProject { + const parsedArn = Stack.of(scope).parseArn(projectArn); + class Import extends ProjectBase { public readonly grantPrincipal: iam.IPrincipal; public readonly projectArn = projectArn; - public readonly projectName = Stack.of(scope).parseArn(projectArn).resourceName!; + public readonly projectName = parsedArn.resourceName!; public readonly role?: iam.Role = undefined; constructor(s: Construct, i: string) { - super(s, i); + super(s, i, { + account: parsedArn.account, + region: parsedArn.region, + }); this.grantPrincipal = new iam.UnknownPrincipal({ resource: this }); } } @@ -707,14 +712,15 @@ export class Project extends ProjectBase { validateNoPlainTextSecrets: boolean = false, principal?: iam.IGrantable): CfnProject.EnvironmentVariableProperty[] { const ret = new Array(); - const ssmVariables = new Array(); - const secretsManagerSecrets = new Array(); + const ssmIamResources = new Array(); + const secretsManagerIamResources = new Array(); for (const [name, envVariable] of Object.entries(environmentVariables)) { + const envVariableValue = envVariable.value?.toString(); const cfnEnvVariable: CfnProject.EnvironmentVariableProperty = { name, type: envVariable.type || BuildEnvironmentVariableType.PLAINTEXT, - value: envVariable.value?.toString(), + value: envVariableValue, }; ret.push(cfnEnvVariable); @@ -733,10 +739,11 @@ export class Project extends ProjectBase { } if (principal) { + const stack = Stack.of(principal); + // save the SSM env variables if (envVariable.type === BuildEnvironmentVariableType.PARAMETER_STORE) { - const envVariableValue = envVariable.value.toString(); - ssmVariables.push(Stack.of(principal).formatArn({ + ssmIamResources.push(stack.formatArn({ service: 'ssm', resource: 'parameter', // If the parameter name starts with / the resource name is not separated with a double '/' @@ -749,27 +756,58 @@ export class Project extends ProjectBase { // save SecretsManager env variables if (envVariable.type === BuildEnvironmentVariableType.SECRETS_MANAGER) { - secretsManagerSecrets.push(Stack.of(principal).formatArn({ - service: 'secretsmanager', - resource: 'secret', - // we don't know the exact ARN of the Secret just from its name, but we can get close - resourceName: `${envVariable.value}-??????`, - sep: ':', - })); + if (Token.isUnresolved(envVariableValue)) { + // the value of the property can be a complex string, separated by ':'; + // see https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec.env.secrets-manager + const secretArn = envVariableValue.split(':')[0]; + + // if we are passed a Token, we should assume it's the ARN of the Secret + // (as the name would not work anyway, because it would be the full name, which CodeBuild does not support) + secretsManagerIamResources.push(secretArn); + } else { + // check if the provided value is a full ARN of the Secret + let parsedArn: ArnComponents | undefined; + try { + parsedArn = stack.parseArn(envVariableValue, ':'); + } catch (e) {} + const secretSpecifier: string = parsedArn ? parsedArn.resourceName : envVariableValue; + + // the value of the property can be a complex string, separated by ':'; + // see https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec.env.secrets-manager + const secretName = secretSpecifier.split(':')[0]; + const secretIamResourceName = parsedArn + // If we were given an ARN, we don't' know whether the name is full, or partial, + // as CodeBuild supports both ARN forms. + // Because of that, follow the name with a '*', which works for both + ? `${secretName}*` + // 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({ + service: 'secretsmanager', + resource: 'secret', + resourceName: secretIamResourceName, + 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, + })); + } } } } - if (ssmVariables.length !== 0) { + if (ssmIamResources.length !== 0) { principal?.grantPrincipal.addToPrincipalPolicy(new iam.PolicyStatement({ actions: ['ssm:GetParameters'], - resources: ssmVariables, + resources: ssmIamResources, })); } - if (secretsManagerSecrets.length !== 0) { + if (secretsManagerIamResources.length !== 0) { principal?.grantPrincipal.addToPrincipalPolicy(new iam.PolicyStatement({ actions: ['secretsmanager:GetSecretValue'], - resources: secretsManagerSecrets, + resources: secretsManagerIamResources, })); } @@ -1831,7 +1869,10 @@ export interface BuildEnvironmentVariable { * The value of the environment variable. * For plain-text variables (the default), this is the literal value of variable. * For SSM parameter variables, pass the name of the parameter here (`parameterName` property of `IParameter`). - * For SecretsManager variables secrets, pass the secret name here (`secretName` property of `ISecret`). + * For SecretsManager variables secrets, pass either the secret name (`secretName` property of `ISecret`) + * or the secret ARN (`secretArn` property of `ISecret`) here, + * along with optional SecretsManager qualifiers separated by ':', like the JSON key, or the version or stage + * (see https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec.env.secrets-manager for details). */ readonly value: any; } diff --git a/packages/@aws-cdk/aws-codebuild/test/test.project.ts b/packages/@aws-cdk/aws-codebuild/test/test.project.ts index 3d7f8e726fd69..d1c6ad1e85b13 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.project.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.project.ts @@ -765,192 +765,568 @@ export = { }, 'EnvironmentVariables': { - 'can use environment variables from parameter store'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); + 'from SSM': { + 'can use environment variables'(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', + }), + environment: { + buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('myimage'), + }, + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE, + value: '/params/param1', + }, + }, + }); - // WHEN - new codebuild.Project(stack, 'Project', { - source: codebuild.Source.s3({ - bucket: new s3.Bucket(stack, 'Bucket'), - path: 'path', - }), - environment: { - buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('myimage'), - }, - environmentVariables: { - 'ENV_VAR1': { - type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE, - value: '/params/param1', + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + Environment: objectLike({ + EnvironmentVariables: [{ + Name: 'ENV_VAR1', + Type: 'PARAMETER_STORE', + Value: '/params/param1', + }], + }), + })); + + test.done(); + }, + + 'grants the correct read permissions'(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', + }), + environment: { + buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('myimage'), }, - }, - }); + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE, + value: '/params/param1', + }, + 'ENV_VAR2': { + type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE, + value: 'params/param2', + }, + }, + }); - // THEN - expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { - Environment: objectLike({ - EnvironmentVariables: [{ - Name: 'ENV_VAR1', - Type: 'PARAMETER_STORE', - Value: '/params/param1', - }], - }), - })); + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith(objectLike({ + 'Action': 'ssm:GetParameters', + 'Effect': 'Allow', + 'Resource': [{ + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ssm:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':parameter/params/param1', + ], + ], + }, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ssm:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':parameter/params/param2', + ], + ], + }], + })), + }, + })); - test.done(); - }, + test.done(); + }, - 'grant read permission for parameter store variables'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); + 'does not grant read permissions when variables are not from parameter store'(test: Test) { - // WHEN - new codebuild.Project(stack, 'Project', { - source: codebuild.Source.s3({ - bucket: new s3.Bucket(stack, 'Bucket'), - path: 'path', - }), - environment: { - buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('myimage'), - }, - environmentVariables: { - 'ENV_VAR1': { - type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE, - value: '/params/param1', + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.Project(stack, 'Project', { + source: codebuild.Source.s3({ + bucket: new s3.Bucket(stack, 'Bucket'), + path: 'path', + }), + environment: { + buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('myimage'), }, - 'ENV_VAR2': { - type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE, - value: 'params/param2', + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, + value: 'var1-value', + }, }, - }, - }); + }); - // THEN - expect(stack).to(haveResourceLike('AWS::IAM::Policy', { - 'PolicyDocument': { - 'Statement': arrayWith(objectLike({ - 'Action': 'ssm:GetParameters', - 'Effect': 'Allow', - 'Resource': [{ - 'Fn::Join': [ - '', - [ + // THEN + expect(stack).notTo(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith(objectLike({ + 'Action': 'ssm:GetParameters', + 'Effect': 'Allow', + })), + }, + })); + + test.done(); + }, + }, + + 'from SecretsManager': { + 'can be provided as a verbatim secret name'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: 'my-secret', + }, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': 'my-secret', + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': { + 'Fn::Join': ['', [ 'arn:', - { - Ref: 'AWS::Partition', - }, - ':ssm:', - { - Ref: 'AWS::Region', - }, + { Ref: 'AWS::Partition' }, + ':secretsmanager:', + { Ref: 'AWS::Region' }, ':', - { - Ref: 'AWS::AccountId', - }, - ':parameter/params/param1', - ], - ], + { Ref: 'AWS::AccountId' }, + ':secret:my-secret-??????', + ]], + }, + }), + }, + })); + + test.done(); + }, + + 'can be provided as a verbatim secret name followed by a JSON key'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: 'my-secret:json-key', }, - { - 'Fn::Join': [ - '', - [ + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': 'my-secret:json-key', + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': { + 'Fn::Join': ['', [ 'arn:', - { - Ref: 'AWS::Partition', - }, - ':ssm:', - { - Ref: 'AWS::Region', - }, + { Ref: 'AWS::Partition' }, + ':secretsmanager:', + { Ref: 'AWS::Region' }, ':', - { - Ref: 'AWS::AccountId', - }, - ':parameter/params/param2', - ], - ], - }], - })), - }, - })); + { Ref: 'AWS::AccountId' }, + ':secret:my-secret-??????', + ]], + }, + }), + }, + })); + test.done(); + }, - test.done(); - }, + 'can be provided as a verbatim full secret ARN followed by a JSON key'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); - 'should not grant read permission when variables are not from parameter store'(test: Test) { + // WHEN + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: 'arn:aws:secretsmanager:us-west-2:123456789012:secret:my-secret-123456:json-key', + }, + }, + }); - // GIVEN - const stack = new cdk.Stack(); + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:my-secret-123456:json-key', + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:my-secret-123456*', + }), + }, + })); - // WHEN - new codebuild.Project(stack, 'Project', { - source: codebuild.Source.s3({ - bucket: new s3.Bucket(stack, 'Bucket'), - path: 'path', - }), - environment: { - buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('myimage'), - }, - environmentVariables: { - 'ENV_VAR1': { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: 'var1-value', + test.done(); + }, + + 'can be provided as a verbatim partial secret ARN'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret', + }, }, - }, - }); + }); - // THEN - expect(stack).notTo(haveResourceLike('AWS::IAM::Policy', { - 'PolicyDocument': { - 'Statement': arrayWith(objectLike({ - 'Action': 'ssm:GetParameters', - 'Effect': 'Allow', - })), - }, - })); + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret', + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret*', + }), + }, + })); - test.done(); - }, + test.done(); + }, - "grants the Project's Role read permissions to the SecretsManager environment variables"(test: Test) { - // GIVEN - const stack = new cdk.Stack(); + 'can be provided as the ARN attribute of a new Secret'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); - // WHEN - new codebuild.PipelineProject(stack, 'Project', { - environmentVariables: { - 'ENV_VAR1': { - type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, - value: 'my-secret', + // WHEN + const secret = new secretsmanager.Secret(stack, 'Secret'); + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: secret.secretArn, + }, }, - }, - }); + }); - // THEN - expect(stack).to(haveResourceLike('AWS::IAM::Policy', { - 'PolicyDocument': { - 'Statement': arrayWith({ - 'Action': 'secretsmanager:GetSecretValue', - 'Effect': 'Allow', - 'Resource': { - 'Fn::Join': ['', [ - 'arn:', - { Ref: 'AWS::Partition' }, - ':secretsmanager:', - { Ref: 'AWS::Region' }, - ':', - { Ref: 'AWS::AccountId' }, - ':secret:my-secret-??????', - ]], + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': { 'Ref': 'SecretA720EF05' }, + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': { 'Ref': 'SecretA720EF05' }, + }), + }, + })); + + test.done(); + }, + + 'can be provided as the ARN attribute of a new Secret, followed by a JSON key'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const secret = new secretsmanager.Secret(stack, 'Secret'); + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: `${secret.secretArn}:json-key:version-stage`, }, - }), - }, - })); + }, + }); - test.done(); + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': { + 'Fn::Join': ['', [ + { 'Ref': 'SecretA720EF05' }, + ':json-key:version-stage', + ]], + }, + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': { 'Ref': 'SecretA720EF05' }, + }), + }, + })); + + test.done(); + }, + + 'can be provided as the name attribute of a Secret imported by name'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const secret = secretsmanager.Secret.fromSecretNameV2(stack, 'Secret', 'mysecret'); + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: secret.secretName, + }, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': 'mysecret', + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': { + 'Fn::Join': ['', [ + 'arn:', + { 'Ref': 'AWS::Partition' }, + ':secretsmanager:', + { 'Ref': 'AWS::Region' }, + ':', + { 'Ref': 'AWS::AccountId' }, + ':secret:mysecret-??????', + ]], + }, + }), + }, + })); + + test.done(); + }, + + 'can be provided as the ARN attribute of a Secret imported by partial ARN, followed by a JSON key'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const secret = secretsmanager.Secret.fromSecretPartialArn(stack, 'Secret', + 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret'); + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: `${secret.secretArn}:json-key`, + }, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret:json-key', + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret*', + }), + }, + })); + + test.done(); + }, + + 'can be provided as the ARN attribute of a Secret imported by complete ARN, followed by a JSON key'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const secret = secretsmanager.Secret.fromSecretCompleteArn(stack, 'Secret', + 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret-123456'); + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: `${secret.secretArn}:json-key`, + }, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret-123456:json-key', + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret-123456*', + }), + }, + })); + + test.done(); + }, }, 'should fail creating when using a secret value in a plaintext variable'(test: Test) { @@ -1010,6 +1386,7 @@ export = { test.done(); }, + 'can override build timeout'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -1031,4 +1408,18 @@ export = { test.done(); }, }, + + 'can be imported': { + 'by ARN'(test: Test) { + const stack = new cdk.Stack(); + const project = codebuild.Project.fromProjectArn(stack, 'Project', + 'arn:aws:codebuild:us-west-2:123456789012:project/My-Project'); + + test.equal(project.projectName, 'My-Project'); + test.equal(project.env.account, '123456789012'); + test.equal(project.env.region, 'us-west-2'); + + test.done(); + }, + }, }; diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts index 2babdecf478be..4e1a436ef9b77 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts @@ -356,7 +356,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { 'if [ -z "$PKG_CMD" ]; then', 'PKG_CMD=apt-get', 'else', - 'PKG=CMD=yum', + 'PKG_CMD=yum', 'fi', '$PKG_CMD update -y', 'set +e', // make sure we don't exit on the next command failing (we check its exit code below) @@ -367,8 +367,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { '$PKG_CMD install -y ruby', 'fi', 'AWS_CLI_PACKAGE_NAME=awscli', - 'if [[ "$PKG_CMD" = "yum" ]];', - 'then', + 'if [ "$PKG_CMD" = "yum" ]; then', 'AWS_CLI_PACKAGE_NAME=aws-cli', 'fi', '$PKG_CMD install -y $AWS_CLI_PACKAGE_NAME', diff --git a/packages/@aws-cdk/aws-codedeploy/test/server/integ.deployment-group.expected.json b/packages/@aws-cdk/aws-codedeploy/test/server/integ.deployment-group.expected.json index 1f5d3d109128d..c5ae20b9ac145 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/server/integ.deployment-group.expected.json +++ b/packages/@aws-cdk/aws-codedeploy/test/server/integ.deployment-group.expected.json @@ -659,7 +659,7 @@ "Fn::Join": [ "", [ - "#!/bin/bash\nset +e\nPKG_CMD=`which yum 2>/dev/null`\nset -e\nif [ -z \"$PKG_CMD\" ]; then\nPKG_CMD=apt-get\nelse\nPKG=CMD=yum\nfi\n$PKG_CMD update -y\nset +e\n$PKG_CMD install -y ruby2.0\nRUBY2_INSTALL=$?\nset -e\nif [ $RUBY2_INSTALL -ne 0 ]; then\n$PKG_CMD install -y ruby\nfi\nAWS_CLI_PACKAGE_NAME=awscli\nif [[ \"$PKG_CMD\" = \"yum\" ]];\nthen\nAWS_CLI_PACKAGE_NAME=aws-cli\nfi\n$PKG_CMD install -y $AWS_CLI_PACKAGE_NAME\nTMP_DIR=`mktemp -d`\ncd $TMP_DIR\naws s3 cp s3://aws-codedeploy-", + "#!/bin/bash\nset +e\nPKG_CMD=`which yum 2>/dev/null`\nset -e\nif [ -z \"$PKG_CMD\" ]; then\nPKG_CMD=apt-get\nelse\nPKG_CMD=yum\nfi\n$PKG_CMD update -y\nset +e\n$PKG_CMD install -y ruby2.0\nRUBY2_INSTALL=$?\nset -e\nif [ $RUBY2_INSTALL -ne 0 ]; then\n$PKG_CMD install -y ruby\nfi\nAWS_CLI_PACKAGE_NAME=awscli\nif [ \"$PKG_CMD\" = \"yum\" ]; then\nAWS_CLI_PACKAGE_NAME=aws-cli\nfi\n$PKG_CMD install -y $AWS_CLI_PACKAGE_NAME\nTMP_DIR=`mktemp -d`\ncd $TMP_DIR\naws s3 cp s3://aws-codedeploy-", { "Ref": "AWS::Region" }, @@ -884,4 +884,4 @@ "Default": "/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2" } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts index 6aa102e97da11..5eaffc0de5741 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts @@ -81,7 +81,7 @@ export = { 'Fn::Join': [ '', [ - '#!/bin/bash\nset +e\nPKG_CMD=`which yum 2>/dev/null`\nset -e\nif [ -z "$PKG_CMD" ]; then\nPKG_CMD=apt-get\nelse\nPKG=CMD=yum\nfi\n$PKG_CMD update -y\nset +e\n$PKG_CMD install -y ruby2.0\nRUBY2_INSTALL=$?\nset -e\nif [ $RUBY2_INSTALL -ne 0 ]; then\n$PKG_CMD install -y ruby\nfi\nAWS_CLI_PACKAGE_NAME=awscli\nif [[ "$PKG_CMD" = "yum" ]];\nthen\nAWS_CLI_PACKAGE_NAME=aws-cli\nfi\n$PKG_CMD install -y $AWS_CLI_PACKAGE_NAME\nTMP_DIR=`mktemp -d`\ncd $TMP_DIR\naws s3 cp s3://aws-codedeploy-', + '#!/bin/bash\nset +e\nPKG_CMD=`which yum 2>/dev/null`\nset -e\nif [ -z "$PKG_CMD" ]; then\nPKG_CMD=apt-get\nelse\nPKG_CMD=yum\nfi\n$PKG_CMD update -y\nset +e\n$PKG_CMD install -y ruby2.0\nRUBY2_INSTALL=$?\nset -e\nif [ $RUBY2_INSTALL -ne 0 ]; then\n$PKG_CMD install -y ruby\nfi\nAWS_CLI_PACKAGE_NAME=awscli\nif [ "$PKG_CMD" = "yum" ]; then\nAWS_CLI_PACKAGE_NAME=aws-cli\nfi\n$PKG_CMD install -y $AWS_CLI_PACKAGE_NAME\nTMP_DIR=`mktemp -d`\ncd $TMP_DIR\naws s3 cp s3://aws-codedeploy-', { 'Ref': 'AWS::Region', }, diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index eff29527ab4af..28e37670b05a2 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -660,22 +660,39 @@ export class UserPool extends UserPoolBase { * Import an existing user pool based on its id. */ public static fromUserPoolId(scope: Construct, id: string, userPoolId: string): IUserPool { - class Import extends UserPoolBase { - public readonly userPoolId = userPoolId; - public readonly userPoolArn = Stack.of(this).formatArn({ - service: 'cognito-idp', - resource: 'userpool', - resourceName: userPoolId, - }); - } - return new Import(scope, id); + let userPoolArn = Stack.of(scope).formatArn({ + service: 'cognito-idp', + resource: 'userpool', + resourceName: userPoolId, + }); + + return UserPool.fromUserPoolArn(scope, id, userPoolArn); } /** * Import an existing user pool based on its ARN. */ public static fromUserPoolArn(scope: Construct, id: string, userPoolArn: string): IUserPool { - return UserPool.fromUserPoolId(scope, id, Stack.of(scope).parseArn(userPoolArn).resourceName!); + const arnParts = Stack.of(scope).parseArn(userPoolArn); + + if (!arnParts.resourceName) { + throw new Error('invalid user pool ARN'); + } + + const userPoolId = arnParts.resourceName; + + class ImportedUserPool extends UserPoolBase { + public readonly userPoolArn = userPoolArn; + public readonly userPoolId = userPoolId; + constructor() { + super(scope, id, { + account: arnParts.account, + region: arnParts.region, + }); + } + } + + return new ImportedUserPool(); } /** diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts index 4aa6a1bf4b85a..db6c036877f7c 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts @@ -211,17 +211,36 @@ describe('User Pool', () => { // WHEN const pool = UserPool.fromUserPoolArn(stack, 'userpool', userPoolArn); expect(pool.userPoolId).toEqual('test-user-pool'); - expect(stack.resolve(pool.userPoolArn)).toEqual({ - 'Fn::Join': ['', [ - 'arn:', - { Ref: 'AWS::Partition' }, - ':cognito-idp:', - { Ref: 'AWS::Region' }, - ':', - { Ref: 'AWS::AccountId' }, - ':userpool/test-user-pool', - ]], + expect(stack.resolve(pool.userPoolArn)).toEqual('arn:aws:cognito-idp:us-east-1:0123456789012:userpool/test-user-pool'); + }); + + test('import using arn without resourceName fails', () => { + // GIVEN + const stack = new Stack(); + const userPoolArn = 'arn:aws:cognito-idp:us-east-1:0123456789012:*'; + + // WHEN + expect(() => { + UserPool.fromUserPoolArn(stack, 'userpool', userPoolArn); + }).toThrowError(/invalid user pool ARN/); + }); + + test('import from different account region using arn', () => { + // GIVEN + const userPoolArn = 'arn:aws:cognito-idp:us-east-1:0123456789012:userpool/test-user-pool'; + + const stack = new Stack(undefined, undefined, { + env: { + account: '111111111111', + region: 'us-east-2', + }, }); + + // WHEN + const pool = UserPool.fromUserPoolArn(stack, 'userpool', userPoolArn); + expect(pool.env.account).toEqual('0123456789012'); + expect(pool.env.region).toEqual('us-east-1'); + expect(pool.userPoolArn).toEqual('arn:aws:cognito-idp:us-east-1:0123456789012:userpool/test-user-pool'); }); test('support tags', () => { diff --git a/packages/@aws-cdk/aws-docdb/README.md b/packages/@aws-cdk/aws-docdb/README.md index 530942578a090..7121a5dee71bb 100644 --- a/packages/@aws-cdk/aws-docdb/README.md +++ b/packages/@aws-cdk/aws-docdb/README.md @@ -5,17 +5,7 @@ ![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) -> All classes with the `Cfn` prefix in this module ([CFN Resources]) are always stable and safe to use. -> -> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib - -![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) --- @@ -32,13 +22,11 @@ const cluster = new DatabaseCluster(this, 'Database', { masterUser: { username: 'myuser' // NOTE: 'admin' is reserved by DocumentDB }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE), - vpcSubnets: { - subnetType: ec2.SubnetType.PUBLIC, - }, - vpc - } + instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE), + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, + }, + vpc }); ``` diff --git a/packages/@aws-cdk/aws-docdb/lib/cluster.ts b/packages/@aws-cdk/aws-docdb/lib/cluster.ts index f60a332d1b77f..71df7275d5cba 100644 --- a/packages/@aws-cdk/aws-docdb/lib/cluster.ts +++ b/packages/@aws-cdk/aws-docdb/lib/cluster.ts @@ -8,7 +8,7 @@ import { DatabaseSecret } from './database-secret'; import { CfnDBCluster, CfnDBInstance, CfnDBSubnetGroup } from './docdb.generated'; import { Endpoint } from './endpoint'; import { IClusterParameterGroup } from './parameter-group'; -import { BackupProps, InstanceProps, Login, RotationMultiUserOptions } from './props'; +import { BackupProps, Login, RotationMultiUserOptions } from './props'; /** * Properties for a new database cluster @@ -82,9 +82,37 @@ export interface DatabaseClusterProps { readonly instanceIdentifierBase?: string; /** - * Settings for the individual instances that are launched + * What type of instance to start for the replicas */ - readonly instanceProps: InstanceProps; + readonly instanceType: ec2.InstanceType; + + /** + * What subnets to run the DocumentDB instances in. + * + * Must be at least 2 subnets in two different AZs. + */ + readonly vpc: ec2.IVpc; + + /** + * Where to place the instances within the VPC + * + * @default private subnets + */ + readonly vpcSubnets?: ec2.SubnetSelection; + + /** + * Security group. + * + * @default a new security group is created. + */ + readonly securityGroup?: ec2.ISecurityGroup; + + /** + * The DB parameter group to associate with the instance. + * + * @default no parameter group + */ + readonly parameterGroup?: IClusterParameterGroup; /** * A weekly time range in which maintenance should preferably execute. @@ -99,13 +127,6 @@ export interface DatabaseClusterProps { */ readonly preferredMaintenanceWindow?: string; - /** - * Additional parameters to pass to the database engine - * - * @default - No parameter group. - */ - readonly parameterGroup?: IClusterParameterGroup; - /** * The removal policy to apply when the cluster and its instances are removed * or replaced during a stack update, or when the stack is deleted. This @@ -275,8 +296,8 @@ export class DatabaseCluster extends DatabaseClusterBase { constructor(scope: Construct, id: string, props: DatabaseClusterProps) { super(scope, id); - this.vpc = props.instanceProps.vpc; - this.vpcSubnets = props.instanceProps.vpcSubnets; + this.vpc = props.vpc; + this.vpcSubnets = props.vpcSubnets; // Determine the subnet(s) to deploy the DocDB cluster to const { subnetIds, internetConnectivityEstablished } = this.vpc.selectSubnets(this.vpcSubnets); @@ -295,8 +316,8 @@ export class DatabaseCluster extends DatabaseClusterBase { // Create the security group for the DB cluster let securityGroup: ec2.ISecurityGroup; - if (props.instanceProps.securityGroup) { - securityGroup = props.instanceProps.securityGroup; + if (props.securityGroup) { + securityGroup = props.securityGroup; } else { securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { description: 'DocumentDB security group', @@ -381,7 +402,7 @@ export class DatabaseCluster extends DatabaseClusterBase { dbClusterIdentifier: cluster.ref, dbInstanceIdentifier: instanceIdentifier, // Instance properties - dbInstanceClass: databaseInstanceType(props.instanceProps.instanceType), + dbInstanceClass: databaseInstanceType(props.instanceType), }); instance.applyRemovalPolicy(props.removalPolicy, { diff --git a/packages/@aws-cdk/aws-docdb/lib/instance.ts b/packages/@aws-cdk/aws-docdb/lib/instance.ts index 3208bda7107f7..a7563cb601388 100644 --- a/packages/@aws-cdk/aws-docdb/lib/instance.ts +++ b/packages/@aws-cdk/aws-docdb/lib/instance.ts @@ -120,7 +120,7 @@ export interface DatabaseInstanceProps { /** * The name of the compute and memory capacity classes. */ - readonly instanceClass: ec2.InstanceType; + readonly instanceType: ec2.InstanceType; /** * The name of the Availability Zone where the DB instance will be located. @@ -202,7 +202,7 @@ export class DatabaseInstance extends DatabaseInstanceBase implements IDatabaseI const instance = new CfnDBInstance(this, 'Resource', { dbClusterIdentifier: props.cluster.clusterIdentifier, - dbInstanceClass: `db.${props.instanceClass}`, + dbInstanceClass: `db.${props.instanceType}`, autoMinorVersionUpgrade: props.autoMinorVersionUpgrade ?? true, availabilityZone: props.availabilityZone, dbInstanceIdentifier: props.dbInstanceName, diff --git a/packages/@aws-cdk/aws-docdb/lib/props.ts b/packages/@aws-cdk/aws-docdb/lib/props.ts index 270bc51cdc203..9cd24b1fce1bc 100644 --- a/packages/@aws-cdk/aws-docdb/lib/props.ts +++ b/packages/@aws-cdk/aws-docdb/lib/props.ts @@ -1,8 +1,6 @@ -import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import { Duration, SecretValue } from '@aws-cdk/core'; -import { IClusterParameterGroup } from './parameter-group'; /** * Backup configuration for DocumentDB databases @@ -57,44 +55,6 @@ export interface Login { readonly kmsKey?: kms.IKey; } -/** - * Instance properties for database instances - */ -export interface InstanceProps { - /** - * What type of instance to start for the replicas - */ - readonly instanceType: ec2.InstanceType; - - /** - * What subnets to run the DocumentDB instances in. - * - * Must be at least 2 subnets in two different AZs. - */ - readonly vpc: ec2.IVpc; - - /** - * Where to place the instances within the VPC - * - * @default private subnets - */ - readonly vpcSubnets?: ec2.SubnetSelection; - - /** - * Security group. - * - * @default a new security group is created. - */ - readonly securityGroup?: ec2.ISecurityGroup; - - /** - * The DB parameter group to associate with the instance. - * - * @default no parameter group - */ - readonly parameterGroup?: IClusterParameterGroup; -} - /** * Options to add the multi user rotation */ diff --git a/packages/@aws-cdk/aws-docdb/package.json b/packages/@aws-cdk/aws-docdb/package.json index 8442223e41977..b9b4f70a7030c 100644 --- a/packages/@aws-cdk/aws-docdb/package.json +++ b/packages/@aws-cdk/aws-docdb/package.json @@ -104,8 +104,8 @@ "attribute-tag:@aws-cdk/aws-docdb.DatabaseSecret.secretName" ] }, - "stability": "experimental", - "maturity": "experimental", + "stability": "stable", + "maturity": "stable", "awscdkio": { "announce": false }, diff --git a/packages/@aws-cdk/aws-docdb/test/cluster.test.ts b/packages/@aws-cdk/aws-docdb/test/cluster.test.ts index f227293310bf3..a868ff2bf83e9 100644 --- a/packages/@aws-cdk/aws-docdb/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-docdb/test/cluster.test.ts @@ -18,10 +18,8 @@ describe('DatabaseCluster', () => { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpc, - }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, }); // THEN @@ -63,10 +61,8 @@ describe('DatabaseCluster', () => { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpc, - }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, }); // THEN @@ -90,10 +86,8 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - vpc, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE), - }, + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE), }); }).toThrowError('At least one instance is required'); }); @@ -112,12 +106,10 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - vpc, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE), - vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE, - }, + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE), + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE, }, }); }).toThrowError('Cluster requires at least 2 subnets, got 1'); @@ -134,10 +126,8 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpc, - }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, }); // THEN @@ -163,11 +153,9 @@ describe('DatabaseCluster', () => { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpc, - securityGroup: sg, - }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, + securityGroup: sg, }); // THEN @@ -197,10 +185,8 @@ describe('DatabaseCluster', () => { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpc, - }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, parameterGroup: group, }); @@ -223,10 +209,8 @@ describe('DatabaseCluster', () => { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpc, - }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, parameterGroup: group, }); @@ -246,10 +230,8 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpc, - }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, }); // THEN @@ -300,10 +282,8 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpc, - }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, kmsKey: new kms.Key(stack, 'Key'), }); @@ -329,10 +309,8 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpc, - }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, }); // THEN @@ -352,10 +330,8 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpc, - }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, kmsKey: new kms.Key(stack, 'Key'), storageEncrypted: false, }); @@ -375,10 +351,8 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), - vpc, - }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, }); // THEN @@ -396,10 +370,8 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - vpc, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), - }, + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), instanceIdentifierBase, }); @@ -420,10 +392,8 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - vpc, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), - }, + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), dbClusterName: clusterIdentifier, }); @@ -494,10 +464,8 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - vpc, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), - }, + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), backup: { retention: cdk.Duration.days(20), }, @@ -519,10 +487,8 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - vpc, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), - }, + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), backup: { retention: cdk.Duration.days(20), preferredWindow: '07:34-08:04', @@ -546,10 +512,8 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - vpc, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), - }, + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), preferredMaintenanceWindow: '07:34-08:04', }); @@ -567,10 +531,8 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - vpc, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), - }, + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), }); // WHEN @@ -630,10 +592,8 @@ describe('DatabaseCluster', () => { username: 'admin', password: cdk.SecretValue.plainText('secret'), }, - instanceProps: { - vpc, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), - }, + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), }); // WHEN @@ -653,10 +613,8 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - vpc, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), - }, + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), }); // WHEN @@ -677,10 +635,8 @@ describe('DatabaseCluster', () => { masterUser: { username: 'admin', }, - instanceProps: { - vpc, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), - }, + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), }); const userSecret = new DatabaseSecret(stack, 'UserSecret', { username: 'seconduser', @@ -749,10 +705,8 @@ describe('DatabaseCluster', () => { username: 'admin', password: cdk.SecretValue.plainText('secret'), }, - instanceProps: { - vpc, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), - }, + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.SMALL), }); const userSecret = new DatabaseSecret(stack, 'UserSecret', { username: 'seconduser', diff --git a/packages/@aws-cdk/aws-docdb/test/instance.test.ts b/packages/@aws-cdk/aws-docdb/test/instance.test.ts index b3785e700ca01..afb16ef61a773 100644 --- a/packages/@aws-cdk/aws-docdb/test/instance.test.ts +++ b/packages/@aws-cdk/aws-docdb/test/instance.test.ts @@ -17,7 +17,7 @@ describe('DatabaseInstance', () => { // WHEN new DatabaseInstance(stack, 'Instance', { cluster: stack.cluster, - instanceClass: SINGLE_INSTANCE_TYPE, + instanceType: SINGLE_INSTANCE_TYPE, }); // THEN @@ -43,7 +43,7 @@ describe('DatabaseInstance', () => { // WHEN new DatabaseInstance(stack, 'Instance', { cluster: stack.cluster, - instanceClass: SINGLE_INSTANCE_TYPE, + instanceType: SINGLE_INSTANCE_TYPE, autoMinorVersionUpgrade: given, }); @@ -64,7 +64,7 @@ describe('DatabaseInstance', () => { const stack = testStack(); const instance = new DatabaseInstance(stack, 'Instance', { cluster: stack.cluster, - instanceClass: SINGLE_INSTANCE_TYPE, + instanceType: SINGLE_INSTANCE_TYPE, }); const exportName = 'DbInstanceEndpoint'; @@ -95,7 +95,7 @@ describe('DatabaseInstance', () => { const stack = testStack(); const instance = new DatabaseInstance(stack, 'Instance', { cluster: stack.cluster, - instanceClass: SINGLE_INSTANCE_TYPE, + instanceType: SINGLE_INSTANCE_TYPE, }); const exportName = 'DbInstanceArn'; @@ -182,10 +182,8 @@ class TestStack extends cdk.Stack { username: 'admin', password: cdk.SecretValue.plainText('tooshort'), }, - instanceProps: { - instanceType: CLUSTER_INSTANCE_TYPE, - vpc: this.vpc, - }, + instanceType: CLUSTER_INSTANCE_TYPE, + vpc: this.vpc, }); } } diff --git a/packages/@aws-cdk/aws-docdb/test/integ.cluster-rotation.lit.ts b/packages/@aws-cdk/aws-docdb/test/integ.cluster-rotation.lit.ts index e8c6ed141e1f9..ae655dd213a4d 100644 --- a/packages/@aws-cdk/aws-docdb/test/integ.cluster-rotation.lit.ts +++ b/packages/@aws-cdk/aws-docdb/test/integ.cluster-rotation.lit.ts @@ -20,10 +20,8 @@ const cluster = new docdb.DatabaseCluster(stack, 'Database', { masterUser: { username: 'docdb', }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE), - vpc, - }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE), + vpc, removalPolicy: cdk.RemovalPolicy.DESTROY, }); diff --git a/packages/@aws-cdk/aws-docdb/test/integ.cluster.ts b/packages/@aws-cdk/aws-docdb/test/integ.cluster.ts index 5e7b2049507aa..084502d0dae65 100644 --- a/packages/@aws-cdk/aws-docdb/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-docdb/test/integ.cluster.ts @@ -35,11 +35,9 @@ class TestStack extends cdk.Stack { username: 'docdb', password: cdk.SecretValue.plainText('7959866cacc02c2d243ecfe177464fe6'), }, - instanceProps: { - instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE), - vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, - vpc, - }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE), + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, + vpc, parameterGroup: params, kmsKey, removalPolicy: cdk.RemovalPolicy.DESTROY, 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 2753afe5af09f..d67e31884cd8a 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.22.0", + "eslint": "^7.23.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/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index a958bf135546e..ed8f6a786d03c 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -3,7 +3,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import { - Aws, CfnCondition, CfnCustomResource, CustomResource, Duration, + Aws, CfnCondition, CfnCustomResource, CfnResource, CustomResource, Duration, Fn, IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token, } from '@aws-cdk/core'; import { Construct } from 'constructs'; @@ -1477,7 +1477,8 @@ export class Table extends TableBase { this.grant(onEventHandlerPolicy, 'dynamodb:*'); this.grant(isCompleteHandlerPolicy, 'dynamodb:DescribeTable'); - let previousRegion; + let previousRegion: CustomResource | undefined; + let previousRegionCondition: CfnCondition | undefined; for (const region of new Set(regions)) { // Remove duplicates // Use multiple custom resources because multiple create/delete // updates cannot be combined in a single API call. @@ -1498,8 +1499,9 @@ export class Table extends TableBase { // Deploy time check to prevent from creating a replica in the region // where this stack is deployed. Only needed for environment agnostic // stacks. + let createReplica: CfnCondition | undefined; if (Token.isUnresolved(stack.region)) { - const createReplica = new CfnCondition(this, `StackRegionNotEquals${region}`, { + createReplica = new CfnCondition(this, `StackRegionNotEquals${region}`, { expression: Fn.conditionNot(Fn.conditionEquals(region, Aws.REGION)), }); const cfnCustomResource = currentRegion.node.defaultChild as CfnCustomResource; @@ -1518,9 +1520,22 @@ export class Table extends TableBase { // have multiple table updates at the same time. The `isCompleteHandler` // of the provider waits until the replica is in an ACTIVE state. if (previousRegion) { - currentRegion.node.addDependency(previousRegion); + if (previousRegionCondition) { + // we can't simply use a Dependency, + // because the previousRegion is protected by the "different region" Condition, + // and you can't have Fn::If in DependsOn. + // Instead, rely on Ref adding a dependency implicitly! + const previousRegionCfnResource = previousRegion.node.defaultChild as CfnResource; + const currentRegionCfnResource = currentRegion.node.defaultChild as CfnResource; + currentRegionCfnResource.addMetadata('DynamoDbReplicationDependency', + Fn.conditionIf(previousRegionCondition.logicalId, previousRegionCfnResource.ref, Aws.NO_VALUE)); + } else { + currentRegion.node.addDependency(previousRegion); + } } + previousRegion = currentRegion; + previousRegionCondition = createReplica; } // Permissions in the destination regions (outside of the loop to diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index 344a68f7e3a96..e59e48a6461f9 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -72,7 +72,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/jest": "^26.0.21", + "@types/jest": "^26.0.22", "aws-sdk": "^2.848.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.global-replicas-provisioned.expected.json b/packages/@aws-cdk/aws-dynamodb/test/integ.global-replicas-provisioned.expected.json index af1e8defceed6..ce43b532ea7c6 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.global-replicas-provisioned.expected.json +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.global-replicas-provisioned.expected.json @@ -199,7 +199,6 @@ "Region": "eu-west-3" }, "DependsOn": [ - "TableReplicauseast28A15C236", "TableSourceTableAttachedManagedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderIsCompleteHandlerServiceRoleBE2B1C1A5DC546D2", "TableSourceTableAttachedManagedPolicyawscdkdynamodbglobalreplicasprovisionedawscdkawsdynamodbReplicaProviderOnEventHandlerServiceRoleD9856B771F8F2CCB", "TableWriteScalingTargetE5669214", @@ -207,6 +206,19 @@ ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete", + "Metadata": { + "DynamoDbReplicationDependency": { + "Fn::If": [ + "TableStackRegionNotEqualsuseast2D20A1E77", + { + "Ref": "TableReplicauseast28A15C236" + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, "Condition": "TableStackRegionNotEqualseuwest302B3591C" }, "TableWriteScalingTargetE5669214": { diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 5ce9a5c500d4d..123ef79f3aa66 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -520,6 +520,27 @@ listener.connections.allowDefaultPortFromAnyIpv4('Allow public'); appFleet.connections.allowDefaultPortTo(rdsDatabase, 'Fleet can access database'); ``` +### Security group rules + +By default, security group wills be added inline to the security group in the output cloud formation +template, if applicable. This includes any static rules by ip address and port range. This +optimization helps to minimize the size of the template. + +In some environments this is not desirable, for example if your security group access is controlled +via tags. You can disable inline rules per security group or globally via the context key +`@aws-cdk/aws-ec2.securityGroupDisableInlineRules`. + +```ts fixture=with-vpc +const mySecurityGroupWithoutInlineRules = new ec2.SecurityGroup(this, 'SecurityGroup', { + vpc, + description: 'Allow ssh access to ec2 instances', + allowAllOutbound: true, + disableInlineRules: true +}); +//This will add the rule as an external cloud formation construct +mySecurityGroupWithoutInlineRules.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'allow ssh access from the world'); +``` + ## Machine Images (AMIs) AMIs control the OS that gets launched when you start your EC2 instance. The EC2 diff --git a/packages/@aws-cdk/aws-ec2/lib/peer.ts b/packages/@aws-cdk/aws-ec2/lib/peer.ts index 7bd343b6f2828..333bd66bc91a9 100644 --- a/packages/@aws-cdk/aws-ec2/lib/peer.ts +++ b/packages/@aws-cdk/aws-ec2/lib/peer.ts @@ -198,4 +198,4 @@ class PrefixList implements IPeer { public toEgressRuleConfig(): any { return { destinationPrefixListId: this.prefixListId }; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index b5c3e4bf5cff2..70bc1311bf8ab 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -4,12 +4,14 @@ import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import { Connections } from './connections'; import { CfnSecurityGroup, CfnSecurityGroupEgress, CfnSecurityGroupIngress } from './ec2.generated'; -import { IPeer } from './peer'; +import { IPeer, Peer } from './peer'; import { Port } from './port'; import { IVpc } from './vpc'; const SECURITY_GROUP_SYMBOL = Symbol.for('@aws-cdk/iam.SecurityGroup'); +const SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY = '@aws-cdk/aws-ec2.securityGroupDisableInlineRules'; + /** * Interface for security group-like objects */ @@ -229,6 +231,22 @@ export interface SecurityGroupProps { * @default true */ readonly allowAllOutbound?: boolean; + + /** + * Whether to disable inline ingress and egress rule optimization. + * + * If this is set to true, ingress and egress rules will not be declared under the + * SecurityGroup in cloudformation, but will be separate elements. + * + * Inlining rules is an optimization for producing smaller stack templates. Sometimes + * this is not desirable, for example when security group access is managed via tags. + * + * The default value can be overriden globally by setting the context variable + * '@aws-cdk/aws-ec2.securityGroupDisableInlineRules'. + * + * @default false + */ + readonly disableInlineRules?: boolean; } /** @@ -390,6 +408,11 @@ export class SecurityGroup extends SecurityGroupBase { private readonly directIngressRules: CfnSecurityGroup.IngressProperty[] = []; private readonly directEgressRules: CfnSecurityGroup.EgressProperty[] = []; + /** + * Whether to disable optimization for inline security group rules. + */ + private readonly disableInlineRules: boolean; + constructor(scope: Construct, id: string, props: SecurityGroupProps) { super(scope, id, { physicalName: props.securityGroupName, @@ -399,6 +422,10 @@ export class SecurityGroup extends SecurityGroupBase { this.allowAllOutbound = props.allowAllOutbound !== false; + this.disableInlineRules = props.disableInlineRules !== undefined ? + !!props.disableInlineRules : + !!this.node.tryGetContext(SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY); + this.securityGroup = new CfnSecurityGroup(this, 'Resource', { groupName: this.physicalName, groupDescription, @@ -415,7 +442,7 @@ export class SecurityGroup extends SecurityGroupBase { } public addIngressRule(peer: IPeer, connection: Port, description?: string, remoteRule?: boolean) { - if (!peer.canInlineRule || !connection.canInlineRule) { + if (!peer.canInlineRule || !connection.canInlineRule || this.disableInlineRules) { super.addIngressRule(peer, connection, description, remoteRule); return; } @@ -445,7 +472,7 @@ export class SecurityGroup extends SecurityGroupBase { this.removeNoTrafficRule(); } - if (!peer.canInlineRule || !connection.canInlineRule) { + if (!peer.canInlineRule || !connection.canInlineRule || this.disableInlineRules) { super.addEgressRule(peer, connection, description, remoteRule); return; } @@ -519,10 +546,14 @@ export class SecurityGroup extends SecurityGroupBase { * strictly necessary). */ private addDefaultEgressRule() { - if (this.allowAllOutbound) { - this.directEgressRules.push(ALLOW_ALL_RULE); + if (this.disableInlineRules) { + const peer = this.allowAllOutbound ? ALL_TRAFFIC_PEER : NO_TRAFFIC_PEER; + const port = this.allowAllOutbound ? ALL_TRAFFIC_PORT : NO_TRAFFIC_PORT; + const description = this.allowAllOutbound ? ALLOW_ALL_RULE.description : MATCH_NO_TRAFFIC.description; + super.addEgressRule(peer, port, description, false); } else { - this.directEgressRules.push(MATCH_NO_TRAFFIC); + const rule = this.allowAllOutbound? ALLOW_ALL_RULE : MATCH_NO_TRAFFIC; + this.directEgressRules.push(rule); } } @@ -530,9 +561,20 @@ export class SecurityGroup extends SecurityGroupBase { * Remove the bogus rule if it exists */ private removeNoTrafficRule() { - const i = this.directEgressRules.findIndex(r => egressRulesEqual(r, MATCH_NO_TRAFFIC)); - if (i > -1) { - this.directEgressRules.splice(i, 1); + if (this.disableInlineRules) { + const [scope, id] = determineRuleScope( + this, + NO_TRAFFIC_PEER, + NO_TRAFFIC_PORT, + 'to', + false); + + scope.node.tryRemoveChild(id); + } else { + const i = this.directEgressRules.findIndex(r => egressRulesEqual(r, MATCH_NO_TRAFFIC)); + if (i > -1) { + this.directEgressRules.splice(i, 1); + } } } } @@ -554,6 +596,9 @@ const MATCH_NO_TRAFFIC = { toPort: 86, }; +const NO_TRAFFIC_PEER = Peer.ipv4(MATCH_NO_TRAFFIC.cidrIp); +const NO_TRAFFIC_PORT = Port.icmpTypeAndCode(MATCH_NO_TRAFFIC.fromPort, MATCH_NO_TRAFFIC.toPort); + /** * Egress rule that matches all traffic */ @@ -563,6 +608,9 @@ const ALLOW_ALL_RULE = { ipProtocol: '-1', }; +const ALL_TRAFFIC_PEER = Peer.anyIpv4(); +const ALL_TRAFFIC_PORT = Port.allTraffic(); + export interface ConnectionRule { /** * The IP protocol name (tcp, udp, icmp) or number (see Protocol Numbers). diff --git a/packages/@aws-cdk/aws-ec2/test/security-group.test.ts b/packages/@aws-cdk/aws-ec2/test/security-group.test.ts index 9851d87235c41..59f7c4192ff99 100644 --- a/packages/@aws-cdk/aws-ec2/test/security-group.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/security-group.test.ts @@ -1,7 +1,9 @@ -import { expect, haveResource, not } from '@aws-cdk/assert'; +import { expect, haveResource, haveResourceLike, not } from '@aws-cdk/assert'; import { App, Intrinsic, Lazy, Stack, Token } from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; -import { Peer, Port, SecurityGroup, Vpc } from '../lib'; +import { Peer, Port, SecurityGroup, SecurityGroupProps, Vpc } from '../lib'; + +const SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY = '@aws-cdk/aws-ec2.securityGroupDisableInlineRules'; nodeunitShim({ 'security group can allows all outbound traffic by default'(test: Test) { @@ -148,6 +150,23 @@ nodeunitShim({ test.done(); }, + 'Inline Rule Control': { + //Not inlined + 'When props.disableInlineRules is true': testRulesAreNotInlined(undefined, true), + 'When context.disableInlineRules is true': testRulesAreNotInlined(true, undefined), + 'When context.disableInlineRules is true and props.disableInlineRules is true': testRulesAreNotInlined(true, true), + 'When context.disableInlineRules is false and props.disableInlineRules is true': testRulesAreNotInlined(false, true), + 'When props.disableInlineRules is true and context.disableInlineRules is null': testRulesAreNotInlined(null, true), + //Inlined + 'When context.disableInlineRules is false and props.disableInlineRules is false': testRulesAreInlined(false, false), + 'When context.disableInlineRules is true and props.disableInlineRules is false': testRulesAreInlined(true, false), + 'When context.disableInlineRules is false': testRulesAreInlined(false, undefined), + 'When props.disableInlineRules is false': testRulesAreInlined(undefined, false), + 'When neither props.disableInlineRules nor context.disableInlineRules are defined': testRulesAreInlined(undefined, undefined), + 'When props.disableInlineRules is undefined and context.disableInlineRules is null': testRulesAreInlined(null, undefined), + 'When props.disableInlineRules is false and context.disableInlineRules is null': testRulesAreInlined(null, false), + }, + 'peer between all types of peers and port range types'(test: Test) { // GIVEN const stack = new Stack(undefined, 'TestStack', { env: { account: '12345678', region: 'dummy' } }); @@ -311,3 +330,432 @@ nodeunitShim({ test.done(); }, }); + +function testRulesAreInlined(contextDisableInlineRules: boolean | undefined | null, optionsDisableInlineRules: boolean | undefined) { + return { + 'When allowAllOutbound': { + 'new SecurityGroup will create an inline SecurityGroupEgress rule to allow all traffic'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext(SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY, contextDisableInlineRules); + const vpc = new Vpc(stack, 'VPC'); + const props: SecurityGroupProps = { vpc, allowAllOutbound: true, disableInlineRules: optionsDisableInlineRules }; + + // WHEN + new SecurityGroup(stack, 'SG1', props); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Default/SG1', + VpcId: stack.resolve(vpc.vpcId), + SecurityGroupEgress: [ + { + CidrIp: '0.0.0.0/0', + Description: 'Allow all outbound traffic by default', + IpProtocol: '-1', + }, + ], + })); + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupEgress', {}))); + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupIngress', {}))); + test.done(); + }, + + 'addEgressRule rule will not modify egress rules'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext(SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY, contextDisableInlineRules); + const vpc = new Vpc(stack, 'VPC'); + const props: SecurityGroupProps = { vpc, allowAllOutbound: true, disableInlineRules: optionsDisableInlineRules }; + + // WHEN + const sg = new SecurityGroup(stack, 'SG1', props); + sg.addEgressRule(Peer.anyIpv4(), Port.tcp(86), 'An external Rule'); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Default/SG1', + VpcId: stack.resolve(vpc.vpcId), + SecurityGroupEgress: [ + { + CidrIp: '0.0.0.0/0', + Description: 'Allow all outbound traffic by default', + IpProtocol: '-1', + }, + ], + })); + + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupEgress', {}))); + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupIngress', {}))); + test.done(); + }, + + 'addIngressRule will add a new ingress rule'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext(SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY, contextDisableInlineRules); + const vpc = new Vpc(stack, 'VPC'); + const props: SecurityGroupProps = { vpc, allowAllOutbound: true, disableInlineRules: optionsDisableInlineRules }; + + // WHEN + const sg = new SecurityGroup(stack, 'SG1', props); + sg.addIngressRule(Peer.anyIpv4(), Port.tcp(86), 'An external Rule'); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Default/SG1', + VpcId: stack.resolve(vpc.vpcId), + SecurityGroupIngress: [ + { + CidrIp: '0.0.0.0/0', + Description: 'An external Rule', + FromPort: 86, + IpProtocol: 'tcp', + ToPort: 86, + }, + ], + SecurityGroupEgress: [ + { + CidrIp: '0.0.0.0/0', + Description: 'Allow all outbound traffic by default', + IpProtocol: '-1', + }, + ], + })); + + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupEgress', {}))); + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupIngress', {}))); + test.done(); + }, + }, + + 'When do not allowAllOutbound': { + 'new SecurityGroup rule will create an egress rule that denies all traffic'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext(SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY, contextDisableInlineRules); + const vpc = new Vpc(stack, 'VPC'); + const props: SecurityGroupProps = { vpc, allowAllOutbound: false, disableInlineRules: optionsDisableInlineRules }; + + // WHEN + new SecurityGroup(stack, 'SG1', props); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Default/SG1', + VpcId: stack.resolve(vpc.vpcId), + SecurityGroupEgress: [ + { + CidrIp: '255.255.255.255/32', + Description: 'Disallow all traffic', + IpProtocol: 'icmp', + FromPort: 252, + ToPort: 86, + }, + ], + })); + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupIngress', {}))); + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupIngress', {}))); + + test.done(); + }, + 'addEgressRule rule will add a new inline egress rule and remove the denyAllTraffic rule'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext(SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY, contextDisableInlineRules); + const vpc = new Vpc(stack, 'VPC'); + const props: SecurityGroupProps = { vpc, allowAllOutbound: false, disableInlineRules: optionsDisableInlineRules }; + + // WHEN + const sg = new SecurityGroup(stack, 'SG1', props); + sg.addEgressRule(Peer.anyIpv4(), Port.tcp(86), 'An inline Rule'); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Default/SG1', + VpcId: stack.resolve(vpc.vpcId), + SecurityGroupEgress: [ + { + CidrIp: '0.0.0.0/0', + Description: 'An inline Rule', + FromPort: 86, + IpProtocol: 'tcp', + ToPort: 86, + }, + ], + })); + + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupEgress', {}))); + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupIngress', {}))); + test.done(); + }, + + 'addIngressRule will add a new ingress rule'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext(SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY, contextDisableInlineRules); + const vpc = new Vpc(stack, 'VPC'); + const props: SecurityGroupProps = { vpc, allowAllOutbound: false, disableInlineRules: optionsDisableInlineRules }; + + // WHEN + const sg = new SecurityGroup(stack, 'SG1', props); + sg.addIngressRule(Peer.anyIpv4(), Port.tcp(86), 'An external Rule'); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Default/SG1', + VpcId: stack.resolve(vpc.vpcId), + SecurityGroupIngress: [ + { + CidrIp: '0.0.0.0/0', + Description: 'An external Rule', + FromPort: 86, + IpProtocol: 'tcp', + ToPort: 86, + }, + ], + SecurityGroupEgress: [ + { + CidrIp: '255.255.255.255/32', + Description: 'Disallow all traffic', + IpProtocol: 'icmp', + FromPort: 252, + ToPort: 86, + }, + ], + })); + + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupEgress', {}))); + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupIngress', {}))); + test.done(); + }, + }, + }; +} + + +function testRulesAreNotInlined(contextDisableInlineRules: boolean | undefined | null, optionsDisableInlineRules: boolean | undefined) { + return { + 'When allowAllOutbound': { + 'new SecurityGroup will create an external SecurityGroupEgress rule'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext(SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY, contextDisableInlineRules); + const vpc = new Vpc(stack, 'VPC'); + const props: SecurityGroupProps = { vpc, allowAllOutbound: true, disableInlineRules: optionsDisableInlineRules }; + + // WHEN + const sg = new SecurityGroup(stack, 'SG1', props); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Default/SG1', + VpcId: stack.resolve(vpc.vpcId), + })); + expect(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', { + GroupId: stack.resolve(sg.securityGroupId), + CidrIp: '0.0.0.0/0', + Description: 'Allow all outbound traffic by default', + IpProtocol: '-1', + })); + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupIngress', {}))); + test.done(); + }, + + 'addIngressRule rule will not remove external allowAllOutbound rule'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext(SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY, contextDisableInlineRules); + const vpc = new Vpc(stack, 'VPC'); + const props: SecurityGroupProps = { vpc, allowAllOutbound: true, disableInlineRules: optionsDisableInlineRules }; + + // WHEN + const sg = new SecurityGroup(stack, 'SG1', props); + sg.addEgressRule(Peer.anyIpv4(), Port.tcp(86), 'An external Rule'); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Default/SG1', + VpcId: stack.resolve(vpc.vpcId), + })); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', { + GroupId: stack.resolve(sg.securityGroupId), + CidrIp: '0.0.0.0/0', + Description: 'Allow all outbound traffic by default', + IpProtocol: '-1', + })); + + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupIngress', {}))); + test.done(); + }, + + 'addIngressRule rule will not add a new egress rule'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext(SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY, contextDisableInlineRules); + const vpc = new Vpc(stack, 'VPC'); + const props: SecurityGroupProps = { vpc, allowAllOutbound: true, disableInlineRules: optionsDisableInlineRules }; + + // WHEN + const sg = new SecurityGroup(stack, 'SG1', props); + sg.addEgressRule(Peer.anyIpv4(), Port.tcp(86), 'An external Rule'); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Default/SG1', + VpcId: stack.resolve(vpc.vpcId), + })); + + expect(stack).to(not(haveResource('AWS::EC2::SecurityGroupEgress', { + GroupId: stack.resolve(sg.securityGroupId), + Description: 'An external Rule', + }))); + + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupIngress', {}))); + test.done(); + }, + + 'addIngressRule rule will add a new external ingress rule even if it could have been inlined'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext(SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY, contextDisableInlineRules); + const vpc = new Vpc(stack, 'VPC'); + const props: SecurityGroupProps = { vpc, allowAllOutbound: true, disableInlineRules: optionsDisableInlineRules }; + + // WHEN + const sg = new SecurityGroup(stack, 'SG1', props); + sg.addIngressRule(Peer.anyIpv4(), Port.tcp(86), 'An external Rule'); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Default/SG1', + VpcId: stack.resolve(vpc.vpcId), + })); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', { + GroupId: stack.resolve(sg.securityGroupId), + CidrIp: '0.0.0.0/0', + Description: 'An external Rule', + FromPort: 86, + IpProtocol: 'tcp', + ToPort: 86, + })); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', { + GroupId: stack.resolve(sg.securityGroupId), + CidrIp: '0.0.0.0/0', + Description: 'Allow all outbound traffic by default', + IpProtocol: '-1', + })); + test.done(); + }, + }, + + 'When do not allowAllOutbound': { + 'new SecurityGroup rule will create an external egress rule that denies all traffic'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext(SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY, contextDisableInlineRules); + const vpc = new Vpc(stack, 'VPC'); + const props: SecurityGroupProps = { vpc, allowAllOutbound: false, disableInlineRules: optionsDisableInlineRules }; + + // WHEN + const sg = new SecurityGroup(stack, 'SG1', props); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Default/SG1', + VpcId: stack.resolve(vpc.vpcId), + })); + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupIngress', {}))); + expect(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', { + GroupId: stack.resolve(sg.securityGroupId), + CidrIp: '255.255.255.255/32', + Description: 'Disallow all traffic', + IpProtocol: 'icmp', + FromPort: 252, + ToPort: 86, + })); + test.done(); + }, + + 'addEgressRule rule will remove the rule that denies all traffic if another egress rule is added'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext(SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY, contextDisableInlineRules); + const vpc = new Vpc(stack, 'VPC'); + const props: SecurityGroupProps = { vpc, allowAllOutbound: false, disableInlineRules: optionsDisableInlineRules }; + + // WHEN + const sg = new SecurityGroup(stack, 'SG1', props); + sg.addEgressRule(Peer.anyIpv4(), Port.tcp(86), 'An external Rule'); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Default/SG1', + VpcId: stack.resolve(vpc.vpcId), + })); + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupIngress', {}))); + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupEgress', { + GroupId: stack.resolve(sg.securityGroupId), + CidrIp: '255.255.255.255/32', + }))); + test.done(); + }, + + 'addEgressRule rule will add a new external egress rule even if it could have been inlined'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext(SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY, contextDisableInlineRules); + const vpc = new Vpc(stack, 'VPC'); + const props: SecurityGroupProps = { vpc, allowAllOutbound: false, disableInlineRules: optionsDisableInlineRules }; + + // WHEN + const sg = new SecurityGroup(stack, 'SG1', props); + sg.addEgressRule(Peer.anyIpv4(), Port.tcp(86), 'An external Rule'); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Default/SG1', + VpcId: stack.resolve(vpc.vpcId), + })); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', { + GroupId: stack.resolve(sg.securityGroupId), + CidrIp: '0.0.0.0/0', + Description: 'An external Rule', + FromPort: 86, + IpProtocol: 'tcp', + ToPort: 86, + })); + + expect(stack).to(not(haveResourceLike('AWS::EC2::SecurityGroupIngress', {}))); + test.done(); + }, + + 'addIngressRule will add a new external ingress rule even if it could have been inlined'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext(SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY, contextDisableInlineRules); + const vpc = new Vpc(stack, 'VPC'); + const props: SecurityGroupProps = { vpc, allowAllOutbound: false, disableInlineRules: optionsDisableInlineRules }; + + // WHEN + const sg = new SecurityGroup(stack, 'SG1', props); + sg.addIngressRule(Peer.anyIpv4(), Port.tcp(86), 'An external Rule'); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'Default/SG1', + VpcId: stack.resolve(vpc.vpcId), + })); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', { + GroupId: stack.resolve(sg.securityGroupId), + CidrIp: '0.0.0.0/0', + Description: 'An external Rule', + FromPort: 86, + IpProtocol: 'tcp', + ToPort: 86, + })); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', { + GroupId: stack.resolve(sg.securityGroupId), + CidrIp: '255.255.255.255/32', + Description: 'Disallow all traffic', + IpProtocol: 'icmp', + FromPort: 252, + ToPort: 86, + })); + test.done(); + }, + }, + }; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts index 8f99d5081f514..67d7b263973f8 100644 --- a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts +++ b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts @@ -1,13 +1,13 @@ import * as fs from 'fs'; import * as path from 'path'; import * as ecr from '@aws-cdk/aws-ecr'; -import { Annotations, FeatureFlags, IgnoreMode, Stack, Token } from '@aws-cdk/core'; +import { Annotations, AssetStaging, FeatureFlags, FileFingerprintOptions, IgnoreMode, Stack, SymlinkFollowMode, Token } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line -import { FingerprintOptions, IAsset, Staging } from '@aws-cdk/assets'; +import { FingerprintOptions, FollowMode, IAsset } from '@aws-cdk/assets'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line no-duplicate-imports, import/order import { Construct as CoreConstruct } from '@aws-cdk/core'; @@ -15,7 +15,7 @@ import { Construct as CoreConstruct } from '@aws-cdk/core'; /** * Options for DockerImageAsset */ -export interface DockerImageAssetOptions extends FingerprintOptions { +export interface DockerImageAssetOptions extends FingerprintOptions, FileFingerprintOptions { /** * ECR repository name * @@ -156,8 +156,9 @@ export class DockerImageAsset extends CoreConstruct implements IAsset { // deletion of the ECR repository the app used). extraHash.version = '1.21.0'; - const staging = new Staging(this, 'Staging', { + const staging = new AssetStaging(this, 'Staging', { ...props, + follow: props.followSymlinks ?? toSymlinkFollow(props.follow), exclude, ignoreMode, sourcePath: dir, @@ -200,3 +201,13 @@ function validateBuildArgs(buildArgs?: { [key: string]: string }) { } } } + +function toSymlinkFollow(follow?: FollowMode): SymlinkFollowMode | undefined { + switch (follow) { + case undefined: return undefined; + case FollowMode.NEVER: return SymlinkFollowMode.NEVER; + case FollowMode.ALWAYS: return SymlinkFollowMode.ALWAYS; + case FollowMode.BLOCK_EXTERNAL: return SymlinkFollowMode.BLOCK_EXTERNAL; + case FollowMode.EXTERNAL: return SymlinkFollowMode.EXTERNAL; + } +} diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts index d41a7684fd677..fa1d71cdb6d4a 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts @@ -1198,7 +1198,7 @@ export = { 'NetworkLoadBalancedEC2Service accepts imported load balancer'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const nlbArn = 'arn:aws:elasticloadbalancing::000000000000::dummyloadbalancer'; + const nlbArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const vpc = new ec2.Vpc(stack, 'Vpc'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); cluster.addCapacity('Capacity', { instanceType: new ec2.InstanceType('t2.micro') }); @@ -1274,7 +1274,7 @@ export = { 'ApplicationLoadBalancedEC2Service accepts imported load balancer'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const albArn = 'arn:aws:elasticloadbalancing::000000000000::dummyloadbalancer'; + const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const vpc = new ec2.Vpc(stack, 'Vpc'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); cluster.addCapacity('Capacity', { instanceType: new ec2.InstanceType('t2.micro') }); 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 3a17962c8b230..e7e847fb5cf45 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 @@ -810,7 +810,7 @@ export = { const stack1 = new cdk.Stack(app, 'MyStack'); const vpc1 = new ec2.Vpc(stack1, 'VPC'); const cluster1 = new ecs.Cluster(stack1, 'Cluster', { vpc: vpc1 }); - const nlbArn = 'arn:aws:elasticloadbalancing::000000000000::dummyloadbalancer'; + const nlbArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const stack2 = new cdk.Stack(stack1, 'Stack2'); const cluster2 = ecs.Cluster.fromClusterAttributes(stack2, 'ImportedCluster', { vpc: vpc1, @@ -887,7 +887,7 @@ export = { 'passing in imported application load balancer and resources to ALB Fargate Service'(test: Test) { // GIVEN const stack1 = new cdk.Stack(); - const albArn = 'arn:aws:elasticloadbalancing::000000000000::dummyloadbalancer'; + const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const vpc = new ec2.Vpc(stack1, 'Vpc'); const cluster = new ecs.Cluster(stack1, 'Cluster', { vpc, clusterName: 'MyClusterName' }); const sg = new ec2.SecurityGroup(stack1, 'SecurityGroup', { vpc }); diff --git a/packages/@aws-cdk/aws-ecs/lib/log-drivers/splunk-log-driver.ts b/packages/@aws-cdk/aws-ecs/lib/log-drivers/splunk-log-driver.ts index ecb4d9f0dd9c1..b536db6003ec3 100644 --- a/packages/@aws-cdk/aws-ecs/lib/log-drivers/splunk-log-driver.ts +++ b/packages/@aws-cdk/aws-ecs/lib/log-drivers/splunk-log-driver.ts @@ -145,6 +145,7 @@ export class SplunkLogDriver extends LogDriver { 'splunk-verify-connection': this.props.verifyConnection, 'splunk-gzip': this.props.gzip, 'splunk-gzip-level': this.props.gzipLevel, + 'splunk-tag': this.props.tag, ...renderCommonLogDriverOptions(this.props), }), }; diff --git a/packages/@aws-cdk/aws-ecs/test/splunk-log-driver.test.ts b/packages/@aws-cdk/aws-ecs/test/splunk-log-driver.test.ts index c8afd01daade1..fb1091a3ae027 100644 --- a/packages/@aws-cdk/aws-ecs/test/splunk-log-driver.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/splunk-log-driver.test.ts @@ -103,4 +103,35 @@ nodeunitShim({ test.done(); }, + + 'create a splunk log driver using splunk-tag property when tag is defined'(test: Test) { + // WHEN + td.addContainer('Container', { + image, + logging: ecs.LogDrivers.splunk({ + token: cdk.SecretValue.secretsManager('my-splunk-token'), + url: 'my-splunk-url', + tag: 'abc', + }), + memoryLimitMiB: 128, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + LogConfiguration: { + LogDriver: 'splunk', + Options: { + 'splunk-token': '{{resolve:secretsmanager:my-splunk-token:SecretString:::}}', + 'splunk-url': 'my-splunk-url', + 'splunk-tag': 'abc', + }, + }, + }, + ], + })); + + test.done(); + }, }); diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index 59909c9ea9a0b..1cc2ce685070b 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -34,7 +34,7 @@ In addition, the library also supports defining Kubernetes resource manifests wi * [Kubernetes Manifests](#kubernetes-manifests) * [Helm Charts](#helm-charts) * [CDK8s Charts](#cdk8s-charts) -* [Patching Kuberentes Resources](#patching-kubernetes-resources) +* [Patching Kubernetes Resources](#patching-kubernetes-resources) * [Querying Kubernetes Resources](#querying-kubernetes-resources) * [Using existing clusters](#using-existing-clusters) * [Known Issues and Limitations](#known-issues-and-limitations) diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/README.md b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/README.md index 9b3a3745cb679..b94a08b0c478a 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/README.md +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/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-elasticloadbalancingv2-targets/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json index 70428533a4624..ff7f061ba0769 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json @@ -89,8 +89,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-elasticloadbalancingv2-targets.InstanceTarget", diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts index 4ad4dcb5fa081..d686396d5e9e0 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts @@ -562,7 +562,10 @@ class ImportedApplicationLoadBalancer extends Resource implements IApplicationLo public readonly vpc?: ec2.IVpc; constructor(scope: Construct, id: string, private readonly props: ApplicationLoadBalancerAttributes) { - super(scope, id); + super(scope, id, { + environmentFromArn: props.loadBalancerArn, + }); + this.vpc = props.vpc; this.loadBalancerArn = props.loadBalancerArn; this.connections = new ec2.Connections({ @@ -601,7 +604,9 @@ class LookedUpApplicationLoadBalancer extends Resource implements IApplicationLo public readonly vpc?: ec2.IVpc; constructor(scope: Construct, id: string, props: cxapi.LoadBalancerContextResponse) { - super(scope, id); + super(scope, id, { + environmentFromArn: props.loadBalancerArn, + }); this.loadBalancerArn = props.loadBalancerArn; this.loadBalancerCanonicalHostedZoneId = props.loadBalancerCanonicalHostedZoneId; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts index d6f7858114102..5261a3ce8a63d 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts @@ -102,7 +102,7 @@ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoa } } - return new Import(scope, id); + return new Import(scope, id, { environmentFromArn: attrs.loadBalancerArn }); } constructor(scope: Construct, id: string, props: NetworkLoadBalancerProps) { @@ -306,7 +306,7 @@ class LookedUpNetworkLoadBalancer extends Resource implements INetworkLoadBalanc public readonly vpc?: ec2.IVpc; constructor(scope: Construct, id: string, props: cxapi.LoadBalancerContextResponse) { - super(scope, id); + super(scope, id, { environmentFromArn: props.loadBalancerArn }); this.loadBalancerArn = props.loadBalancerArn; this.loadBalancerCanonicalHostedZoneId = props.loadBalancerCanonicalHostedZoneId; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts index f017a4a67f3fa..1ae34a272dac4 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts @@ -284,7 +284,7 @@ describe('tests', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc'); - const albArn = 'myArn'; + const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const sg = new ec2.SecurityGroup(stack, 'sg', { vpc, securityGroupName: 'mySg', @@ -303,7 +303,7 @@ describe('tests', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc'); - const albArn = 'MyArn'; + const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const sg = new ec2.SecurityGroup(stack, 'sg', { vpc, securityGroupName: 'mySg', @@ -319,6 +319,20 @@ describe('tests', () => { expect(() => listener.addTargets('Targets', { port: 8080 })).not.toThrow(); }); + test('imported load balancer knows its region', () => { + const stack = new cdk.Stack(); + + // WHEN + const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; + const alb = elbv2.ApplicationLoadBalancer.fromApplicationLoadBalancerAttributes(stack, 'ALB', { + loadBalancerArn: albArn, + securityGroupId: 'sg-1234', + }); + + // THEN + expect(alb.env.region).toEqual('us-west-2'); + }); + test('can add secondary security groups', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); @@ -364,6 +378,7 @@ describe('tests', () => { expect(loadBalancer.loadBalancerDnsName).toEqual('my-load-balancer-1234567890.us-west-2.elb.amazonaws.com'); expect(loadBalancer.ipAddressType).toEqual(elbv2.IpAddressType.DUAL_STACK); expect(loadBalancer.connections.securityGroups[0].securityGroupId).toEqual('sg-12345'); + expect(loadBalancer.env.region).toEqual('us-west-2'); }); test('Can add listeners to a looked-up ApplicationLoadBalancer', () => { diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts index 546b88ab7d541..bb9e6a30ddecd 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts @@ -227,7 +227,7 @@ describe('tests', () => { test('imported network load balancer with no vpc specified throws error when calling addTargets', () => { // GIVEN const stack = new cdk.Stack(); - const nlbArn = 'arn:aws:elasticloadbalancing::000000000000::dummyloadbalancer'; + const nlbArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const nlb = elbv2.NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'NLB', { loadBalancerArn: nlbArn, }); @@ -240,7 +240,7 @@ describe('tests', () => { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Vpc'); - const nlbArn = 'arn:aws:elasticloadbalancing::000000000000::dummyloadbalancer'; + const nlbArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; const nlb = elbv2.NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'NLB', { loadBalancerArn: nlbArn, vpc, @@ -250,6 +250,19 @@ describe('tests', () => { expect(() => listener.addTargets('targetgroup', { port: 8080 })).not.toThrow(); }); + test('imported load balancer knows its region', () => { + const stack = new cdk.Stack(); + + // WHEN + const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; + const alb = elbv2.NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'ALB', { + loadBalancerArn: albArn, + }); + + // THEN + expect(alb.env.region).toEqual('us-west-2'); + }); + test('Trivial construction: internal with Isolated subnets only', () => { // GIVEN const stack = new cdk.Stack(); @@ -429,6 +442,7 @@ describe('tests', () => { expect(loadBalancer.loadBalancerArn).toEqual('arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/network/my-load-balancer/50dc6c495c0c9188'); expect(loadBalancer.loadBalancerCanonicalHostedZoneId).toEqual('Z3DZXE0EXAMPLE'); expect(loadBalancer.loadBalancerDnsName).toEqual('my-load-balancer-1234567890.us-west-2.elb.amazonaws.com'); + expect(loadBalancer.env.region).toEqual('us-west-2'); }); test('Can add listeners to a looked-up NetworkLoadBalancer', () => { diff --git a/packages/@aws-cdk/aws-fsx/README.md b/packages/@aws-cdk/aws-fsx/README.md index bcb2497f145ff..ebcd311266036 100644 --- a/packages/@aws-cdk/aws-fsx/README.md +++ b/packages/@aws-cdk/aws-fsx/README.md @@ -5,17 +5,7 @@ ![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) -> All classes with the `Cfn` prefix in this module ([CFN Resources]) are always stable and safe to use. -> -> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib - -![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-fsx/package.json b/packages/@aws-cdk/aws-fsx/package.json index 016a7bb6a7a03..419bafd1d7ef3 100644 --- a/packages/@aws-cdk/aws-fsx/package.json +++ b/packages/@aws-cdk/aws-fsx/package.json @@ -102,8 +102,8 @@ "resource-interface:@aws-cdk/aws-fsx.LustreFileSystem" ] }, - "stability": "experimental", - "maturity": "experimental", + "stability": "stable", + "maturity": "stable", "awscdkio": { "announce": false }, diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/.eslintrc.js b/packages/@aws-cdk/aws-globalaccelerator-endpoints/.eslintrc.js new file mode 100644 index 0000000000000..61dd8dd001f63 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/.gitignore b/packages/@aws-cdk/aws-globalaccelerator-endpoints/.gitignore new file mode 100644 index 0000000000000..2ed02868c78fb --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/.gitignore @@ -0,0 +1,22 @@ +*.js +tsconfig.json +*.js.map +*.d.ts +*.generated.ts +dist +lib/generated/resources.ts +.jsii + +.LAST_BUILD +.nyc_output +coverage +nyc.config.js +.LAST_PACKAGE +*.snk +.cdk.staging + +lib/sdk-api-metadata.json +!.eslintrc.js +!jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/.npmignore b/packages/@aws-cdk/aws-globalaccelerator-endpoints/.npmignore new file mode 100644 index 0000000000000..63ab95621c764 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/.npmignore @@ -0,0 +1,27 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js + +# Include .jsii +!.jsii + +*.snk + +*.tsbuildinfo + +tsconfig.json +.eslintrc.js +jest.config.js + +# exclude cdk artifacts +**/cdk.out +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/LICENSE b/packages/@aws-cdk/aws-globalaccelerator-endpoints/LICENSE new file mode 100644 index 0000000000000..28e4bdcec77ec --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/NOTICE b/packages/@aws-cdk/aws-globalaccelerator-endpoints/NOTICE new file mode 100644 index 0000000000000..5fc3826926b5b --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/README.md b/packages/@aws-cdk/aws-globalaccelerator-endpoints/README.md new file mode 100644 index 0000000000000..d4fe7f8159b5d --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/README.md @@ -0,0 +1,18 @@ +# Endpoints for AWS Global Accelerator + + +--- + +![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge) + +--- + + + +This library contains integration classes to reference endpoints in AWS +Global Accelerator. Instances of these classes should be passed to the +`endpointGroup.addEndpoint()` method. + +See the README of the `@aws-cdk/aws-globalaccelerator` library for more information on +AWS Global Accelerator, and examples of all the integration classes available in +this module. diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/jest.config.js b/packages/@aws-cdk/aws-globalaccelerator-endpoints/jest.config.js new file mode 100644 index 0000000000000..49e81658a0875 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/jest.config.js @@ -0,0 +1,11 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = { + ...baseConfig, + coverageThreshold: { + global: { + ...baseConfig.coverageThreshold.global, + branches: 50, + }, + }, +}; + diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/_util.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/_util.ts new file mode 100644 index 0000000000000..aee44257d056b --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/_util.ts @@ -0,0 +1,7 @@ +import { Token } from '@aws-cdk/core'; + +export function validateWeight(x?: number) { + if (x !== undefined && !Token.isUnresolved(x) && (x < 0 || x > 255)) { + throw new Error(`'weight' must be between 0 and 255, got: ${x}`); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/alb.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/alb.ts new file mode 100644 index 0000000000000..1c4f618c16235 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/alb.ts @@ -0,0 +1,50 @@ +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as ga from '@aws-cdk/aws-globalaccelerator'; +import { validateWeight } from './_util'; + +/** + * Properties for a ApplicationLoadBalancerEndpoint + */ +export interface ApplicationLoadBalancerEndpointOptions { + /** + * Endpoint weight across all endpoints in the group + * + * Must be a value between 0 and 255. + * + * @default 128 + */ + readonly weight?: number; + + /** + * Forward the client IP address in an `X-Forwarded-For` header + * + * GlobalAccelerator will create Network Interfaces in your VPC in order + * to preserve the client IP address. + * + * Client IP address preservation is supported only in specific AWS Regions. + * See the GlobalAccelerator Developer Guide for a list. + * + * @default true if available + */ + readonly preserveClientIp?: boolean; +} + +/** + * Use an Application Load Balancer as a Global Accelerator Endpoint + */ +export class ApplicationLoadBalancerEndpoint implements ga.IEndpoint { + public readonly region?: string; + + constructor(private readonly loadBalancer: elbv2.IApplicationLoadBalancer, private readonly options: ApplicationLoadBalancerEndpointOptions = {}) { + validateWeight(options.weight); + this.region = loadBalancer.env.region; + } + + public renderEndpointConfiguration(): any { + return { + endpointId: this.loadBalancer.loadBalancerArn, + weight: this.options.weight, + clientIpPreservationEnabled: this.options.preserveClientIp, + } as ga.CfnEndpointGroup.EndpointConfigurationProperty; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/eip.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/eip.ts new file mode 100644 index 0000000000000..924eb391efc0b --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/eip.ts @@ -0,0 +1,38 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ga from '@aws-cdk/aws-globalaccelerator'; +import { Stack } from '@aws-cdk/core'; +import { validateWeight } from './_util'; + +/** + * Properties for a NetworkLoadBalancerEndpoint + */ +export interface CfnEipEndpointProps { + /** + * Endpoint weight across all endpoints in the group + * + * Must be a value between 0 and 255. + * + * @default 128 + */ + readonly weight?: number; +} + +/** + * Use an EC2 Instance as a Global Accelerator Endpoint + */ +export class CfnEipEndpoint implements ga.IEndpoint { + public readonly region?: string; + + constructor(private readonly eip: ec2.CfnEIP, private readonly options: CfnEipEndpointProps = {}) { + validateWeight(options.weight); + + this.region = Stack.of(eip).region; + } + + public renderEndpointConfiguration(): any { + return { + endpointId: this.eip.attrAllocationId, + weight: this.options.weight, + } as ga.CfnEndpointGroup.EndpointConfigurationProperty; + } +} diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/index.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/index.ts new file mode 100644 index 0000000000000..4286ae4cbd68c --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/index.ts @@ -0,0 +1,4 @@ +export * from './alb'; +export * from './nlb'; +export * from './instance'; +export * from './eip'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/instance.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/instance.ts new file mode 100644 index 0000000000000..a5d16298ec514 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/instance.ts @@ -0,0 +1,51 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ga from '@aws-cdk/aws-globalaccelerator'; +import { validateWeight } from './_util'; + +/** + * Properties for a NetworkLoadBalancerEndpoint + */ +export interface InstanceEndpointProps { + /** + * Endpoint weight across all endpoints in the group + * + * Must be a value between 0 and 255. + * + * @default 128 + */ + readonly weight?: number; + + /** + * Forward the client IP address + * + * GlobalAccelerator will create Network Interfaces in your VPC in order + * to preserve the client IP address. + * + * Client IP address preservation is supported only in specific AWS Regions. + * See the GlobalAccelerator Developer Guide for a list. + * + * @default true if available + */ + readonly preserveClientIp?: boolean; +} + +/** + * Use an EC2 Instance as a Global Accelerator Endpoint + */ +export class InstanceEndpoint implements ga.IEndpoint { + public readonly region?: string; + + constructor(private readonly instance: ec2.IInstance, private readonly options: InstanceEndpointProps = {}) { + validateWeight(options.weight); + + this.region = instance.env.region; + } + + public renderEndpointConfiguration(): any { + return { + endpointId: this.instance.instanceId, + weight: this.options.weight, + clientIpPreservationEnabled: this.options.preserveClientIp, + } as ga.CfnEndpointGroup.EndpointConfigurationProperty; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/nlb.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/nlb.ts new file mode 100644 index 0000000000000..f94bfff5f5357 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/lib/nlb.ts @@ -0,0 +1,36 @@ +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as ga from '@aws-cdk/aws-globalaccelerator'; +import { validateWeight } from './_util'; + +/** + * Properties for a NetworkLoadBalancerEndpoint + */ +export interface NetworkLoadBalancerEndpointProps { + /** + * Endpoint weight across all endpoints in the group + * + * Must be a value between 0 and 255. + * + * @default 128 + */ + readonly weight?: number; +} + +/** + * Use a Network Load Balancer as a Global Accelerator Endpoint + */ +export class NetworkLoadBalancerEndpoint implements ga.IEndpoint { + public readonly region?: string; + + constructor(private readonly loadBalancer: elbv2.INetworkLoadBalancer, private readonly options: NetworkLoadBalancerEndpointProps = {}) { + validateWeight(options.weight); + this.region = loadBalancer.env.region; + } + + public renderEndpointConfiguration(): any { + return { + endpointId: this.loadBalancer.loadBalancerArn, + weight: this.options.weight, + } as ga.CfnEndpointGroup.EndpointConfigurationProperty; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json b/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json new file mode 100644 index 0000000000000..fa8fc01ec835a --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/package.json @@ -0,0 +1,105 @@ +{ + "name": "@aws-cdk/aws-globalaccelerator-endpoints", + "version": "0.0.0", + "description": "Endpoints for AWS Global Accelerator", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.GlobalAccelerator.Endpoints", + "packageId": "Amazon.CDK.AWS.GlobalAccelerator.Endpoints", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "java": { + "package": "software.amazon.awscdk.services.globalaccelerator.endpoints", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "globalaccelerator-endpoints" + } + }, + "python": { + "distName": "aws-cdk.aws-globalaccelerator-endpoints", + "module": "aws_cdk.aws_globalaccelerator_endpoints", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] + } + }, + "projectReferences": true + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-globalaccelerator-endpoints" + }, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "build+test+package": "yarn build+test && yarn package", + "build+test": "yarn build && yarn test", + "compat": "cdk-compat", + "rosetta:extract": "yarn --silent jsii-rosetta extract" + }, + "cdk-build": { + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "globalaccelerator" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "0.0.0", + "aws-sdk": "^2.848.0", + "aws-sdk-mock": "^5.1.0", + "cdk-build-tools": "0.0.0", + "cdk-integ-tools": "0.0.0", + "jest": "^26.6.3", + "pkglint": "0.0.0" + }, + "dependencies": { + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", + "@aws-cdk/aws-globalaccelerator": "0.0.0", + "@aws-cdk/core": "0.0.0", + "constructs": "^3.3.69" + }, + "homepage": "https://github.com/aws/aws-cdk", + "peerDependencies": { + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", + "@aws-cdk/aws-globalaccelerator": "0.0.0", + "@aws-cdk/core": "0.0.0", + "constructs": "^3.3.69" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "stable", + "awscdkio": { + "announce": false + }, + "maturity": "stable", + "publishConfig": { + "tag": "latest" + } +} diff --git a/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/endpoints.test.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/endpoints.test.ts new file mode 100644 index 0000000000000..068d885c32586 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/endpoints.test.ts @@ -0,0 +1,183 @@ +import '@aws-cdk/assert/jest'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as ga from '@aws-cdk/aws-globalaccelerator'; +import { Stack } from '@aws-cdk/core'; +import * as endpoints from '../lib'; + +let stack: Stack; +let vpc: ec2.Vpc; +let accelerator: ga.Accelerator; +let listener: ga.Listener; +beforeEach(() => { + stack = new Stack(); + vpc = new ec2.Vpc(stack, 'Vpc'); + + accelerator = new ga.Accelerator(stack, 'Accelerator'); + listener = accelerator.addListener('Listener', { + portRanges: [{ fromPort: 80 }], + }); +}); + +test('Application Load Balancer with all properties', () => { + // WHEN + const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc, internetFacing: true }); + listener.addEndpointGroup('Group', { + endpoints: [ + new endpoints.ApplicationLoadBalancerEndpoint(alb, { + weight: 50, + preserveClientIp: true, + }), + ], + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointConfigurations: [ + { + EndpointId: { Ref: 'ALBAEE750D2' }, + Weight: 50, + ClientIPPreservationEnabled: true, + }, + ], + }); +}); + +// Doesn't work yet because 'fromApplicationLoadBalancerAttributes' doesn't set the imported resource env +test('Get region from imported ALB', () => { + // WHEN + const alb = elbv2.ApplicationLoadBalancer.fromApplicationLoadBalancerAttributes(stack, 'ALB', { + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', + securityGroupId: 'sg-1234', + }); + listener.addEndpointGroup('Group', { + endpoints: [ + new endpoints.ApplicationLoadBalancerEndpoint(alb), + ], + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointGroupRegion: 'us-west-2', + EndpointConfigurations: [ + { + EndpointId: 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', + }, + ], + }); +}); + +test('Network Load Balancer with all properties', () => { + // WHEN + const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', { vpc, internetFacing: true }); + listener.addEndpointGroup('Group', { + endpoints: [ + new endpoints.NetworkLoadBalancerEndpoint(nlb, { + weight: 50, + }), + ], + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointConfigurations: [ + { + EndpointId: { Ref: 'NLB55158F82' }, + Weight: 50, + }, + ], + }); +}); + +// Doesn't work yet because 'fromNetworkLoadBalancerAttributes' doesn't set the imported resource env +test('Get region from imported NLB', () => { + // WHEN + const nlb = elbv2.NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'NLB', { + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', + }); + listener.addEndpointGroup('Group', { + endpoints: [ + new endpoints.NetworkLoadBalancerEndpoint(nlb), + ], + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointGroupRegion: 'us-west-2', + EndpointConfigurations: [ + { + EndpointId: 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', + }, + ], + }); +}); + +test('CFN EIP with all properties', () => { + // WHEN + const eip = new ec2.CfnEIP(stack, 'ElasticIpAddress'); + listener.addEndpointGroup('Group', { + endpoints: [ + new endpoints.CfnEipEndpoint(eip, { + weight: 50, + }), + ], + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointConfigurations: [ + { + EndpointId: { 'Fn::GetAtt': ['ElasticIpAddress', 'AllocationId'] }, + Weight: 50, + }, + ], + }); +}); + +test('EC2 Instance with all properties', () => { + // WHEN + const instance = new ec2.Instance(stack, 'Instance', { + vpc, + machineImage: new ec2.AmazonLinuxImage(), + instanceType: new ec2.InstanceType('t3.small'), + }); + listener.addEndpointGroup('Group', { + endpoints: [ + new endpoints.InstanceEndpoint(instance, { + weight: 50, + preserveClientIp: true, + }), + ], + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { + EndpointConfigurations: [ + { + EndpointId: { Ref: 'InstanceC1063A87' }, + Weight: 50, + ClientIPPreservationEnabled: true, + }, + ], + }); +}); + +test('throws if weight is not in range', () => { + // WHEN + const instance = new ec2.Instance(stack, 'Instance', { + vpc, + machineImage: new ec2.AmazonLinuxImage(), + instanceType: new ec2.InstanceType('t3.small'), + }); + + expect(() => { + listener.addEndpointGroup('Group', { + endpoints: [ + new endpoints.InstanceEndpoint(instance, { + weight: 300, + preserveClientIp: true, + }), + ], + }); + }).toThrow(/'weight' must be between 0 and 255/); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.expected.json b/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.expected.json similarity index 79% rename from packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.expected.json rename to packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.expected.json index 6e141037d0f36..36f2af987a0e2 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.expected.json +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.expected.json @@ -453,7 +453,7 @@ "Accelerator8EB0B6B1": { "Type": "AWS::GlobalAccelerator::Accelerator", "Properties": { - "Name": "Accelerator", + "Name": "integglobalacceleratorAccelerator5D88FB42", "Enabled": true } }, @@ -476,50 +476,6 @@ "ClientAffinity": "NONE" } }, - "GroupC77FDACD": { - "Type": "AWS::GlobalAccelerator::EndpointGroup", - "Properties": { - "EndpointGroupRegion": { - "Ref": "AWS::Region" - }, - "ListenerArn": { - "Fn::GetAtt": [ - "Listener828B0E81", - "ListenerArn" - ] - }, - "EndpointConfigurations": [ - { - "EndpointId": { - "Ref": "ALBAEE750D2" - } - }, - { - "EndpointId": { - "Ref": "NLB55158F82" - } - }, - { - "EndpointId": { - "Fn::GetAtt": [ - "ElasticIpAddress", - "AllocationId" - ] - } - }, - { - "EndpointId": { - "Ref": "Instance008A4B15C" - } - }, - { - "EndpointId": { - "Ref": "Instance14BC3991D" - } - } - ] - } - }, "ALBAEE750D2": { "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "Properties": { @@ -575,6 +531,27 @@ } } }, + "ALBSecurityGroupfromGlobalAcceleratorGroup4435D2AC398": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from GlobalAcceleratorGroup:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "ALBSecurityGroup8B8624F8", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "GroupPeerCustomResourceB3A15D36", + "SecurityGroups.0.GroupId" + ] + }, + "ToPort": 443 + } + }, "NLB55158F82": { "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "Properties": { @@ -808,12 +785,208 @@ "DependsOn": [ "Instance1InstanceRoleBC4D05C6" ] + }, + "GroupC77FDACD": { + "Type": "AWS::GlobalAccelerator::EndpointGroup", + "Properties": { + "EndpointGroupRegion": { + "Ref": "AWS::Region" + }, + "ListenerArn": { + "Fn::GetAtt": [ + "Listener828B0E81", + "ListenerArn" + ] + }, + "EndpointConfigurations": [ + { + "EndpointId": { + "Ref": "ALBAEE750D2" + } + }, + { + "EndpointId": { + "Ref": "NLB55158F82" + } + }, + { + "EndpointId": { + "Fn::GetAtt": [ + "ElasticIpAddress", + "AllocationId" + ] + } + }, + { + "EndpointId": { + "Ref": "Instance008A4B15C" + } + }, + { + "EndpointId": { + "Ref": "Instance14BC3991D" + } + } + ] + } + }, + "GroupPeerCustomResourceCustomResourcePolicy42EF8263": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "ec2:DescribeSecurityGroups", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "GroupPeerCustomResourceCustomResourcePolicy42EF8263", + "Roles": [ + { + "Ref": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2" + } + ] + }, + "DependsOn": [ + "GroupC77FDACD" + ] + }, + "GroupPeerCustomResourceB3A15D36": { + "Type": "Custom::AWS", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "AWS679f53fac002430cb0da5b7982bd22872D164C4C", + "Arn" + ] + }, + "Create": { + "Fn::Join": [ + "", + [ + "{\"service\":\"EC2\",\"action\":\"describeSecurityGroups\",\"parameters\":{\"Filters\":[{\"Name\":\"group-name\",\"Values\":[\"GlobalAccelerator\"]},{\"Name\":\"vpc-id\",\"Values\":[\"", + { + "Ref": "VPCB9E5F0B4" + }, + "\"]}]},\"physicalResourceId\":{\"responsePath\":\"SecurityGroups.0.GroupId\"}}" + ] + ] + }, + "InstallLatestAwsSdk": true + }, + "DependsOn": [ + "GroupPeerCustomResourceCustomResourcePolicy42EF8263", + "GroupC77FDACD" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "AWS679f53fac002430cb0da5b7982bd22872D164C4C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343S3Bucket65227904" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343S3VersionKey3AF0E7DF" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343S3VersionKey3AF0E7DF" + } + ] + } + ] + } + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "nodejs12.x", + "Timeout": 120 + }, + "DependsOn": [ + "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2" + ] } }, "Parameters": { "SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", "Default": "/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2" + }, + "AssetParameters0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343S3Bucket65227904": { + "Type": "String", + "Description": "S3 bucket for asset \"0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343\"" + }, + "AssetParameters0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343S3VersionKey3AF0E7DF": { + "Type": "String", + "Description": "S3 key for asset version \"0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343\"" + }, + "AssetParameters0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343ArtifactHash0C561FF5": { + "Type": "String", + "Description": "Artifact hash for asset \"0625b1566df06e0ffd948f0f65f97a3d22d48242e66196d3f72b480f5309b343\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.ts b/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.ts similarity index 63% rename from packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.ts rename to packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.ts index bee56b820d7ec..d4b5c2ada5ca1 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/test/integ.globalaccelerator.ts +++ b/packages/@aws-cdk/aws-globalaccelerator-endpoints/test/integ.globalaccelerator.ts @@ -1,12 +1,11 @@ - import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; -import * as cdk from '@aws-cdk/core'; +import * as ga from '@aws-cdk/aws-globalaccelerator'; +import { App, Stack } from '@aws-cdk/core'; import * as constructs from 'constructs'; -import * as ga from '../lib'; -import * as testfixture from './util'; +import * as endpoints from '../lib'; -class GaStack extends testfixture.TestStack { +class GaStack extends Stack { constructor(scope: constructs.Construct, id: string) { super(scope, id); @@ -21,7 +20,6 @@ class GaStack extends testfixture.TestStack { }, ], }); - const endpointGroup = new ga.EndpointGroup(this, 'Group', { listener }); const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', { vpc, internetFacing: true }); const nlb = new elbv2.NetworkLoadBalancer(this, 'NLB', { vpc, internetFacing: true }); const eip = new ec2.CfnEIP(this, 'ElasticIpAddress'); @@ -35,15 +33,20 @@ class GaStack extends testfixture.TestStack { })); } - endpointGroup.addLoadBalancer('AlbEndpoint', alb); - endpointGroup.addLoadBalancer('NlbEndpoint', nlb); - endpointGroup.addElasticIpAddress('EipEndpoint', eip); - endpointGroup.addEc2Instance('InstanceEndpoint', instances[0]); - endpointGroup.addEndpoint('InstanceEndpoint2', instances[1].instanceId); + const group = new ga.EndpointGroup(this, 'Group', { + listener, + endpoints: [ + new endpoints.ApplicationLoadBalancerEndpoint(alb), + new endpoints.NetworkLoadBalancerEndpoint(nlb), + new endpoints.CfnEipEndpoint(eip), + new endpoints.InstanceEndpoint(instances[0]), + new endpoints.InstanceEndpoint(instances[1]), + ], + }); + alb.connections.allowFrom(group.connectionsPeer('Peer', vpc), ec2.Port.tcp(443)); } } -const app = new cdk.App(); - +const app = new App(); new GaStack(app, 'integ-globalaccelerator'); diff --git a/packages/@aws-cdk/aws-globalaccelerator/README.md b/packages/@aws-cdk/aws-globalaccelerator/README.md index d959a2e9238ad..bdbb6780fadd2 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/README.md +++ b/packages/@aws-cdk/aws-globalaccelerator/README.md @@ -5,17 +5,7 @@ ![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) -> All classes with the `Cfn` prefix in this module ([CFN Resources]) are always stable and safe to use. -> -> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib - -![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) --- @@ -23,115 +13,178 @@ ## Introduction -AWS Global Accelerator (AGA) is a service that improves the availability and performance of your applications with local or global users. It provides static IP addresses that act as a fixed entry point to your application endpoints in a single or multiple AWS Regions, such as your Application Load Balancers, Network Load Balancers or Amazon EC2 instances. +AWS Global Accelerator (AGA) is a service that improves the availability and +performance of your applications with local or global users. + +It intercepts your user's network connection at an edge location close to +them, and routes it to one of potentially multiple, redundant backends across +the more reliable and less congested AWS global network. -This module supports features under [AWS Global Accelerator](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_GlobalAccelerator.html) that allows users set up resources using the `@aws-cdk/aws-globalaccelerator` module. +AGA can be used to route traffic to Application Load Balancers, Network Load +Balancers, EC2 Instances and Elastic IP Addresses. -## Accelerator +For more information, see the [AWS Global +Accelerator Developer Guide](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_GlobalAccelerator.html). -The `Accelerator` resource is a Global Accelerator resource type that contains information about how you create an accelerator. An accelerator includes one or more listeners that process inbound connections and direct traffic to one or more endpoint groups, each of which includes endpoints, such as Application Load Balancers, Network Load Balancers, and Amazon EC2 instances. +## Example -To create the `Accelerator`: +Here's an example that sets up a Global Accelerator for two Application Load +Balancers in two different AWS Regions: ```ts import globalaccelerator = require('@aws-cdk/aws-globalaccelerator'); +import ga_endpoints = require('@aws-cdk/aws-globalaccelerator-endpoints'); +import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); -new globalaccelerator.Accelerator(stack, 'Accelerator'); +// Create an Accelerator +const accelerator = new globalaccelerator.Accelerator(stack, 'Accelerator'); + +// Create a Listener +const listener = accelerator.addListener('Listener', { + portRanges: [ + { fromPort: 80 }, + { fromPort: 443 }, + ], +}); + +// Import the Load Balancers +const nlb1 = elbv2.NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'NLB1', { + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-west-2:111111111111:loadbalancer/app/my-load-balancer1/e16bef66805b', +}); +const nlb2 = elbv2.NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'NLB2', { + loadBalancerArn: 'arn:aws:elasticloadbalancing:ap-south-1:111111111111:loadbalancer/app/my-load-balancer2/5513dc2ea8a1', +}); +// Add one EndpointGroup for each Region we are targeting +listener.addEndpointGroup('Group1', { + endpoints: [new ga_endpoints.NetworkLoadBalancerEndpoint(nlb1)], +}); +listener.addEndpointGroup('Group2', { + // Imported load balancers automatically calculate their Region from the ARN. + // If you are load balancing to other resources, you must also pass a `region` + // parameter here. + endpoints: [new ga_endpoints.NetworkLoadBalancerEndpoint(nlb2)], +}); ``` -## Listener +## Concepts + +The **Accelerator** construct defines a Global Accelerator resource. + +An Accelerator includes one or more **Listeners** that accepts inbound +connections on one or more ports. + +Each Listener has one or more **Endpoint Groups**, representing multiple +geographically distributed copies of your application. There is one Endpoint +Group per Region, and user traffic is routed to the closest Region by default. + +An Endpoint Group consists of one or more **Endpoints**, which is where the +user traffic coming in on the Listener is ultimately sent. The Endpoint port +used is the same as the traffic came in on at the Listener, unless overridden. + +## Types of Endpoints + +There are 4 types of Endpoints, and they can be found in the +`@aws-cdk/aws-globalaccelerator-endpoints` package: -The `Listener` resource is a Global Accelerator resource type that contains information about how you create a listener to process inbound connections from clients to an accelerator. Connections arrive to assigned static IP addresses on a port, port range, or list of port ranges that you specify. +* Application Load Balancers +* Network Load Balancers +* EC2 Instances +* Elastic IP Addresses -To create the `Listener` listening on TCP 80: +### Application Load Balancers ```ts -new globalaccelerator.Listener(stack, 'Listener', { - accelerator, - portRanges: [ - { - fromPort: 80, - toPort: 80, - }, +const alb = new elbv2.ApplicationLoadBalancer(...); + +listener.addEndpointGroup('Group', { + endpoints: [ + new ga_endpoints.ApplicationLoadBalancerEndpoint(alb, { + weight: 128, + preserveClientIp: true, + }), ], }); ``` - -## EndpointGroup - -The `EndpointGroup` resource is a Global Accelerator resource type that contains information about how you create an endpoint group for the specified listener. An endpoint group is a collection of endpoints in one AWS Region. - -To create the `EndpointGroup`: +### Network Load Balancers ```ts -new globalaccelerator.EndpointGroup(stack, 'Group', { listener }); +const nlb = new elbv2.NetworkLoadBalancer(...); +listener.addEndpointGroup('Group', { + endpoints: [ + new ga_endpoints.NetworkLoadBalancerEndpoint(nlb, { + weight: 128, + }), + ], +}); ``` -## Add Endpoint into EndpointGroup - -You may use the following methods to add endpoints into the `EndpointGroup`: +### EC2 Instances -- `addEndpoint` to add a generic `endpoint` into the `EndpointGroup`. -- `addLoadBalancer` to add an Application Load Balancer or Network Load Balancer. -- `addEc2Instance` to add an EC2 Instance. -- `addElasticIpAddress` to add an Elastic IP Address. +```ts +const instance = new ec2.instance(...); + +listener.addEndpointGroup('Group', { + endpoints: [ + new ga_endpoints.InstanceEndpoint(instance, { + weight: 128, + preserveClientIp: true, + }), + ], +}); +``` +### Elastic IP Addresses ```ts -const endpointGroup = new globalaccelerator.EndpointGroup(stack, 'Group', { listener }); -const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc, internetFacing: true }); -const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', { vpc, internetFacing: true }); -const eip = new ec2.CfnEIP(stack, 'ElasticIpAddress'); -const instances = new Array(); - -for ( let i = 0; i < 2; i++) { - instances.push(new ec2.Instance(stack, `Instance${i}`, { - vpc, - machineImage: new ec2.AmazonLinuxImage(), - instanceType: new ec2.InstanceType('t3.small'), - })); -} - -endpointGroup.addLoadBalancer('AlbEndpoint', alb); -endpointGroup.addLoadBalancer('NlbEndpoint', nlb); -endpointGroup.addElasticIpAddress('EipEndpoint', eip); -endpointGroup.addEc2Instance('InstanceEndpoint', instances[0]); -endpointGroup.addEndpoint('InstanceEndpoint2', instances[1].instanceId); +const eip = new ec2.CfnEIP(...); + +listener.addEndpointGroup('Group', { + endpoints: [ + new ga_endpoints.CfnEipEndpoint(eip, { + weight: 128, + }), + ], +}); ``` -## Accelerator Security Groups +## Client IP Address Preservation and Security Groups -When using certain AGA features (client IP address preservation), AGA creates elastic network interfaces (ENI) in your AWS account which are -associated with a Security Group, and which are reused for all AGAs associated with that VPC. Per the -[best practices](https://docs.aws.amazon.com/global-accelerator/latest/dg/best-practices-aga.html) page, AGA creates a specific security group -called `GlobalAccelerator` for each VPC it has an ENI in. You can use the security group created by AGA as a source group in other security -groups, such as those for EC2 instances or Elastic Load Balancers, in order to implement least-privilege security group rules. +When using the `preserveClientIp` feature, AGA creates +**Elastic Network Interfaces** (ENIs) in your AWS account, that are +associated with a Security Group AGA creates for you. You can use the +security group created by AGA as a source group in other security groups +(such as those for EC2 instances or Elastic Load Balancers), if you want to +restrict incoming traffic to the AGA security group rules. -CloudFormation doesn't support referencing the security group created by AGA. CDK has a library that enables you to reference the AGA security group -for a VPC using an AwsCustomResource. +AGA creates a specific security group called `GlobalAccelerator` for each VPC +it has an ENI in (this behavior can not be changed). CloudFormation doesn't +support referencing the security group created by AGA, but this construct +library comes with a custom resource that enables you to reference the AGA +security group. + +Call `endpointGroup.connectionsPeer()` to obtain a reference to the Security Group +which you can use in connection rules. You must pass a reference to the VPC in whose +context the security group will be looked up. Example: ```ts -const vpc = new Vpc(stack, 'VPC', {}); -const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc, internetFacing: false }); -const accelerator = new ga.Accelerator(stack, 'Accelerator'); -const listener = new ga.Listener(stack, 'Listener', { - accelerator, - portRanges: [ - { - fromPort: 443, - toPort: 443, - }, +// ... + +// Non-open ALB +const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { /* ... */ }); + +const endpointGroup = listener.addEndpointGroup('Group', { + endpoints: [ + new ga_endpoints.ApplicationLoadBalancerEndpoint(alb, { + preserveClientIps: true, + })], ], }); -const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); -endpointGroup.addLoadBalancer('AlbEndpoint', alb); // Remember that there is only one AGA security group per VPC. -// This code will fail at CloudFormation deployment time if you do not have an AGA -const agaSg = ga.AcceleratorSecurityGroup.fromVpc(stack, 'GlobalAcceleratorSG', vpc); +const agaSg = endpointGroup.connectionsPeer('GlobalAcceleratorSG', vpc); // Allow connections from the AGA to the ALB alb.connections.allowFrom(agaSg, Port.tcp(443)); diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator-security-group.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/_accelerator-security-group.ts similarity index 72% rename from packages/@aws-cdk/aws-globalaccelerator/lib/accelerator-security-group.ts rename to packages/@aws-cdk/aws-globalaccelerator/lib/_accelerator-security-group.ts index 9197613d69b61..d59f572ecaebc 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator-security-group.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/_accelerator-security-group.ts @@ -1,16 +1,14 @@ -import { ISecurityGroup, SecurityGroup, IVpc } from '@aws-cdk/aws-ec2'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import { CfnResource } from '@aws-cdk/core'; import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from '@aws-cdk/custom-resources'; +import { Construct } from 'constructs'; import { EndpointGroup } from '../lib'; -// keep this import separate from other imports to reduce chance for merge conflicts with v2-main -// eslint-disable-next-line no-duplicate-imports, import/order -import { Construct } from '@aws-cdk/core'; - /** * The security group used by a Global Accelerator to send traffic to resources in a VPC. */ -export class AcceleratorSecurityGroup { +export class AcceleratorSecurityGroupPeer implements ec2.IPeer { /** * Lookup the Global Accelerator security group at CloudFormation deployment time. * @@ -21,7 +19,7 @@ export class AcceleratorSecurityGroup { * the AGA security group for a given VPC at CloudFormation deployment time, and lets you create rules for traffic from AGA * to other resources created by CDK. */ - public static fromVpc(scope: Construct, id: string, vpc: IVpc, endpointGroup: EndpointGroup): ISecurityGroup { + public static fromVpc(scope: Construct, id: string, vpc: ec2.IVpc, endpointGroup: EndpointGroup) { // The security group name is always 'GlobalAccelerator' const globalAcceleratorSGName = 'GlobalAccelerator'; @@ -59,16 +57,27 @@ export class AcceleratorSecurityGroup { }), }); - // Look up the security group ID - const sg = SecurityGroup.fromSecurityGroupId(scope, - id, - lookupAcceleratorSGCustomResource.getResponseField(ec2ResponseSGIdField)); // We add a dependency on the endpoint group, guaranteeing that CloudFormation won't // try and look up the SG before AGA creates it. The SG is created when a VPC resource // is associated with an AGA - lookupAcceleratorSGCustomResource.node.addDependency(endpointGroup); - return sg; + lookupAcceleratorSGCustomResource.node.addDependency(endpointGroup.node.defaultChild as CfnResource); + + // Look up the security group ID + return new AcceleratorSecurityGroupPeer(lookupAcceleratorSGCustomResource.getResponseField(ec2ResponseSGIdField)); + } + + public readonly canInlineRule = false; + public readonly connections: ec2.Connections = new ec2.Connections({ peer: this }); + public readonly uniqueId: string = 'GlobalAcceleratorGroup'; + + private constructor(private readonly securityGroupId: string) { } - private constructor() {} + public toIngressRuleConfig(): any { + return { sourceSecurityGroupId: this.securityGroupId }; + } + + public toEgressRuleConfig(): any { + return { destinationSecurityGroupId: this.securityGroupId }; + } } diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator.ts index 721f641d3c19b..939e91f0a2839 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator.ts @@ -1,6 +1,7 @@ import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import * as ga from './globalaccelerator.generated'; +import { Listener, ListenerOptions } from './listener'; /** * The interface of the Accelerator @@ -63,7 +64,7 @@ export class Accelerator extends cdk.Resource implements IAccelerator { /** * import from attributes */ - public static fromAcceleratorAttributes(scope: Construct, id: string, attrs: AcceleratorAttributes ): IAccelerator { + public static fromAcceleratorAttributes(scope: Construct, id: string, attrs: AcceleratorAttributes): IAccelerator { class Import extends cdk.Resource implements IAccelerator { public readonly acceleratorArn = attrs.acceleratorArn; public readonly dnsName = attrs.dnsName; @@ -86,10 +87,20 @@ export class Accelerator extends cdk.Resource implements IAccelerator { const resource = new ga.CfnAccelerator(this, 'Resource', { enabled: props.enabled ?? true, - name: props.acceleratorName ?? id, + name: props.acceleratorName ?? cdk.Names.uniqueId(this), }); this.acceleratorArn = resource.attrAcceleratorArn; this.dnsName = resource.attrDnsName; } + + /** + * Add a listener to the accelerator + */ + public addListener(id: string, options: ListenerOptions) { + return new Listener(this, id, { + accelerator: this, + ...options, + }); + } } diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint-group.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint-group.ts index b5c96bcd547ba..635ac85ff5708 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint-group.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint-group.ts @@ -1,12 +1,11 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; +import { AcceleratorSecurityGroupPeer } from './_accelerator-security-group'; +import { IEndpoint } from './endpoint'; import * as ga from './globalaccelerator.generated'; import { IListener } from './listener'; -// keep this import separate from other imports to reduce chance for merge conflicts with v2-main -// eslint-disable-next-line no-duplicate-imports, import/order -import { Construct as CoreConstruct } from '@aws-cdk/core'; - /** * The interface of the EndpointGroup */ @@ -19,125 +18,135 @@ export interface IEndpointGroup extends cdk.IResource { } /** - * Options for `addLoadBalancer`, `addElasticIpAddress` and `addEc2Instance` to add endpoints into the endpoint group + * Basic options for creating a new EndpointGroup */ -export interface EndpointConfigurationOptions { +export interface EndpointGroupOptions { /** - * Indicates whether client IP address preservation is enabled for an Application Load Balancer endpoint + * Name of the endpoint group * - * @default true + * @default - logical ID of the resource */ - readonly clientIpReservation?: boolean; + readonly endpointGroupName?: string; /** - * The weight associated with the endpoint. When you add weights to endpoints, you configure AWS Global Accelerator - * to route traffic based on proportions that you specify. For example, you might specify endpoint weights of 4, 5, - * 5, and 6 (sum=20). The result is that 4/20 of your traffic, on average, is routed to the first endpoint, 5/20 is - * routed both to the second and third endpoints, and 6/20 is routed to the last endpoint. - * @see https://docs.aws.amazon.com/global-accelerator/latest/dg/about-endpoints-endpoint-weights.html - * @default - not specified + * The AWS Region where the endpoint group is located. + * + * @default - region of the first endpoint in this group, or the stack region if that region can't be determined */ - readonly weight?: number; -} + readonly region?: string; -/** - * Properties to create EndpointConfiguration - * - */ -export interface EndpointConfigurationProps extends EndpointConfigurationOptions { /** - * The endopoint group reesource + * The time between health checks for each endpoint + * + * Must be either 10 or 30 seconds. * - * [disable-awslint:ref-via-interface] + * @default Duration.seconds(30) */ - readonly endpointGroup: EndpointGroup; + readonly healthCheckInterval?: cdk.Duration; /** - * An ID for the endpoint. If the endpoint is a Network Load Balancer or Application Load Balancer, - * this is the Amazon Resource Name (ARN) of the resource. If the endpoint is an Elastic IP address, - * this is the Elastic IP address allocation ID. For EC2 instances, this is the EC2 instance ID. + * The ping path for health checks (if the protocol is HTTP(S)). + * + * @default '/' */ - readonly endpointId: string; -} + readonly healthCheckPath?: string; -/** - * LoadBalancer Interface - */ -export interface LoadBalancer { /** - * The ARN of this load balancer + * The port used to perform health checks + * + * @default - The listener's port */ - readonly loadBalancerArn: string; -} + readonly healthCheckPort?: number; -/** - * EC2 Instance interface - */ -export interface Ec2Instance { /** - * The id of the instance resource + * The protocol used to perform health checks + * + * @default HealthCheckProtocol.TCP */ - readonly instanceId: string; -} + readonly healthCheckProtocol?: HealthCheckProtocol; -/** - * EIP Interface - */ -export interface ElasticIpAddress { /** - * allocation ID of the EIP resoruce + * The number of consecutive health checks required to set the state of a + * healthy endpoint to unhealthy, or to set an unhealthy endpoint to healthy. + * + * @default 3 */ - readonly attrAllocationId: string -} + readonly healthCheckThreshold?: number; -/** - * Property of the EndpointGroup - */ -export interface EndpointGroupProps { /** - * Name of the endpoint group + * The percentage of traffic to send to this AWS Region. * - * @default - logical ID of the resource + * The percentage is applied to the traffic that would otherwise have been + * routed to the Region based on optimal routing. Additional traffic is + * distributed to other endpoint groups for this listener. + * + * @default 100 */ - readonly endpointGroupName?: string; + readonly trafficDialPercentage?: number; /** - * The Amazon Resource Name (ARN) of the listener. + * Override the destination ports used to route traffic to an endpoint. + * + * Unless overridden, the port used to hit the endpoint will be the same as the port + * that traffic arrives on at the listener. + * + * @default - No overrides */ - readonly listener: IListener; + readonly portOverrides?: PortOverride[] /** - * The AWS Region where the endpoint group is located. + * Initial list of endpoints for this group * - * @default - the region of the current stack + * @default - Group is initially empty */ - readonly region?: string; + readonly endpoints?: IEndpoint[]; } /** - * The class for endpoint configuration + * Override specific listener ports used to route traffic to endpoints that are part of an endpoint group. */ -export class EndpointConfiguration extends CoreConstruct { +export interface PortOverride { /** - * The property containing all the configuration to be rendered + * The listener port that you want to map to a specific endpoint port. + * + * This is the port that user traffic arrives to the Global Accelerator on. */ - public readonly props: EndpointConfigurationProps; - constructor(scope: Construct, id: string, props: EndpointConfigurationProps) { - super(scope, id); - this.props = props; - props.endpointGroup._linkEndpoint(this); - } + readonly listenerPort: number; /** - * render the endpoint configuration for the endpoint group + * The endpoint port that you want a listener port to be mapped to. + * + * This is the port on the endpoint, such as the Application Load Balancer or Amazon EC2 instance. */ - public renderEndpointConfiguration(): ga.CfnEndpointGroup.EndpointConfigurationProperty { - return { - clientIpPreservationEnabled: this.props.clientIpReservation, - endpointId: this.props.endpointId, - weight: this.props.weight, - }; - } + readonly endpointPort: number; +} + +/** + * The protocol for the connections from clients to the accelerator. + */ +export enum HealthCheckProtocol { + /** + * TCP + */ + TCP = 'TCP', + /** + * HTTP + */ + HTTP = 'HTTP', + /** + * HTTPS + */ + HTTPS = 'HTTPS', +} + +/** + * Property of the EndpointGroup + */ +export interface EndpointGroupProps extends EndpointGroupOptions { + /** + * The Amazon Resource Name (ARN) of the listener. + */ + readonly listener: IListener; } /** @@ -165,75 +174,72 @@ export class EndpointGroup extends cdk.Resource implements IEndpointGroup { /** * The array of the endpoints in this endpoint group */ - protected readonly endpoints = new Array(); + protected readonly endpoints = new Array(); constructor(scope: Construct, id: string, props: EndpointGroupProps) { super(scope, id); const resource = new ga.CfnEndpointGroup(this, 'Resource', { listenerArn: props.listener.listenerArn, - endpointGroupRegion: props.region ?? cdk.Stack.of(this).region, + endpointGroupRegion: props.region ?? cdk.Lazy.string({ produce: () => this.firstEndpointRegion() }), endpointConfigurations: cdk.Lazy.any({ produce: () => this.renderEndpoints() }, { omitEmptyArray: true }), + healthCheckIntervalSeconds: props.healthCheckInterval?.toSeconds({ integral: true }), + healthCheckPath: props.healthCheckPath, + healthCheckPort: props.healthCheckPort, + healthCheckProtocol: props.healthCheckProtocol, + thresholdCount: props.healthCheckThreshold, + trafficDialPercentage: props.trafficDialPercentage, + portOverrides: props.portOverrides?.map(o => ({ + endpointPort: o.endpointPort, + listenerPort: o.listenerPort, + })), }); this.endpointGroupArn = resource.attrEndpointGroupArn; this.endpointGroupName = props.endpointGroupName ?? resource.logicalId; - } - /** - * Add an endpoint - */ - public addEndpoint(id: string, endpointId: string, props: EndpointConfigurationOptions = - {}) { - return new EndpointConfiguration(this, id, { - endpointGroup: this, - endpointId, - ...props, - }); + for (const endpoint of props.endpoints ?? []) { + this.addEndpoint(endpoint); + } } /** - * Add an Elastic Load Balancer as an endpoint in this endpoint group + * Add an endpoint */ - public addLoadBalancer(id: string, lb: LoadBalancer, props: EndpointConfigurationOptions = {}) { - return new EndpointConfiguration(this, id, { - endpointId: lb.loadBalancerArn, - endpointGroup: this, - ...props, - }); + public addEndpoint(endpoint: IEndpoint) { + this.endpoints.push(endpoint); } /** - * Add an EIP as an endpoint in this endpoint group + * Return an object that represents the Accelerator's Security Group + * + * Uses a Custom Resource to look up the Security Group that Accelerator + * creates at deploy time. Requires your VPC ID to perform the lookup. + * + * The Security Group will only be created if you enable **Client IP + * Preservation** on any of the endpoints. + * + * You cannot manipulate the rules inside this security group, but you can + * use this security group as a Peer in Connections rules on other + * constructs. */ - public addElasticIpAddress(id: string, eip: ElasticIpAddress, props: EndpointConfigurationOptions = {}) { - return new EndpointConfiguration(this, id, { - endpointId: eip.attrAllocationId, - endpointGroup: this, - ...props, - }); + public connectionsPeer(id: string, vpc: ec2.IVpc): ec2.IPeer { + return AcceleratorSecurityGroupPeer.fromVpc(this, id, vpc, this); } - /** - * Add an EC2 Instance as an endpoint in this endpoint group - */ - public addEc2Instance(id: string, instance: Ec2Instance, props: EndpointConfigurationOptions = {}) { - return new EndpointConfiguration(this, id, { - endpointId: instance.instanceId, - endpointGroup: this, - ...props, - }); + private renderEndpoints() { + return this.endpoints.map(e => e.renderEndpointConfiguration()); } /** - * Links a endpoint to this endpoint group - * @internal + * Return the first (readable) region of the endpoints in this group */ - public _linkEndpoint(endpoint: EndpointConfiguration) { - this.endpoints.push(endpoint); - } - - private renderEndpoints() { - return this.endpoints.map(e => e.renderEndpointConfiguration()); + private firstEndpointRegion() { + for (const endpoint of this.endpoints) { + if (endpoint.region) { + return endpoint.region; + } + } + return cdk.Stack.of(this).region; } } diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint.ts new file mode 100644 index 0000000000000..a99f54421fab1 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/endpoint.ts @@ -0,0 +1,87 @@ +import { CfnEndpointGroup } from './globalaccelerator.generated'; + +/** + * An endpoint for the endpoint group + * + * Implementations of `IEndpoint` can be found in the `aws-globalaccelerator-endpoints` package. + */ +export interface IEndpoint { + /** + * The region where the endpoint is located + * + * If the region cannot be determined, `undefined` is returned + */ + readonly region?: string; + + /** + * Render the endpoint to an endpoint configuration + */ + renderEndpointConfiguration(): any; +} + +/** + * Properties for RawEndpoint + */ +export interface RawEndpointProps { + /** + * Identifier of the endpoint + * + * Load balancer ARN, instance ID or EIP allocation ID. + */ + readonly endpointId: string; + + /** + * Endpoint weight across all endpoints in the group + * + * Must be a value between 0 and 255. + * + * @default 128 + */ + readonly weight?: number; + + /** + * Forward the client IP address + * + * GlobalAccelerator will create Network Interfaces in your VPC in order + * to preserve the client IP address. + * + * Only applies to Application Load Balancers and EC2 instances. + * + * Client IP address preservation is supported only in specific AWS Regions. + * See the GlobalAccelerator Developer Guide for a list. + * + * @default true if possible and available + */ + readonly preserveClientIp?: boolean; + + /** + * The region where this endpoint is located + * + * @default - Unknown what region this endpoint is located + */ + readonly region?: string; +} + +/** + * Untyped endpoint implementation + * + * Prefer using the classes in the `aws-globalaccelerator-endpoints` package instead, + * as they accept typed constructs. You can use this class if you want to use an + * endpoint type that does not have an appropriate class in that package yet. + */ +export class RawEndpoint implements IEndpoint { + public readonly region?: string; + + constructor(private readonly props: RawEndpointProps) { + this.region = props.region; + } + + public renderEndpointConfiguration(): any { + return { + endpointId: this.props.endpointId, + weight: this.props.weight, + clientIpPreservationEnabled: this.props.preserveClientIp, + } as CfnEndpointGroup.EndpointConfigurationProperty; + } +} + diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/index.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/index.ts index ff4675e6af2e5..de29a6d6ed08b 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/lib/index.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/index.ts @@ -1,6 +1,6 @@ // AWS::GlobalAccelerator CloudFormation Resources: export * from './globalaccelerator.generated'; export * from './accelerator'; -export * from './accelerator-security-group'; export * from './listener'; export * from './endpoint-group'; +export * from './endpoint'; diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/listener.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/listener.ts index 2f29bde4b03c7..39c5c0e4699b1 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/lib/listener.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/listener.ts @@ -1,6 +1,7 @@ import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IAccelerator } from './accelerator'; +import { EndpointGroup, EndpointGroupOptions } from './endpoint-group'; import * as ga from './globalaccelerator.generated'; /** @@ -16,9 +17,9 @@ export interface IListener extends cdk.IResource { } /** - * construct properties for Listener + * Construct options for Listener */ -export interface ListenerProps { +export interface ListenerOptions { /** * Name of the listener * @@ -26,11 +27,6 @@ export interface ListenerProps { */ readonly listenerName?: string; - /** - * The accelerator for this listener - */ - readonly accelerator: IAccelerator; - /** * The list of port ranges for the connections from clients to the accelerator */ @@ -39,18 +35,35 @@ export interface ListenerProps { /** * The protocol for the connections from clients to the accelerator * - * @default TCP + * @default ConnectionProtocol.TCP */ readonly protocol?: ConnectionProtocol; /** * Client affinity to direct all requests from a user to the same endpoint * - * @default NONE + * If you have stateful applications, client affinity lets you direct all + * requests from a user to the same endpoint. + * + * By default, each connection from each client is routed to seperate + * endpoints. Set client affinity to SOURCE_IP to route all connections from + * a single client to the same endpoint. + * + * @default ClientAffinity.NONE */ readonly clientAffinity?: ClientAffinity; } +/** + * Construct properties for Listener + */ +export interface ListenerProps extends ListenerOptions { + /** + * The accelerator for this listener + */ + readonly accelerator: IAccelerator; +} + /** * The list of port ranges for the connections from clients to the accelerator. */ @@ -58,11 +71,14 @@ export interface PortRange { /** * The first port in the range of ports, inclusive. */ - readonly fromPort: number, + readonly fromPort: number; + /** * The last port in the range of ports, inclusive. + * + * @default - same as `fromPort` */ - readonly toPort: number, + readonly toPort?: number; } /** @@ -80,20 +96,20 @@ export enum ConnectionProtocol { } /** - * Client affinity lets you direct all requests from a user to the same endpoint, if you have stateful applications, - * regardless of the port and protocol of the client request. Client affinity gives you control over whether to always - * route each client to the same specific endpoint. If you want a given client to always be routed to the same - * endpoint, set client affinity to SOURCE_IP. + * Client affinity gives you control over whether to always route each client to the same specific endpoint. * * @see https://docs.aws.amazon.com/global-accelerator/latest/dg/about-listeners.html#about-listeners-client-affinity */ export enum ClientAffinity { /** - * default affinity + * Route traffic based on the 5-tuple `(source IP, source port, destination IP, destination port, protocol)` */ NONE = 'NONE', + /** - * affinity by source IP + * Route traffic based on the 2-tuple `(source IP, destination IP)` + * + * The result is that multiple connections from the same client will be routed the same. */ SOURCE_IP = 'SOURCE_IP', } @@ -127,7 +143,7 @@ export class Listener extends cdk.Resource implements IListener { acceleratorArn: props.accelerator.acceleratorArn, portRanges: props.portRanges.map(m => ({ fromPort: m.fromPort, - toPort: m.toPort, + toPort: m.toPort ?? m.fromPort, })), protocol: props.protocol ?? ConnectionProtocol.TCP, clientAffinity: props.clientAffinity ?? ClientAffinity.NONE, @@ -135,6 +151,15 @@ export class Listener extends cdk.Resource implements IListener { this.listenerArn = resource.attrListenerArn; this.listenerName = props.listenerName ?? resource.logicalId; + } + /** + * Add a new endpoint group to this listener + */ + public addEndpointGroup(id: string, options: EndpointGroupOptions = {}) { + return new EndpointGroup(this, id, { + listener: this, + ...options, + }); } } diff --git a/packages/@aws-cdk/aws-globalaccelerator/package.json b/packages/@aws-cdk/aws-globalaccelerator/package.json index 1e98de7ee0bc8..f325ec000d862 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/package.json +++ b/packages/@aws-cdk/aws-globalaccelerator/package.json @@ -74,7 +74,6 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", "cdk-integ-tools": "0.0.0", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", @@ -95,8 +94,8 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, - "stability": "experimental", - "maturity": "experimental", + "stability": "stable", + "maturity": "stable", "awscdkio": { "announce": false }, diff --git a/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator-security-group.test.ts b/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator-security-group.test.ts index 20881d152f396..8101fa98acfba 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator-security-group.test.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator-security-group.test.ts @@ -1,7 +1,7 @@ import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; -import { Port } from '@aws-cdk/aws-ec2'; +import * as ec2 from '@aws-cdk/aws-ec2'; import * as ga from '../lib'; -import { testFixture, testFixtureAlb } from './util'; +import { testFixture } from './util'; test('custom resource exists', () => { // GIVEN @@ -19,7 +19,7 @@ test('custom resource exists', () => { const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); // WHEN - ga.AcceleratorSecurityGroup.fromVpc(stack, 'GlobalAcceleratorSG', vpc, endpointGroup); + endpointGroup.connectionsPeer('GlobalAcceleratorSG', vpc); // THEN expect(stack).to(haveResource('Custom::AWS', { @@ -45,7 +45,7 @@ test('custom resource exists', () => { InstallLatestAwsSdk: true, }, DependsOn: [ - 'GlobalAcceleratorSGCustomResourceCustomResourcePolicyF3294553', + 'GroupGlobalAcceleratorSGCustomResourceCustomResourcePolicy9C957AD2', 'GroupC77FDACD', ], }, ResourcePart.CompleteDefinition)); @@ -53,7 +53,7 @@ test('custom resource exists', () => { test('can create security group rule', () => { // GIVEN - const { stack, alb, vpc } = testFixtureAlb(); + const { stack, vpc } = testFixture(); const accelerator = new ga.Accelerator(stack, 'Accelerator'); const listener = new ga.Listener(stack, 'Listener', { accelerator, @@ -65,11 +65,12 @@ test('can create security group rule', () => { ], }); const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); - endpointGroup.addLoadBalancer('endpoint', alb); // WHEN - const sg = ga.AcceleratorSecurityGroup.fromVpc(stack, 'GlobalAcceleratorSG', vpc, endpointGroup); - alb.connections.allowFrom(sg, Port.tcp(443)); + const gaSg = endpointGroup.connectionsPeer('GlobalAcceleratorSG', vpc); + const instanceSg = new ec2.SecurityGroup(stack, 'SG', { vpc }); + const instanceConnections = new ec2.Connections({ securityGroups: [instanceSg] }); + instanceConnections.allowFrom(gaSg, ec2.Port.tcp(443)); // THEN expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', { @@ -77,13 +78,13 @@ test('can create security group rule', () => { FromPort: 443, GroupId: { 'Fn::GetAtt': [ - 'ALBSecurityGroup8B8624F8', + 'SGADB53937', 'GroupId', ], }, SourceSecurityGroupId: { 'Fn::GetAtt': [ - 'GlobalAcceleratorSGCustomResourceC1DB5287', + 'GroupGlobalAcceleratorSGCustomResource0C8056E9', 'SecurityGroups.0.GroupId', ], }, diff --git a/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator.test.ts b/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator.test.ts index 72e1c79e586dd..ddbd69c269ca2 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator.test.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator.test.ts @@ -1,6 +1,5 @@ import { expect, haveResourceLike } from '@aws-cdk/assert'; -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import { Duration } from '@aws-cdk/core'; import * as ga from '../lib'; import { testFixture } from './util'; @@ -52,6 +51,29 @@ test('create listener', () => { })); }); +test('toPort defaults to fromPort if left out', () => { + // GIVEN + const { stack } = testFixture(); + + // WHEN + const accelerator = new ga.Accelerator(stack, 'Accelerator'); + accelerator.addListener('Listener', { + portRanges: [ + { fromPort: 123 }, + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::Listener', { + PortRanges: [ + { + FromPort: 123, + ToPort: 123, + }, + ], + })); +}); + test('create endpointgroup', () => { // GIVEN const { stack } = testFixture(); @@ -83,73 +105,75 @@ test('create endpointgroup', () => { })); }); -test('addEndpoint', () => { +test('endpointgroup region is the first endpoint\'s region', () => { // GIVEN - const { stack, vpc } = testFixture(); + const { stack } = testFixture(); // WHEN const accelerator = new ga.Accelerator(stack, 'Accelerator'); const listener = new ga.Listener(stack, 'Listener', { accelerator, - portRanges: [ - { - fromPort: 80, - toPort: 80, - }, - ], + portRanges: [{ fromPort: 80 }], }); - const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); - const instance = new ec2.Instance(stack, 'Instance', { - vpc, - machineImage: new ec2.AmazonLinuxImage(), - instanceType: new ec2.InstanceType('t3.small'), + listener.addEndpointGroup('Group', { + endpoints: [ + new ga.RawEndpoint({ + endpointId: 'x-123', + region: 'us-bla-5', + }), + ], }); - endpointGroup.addEndpoint('endpoint', instance.instanceId); // THEN expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { - EndpointConfigurations: [ - { - EndpointId: { - Ref: 'InstanceC1063A87', - }, - }, - ], + EndpointGroupRegion: 'us-bla-5', })); }); -test('addLoadBalancer', () => { +test('endpointgroup with all parameters', () => { // GIVEN - const { stack, vpc } = testFixture(); + const { stack } = testFixture(); // WHEN const accelerator = new ga.Accelerator(stack, 'Accelerator'); - const listener = new ga.Listener(stack, 'Listener', { - accelerator, - portRanges: [ + const listener = accelerator.addListener('Listener', { + portRanges: [{ fromPort: 80 }], + }); + listener.addEndpointGroup('Group', { + region: 'us-bla-5', + healthCheckInterval: Duration.seconds(10), + healthCheckPath: '/ping', + healthCheckPort: 123, + healthCheckProtocol: ga.HealthCheckProtocol.HTTPS, + healthCheckThreshold: 23, + trafficDialPercentage: 86, + portOverrides: [ { - fromPort: 80, - toPort: 80, + listenerPort: 80, + endpointPort: 8080, }, ], }); - const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); - const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc, internetFacing: true }); - endpointGroup.addLoadBalancer('endpoint', alb); // THEN expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { - EndpointConfigurations: [ + EndpointGroupRegion: 'us-bla-5', + HealthCheckIntervalSeconds: 10, + HealthCheckPath: '/ping', + HealthCheckPort: 123, + HealthCheckProtocol: 'HTTPS', + PortOverrides: [ { - EndpointId: { - Ref: 'ALBAEE750D2', - }, + EndpointPort: 8080, + ListenerPort: 80, }, ], + ThresholdCount: 23, + TrafficDialPercentage: 86, })); }); -test('addElasticIpAddress', () => { +test('addEndpoint', () => { // GIVEN const { stack } = testFixture(); @@ -164,56 +188,26 @@ test('addElasticIpAddress', () => { }, ], }); - const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); - const eip = new ec2.CfnEIP(stack, 'ElasticIpAddress'); - endpointGroup.addElasticIpAddress('endpoint', eip); - - // THEN - expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { - EndpointConfigurations: [ - { - EndpointId: { - 'Fn::GetAtt': [ - 'ElasticIpAddress', - 'AllocationId', - ], - }, - }, - ], - })); -}); -test('addEc2Instance', () => { - // GIVEN - const { stack, vpc } = testFixture(); - // WHEN - const accelerator = new ga.Accelerator(stack, 'Accelerator'); - const listener = new ga.Listener(stack, 'Listener', { - accelerator, - portRanges: [ - { - fromPort: 80, - toPort: 80, - }, + listener.addEndpointGroup('Group', { + endpoints: [ + new ga.RawEndpoint({ + endpointId: 'i-123', + preserveClientIp: true, + weight: 30, + }), ], }); - const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener }); - const instance = new ec2.Instance(stack, 'Instance', { - vpc, - machineImage: new ec2.AmazonLinuxImage(), - instanceType: new ec2.InstanceType('t3.small'), - }); - endpointGroup.addEc2Instance('endpoint', instance); // THEN expect(stack).to(haveResourceLike('AWS::GlobalAccelerator::EndpointGroup', { EndpointConfigurations: [ { - EndpointId: { - Ref: 'InstanceC1063A87', - }, + EndpointId: 'i-123', + ClientIPPreservationEnabled: true, + Weight: 30, }, ], })); -}); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-globalaccelerator/test/util.ts b/packages/@aws-cdk/aws-globalaccelerator/test/util.ts index 9cf60a33a2064..0ad64f2329cf2 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/test/util.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/test/util.ts @@ -1,55 +1,10 @@ import * as ec2 from '@aws-cdk/aws-ec2'; -import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import { App, Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; export function testFixture() { - const { stack, app } = testFixtureNoVpc(); - const vpc = new ec2.Vpc(stack, 'VPC'); - - return { stack, vpc, app }; -} - -export function testFixtureNoVpc() { const app = new App(); const stack = new Stack(app, 'Stack'); - return { stack, app }; -} - -export function testFixtureAlb() { - const { stack, app, vpc } = testFixture(); - const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc, internetFacing: true }); - - return { stack, app, alb, vpc }; -} - -export function testFixtureNlb() { - const { stack, app, vpc } = testFixture(); - const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', { vpc, internetFacing: true }); - - return { stack, app, nlb }; -} - -export function testFixtureEip() { - const { stack, app } = testFixtureNoVpc(); - const eip = new ec2.CfnEIP(stack, 'ElasticIpAddress'); - - return { stack, app, eip }; -} - -export function testFixtureEc2() { - const { stack, app, vpc } = testFixture(); - const instance = new ec2.Instance(stack, 'Ec2', { - vpc, - machineImage: new ec2.AmazonLinuxImage(), - instanceType: new ec2.InstanceType('t3.small'), - }); - - return { stack, app, instance }; -} + const vpc = new ec2.Vpc(stack, 'VPC'); -export class TestStack extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - } -} + return { stack, vpc, app }; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts b/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts index afe6fb576f298..abc7db01d35a6 100644 --- a/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts +++ b/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts @@ -1,5 +1,5 @@ import '@aws-cdk/assert/jest'; -import { App, CfnElement, Lazy, Stack } from '@aws-cdk/core'; +import { App, Aws, CfnElement, Lazy, Stack } from '@aws-cdk/core'; import { AnyPrincipal, ArnPrincipal, IRole, Policy, PolicyStatement, Role } from '../lib'; /* eslint-disable quote-props */ @@ -519,6 +519,21 @@ describe('IAM Role.fromRoleArn', () => { }); }); }); + + describe('for an incorrect ARN', () => { + beforeEach(() => { + roleStack = new Stack(app, 'RoleStack'); + }); + + describe("that accidentally skipped the 'region' fragment of the ARN", () => { + test('throws an exception, indicating that error', () => { + expect(() => { + Role.fromRoleArn(roleStack, 'Role', + `arn:${Aws.PARTITION}:iam:${Aws.ACCOUNT_ID}:role/AwsCicd-${Aws.REGION}-CodeBuildRole`); + }).toThrow(/The `resource` component \(6th component\) of an ARN is required:/); + }); + }); + }); }); function somePolicyStatement() { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/README.md b/packages/@aws-cdk/aws-lambda-event-sources/README.md index eb4f206c8f3c9..55cd7e864125a 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/README.md +++ b/packages/@aws-cdk/aws-lambda-event-sources/README.md @@ -53,6 +53,8 @@ behavior: * __receiveMessageWaitTime__: Will determine [long poll](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-long-polling.html) duration. The default value is 20 seconds. +* __batchSize__: Determines how many records are buffered before invoking your lambda function. +* __maxBatchingWindow__: The maximum amount of time to gather records before invoking the lambda. This increases the likelihood of a full batch at the cost of delayed processing. * __enabled__: If the SQS event source mapping should be enabled. The default is true. ```ts @@ -67,6 +69,7 @@ const queue = new sqs.Queue(this, 'MyQueue', { lambda.addEventSource(new SqsEventSource(queue, { batchSize: 10, // default + maxBatchingWindow: Duration.minutes(5), })); ``` @@ -148,6 +151,7 @@ and add it to your Lambda function. The following parameters will impact Amazon * __parallelizationFactor__: The number of batches to concurrently process on each shard. * __retryAttempts__: The maximum number of times a record should be retried in the event of failure. * __startingPosition__: Will determine where to being consumption, either at the most recent ('LATEST') record or the oldest record ('TRIM_HORIZON'). 'TRIM_HORIZON' will ensure you process all available data, while 'LATEST' will ignore all records that arrived prior to attaching the event source. +* __tumblingWindow__: The duration in seconds of a processing window when using streams. * __enabled__: If the DynamoDB Streams event source mapping should be enabled. The default is true. ```ts @@ -192,6 +196,7 @@ behavior: * __parallelizationFactor__: The number of batches to concurrently process on each shard. * __retryAttempts__: The maximum number of times a record should be retried in the event of failure. * __startingPosition__: Will determine where to being consumption, either at the most recent ('LATEST') record or the oldest record ('TRIM_HORIZON'). 'TRIM_HORIZON' will ensure you process all available data, while 'LATEST' will ignore all records that arrived prior to attaching the event source. +* __tumblingWindow__: The duration in seconds of a processing window when using streams. * __enabled__: If the DynamoDB Streams event source mapping should be enabled. The default is true. ```ts @@ -212,7 +217,7 @@ myFunction.addEventSource(new KinesisEventSource(stream, { You can write Lambda functions to process data either from [Amazon MSK](https://docs.aws.amazon.com/lambda/latest/dg/with-msk.html) or a [self managed Kafka](https://docs.aws.amazon.com/lambda/latest/dg/kafka-smaa.html) cluster. The following code sets up Amazon MSK as an event source for a lambda function. Credentials will need to be configured to access the -MSK cluster, as described in [Username/Password authentication](https://docs.aws.amazon.com/msk/latest/developerguide/msk-password.html). +MSK cluster, as described in [Username/Password authentication](https://docs.aws.amazon.com/msk/latest/developerguide/msk-password.html). ```ts import * as lambda from '@aws-cdk/aws-lambda'; diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts index 88cc1682f2be5..8a67e63fb6199 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts @@ -1,6 +1,6 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as sqs from '@aws-cdk/aws-sqs'; -import { Names } from '@aws-cdk/core'; +import { Duration, Names, Token } from '@aws-cdk/core'; export interface SqsEventSourceProps { /** @@ -14,6 +14,15 @@ export interface SqsEventSourceProps { */ readonly batchSize?: number; + /** + * The maximum amount of time to gather records before invoking the function. + * + * Valid Range: Minimum value of 0 minutes. Maximum value of 5 minutes. + * + * @default - no batching window. The lambda function will be invoked immediately with the records that are available. + */ + readonly maxBatchingWindow?: Duration; + /** * If the SQS event source mapping should be enabled. * @@ -29,14 +38,28 @@ export class SqsEventSource implements lambda.IEventSource { private _eventSourceMappingId?: string = undefined; constructor(readonly queue: sqs.IQueue, private readonly props: SqsEventSourceProps = { }) { - if (this.props.batchSize !== undefined && (this.props.batchSize < 1 || this.props.batchSize > 10)) { - throw new Error(`Maximum batch size must be between 1 and 10 inclusive (given ${this.props.batchSize})`); + if (this.props.maxBatchingWindow !== undefined) { + if (queue.fifo) { + throw new Error('Batching window is not supported for FIFO queues'); + } + if (!this.props.maxBatchingWindow.isUnresolved() && this.props.maxBatchingWindow.toSeconds() > 300) { + throw new Error(`Maximum batching window must be 300 seconds or less (given ${this.props.maxBatchingWindow.toHumanString()})`); + } + } + if (this.props.batchSize !== undefined && !Token.isUnresolved(this.props.batchSize)) { + if (this.props.maxBatchingWindow !== undefined && (this.props.batchSize < 1 || this.props.batchSize > 10000)) { + throw new Error(`Maximum batch size must be between 1 and 10000 inclusive (given ${this.props.batchSize}) when batching window is specified.`); + } + if (this.props.maxBatchingWindow === undefined && (this.props.batchSize < 1 || this.props.batchSize > 10)) { + throw new Error(`Maximum batch size must be between 1 and 10 inclusive (given ${this.props.batchSize}) when batching window is not specified.`); + } } } public bind(target: lambda.IFunction) { const eventSourceMapping = target.addEventSourceMapping(`SqsEventSource:${Names.nodeUniqueId(this.queue.node)}`, { batchSize: this.props.batchSize, + maxBatchingWindow: this.props.maxBatchingWindow, enabled: this.props.enabled, eventSourceArn: this.queue.queueArn, }); diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/stream.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/stream.ts index 96907b97835fc..c9de62653a93e 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/stream.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/stream.ts @@ -78,6 +78,14 @@ export interface StreamEventSourceProps { */ readonly maxBatchingWindow?: Duration; + /** + * The size of the tumbling windows to group records sent to DynamoDB or Kinesis + * Valid Range: 0 - 15 minutes + * + * @default - None + */ + readonly tumblingWindow?: Duration; + /** * If the stream event source mapping should be enabled. * @@ -106,6 +114,7 @@ export abstract class StreamEventSource implements lambda.IEventSource { retryAttempts: this.props.retryAttempts, parallelizationFactor: this.props.parallelizationFactor, onFailure: this.props.onFailure, + tumblingWindow: this.props.tumblingWindow, enabled: this.props.enabled, }; } diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.expected.json index 406a123559d74..0f3557acedc33 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.expected.json @@ -99,6 +99,7 @@ "StreamArn" ] }, + "TumblingWindowInSeconds": 60, "StartingPosition": "TRIM_HORIZON" } }, diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.ts index ea5089bf98706..aa4f6f244ee94 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.ts @@ -21,6 +21,7 @@ class DynamoEventSourceTest extends cdk.Stack { fn.addEventSource(new DynamoEventSource(queue, { batchSize: 5, startingPosition: lambda.StartingPosition.TRIM_HORIZON, + tumblingWindow: cdk.Duration.seconds(60), })); } } diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json index 54bfa6f32f361..aafb84ca19c72 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.expected.json @@ -106,6 +106,7 @@ "Arn" ] }, + "TumblingWindowInSeconds": 60, "StartingPosition": "TRIM_HORIZON" } }, diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.ts index 40dde2a809fc9..212c66e44ec11 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.ts @@ -13,6 +13,7 @@ class KinesisEventSourceTest extends cdk.Stack { fn.addEventSource(new KinesisEventSource(stream, { startingPosition: lambda.StartingPosition.TRIM_HORIZON, + tumblingWindow: cdk.Duration.seconds(60), })); } } diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts index 41d8535d90235..7793cf54b9cc0 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts @@ -76,6 +76,33 @@ export = { test.done(); }, + 'specific tumblingWindow'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const table = new dynamodb.Table(stack, 'T', { + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING, + }, + stream: dynamodb.StreamViewType.NEW_IMAGE, + }); + + // WHEN + fn.addEventSource(new sources.DynamoEventSource(table, { + batchSize: 50, + startingPosition: lambda.StartingPosition.LATEST, + tumblingWindow: cdk.Duration.seconds(60), + })); + + // THEN + expect(stack).to(haveResource('AWS::Lambda::EventSourceMapping', { + TumblingWindowInSeconds: 60, + })); + + test.done(); + }, + 'specific batch size'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.kinesis.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.kinesis.ts index 76e437de0ae34..c36ecba6156ce 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.kinesis.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.kinesis.ts @@ -76,6 +76,38 @@ export = { test.done(); }, + 'specific tumblingWindowInSeconds'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const stream = new kinesis.Stream(stack, 'S'); + + // WHEN + fn.addEventSource(new sources.KinesisEventSource(stream, { + batchSize: 50, + startingPosition: lambda.StartingPosition.LATEST, + tumblingWindow: cdk.Duration.seconds(60), + })); + + // THEN + expect(stack).to(haveResource('AWS::Lambda::EventSourceMapping', { + 'EventSourceArn': { + 'Fn::GetAtt': [ + 'S509448A1', + 'Arn', + ], + }, + 'FunctionName': { + 'Ref': 'Fn9270CBC0', + }, + 'BatchSize': 50, + 'StartingPosition': 'LATEST', + 'TumblingWindowInSeconds': 60, + })); + + test.done(); + }, + 'specific batch size'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.sqs.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.sqs.ts index fd02dda47a304..e67204f2c590c 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.sqs.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.sqs.ts @@ -85,6 +85,30 @@ export = { test.done(); }, + 'unresolved batch size'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const q = new sqs.Queue(stack, 'Q'); + const batchSize : number = 500; + + // WHEN + fn.addEventSource(new sources.SqsEventSource(q, { + batchSize: cdk.Lazy.number({ + produce() { + return batchSize; + }, + }), + })); + + // THEN + expect(stack).to(haveResource('AWS::Lambda::EventSourceMapping', { + 'BatchSize': 500, + })); + + test.done(); + }, + 'fails if batch size is < 1'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -94,7 +118,7 @@ export = { // WHEN/THEN test.throws(() => fn.addEventSource(new sources.SqsEventSource(q, { batchSize: 0, - })), /Maximum batch size must be between 1 and 10 inclusive \(given 0\)/); + })), /Maximum batch size must be between 1 and 10 inclusive \(given 0\) when batching window is not specified\./); test.done(); }, @@ -108,7 +132,92 @@ export = { // WHEN/THEN test.throws(() => fn.addEventSource(new sources.SqsEventSource(q, { batchSize: 11, - })), /Maximum batch size must be between 1 and 10 inclusive \(given 11\)/); + })), /Maximum batch size must be between 1 and 10 inclusive \(given 11\) when batching window is not specified\./); + + test.done(); + }, + + 'batch size is > 10 and batch window is defined'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const q = new sqs.Queue(stack, 'Q'); + + // WHEN + fn.addEventSource(new sources.SqsEventSource(q, { + batchSize: 1000, + maxBatchingWindow: cdk.Duration.minutes(5), + })); + + // THEN + expect(stack).to(haveResource('AWS::Lambda::EventSourceMapping', { + 'BatchSize': 1000, + 'MaximumBatchingWindowInSeconds': 300, + })); + + test.done(); + }, + + 'fails if batch size is > 10000 and batch window is defined'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const q = new sqs.Queue(stack, 'Q'); + + // WHEN/THEN + test.throws(() => fn.addEventSource(new sources.SqsEventSource(q, { + batchSize: 11000, + maxBatchingWindow: cdk.Duration.minutes(5), + })), /Maximum batch size must be between 1 and 10000 inclusive/i); + + test.done(); + }, + + 'specific batch window'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const q = new sqs.Queue(stack, 'Q'); + + // WHEN + fn.addEventSource(new sources.SqsEventSource(q, { + maxBatchingWindow: cdk.Duration.minutes(5), + })); + + // THEN + expect(stack).to(haveResource('AWS::Lambda::EventSourceMapping', { + 'MaximumBatchingWindowInSeconds': 300, + })); + + test.done(); + }, + + 'fails if batch window defined for FIFO queue'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const q = new sqs.Queue(stack, 'Q', { + fifo: true, + }); + + // WHEN/THEN + test.throws(() => fn.addEventSource(new sources.SqsEventSource(q, { + maxBatchingWindow: cdk.Duration.minutes(5), + })), /Batching window is not supported for FIFO queues/); + + test.done(); + }, + + 'fails if batch window is > 5'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const q = new sqs.Queue(stack, 'Q'); + + // WHEN/THEN + test.throws(() => fn.addEventSource(new sources.SqsEventSource(q, { + maxBatchingWindow: cdk.Duration.minutes(7), + })), /Maximum batching window must be 300 seconds or less/i); test.done(); }, diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index d11602c5e656e..42257f7df5225 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/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) --- @@ -67,7 +61,7 @@ used by your function. Otherwise bundling will fail. ## Local bundling If `esbuild` is available it will be used to bundle your code in your environment. Otherwise, -bundling will happen in a [Lambda compatible Docker container](https://hub.docker.com/r/amazon/aws-sam-cli-build-image-nodejs12.x). +bundling will happen in a [Lambda compatible Docker container](https://gallery.ecr.aws/sam/build-nodejs12.x). For macOS the recommendend approach is to install `esbuild` as Docker volume performance is really poor. diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile b/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile index a92d2fd2092cc..27fe83af43c35 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile @@ -1,6 +1,6 @@ # The correct AWS SAM build image based on the runtime of the function will be # passed as build arg. The default allows to do `docker build .` when testing. -ARG IMAGE=amazon/aws-sam-cli-build-image-nodejs12.x +ARG IMAGE=public.ecr.aws/sam/build-nodejs12.x FROM $IMAGE # Install yarn diff --git a/packages/@aws-cdk/aws-lambda-nodejs/package.json b/packages/@aws-cdk/aws-lambda-nodejs/package.json index 7fb257ff676b9..4fc08212d2961 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/package.json +++ b/packages/@aws-cdk/aws-lambda-nodejs/package.json @@ -67,7 +67,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "delay": "5.0.0", - "esbuild": "^0.9.6", + "esbuild": "^0.11.2", "pkglint": "0.0.0" }, "dependencies": { @@ -84,8 +84,8 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, - "stability": "experimental", - "maturity": "experimental", + "stability": "stable", + "maturity": "stable", "awscdkio": { "announce": false }, diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/integ.dependencies.expected.json b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.dependencies.expected.json index 75ecb420e7ef5..1800768023419 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/integ.dependencies.expected.json +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.dependencies.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters2ff0fab1efcce787182abdd9a3c77a111379358a40cb16d90d7eb7c71ed11835S3BucketF29906B2" + "Ref": "AssetParameters5f9b499dbba1111518df1120b55b863471ac359778441164007b5518a70b9746S3Bucket01854DA0" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters2ff0fab1efcce787182abdd9a3c77a111379358a40cb16d90d7eb7c71ed11835S3VersionKey320A7F12" + "Ref": "AssetParameters5f9b499dbba1111518df1120b55b863471ac359778441164007b5518a70b9746S3VersionKey1CC8C283" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters2ff0fab1efcce787182abdd9a3c77a111379358a40cb16d90d7eb7c71ed11835S3VersionKey320A7F12" + "Ref": "AssetParameters5f9b499dbba1111518df1120b55b863471ac359778441164007b5518a70b9746S3VersionKey1CC8C283" } ] } @@ -72,19 +72,19 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "externalServiceRole85A00A90", "Arn" ] }, - "Runtime": "nodejs12.x", "Environment": { "Variables": { "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" } - } + }, + "Handler": "index.handler", + "Runtime": "nodejs12.x" }, "DependsOn": [ "externalServiceRole85A00A90" @@ -92,17 +92,17 @@ } }, "Parameters": { - "AssetParameters2ff0fab1efcce787182abdd9a3c77a111379358a40cb16d90d7eb7c71ed11835S3BucketF29906B2": { + "AssetParameters5f9b499dbba1111518df1120b55b863471ac359778441164007b5518a70b9746S3Bucket01854DA0": { "Type": "String", - "Description": "S3 bucket for asset \"2ff0fab1efcce787182abdd9a3c77a111379358a40cb16d90d7eb7c71ed11835\"" + "Description": "S3 bucket for asset \"5f9b499dbba1111518df1120b55b863471ac359778441164007b5518a70b9746\"" }, - "AssetParameters2ff0fab1efcce787182abdd9a3c77a111379358a40cb16d90d7eb7c71ed11835S3VersionKey320A7F12": { + "AssetParameters5f9b499dbba1111518df1120b55b863471ac359778441164007b5518a70b9746S3VersionKey1CC8C283": { "Type": "String", - "Description": "S3 key for asset version \"2ff0fab1efcce787182abdd9a3c77a111379358a40cb16d90d7eb7c71ed11835\"" + "Description": "S3 key for asset version \"5f9b499dbba1111518df1120b55b863471ac359778441164007b5518a70b9746\"" }, - "AssetParameters2ff0fab1efcce787182abdd9a3c77a111379358a40cb16d90d7eb7c71ed11835ArtifactHash9E439F77": { + "AssetParameters5f9b499dbba1111518df1120b55b863471ac359778441164007b5518a70b9746ArtifactHashAA3B8064": { "Type": "String", - "Description": "Artifact hash for asset \"2ff0fab1efcce787182abdd9a3c77a111379358a40cb16d90d7eb7c71ed11835\"" + "Description": "Artifact hash for asset \"5f9b499dbba1111518df1120b55b863471ac359778441164007b5518a70b9746\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/integ.function.expected.json b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.function.expected.json index e41f6a5cc565e..061ad0bc939a7 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/integ.function.expected.json +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.function.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters1f516d02fb984ea91e74064999e8508f09dd0001d2cf198ccc376d2c0fcd14dcS3Bucket499E594B" + "Ref": "AssetParameterse693e416c0e5591cb0eaa424f7526a449f788de8aa8a89f06f27671feaba8031S3Bucket29060E55" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters1f516d02fb984ea91e74064999e8508f09dd0001d2cf198ccc376d2c0fcd14dcS3VersionKey0D51A516" + "Ref": "AssetParameterse693e416c0e5591cb0eaa424f7526a449f788de8aa8a89f06f27671feaba8031S3VersionKeyE68A6B82" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters1f516d02fb984ea91e74064999e8508f09dd0001d2cf198ccc376d2c0fcd14dcS3VersionKey0D51A516" + "Ref": "AssetParameterse693e416c0e5591cb0eaa424f7526a449f788de8aa8a89f06f27671feaba8031S3VersionKeyE68A6B82" } ] } @@ -72,19 +72,19 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "tshandlerServiceRole8876B8E7", "Arn" ] }, - "Runtime": "nodejs12.x", "Environment": { "Variables": { "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" } - } + }, + "Handler": "index.handler", + "Runtime": "nodejs12.x" }, "DependsOn": [ "tshandlerServiceRole8876B8E7" @@ -126,7 +126,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters565126ff75ab727f0b3a67e78cf0fa9996426d1458123e8d6ee81c7ea93a6cfaS3BucketDFAA619E" + "Ref": "AssetParameters8bda5a67feb4905ef8b67b45ee665d3e466be7357e6c361ad2aa773e5867db39S3Bucket4A9B4410" }, "S3Key": { "Fn::Join": [ @@ -139,7 +139,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters565126ff75ab727f0b3a67e78cf0fa9996426d1458123e8d6ee81c7ea93a6cfaS3VersionKey2DF01059" + "Ref": "AssetParameters8bda5a67feb4905ef8b67b45ee665d3e466be7357e6c361ad2aa773e5867db39S3VersionKeyA27DDFEA" } ] } @@ -152,7 +152,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters565126ff75ab727f0b3a67e78cf0fa9996426d1458123e8d6ee81c7ea93a6cfaS3VersionKey2DF01059" + "Ref": "AssetParameters8bda5a67feb4905ef8b67b45ee665d3e466be7357e6c361ad2aa773e5867db39S3VersionKeyA27DDFEA" } ] } @@ -162,19 +162,19 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "jshandlerServiceRole781AF366", "Arn" ] }, - "Runtime": "nodejs12.x", "Environment": { "Variables": { "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" } - } + }, + "Handler": "index.handler", + "Runtime": "nodejs12.x" }, "DependsOn": [ "jshandlerServiceRole781AF366" @@ -758,7 +758,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersb4497848198c9836bf6ea202ecdc73dd7e3bee7c77f78fff0a61d19b8161adc2S3Bucket42C3CE17" + "Ref": "AssetParameters9aa4dd3191867438d7cf78d5509ee4ffc26b9f3954f6d9a2977c478b7728736cS3BucketA6D7D091" }, "S3Key": { "Fn::Join": [ @@ -771,7 +771,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersb4497848198c9836bf6ea202ecdc73dd7e3bee7c77f78fff0a61d19b8161adc2S3VersionKey4389D827" + "Ref": "AssetParameters9aa4dd3191867438d7cf78d5509ee4ffc26b9f3954f6d9a2977c478b7728736cS3VersionKeyD716694C" } ] } @@ -784,7 +784,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersb4497848198c9836bf6ea202ecdc73dd7e3bee7c77f78fff0a61d19b8161adc2S3VersionKey4389D827" + "Ref": "AssetParameters9aa4dd3191867438d7cf78d5509ee4ffc26b9f3954f6d9a2977c478b7728736cS3VersionKeyD716694C" } ] } @@ -794,19 +794,19 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "tshandlervpcServiceRoleF6D326A3", "Arn" ] }, - "Runtime": "nodejs12.x", "Environment": { "Variables": { "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" } }, + "Handler": "index.handler", + "Runtime": "nodejs12.x", "VpcConfig": { "SecurityGroupIds": [ { @@ -835,41 +835,41 @@ } }, "Parameters": { - "AssetParameters1f516d02fb984ea91e74064999e8508f09dd0001d2cf198ccc376d2c0fcd14dcS3Bucket499E594B": { + "AssetParameterse693e416c0e5591cb0eaa424f7526a449f788de8aa8a89f06f27671feaba8031S3Bucket29060E55": { "Type": "String", - "Description": "S3 bucket for asset \"1f516d02fb984ea91e74064999e8508f09dd0001d2cf198ccc376d2c0fcd14dc\"" + "Description": "S3 bucket for asset \"e693e416c0e5591cb0eaa424f7526a449f788de8aa8a89f06f27671feaba8031\"" }, - "AssetParameters1f516d02fb984ea91e74064999e8508f09dd0001d2cf198ccc376d2c0fcd14dcS3VersionKey0D51A516": { + "AssetParameterse693e416c0e5591cb0eaa424f7526a449f788de8aa8a89f06f27671feaba8031S3VersionKeyE68A6B82": { "Type": "String", - "Description": "S3 key for asset version \"1f516d02fb984ea91e74064999e8508f09dd0001d2cf198ccc376d2c0fcd14dc\"" + "Description": "S3 key for asset version \"e693e416c0e5591cb0eaa424f7526a449f788de8aa8a89f06f27671feaba8031\"" }, - "AssetParameters1f516d02fb984ea91e74064999e8508f09dd0001d2cf198ccc376d2c0fcd14dcArtifactHashECB545C0": { + "AssetParameterse693e416c0e5591cb0eaa424f7526a449f788de8aa8a89f06f27671feaba8031ArtifactHash0218547C": { "Type": "String", - "Description": "Artifact hash for asset \"1f516d02fb984ea91e74064999e8508f09dd0001d2cf198ccc376d2c0fcd14dc\"" + "Description": "Artifact hash for asset \"e693e416c0e5591cb0eaa424f7526a449f788de8aa8a89f06f27671feaba8031\"" }, - "AssetParameters565126ff75ab727f0b3a67e78cf0fa9996426d1458123e8d6ee81c7ea93a6cfaS3BucketDFAA619E": { + "AssetParameters8bda5a67feb4905ef8b67b45ee665d3e466be7357e6c361ad2aa773e5867db39S3Bucket4A9B4410": { "Type": "String", - "Description": "S3 bucket for asset \"565126ff75ab727f0b3a67e78cf0fa9996426d1458123e8d6ee81c7ea93a6cfa\"" + "Description": "S3 bucket for asset \"8bda5a67feb4905ef8b67b45ee665d3e466be7357e6c361ad2aa773e5867db39\"" }, - "AssetParameters565126ff75ab727f0b3a67e78cf0fa9996426d1458123e8d6ee81c7ea93a6cfaS3VersionKey2DF01059": { + "AssetParameters8bda5a67feb4905ef8b67b45ee665d3e466be7357e6c361ad2aa773e5867db39S3VersionKeyA27DDFEA": { "Type": "String", - "Description": "S3 key for asset version \"565126ff75ab727f0b3a67e78cf0fa9996426d1458123e8d6ee81c7ea93a6cfa\"" + "Description": "S3 key for asset version \"8bda5a67feb4905ef8b67b45ee665d3e466be7357e6c361ad2aa773e5867db39\"" }, - "AssetParameters565126ff75ab727f0b3a67e78cf0fa9996426d1458123e8d6ee81c7ea93a6cfaArtifactHashF74A21AB": { + "AssetParameters8bda5a67feb4905ef8b67b45ee665d3e466be7357e6c361ad2aa773e5867db39ArtifactHash13E6F6BF": { "Type": "String", - "Description": "Artifact hash for asset \"565126ff75ab727f0b3a67e78cf0fa9996426d1458123e8d6ee81c7ea93a6cfa\"" + "Description": "Artifact hash for asset \"8bda5a67feb4905ef8b67b45ee665d3e466be7357e6c361ad2aa773e5867db39\"" }, - "AssetParametersb4497848198c9836bf6ea202ecdc73dd7e3bee7c77f78fff0a61d19b8161adc2S3Bucket42C3CE17": { + "AssetParameters9aa4dd3191867438d7cf78d5509ee4ffc26b9f3954f6d9a2977c478b7728736cS3BucketA6D7D091": { "Type": "String", - "Description": "S3 bucket for asset \"b4497848198c9836bf6ea202ecdc73dd7e3bee7c77f78fff0a61d19b8161adc2\"" + "Description": "S3 bucket for asset \"9aa4dd3191867438d7cf78d5509ee4ffc26b9f3954f6d9a2977c478b7728736c\"" }, - "AssetParametersb4497848198c9836bf6ea202ecdc73dd7e3bee7c77f78fff0a61d19b8161adc2S3VersionKey4389D827": { + "AssetParameters9aa4dd3191867438d7cf78d5509ee4ffc26b9f3954f6d9a2977c478b7728736cS3VersionKeyD716694C": { "Type": "String", - "Description": "S3 key for asset version \"b4497848198c9836bf6ea202ecdc73dd7e3bee7c77f78fff0a61d19b8161adc2\"" + "Description": "S3 key for asset version \"9aa4dd3191867438d7cf78d5509ee4ffc26b9f3954f6d9a2977c478b7728736c\"" }, - "AssetParametersb4497848198c9836bf6ea202ecdc73dd7e3bee7c77f78fff0a61d19b8161adc2ArtifactHash7EA2459D": { + "AssetParameters9aa4dd3191867438d7cf78d5509ee4ffc26b9f3954f6d9a2977c478b7728736cArtifactHashF03B1BE8": { "Type": "String", - "Description": "Artifact hash for asset \"b4497848198c9836bf6ea202ecdc73dd7e3bee7c77f78fff0a61d19b8161adc2\"" + "Description": "Artifact hash for asset \"9aa4dd3191867438d7cf78d5509ee4ffc26b9f3954f6d9a2977c478b7728736c\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-python/README.md b/packages/@aws-cdk/aws-lambda-python/README.md index 7f9afc15072b9..77c9fee5a6f7b 100644 --- a/packages/@aws-cdk/aws-lambda-python/README.md +++ b/packages/@aws-cdk/aws-lambda-python/README.md @@ -41,7 +41,7 @@ All other properties of `lambda.Function` are supported, see also the [AWS Lambd ## Module Dependencies If `requirements.txt` or `Pipfile` exists at the entry path, the construct will handle installing -all required modules in a [Lambda compatible Docker container](https://hub.docker.com/r/amazon/aws-sam-cli-build-image-python3.7) +all required modules in a [Lambda compatible Docker container](https://gallery.ecr.aws/sam/build-python3.7) according to the `runtime`. **Lambda with a requirements.txt** diff --git a/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile b/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile index 057512cf4a35c..ab45f0480735f 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile +++ b/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile @@ -1,6 +1,6 @@ # The correct AWS SAM build image based on the runtime of the function will be # passed as build arg. The default allows to do `docker build .` when testing. -ARG IMAGE=amazon/aws-sam-cli-build-image-python3.7 +ARG IMAGE=public.ecr.aws/sam/build-python3.7 FROM $IMAGE # Ensure rsync is installed diff --git a/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile.dependencies b/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile.dependencies index 536887fd69a9a..6793987212603 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile.dependencies +++ b/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile.dependencies @@ -1,6 +1,6 @@ # The correct AWS SAM build image based on the runtime of the function will be # passed as build arg. The default allows to do `docker build .` when testing. -ARG IMAGE=amazon/aws-sam-cli-build-image-python3.7 +ARG IMAGE=public.ecr.aws/sam/build-python3.7 FROM $IMAGE # Ensure rsync is installed diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json index 45fb46b70aeb9..6ed7f497e9ee0 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters79d8f328899b90e2c16929e9393ebf344f098abde8981abdff0168fc9b0589acS3Bucket424FEB44" + "Ref": "AssetParameters5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3cS3Bucket5B146B0B" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters79d8f328899b90e2c16929e9393ebf344f098abde8981abdff0168fc9b0589acS3VersionKeyCEB2635C" + "Ref": "AssetParameters5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3cS3VersionKeyC0C8A627" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters79d8f328899b90e2c16929e9393ebf344f098abde8981abdff0168fc9b0589acS3VersionKeyCEB2635C" + "Ref": "AssetParameters5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3cS3VersionKeyC0C8A627" } ] } @@ -87,17 +87,17 @@ } }, "Parameters": { - "AssetParameters79d8f328899b90e2c16929e9393ebf344f098abde8981abdff0168fc9b0589acS3Bucket424FEB44": { + "AssetParameters5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3cS3Bucket5B146B0B": { "Type": "String", - "Description": "S3 bucket for asset \"79d8f328899b90e2c16929e9393ebf344f098abde8981abdff0168fc9b0589ac\"" + "Description": "S3 bucket for asset \"5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3c\"" }, - "AssetParameters79d8f328899b90e2c16929e9393ebf344f098abde8981abdff0168fc9b0589acS3VersionKeyCEB2635C": { + "AssetParameters5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3cS3VersionKeyC0C8A627": { "Type": "String", - "Description": "S3 key for asset version \"79d8f328899b90e2c16929e9393ebf344f098abde8981abdff0168fc9b0589ac\"" + "Description": "S3 key for asset version \"5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3c\"" }, - "AssetParameters79d8f328899b90e2c16929e9393ebf344f098abde8981abdff0168fc9b0589acArtifactHashE38133D4": { + "AssetParameters5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3cArtifactHashB6A7723E": { "Type": "String", - "Description": "Artifact hash for asset \"79d8f328899b90e2c16929e9393ebf344f098abde8981abdff0168fc9b0589ac\"" + "Description": "Artifact hash for asset \"5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3c\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json index aa7bc5fbbcd68..6766584717e3d 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters50fe6f46cf18fb257f00fed007da5c80fbf5dee08bec37fe765c50188eecae0bS3Bucket4125B4E4" + "Ref": "AssetParameters1a697367cf267ba8a72f772b462c503dd5a6e7bcdd8cb353d89604e9d18802eeS3BucketADC4FFB3" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters50fe6f46cf18fb257f00fed007da5c80fbf5dee08bec37fe765c50188eecae0bS3VersionKey1CF28CF5" + "Ref": "AssetParameters1a697367cf267ba8a72f772b462c503dd5a6e7bcdd8cb353d89604e9d18802eeS3VersionKey784D7689" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters50fe6f46cf18fb257f00fed007da5c80fbf5dee08bec37fe765c50188eecae0bS3VersionKey1CF28CF5" + "Ref": "AssetParameters1a697367cf267ba8a72f772b462c503dd5a6e7bcdd8cb353d89604e9d18802eeS3VersionKey784D7689" } ] } @@ -121,7 +121,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters314bcf2cee9a5b01d3be2c2815602d1d784611fac220dde672aca6cb54299076S3BucketD6F6E4F2" + "Ref": "AssetParameters8eb49c58869010ec84a5b407369006e9cb5fdf9667d8609638d6fa59265fc3ecS3Bucket5B706387" }, "S3Key": { "Fn::Join": [ @@ -134,7 +134,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters314bcf2cee9a5b01d3be2c2815602d1d784611fac220dde672aca6cb54299076S3VersionKeyE0B47D8E" + "Ref": "AssetParameters8eb49c58869010ec84a5b407369006e9cb5fdf9667d8609638d6fa59265fc3ecS3VersionKey48CF3C03" } ] } @@ -147,7 +147,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters314bcf2cee9a5b01d3be2c2815602d1d784611fac220dde672aca6cb54299076S3VersionKeyE0B47D8E" + "Ref": "AssetParameters8eb49c58869010ec84a5b407369006e9cb5fdf9667d8609638d6fa59265fc3ecS3VersionKey48CF3C03" } ] } @@ -206,7 +206,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters11846740cde0308720709e44dce627bf1ceb557ee9d0dbb556a05632da565ef2S3BucketE17A9F3E" + "Ref": "AssetParameters992750d379dbbe94426dbd352099e3344b9edcfd098a56691b53eafaeb227b10S3Bucket9E9FE80F" }, "S3Key": { "Fn::Join": [ @@ -219,7 +219,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters11846740cde0308720709e44dce627bf1ceb557ee9d0dbb556a05632da565ef2S3VersionKey0A0F7F93" + "Ref": "AssetParameters992750d379dbbe94426dbd352099e3344b9edcfd098a56691b53eafaeb227b10S3VersionKeyFFAE128A" } ] } @@ -232,7 +232,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters11846740cde0308720709e44dce627bf1ceb557ee9d0dbb556a05632da565ef2S3VersionKey0A0F7F93" + "Ref": "AssetParameters992750d379dbbe94426dbd352099e3344b9edcfd098a56691b53eafaeb227b10S3VersionKeyFFAE128A" } ] } @@ -257,41 +257,41 @@ } }, "Parameters": { - "AssetParameters50fe6f46cf18fb257f00fed007da5c80fbf5dee08bec37fe765c50188eecae0bS3Bucket4125B4E4": { + "AssetParameters1a697367cf267ba8a72f772b462c503dd5a6e7bcdd8cb353d89604e9d18802eeS3BucketADC4FFB3": { "Type": "String", - "Description": "S3 bucket for asset \"50fe6f46cf18fb257f00fed007da5c80fbf5dee08bec37fe765c50188eecae0b\"" + "Description": "S3 bucket for asset \"1a697367cf267ba8a72f772b462c503dd5a6e7bcdd8cb353d89604e9d18802ee\"" }, - "AssetParameters50fe6f46cf18fb257f00fed007da5c80fbf5dee08bec37fe765c50188eecae0bS3VersionKey1CF28CF5": { + "AssetParameters1a697367cf267ba8a72f772b462c503dd5a6e7bcdd8cb353d89604e9d18802eeS3VersionKey784D7689": { "Type": "String", - "Description": "S3 key for asset version \"50fe6f46cf18fb257f00fed007da5c80fbf5dee08bec37fe765c50188eecae0b\"" + "Description": "S3 key for asset version \"1a697367cf267ba8a72f772b462c503dd5a6e7bcdd8cb353d89604e9d18802ee\"" }, - "AssetParameters50fe6f46cf18fb257f00fed007da5c80fbf5dee08bec37fe765c50188eecae0bArtifactHashC28E4EDF": { + "AssetParameters1a697367cf267ba8a72f772b462c503dd5a6e7bcdd8cb353d89604e9d18802eeArtifactHash76BB0F66": { "Type": "String", - "Description": "Artifact hash for asset \"50fe6f46cf18fb257f00fed007da5c80fbf5dee08bec37fe765c50188eecae0b\"" + "Description": "Artifact hash for asset \"1a697367cf267ba8a72f772b462c503dd5a6e7bcdd8cb353d89604e9d18802ee\"" }, - "AssetParameters314bcf2cee9a5b01d3be2c2815602d1d784611fac220dde672aca6cb54299076S3BucketD6F6E4F2": { + "AssetParameters8eb49c58869010ec84a5b407369006e9cb5fdf9667d8609638d6fa59265fc3ecS3Bucket5B706387": { "Type": "String", - "Description": "S3 bucket for asset \"314bcf2cee9a5b01d3be2c2815602d1d784611fac220dde672aca6cb54299076\"" + "Description": "S3 bucket for asset \"8eb49c58869010ec84a5b407369006e9cb5fdf9667d8609638d6fa59265fc3ec\"" }, - "AssetParameters314bcf2cee9a5b01d3be2c2815602d1d784611fac220dde672aca6cb54299076S3VersionKeyE0B47D8E": { + "AssetParameters8eb49c58869010ec84a5b407369006e9cb5fdf9667d8609638d6fa59265fc3ecS3VersionKey48CF3C03": { "Type": "String", - "Description": "S3 key for asset version \"314bcf2cee9a5b01d3be2c2815602d1d784611fac220dde672aca6cb54299076\"" + "Description": "S3 key for asset version \"8eb49c58869010ec84a5b407369006e9cb5fdf9667d8609638d6fa59265fc3ec\"" }, - "AssetParameters314bcf2cee9a5b01d3be2c2815602d1d784611fac220dde672aca6cb54299076ArtifactHash45C2DF56": { + "AssetParameters8eb49c58869010ec84a5b407369006e9cb5fdf9667d8609638d6fa59265fc3ecArtifactHashD17A32B8": { "Type": "String", - "Description": "Artifact hash for asset \"314bcf2cee9a5b01d3be2c2815602d1d784611fac220dde672aca6cb54299076\"" + "Description": "Artifact hash for asset \"8eb49c58869010ec84a5b407369006e9cb5fdf9667d8609638d6fa59265fc3ec\"" }, - "AssetParameters11846740cde0308720709e44dce627bf1ceb557ee9d0dbb556a05632da565ef2S3BucketE17A9F3E": { + "AssetParameters992750d379dbbe94426dbd352099e3344b9edcfd098a56691b53eafaeb227b10S3Bucket9E9FE80F": { "Type": "String", - "Description": "S3 bucket for asset \"11846740cde0308720709e44dce627bf1ceb557ee9d0dbb556a05632da565ef2\"" + "Description": "S3 bucket for asset \"992750d379dbbe94426dbd352099e3344b9edcfd098a56691b53eafaeb227b10\"" }, - "AssetParameters11846740cde0308720709e44dce627bf1ceb557ee9d0dbb556a05632da565ef2S3VersionKey0A0F7F93": { + "AssetParameters992750d379dbbe94426dbd352099e3344b9edcfd098a56691b53eafaeb227b10S3VersionKeyFFAE128A": { "Type": "String", - "Description": "S3 key for asset version \"11846740cde0308720709e44dce627bf1ceb557ee9d0dbb556a05632da565ef2\"" + "Description": "S3 key for asset version \"992750d379dbbe94426dbd352099e3344b9edcfd098a56691b53eafaeb227b10\"" }, - "AssetParameters11846740cde0308720709e44dce627bf1ceb557ee9d0dbb556a05632da565ef2ArtifactHash743A82BD": { + "AssetParameters992750d379dbbe94426dbd352099e3344b9edcfd098a56691b53eafaeb227b10ArtifactHashCAE919EE": { "Type": "String", - "Description": "Artifact hash for asset \"11846740cde0308720709e44dce627bf1ceb557ee9d0dbb556a05632da565ef2\"" + "Description": "Artifact hash for asset \"992750d379dbbe94426dbd352099e3344b9edcfd098a56691b53eafaeb227b10\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json index c2ad372f25649..9b5e2aba2f075 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters3bc4d0e28b60c2b2468004185dabbe33a91c04563872cafb35b5b71e8a8f33d4S3Bucket68AF28A9" + "Ref": "AssetParameters75ddddb47479a218a15821c4afd818c51d26c8340cbbe49f6eca0b7b802e3923S3Bucket1A5FBFC5" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3bc4d0e28b60c2b2468004185dabbe33a91c04563872cafb35b5b71e8a8f33d4S3VersionKeyACC0084B" + "Ref": "AssetParameters75ddddb47479a218a15821c4afd818c51d26c8340cbbe49f6eca0b7b802e3923S3VersionKey67EF2E81" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3bc4d0e28b60c2b2468004185dabbe33a91c04563872cafb35b5b71e8a8f33d4S3VersionKeyACC0084B" + "Ref": "AssetParameters75ddddb47479a218a15821c4afd818c51d26c8340cbbe49f6eca0b7b802e3923S3VersionKey67EF2E81" } ] } @@ -121,7 +121,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersc1614067648b8b7e151e321ce82879d259a2b8f2bd10dddd61f0f2ce26287c17S3Bucket980A99E7" + "Ref": "AssetParameters929810b8fc5030a232173abd44cd2d54f4735eece74ac8cf1c34e7b9dd9161a3S3Bucket0ECFE319" }, "S3Key": { "Fn::Join": [ @@ -134,7 +134,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersc1614067648b8b7e151e321ce82879d259a2b8f2bd10dddd61f0f2ce26287c17S3VersionKeyC4E1E9B5" + "Ref": "AssetParameters929810b8fc5030a232173abd44cd2d54f4735eece74ac8cf1c34e7b9dd9161a3S3VersionKeyA373952D" } ] } @@ -147,7 +147,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersc1614067648b8b7e151e321ce82879d259a2b8f2bd10dddd61f0f2ce26287c17S3VersionKeyC4E1E9B5" + "Ref": "AssetParameters929810b8fc5030a232173abd44cd2d54f4735eece74ac8cf1c34e7b9dd9161a3S3VersionKeyA373952D" } ] } @@ -206,7 +206,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters3610bde00ecd0013f7806e2ab0e80d7ac26232cd3ffc2934b5ca28fef120bdf6S3Bucket91AABE39" + "Ref": "AssetParameters5a08158e59eb223498febeed20bc4005c3e81534f6c47bd7d8a2079f256f25d0S3BucketDB10730C" }, "S3Key": { "Fn::Join": [ @@ -219,7 +219,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3610bde00ecd0013f7806e2ab0e80d7ac26232cd3ffc2934b5ca28fef120bdf6S3VersionKeyEE0FAD90" + "Ref": "AssetParameters5a08158e59eb223498febeed20bc4005c3e81534f6c47bd7d8a2079f256f25d0S3VersionKeyE7AE1114" } ] } @@ -232,7 +232,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3610bde00ecd0013f7806e2ab0e80d7ac26232cd3ffc2934b5ca28fef120bdf6S3VersionKeyEE0FAD90" + "Ref": "AssetParameters5a08158e59eb223498febeed20bc4005c3e81534f6c47bd7d8a2079f256f25d0S3VersionKeyE7AE1114" } ] } @@ -257,41 +257,41 @@ } }, "Parameters": { - "AssetParameters3bc4d0e28b60c2b2468004185dabbe33a91c04563872cafb35b5b71e8a8f33d4S3Bucket68AF28A9": { + "AssetParameters75ddddb47479a218a15821c4afd818c51d26c8340cbbe49f6eca0b7b802e3923S3Bucket1A5FBFC5": { "Type": "String", - "Description": "S3 bucket for asset \"3bc4d0e28b60c2b2468004185dabbe33a91c04563872cafb35b5b71e8a8f33d4\"" + "Description": "S3 bucket for asset \"75ddddb47479a218a15821c4afd818c51d26c8340cbbe49f6eca0b7b802e3923\"" }, - "AssetParameters3bc4d0e28b60c2b2468004185dabbe33a91c04563872cafb35b5b71e8a8f33d4S3VersionKeyACC0084B": { + "AssetParameters75ddddb47479a218a15821c4afd818c51d26c8340cbbe49f6eca0b7b802e3923S3VersionKey67EF2E81": { "Type": "String", - "Description": "S3 key for asset version \"3bc4d0e28b60c2b2468004185dabbe33a91c04563872cafb35b5b71e8a8f33d4\"" + "Description": "S3 key for asset version \"75ddddb47479a218a15821c4afd818c51d26c8340cbbe49f6eca0b7b802e3923\"" }, - "AssetParameters3bc4d0e28b60c2b2468004185dabbe33a91c04563872cafb35b5b71e8a8f33d4ArtifactHashC2D4B1C3": { + "AssetParameters75ddddb47479a218a15821c4afd818c51d26c8340cbbe49f6eca0b7b802e3923ArtifactHash122807F1": { "Type": "String", - "Description": "Artifact hash for asset \"3bc4d0e28b60c2b2468004185dabbe33a91c04563872cafb35b5b71e8a8f33d4\"" + "Description": "Artifact hash for asset \"75ddddb47479a218a15821c4afd818c51d26c8340cbbe49f6eca0b7b802e3923\"" }, - "AssetParametersc1614067648b8b7e151e321ce82879d259a2b8f2bd10dddd61f0f2ce26287c17S3Bucket980A99E7": { + "AssetParameters929810b8fc5030a232173abd44cd2d54f4735eece74ac8cf1c34e7b9dd9161a3S3Bucket0ECFE319": { "Type": "String", - "Description": "S3 bucket for asset \"c1614067648b8b7e151e321ce82879d259a2b8f2bd10dddd61f0f2ce26287c17\"" + "Description": "S3 bucket for asset \"929810b8fc5030a232173abd44cd2d54f4735eece74ac8cf1c34e7b9dd9161a3\"" }, - "AssetParametersc1614067648b8b7e151e321ce82879d259a2b8f2bd10dddd61f0f2ce26287c17S3VersionKeyC4E1E9B5": { + "AssetParameters929810b8fc5030a232173abd44cd2d54f4735eece74ac8cf1c34e7b9dd9161a3S3VersionKeyA373952D": { "Type": "String", - "Description": "S3 key for asset version \"c1614067648b8b7e151e321ce82879d259a2b8f2bd10dddd61f0f2ce26287c17\"" + "Description": "S3 key for asset version \"929810b8fc5030a232173abd44cd2d54f4735eece74ac8cf1c34e7b9dd9161a3\"" }, - "AssetParametersc1614067648b8b7e151e321ce82879d259a2b8f2bd10dddd61f0f2ce26287c17ArtifactHash5B02FA4D": { + "AssetParameters929810b8fc5030a232173abd44cd2d54f4735eece74ac8cf1c34e7b9dd9161a3ArtifactHash196D379D": { "Type": "String", - "Description": "Artifact hash for asset \"c1614067648b8b7e151e321ce82879d259a2b8f2bd10dddd61f0f2ce26287c17\"" + "Description": "Artifact hash for asset \"929810b8fc5030a232173abd44cd2d54f4735eece74ac8cf1c34e7b9dd9161a3\"" }, - "AssetParameters3610bde00ecd0013f7806e2ab0e80d7ac26232cd3ffc2934b5ca28fef120bdf6S3Bucket91AABE39": { + "AssetParameters5a08158e59eb223498febeed20bc4005c3e81534f6c47bd7d8a2079f256f25d0S3BucketDB10730C": { "Type": "String", - "Description": "S3 bucket for asset \"3610bde00ecd0013f7806e2ab0e80d7ac26232cd3ffc2934b5ca28fef120bdf6\"" + "Description": "S3 bucket for asset \"5a08158e59eb223498febeed20bc4005c3e81534f6c47bd7d8a2079f256f25d0\"" }, - "AssetParameters3610bde00ecd0013f7806e2ab0e80d7ac26232cd3ffc2934b5ca28fef120bdf6S3VersionKeyEE0FAD90": { + "AssetParameters5a08158e59eb223498febeed20bc4005c3e81534f6c47bd7d8a2079f256f25d0S3VersionKeyE7AE1114": { "Type": "String", - "Description": "S3 key for asset version \"3610bde00ecd0013f7806e2ab0e80d7ac26232cd3ffc2934b5ca28fef120bdf6\"" + "Description": "S3 key for asset version \"5a08158e59eb223498febeed20bc4005c3e81534f6c47bd7d8a2079f256f25d0\"" }, - "AssetParameters3610bde00ecd0013f7806e2ab0e80d7ac26232cd3ffc2934b5ca28fef120bdf6ArtifactHash09CEB444": { + "AssetParameters5a08158e59eb223498febeed20bc4005c3e81534f6c47bd7d8a2079f256f25d0ArtifactHashF5CC0D13": { "Type": "String", - "Description": "Artifact hash for asset \"3610bde00ecd0013f7806e2ab0e80d7ac26232cd3ffc2934b5ca28fef120bdf6\"" + "Description": "Artifact hash for asset \"5a08158e59eb223498febeed20bc4005c3e81534f6c47bd7d8a2079f256f25d0\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.expected.json index aa13e73295e64..68838ca5bb2f0 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.expected.json @@ -5,7 +5,7 @@ "Properties": { "Content": { "S3Bucket": { - "Ref": "AssetParameterse174a6a88cb48eb510c29b2bf0203c181cfa059320745e6ae6429e522b36c66eS3Bucket7A00FCB4" + "Ref": "AssetParameters66cf329731482a1903f36e710c64efa07dd7fc320739e9a1c0d915fe3cfa3aa0S3BucketF123FCA1" }, "S3Key": { "Fn::Join": [ @@ -18,7 +18,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse174a6a88cb48eb510c29b2bf0203c181cfa059320745e6ae6429e522b36c66eS3VersionKey8BF2F9D6" + "Ref": "AssetParameters66cf329731482a1903f36e710c64efa07dd7fc320739e9a1c0d915fe3cfa3aa0S3VersionKey6FABFCA9" } ] } @@ -31,7 +31,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse174a6a88cb48eb510c29b2bf0203c181cfa059320745e6ae6429e522b36c66eS3VersionKey8BF2F9D6" + "Ref": "AssetParameters66cf329731482a1903f36e710c64efa07dd7fc320739e9a1c0d915fe3cfa3aa0S3VersionKey6FABFCA9" } ] } @@ -82,7 +82,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters907ec4b12820d4b2dc64e7f0f1f1f6267c1db622bc42aa57f5361efa12b1aafbS3Bucket16F02289" + "Ref": "AssetParameters2355f1daf8ff0670c3287d5f2b9bf5061dce576b20f03dddb2f016ba7a203c86S3Bucket03A296A2" }, "S3Key": { "Fn::Join": [ @@ -95,7 +95,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters907ec4b12820d4b2dc64e7f0f1f1f6267c1db622bc42aa57f5361efa12b1aafbS3VersionKeyDAF0A5BD" + "Ref": "AssetParameters2355f1daf8ff0670c3287d5f2b9bf5061dce576b20f03dddb2f016ba7a203c86S3VersionKey90B885D0" } ] } @@ -108,7 +108,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters907ec4b12820d4b2dc64e7f0f1f1f6267c1db622bc42aa57f5361efa12b1aafbS3VersionKeyDAF0A5BD" + "Ref": "AssetParameters2355f1daf8ff0670c3287d5f2b9bf5061dce576b20f03dddb2f016ba7a203c86S3VersionKey90B885D0" } ] } @@ -138,29 +138,29 @@ } }, "Parameters": { - "AssetParameterse174a6a88cb48eb510c29b2bf0203c181cfa059320745e6ae6429e522b36c66eS3Bucket7A00FCB4": { + "AssetParameters66cf329731482a1903f36e710c64efa07dd7fc320739e9a1c0d915fe3cfa3aa0S3BucketF123FCA1": { "Type": "String", - "Description": "S3 bucket for asset \"e174a6a88cb48eb510c29b2bf0203c181cfa059320745e6ae6429e522b36c66e\"" + "Description": "S3 bucket for asset \"66cf329731482a1903f36e710c64efa07dd7fc320739e9a1c0d915fe3cfa3aa0\"" }, - "AssetParameterse174a6a88cb48eb510c29b2bf0203c181cfa059320745e6ae6429e522b36c66eS3VersionKey8BF2F9D6": { + "AssetParameters66cf329731482a1903f36e710c64efa07dd7fc320739e9a1c0d915fe3cfa3aa0S3VersionKey6FABFCA9": { "Type": "String", - "Description": "S3 key for asset version \"e174a6a88cb48eb510c29b2bf0203c181cfa059320745e6ae6429e522b36c66e\"" + "Description": "S3 key for asset version \"66cf329731482a1903f36e710c64efa07dd7fc320739e9a1c0d915fe3cfa3aa0\"" }, - "AssetParameterse174a6a88cb48eb510c29b2bf0203c181cfa059320745e6ae6429e522b36c66eArtifactHash2DECF34E": { + "AssetParameters66cf329731482a1903f36e710c64efa07dd7fc320739e9a1c0d915fe3cfa3aa0ArtifactHash0384F1C8": { "Type": "String", - "Description": "Artifact hash for asset \"e174a6a88cb48eb510c29b2bf0203c181cfa059320745e6ae6429e522b36c66e\"" + "Description": "Artifact hash for asset \"66cf329731482a1903f36e710c64efa07dd7fc320739e9a1c0d915fe3cfa3aa0\"" }, - "AssetParameters907ec4b12820d4b2dc64e7f0f1f1f6267c1db622bc42aa57f5361efa12b1aafbS3Bucket16F02289": { + "AssetParameters2355f1daf8ff0670c3287d5f2b9bf5061dce576b20f03dddb2f016ba7a203c86S3Bucket03A296A2": { "Type": "String", - "Description": "S3 bucket for asset \"907ec4b12820d4b2dc64e7f0f1f1f6267c1db622bc42aa57f5361efa12b1aafb\"" + "Description": "S3 bucket for asset \"2355f1daf8ff0670c3287d5f2b9bf5061dce576b20f03dddb2f016ba7a203c86\"" }, - "AssetParameters907ec4b12820d4b2dc64e7f0f1f1f6267c1db622bc42aa57f5361efa12b1aafbS3VersionKeyDAF0A5BD": { + "AssetParameters2355f1daf8ff0670c3287d5f2b9bf5061dce576b20f03dddb2f016ba7a203c86S3VersionKey90B885D0": { "Type": "String", - "Description": "S3 key for asset version \"907ec4b12820d4b2dc64e7f0f1f1f6267c1db622bc42aa57f5361efa12b1aafb\"" + "Description": "S3 key for asset version \"2355f1daf8ff0670c3287d5f2b9bf5061dce576b20f03dddb2f016ba7a203c86\"" }, - "AssetParameters907ec4b12820d4b2dc64e7f0f1f1f6267c1db622bc42aa57f5361efa12b1aafbArtifactHashD7592B0F": { + "AssetParameters2355f1daf8ff0670c3287d5f2b9bf5061dce576b20f03dddb2f016ba7a203c86ArtifactHash937218E0": { "Type": "String", - "Description": "Artifact hash for asset \"907ec4b12820d4b2dc64e7f0f1f1f6267c1db622bc42aa57f5361efa12b1aafb\"" + "Description": "Artifact hash for asset \"2355f1daf8ff0670c3287d5f2b9bf5061dce576b20f03dddb2f016ba7a203c86\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json index c49f5e312d873..df601bc92acd9 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters6cc4994756d4085a860e734568c92826773e52c22c58894ce368b1e698da413dS3Bucket74CBB570" + "Ref": "AssetParameters29cd01a5fe529da311cdec40c163fb1c04b1b3dc8caadff0ea696fb6e63610feS3Bucket38F00249" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters6cc4994756d4085a860e734568c92826773e52c22c58894ce368b1e698da413dS3VersionKey3CBAF21A" + "Ref": "AssetParameters29cd01a5fe529da311cdec40c163fb1c04b1b3dc8caadff0ea696fb6e63610feS3VersionKey60957E7E" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters6cc4994756d4085a860e734568c92826773e52c22c58894ce368b1e698da413dS3VersionKey3CBAF21A" + "Ref": "AssetParameters29cd01a5fe529da311cdec40c163fb1c04b1b3dc8caadff0ea696fb6e63610feS3VersionKey60957E7E" } ] } @@ -87,17 +87,17 @@ } }, "Parameters": { - "AssetParameters6cc4994756d4085a860e734568c92826773e52c22c58894ce368b1e698da413dS3Bucket74CBB570": { + "AssetParameters29cd01a5fe529da311cdec40c163fb1c04b1b3dc8caadff0ea696fb6e63610feS3Bucket38F00249": { "Type": "String", - "Description": "S3 bucket for asset \"6cc4994756d4085a860e734568c92826773e52c22c58894ce368b1e698da413d\"" + "Description": "S3 bucket for asset \"29cd01a5fe529da311cdec40c163fb1c04b1b3dc8caadff0ea696fb6e63610fe\"" }, - "AssetParameters6cc4994756d4085a860e734568c92826773e52c22c58894ce368b1e698da413dS3VersionKey3CBAF21A": { + "AssetParameters29cd01a5fe529da311cdec40c163fb1c04b1b3dc8caadff0ea696fb6e63610feS3VersionKey60957E7E": { "Type": "String", - "Description": "S3 key for asset version \"6cc4994756d4085a860e734568c92826773e52c22c58894ce368b1e698da413d\"" + "Description": "S3 key for asset version \"29cd01a5fe529da311cdec40c163fb1c04b1b3dc8caadff0ea696fb6e63610fe\"" }, - "AssetParameters6cc4994756d4085a860e734568c92826773e52c22c58894ce368b1e698da413dArtifactHash750F3AF8": { + "AssetParameters29cd01a5fe529da311cdec40c163fb1c04b1b3dc8caadff0ea696fb6e63610feArtifactHash29C18922": { "Type": "String", - "Description": "Artifact hash for asset \"6cc4994756d4085a860e734568c92826773e52c22c58894ce368b1e698da413d\"" + "Description": "Artifact hash for asset \"29cd01a5fe529da311cdec40c163fb1c04b1b3dc8caadff0ea696fb6e63610fe\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json index c70f4a0ca8933..131ca14b5e5c9 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters6fa8e0c54d06a6402126a86fab5da1fa1397bcce628a0fb56f8356a2edf6280dS3Bucket2F189DB9" + "Ref": "AssetParameterse6fadc5eeabdb3ea6ecd571e8ec74e7943cd34cd4ec9d46d0506a200e6163a93S3BucketBA49B914" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters6fa8e0c54d06a6402126a86fab5da1fa1397bcce628a0fb56f8356a2edf6280dS3VersionKeyDF03C812" + "Ref": "AssetParameterse6fadc5eeabdb3ea6ecd571e8ec74e7943cd34cd4ec9d46d0506a200e6163a93S3VersionKey74688352" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters6fa8e0c54d06a6402126a86fab5da1fa1397bcce628a0fb56f8356a2edf6280dS3VersionKeyDF03C812" + "Ref": "AssetParameterse6fadc5eeabdb3ea6ecd571e8ec74e7943cd34cd4ec9d46d0506a200e6163a93S3VersionKey74688352" } ] } @@ -87,17 +87,17 @@ } }, "Parameters": { - "AssetParameters6fa8e0c54d06a6402126a86fab5da1fa1397bcce628a0fb56f8356a2edf6280dS3Bucket2F189DB9": { + "AssetParameterse6fadc5eeabdb3ea6ecd571e8ec74e7943cd34cd4ec9d46d0506a200e6163a93S3BucketBA49B914": { "Type": "String", - "Description": "S3 bucket for asset \"6fa8e0c54d06a6402126a86fab5da1fa1397bcce628a0fb56f8356a2edf6280d\"" + "Description": "S3 bucket for asset \"e6fadc5eeabdb3ea6ecd571e8ec74e7943cd34cd4ec9d46d0506a200e6163a93\"" }, - "AssetParameters6fa8e0c54d06a6402126a86fab5da1fa1397bcce628a0fb56f8356a2edf6280dS3VersionKeyDF03C812": { + "AssetParameterse6fadc5eeabdb3ea6ecd571e8ec74e7943cd34cd4ec9d46d0506a200e6163a93S3VersionKey74688352": { "Type": "String", - "Description": "S3 key for asset version \"6fa8e0c54d06a6402126a86fab5da1fa1397bcce628a0fb56f8356a2edf6280d\"" + "Description": "S3 key for asset version \"e6fadc5eeabdb3ea6ecd571e8ec74e7943cd34cd4ec9d46d0506a200e6163a93\"" }, - "AssetParameters6fa8e0c54d06a6402126a86fab5da1fa1397bcce628a0fb56f8356a2edf6280dArtifactHash1F692755": { + "AssetParameterse6fadc5eeabdb3ea6ecd571e8ec74e7943cd34cd4ec9d46d0506a200e6163a93ArtifactHash6870D06D": { "Type": "String", - "Description": "Artifact hash for asset \"6fa8e0c54d06a6402126a86fab5da1fa1397bcce628a0fb56f8356a2edf6280d\"" + "Description": "Artifact hash for asset \"e6fadc5eeabdb3ea6ecd571e8ec74e7943cd34cd4ec9d46d0506a200e6163a93\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json index 6787327a15e40..1efa22d57d44f 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json @@ -296,7 +296,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters83be407310ab5911f40fa4934091a233f92ce3be1d81c48846f30fa0a9330530S3Bucket82392CBF" + "Ref": "AssetParameters5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3cS3Bucket5B146B0B" }, "S3Key": { "Fn::Join": [ @@ -309,7 +309,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters83be407310ab5911f40fa4934091a233f92ce3be1d81c48846f30fa0a9330530S3VersionKey292822E7" + "Ref": "AssetParameters5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3cS3VersionKeyC0C8A627" } ] } @@ -322,7 +322,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters83be407310ab5911f40fa4934091a233f92ce3be1d81c48846f30fa0a9330530S3VersionKey292822E7" + "Ref": "AssetParameters5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3cS3VersionKeyC0C8A627" } ] } @@ -368,17 +368,17 @@ } }, "Parameters": { - "AssetParameters83be407310ab5911f40fa4934091a233f92ce3be1d81c48846f30fa0a9330530S3Bucket82392CBF": { + "AssetParameters5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3cS3Bucket5B146B0B": { "Type": "String", - "Description": "S3 bucket for asset \"83be407310ab5911f40fa4934091a233f92ce3be1d81c48846f30fa0a9330530\"" + "Description": "S3 bucket for asset \"5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3c\"" }, - "AssetParameters83be407310ab5911f40fa4934091a233f92ce3be1d81c48846f30fa0a9330530S3VersionKey292822E7": { + "AssetParameters5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3cS3VersionKeyC0C8A627": { "Type": "String", - "Description": "S3 key for asset version \"83be407310ab5911f40fa4934091a233f92ce3be1d81c48846f30fa0a9330530\"" + "Description": "S3 key for asset version \"5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3c\"" }, - "AssetParameters83be407310ab5911f40fa4934091a233f92ce3be1d81c48846f30fa0a9330530ArtifactHash8818CE02": { + "AssetParameters5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3cArtifactHashB6A7723E": { "Type": "String", - "Description": "Artifact hash for asset \"83be407310ab5911f40fa4934091a233f92ce3be1d81c48846f30fa0a9330530\"" + "Description": "Artifact hash for asset \"5b8ff93384af5b488025e13b274c2dd894e474a810f1a406af1aeb4edbba6a3c\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts index 239bf58671b7e..924868449a973 100644 --- a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts +++ b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts @@ -168,6 +168,17 @@ export interface EventSourceMappingOptions { */ readonly kafkaTopic?: string; + /** + * The size of the tumbling windows to group records sent to DynamoDB or Kinesis + * + * @see https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-windows + * + * Valid Range: 0 - 15 minutes + * + * @default - None + */ + readonly tumblingWindow?: cdk.Duration; + /** * A list of host and port pairs that are the addresses of the Kafka brokers in a self managed "bootstrap" Kafka cluster * that a Kafka client connects to initially to bootstrap itself. @@ -269,6 +280,10 @@ export class EventSourceMapping extends cdk.Resource implements IEventSourceMapp } }); + if (props.tumblingWindow && !cdk.Token.isUnresolved(props.tumblingWindow) && props.tumblingWindow.toSeconds() > 900) { + throw new Error(`tumblingWindow cannot be over 900 seconds, got ${props.tumblingWindow.toSeconds()}`); + } + let destinationConfig; @@ -296,6 +311,7 @@ export class EventSourceMapping extends cdk.Resource implements IEventSourceMapp maximumRetryAttempts: props.retryAttempts, parallelizationFactor: props.parallelizationFactor, topics: props.kafkaTopic !== undefined ? [props.kafkaTopic] : undefined, + tumblingWindowInSeconds: props.tumblingWindow?.toSeconds(), sourceAccessConfigurations: props.sourceAccessConfigurations?.map((o) => {return { type: o.type.type, uri: o.uri };}), selfManagedEventSource, }); diff --git a/packages/@aws-cdk/aws-lambda/lib/log-retention.ts b/packages/@aws-cdk/aws-lambda/lib/log-retention.ts index e499764f96e20..3fe6db5e81880 100644 --- a/packages/@aws-cdk/aws-lambda/lib/log-retention.ts +++ b/packages/@aws-cdk/aws-lambda/lib/log-retention.ts @@ -3,8 +3,6 @@ import { Construct } from 'constructs'; /** * Retry options for all AWS API calls. - * - * @deprecated use `LogRetentionRetryOptions` from '@aws-cdk/aws-logs' instead */ export interface LogRetentionRetryOptions extends logs.LogRetentionRetryOptions { } diff --git a/packages/@aws-cdk/aws-lambda/lib/runtime.ts b/packages/@aws-cdk/aws-lambda/lib/runtime.ts index bb24fbba9eeae..e354e5862cc6e 100644 --- a/packages/@aws-cdk/aws-lambda/lib/runtime.ts +++ b/packages/@aws-cdk/aws-lambda/lib/runtime.ts @@ -9,7 +9,7 @@ export interface LambdaRuntimeProps { /** * The Docker image name to be used for bundling in this runtime. - * @default - the latest docker image "amazon/aws-sam-cli-build-image-" from https://hub.docker.com/u/amazon + * @default - the latest docker image "amazon/public.ecr.aws/sam/build-" from https://gallery.ecr.aws */ readonly bundlingDockerImage?: string; @@ -209,16 +209,24 @@ export class Runtime { public readonly family?: RuntimeFamily; /** - * The bundling Docker image for this runtime. + * DEPRECATED + * @deprecated use `bundlingImage` */ public readonly bundlingDockerImage: BundlingDockerImage; - constructor(name: string, family?: RuntimeFamily, props: LambdaRuntimeProps = { }) { + /** + * The bundling Docker image for this runtime. + */ + public readonly bundlingImage: DockerImage; + + constructor(name: string, family?: RuntimeFamily, props: LambdaRuntimeProps = {}) { this.name = name; this.supportsInlineCode = !!props.supportsInlineCode; this.family = family; - const imageName = props.bundlingDockerImage ?? `amazon/aws-sam-cli-build-image-${name}`; + + const imageName = props.bundlingDockerImage ?? `public.ecr.aws/sam/build-${name}`; this.bundlingDockerImage = DockerImage.fromRegistry(imageName); + this.bundlingImage = this.bundlingDockerImage; this.supportsCodeGuruProfiling = props.supportsCodeGuruProfiling ?? false; Runtime.ALL.push(this); @@ -230,7 +238,7 @@ export class Runtime { public runtimeEquals(other: Runtime): boolean { return other.name === this.name && - other.family === this.family && - other.supportsInlineCode === this.supportsInlineCode; + other.family === this.family && + other.supportsInlineCode === this.supportsInlineCode; } } diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index 667068af6bcf1..48b706274796e 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -76,7 +76,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/aws-lambda": "^8.10.72", + "@types/aws-lambda": "^8.10.73", "@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/event-source-mapping.test.ts b/packages/@aws-cdk/aws-lambda/test/event-source-mapping.test.ts index 5d833a2d865c6..92e60d3959f29 100644 --- a/packages/@aws-cdk/aws-lambda/test/event-source-mapping.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/event-source-mapping.test.ts @@ -261,4 +261,35 @@ describe('event source mapping', () => { SelfManagedEventSource: { Endpoints: { KafkaBootstrapServers: kafkaBootstrapServers } }, }); }); -}); + + test('throws if tumblingWindow > 900 seconds', () => { + const stack = new cdk.Stack(); + const fn = new Function(stack, 'fn', { + handler: 'index.handler', + code: Code.fromInline('exports.handler = ${handler.toString()}'), + runtime: Runtime.NODEJS_10_X, + }); + + expect(() => new EventSourceMapping(stack, 'test', { + target: fn, + eventSourceArn: '', + tumblingWindow: cdk.Duration.seconds(901), + })).toThrow(/tumblingWindow cannot be over 900 seconds/); + }); + + test('accepts if tumblingWindow is a token', () => { + const stack = new cdk.Stack(); + const fn = new Function(stack, 'fn', { + handler: 'index.handler', + code: Code.fromInline('exports.handler = ${handler.toString()}'), + runtime: Runtime.NODEJS_10_X, + }); + const lazyDuration = cdk.Duration.seconds(cdk.Lazy.number({ produce: () => 60 })); + + new EventSourceMapping(stack, 'test', { + target: fn, + eventSourceArn: '', + tumblingWindow: lazyDuration, + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/integ.bundling.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.bundling.expected.json index f91b4ba673c9e..75863fbac5fab 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.bundling.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.bundling.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersfbf992a3e922c30f9aed74db034a1ec115ec16544160e6820e5d2463bf1e29d1S3BucketF4EA3D4A" + "Ref": "AssetParameters4096fd7ad39dc95026cb4c6254d2421d276c3170018ff7abdb41197d50ebd47bS3Bucket48F36117" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersfbf992a3e922c30f9aed74db034a1ec115ec16544160e6820e5d2463bf1e29d1S3VersionKey50AB224E" + "Ref": "AssetParameters4096fd7ad39dc95026cb4c6254d2421d276c3170018ff7abdb41197d50ebd47bS3VersionKey5B24FA75" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersfbf992a3e922c30f9aed74db034a1ec115ec16544160e6820e5d2463bf1e29d1S3VersionKey50AB224E" + "Ref": "AssetParameters4096fd7ad39dc95026cb4c6254d2421d276c3170018ff7abdb41197d50ebd47bS3VersionKey5B24FA75" } ] } @@ -72,13 +72,13 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "FunctionServiceRole675BB04A", "Arn" ] }, + "Handler": "index.handler", "Runtime": "python3.6" }, "DependsOn": [ @@ -87,17 +87,17 @@ } }, "Parameters": { - "AssetParametersfbf992a3e922c30f9aed74db034a1ec115ec16544160e6820e5d2463bf1e29d1S3BucketF4EA3D4A": { + "AssetParameters4096fd7ad39dc95026cb4c6254d2421d276c3170018ff7abdb41197d50ebd47bS3Bucket48F36117": { "Type": "String", - "Description": "S3 bucket for asset \"fbf992a3e922c30f9aed74db034a1ec115ec16544160e6820e5d2463bf1e29d1\"" + "Description": "S3 bucket for asset \"4096fd7ad39dc95026cb4c6254d2421d276c3170018ff7abdb41197d50ebd47b\"" }, - "AssetParametersfbf992a3e922c30f9aed74db034a1ec115ec16544160e6820e5d2463bf1e29d1S3VersionKey50AB224E": { + "AssetParameters4096fd7ad39dc95026cb4c6254d2421d276c3170018ff7abdb41197d50ebd47bS3VersionKey5B24FA75": { "Type": "String", - "Description": "S3 key for asset version \"fbf992a3e922c30f9aed74db034a1ec115ec16544160e6820e5d2463bf1e29d1\"" + "Description": "S3 key for asset version \"4096fd7ad39dc95026cb4c6254d2421d276c3170018ff7abdb41197d50ebd47b\"" }, - "AssetParametersfbf992a3e922c30f9aed74db034a1ec115ec16544160e6820e5d2463bf1e29d1ArtifactHashDD1BB80E": { + "AssetParameters4096fd7ad39dc95026cb4c6254d2421d276c3170018ff7abdb41197d50ebd47bArtifactHashFE4A3131": { "Type": "String", - "Description": "Artifact hash for asset \"fbf992a3e922c30f9aed74db034a1ec115ec16544160e6820e5d2463bf1e29d1\"" + "Description": "Artifact hash for asset \"4096fd7ad39dc95026cb4c6254d2421d276c3170018ff7abdb41197d50ebd47b\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda/test/runtime.test.ts b/packages/@aws-cdk/aws-lambda/test/runtime.test.ts index e90d7535d8045..8e275927319a0 100644 --- a/packages/@aws-cdk/aws-lambda/test/runtime.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/runtime.test.ts @@ -43,7 +43,7 @@ describe('runtime', () => { const runtime = new lambda.Runtime('my-runtime-name'); // THEN - expect(runtime.bundlingDockerImage.image).toEqual('amazon/aws-sam-cli-build-image-my-runtime-name'); + expect(runtime.bundlingDockerImage.image).toEqual('public.ecr.aws/sam/build-my-runtime-name'); }); test('overridde to bundlingDockerImage points to the correct image', () => { diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index a3f06555afdb9..4e4e86f6a4d7f 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -651,6 +651,9 @@ interface InstanceConfig { */ function createInstances(cluster: DatabaseClusterNew, props: DatabaseClusterBaseProps, subnetGroup: ISubnetGroup): InstanceConfig { const instanceCount = props.instances != null ? props.instances : 2; + if (Token.isUnresolved(instanceCount)) { + throw new Error('The number of instances an RDS Cluster consists of cannot be provided as a deploy-time only value!'); + } if (instanceCount < 1) { throw new Error('At least one instance is required'); } diff --git a/packages/@aws-cdk/aws-rds/test/cluster.test.ts b/packages/@aws-cdk/aws-rds/test/cluster.test.ts index 74f538be5d998..de7c5900de836 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-rds/test/cluster.test.ts @@ -50,8 +50,22 @@ describe('cluster', () => { DeletionPolicy: 'Delete', UpdateReplacePolicy: 'Delete', }, ResourcePart.CompleteDefinition); + }); + test('validates that the number of instances is not a deploy-time value', () => { + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const parameter = new cdk.CfnParameter(stack, 'Param', { type: 'Number' }); + expect(() => { + new DatabaseCluster(stack, 'Database', { + instances: parameter.valueAsNumber, + engine: DatabaseClusterEngine.AURORA, + instanceProps: { + vpc, + }, + }); + }).toThrow('The number of instances an RDS Cluster consists of cannot be provided as a deploy-time only value!'); }); test('can create a cluster with a single instance', () => { @@ -81,8 +95,6 @@ describe('cluster', () => { MasterUserPassword: 'tooshort', VpcSecurityGroupIds: [{ 'Fn::GetAtt': ['DatabaseSecurityGroup5C91FDCB', 'GroupId'] }], }); - - }); test('can create a cluster with imported vpc and security group', () => { @@ -116,8 +128,6 @@ describe('cluster', () => { MasterUserPassword: 'tooshort', VpcSecurityGroupIds: ['SecurityGroupId12345'], }); - - }); test('cluster with parameter group', () => { @@ -150,8 +160,6 @@ describe('cluster', () => { expect(stack).toHaveResource('AWS::RDS::DBCluster', { DBClusterParameterGroupName: { Ref: 'ParamsA8366201' }, }); - - }); test("sets the retention policy of the SubnetGroup to 'Retain' if the Cluster is created with 'Retain'", () => { @@ -172,8 +180,6 @@ describe('cluster', () => { DeletionPolicy: 'Retain', UpdateReplacePolicy: 'Retain', }, ResourcePart.CompleteDefinition); - - }); test('creates a secret when master credentials are not specified', () => { @@ -230,8 +236,6 @@ describe('cluster', () => { SecretStringTemplate: '{"username":"admin"}', }, }); - - }); test('create an encrypted cluster with custom KMS key', () => { @@ -261,8 +265,6 @@ describe('cluster', () => { ], }, }); - - }); test('cluster with instance parameter group', () => { @@ -294,8 +296,6 @@ describe('cluster', () => { Ref: 'ParameterGroup5E32DECB', }, }); - - }); describe('performance insights', () => { @@ -323,8 +323,6 @@ describe('cluster', () => { PerformanceInsightsRetentionPeriod: 731, PerformanceInsightsKMSKeyId: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, }); - - }); test('setting performance insights fields enables performance insights', () => { @@ -348,8 +346,6 @@ describe('cluster', () => { EnablePerformanceInsights: true, PerformanceInsightsRetentionPeriod: 731, }); - - }); test('throws if performance insights fields are set but performance insights is disabled', () => { @@ -370,8 +366,6 @@ describe('cluster', () => { }, }); }).toThrow(/`enablePerformanceInsights` disabled, but `performanceInsightRetention` or `performanceInsightEncryptionKey` was set/); - - }); }); @@ -392,8 +386,6 @@ describe('cluster', () => { expect(stack).toHaveResource('AWS::RDS::DBInstance', { AutoMinorVersionUpgrade: false, }); - - }); test('cluster with allow upgrade of major version', () => { @@ -413,8 +405,6 @@ describe('cluster', () => { expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { AllowMajorVersionUpgrade: true, }); - - }); test('cluster with disallow remove backups', () => { @@ -434,8 +424,6 @@ describe('cluster', () => { expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { DeleteAutomatedBackups: false, }); - - }); test('create a cluster using a specific version of MySQL', () => { @@ -462,8 +450,6 @@ describe('cluster', () => { Engine: 'aurora-mysql', EngineVersion: '5.7.mysql_aurora.2.04.4', }); - - }); test('create a cluster using a specific version of Postgresql', () => { @@ -513,8 +499,6 @@ describe('cluster', () => { // THEN expect(stack.resolve(cluster.clusterEndpoint)).not.toEqual(stack.resolve(cluster.clusterReadEndpoint)); - - }); test('imported cluster with imported security group honors allowAllOutbound', () => { @@ -540,8 +524,6 @@ describe('cluster', () => { expect(stack).toHaveResource('AWS::EC2::SecurityGroupEgress', { GroupId: 'sg-123456789', }); - - }); test('can import a cluster with minimal attributes', () => { @@ -567,8 +549,6 @@ describe('cluster', () => { expect(() => cluster.clusterReadEndpoint).toThrow(/Cannot access `clusterReadEndpoint` of an imported cluster/); expect(() => cluster.instanceIdentifiers).toThrow(/Cannot access `instanceIdentifiers` of an imported cluster/); expect(() => cluster.instanceEndpoints).toThrow(/Cannot access `instanceEndpoints` of an imported cluster/); - - }); test('imported cluster can access properties if attributes are provided', () => { @@ -590,8 +570,6 @@ describe('cluster', () => { expect(cluster.clusterReadEndpoint.socketAddress).toEqual('reader-address:3306'); expect(cluster.instanceIdentifiers).toEqual(['identifier']); expect(cluster.instanceEndpoints.map(endpoint => endpoint.socketAddress)).toEqual(['instance-addr:3306']); - - }); test('cluster supports metrics', () => { diff --git a/packages/@aws-cdk/aws-s3-assets/lib/asset.ts b/packages/@aws-cdk/aws-s3-assets/lib/asset.ts index aa99ab67e40a3..0777602e788f6 100644 --- a/packages/@aws-cdk/aws-s3-assets/lib/asset.ts +++ b/packages/@aws-cdk/aws-s3-assets/lib/asset.ts @@ -14,7 +14,7 @@ import { CopyOptions } from '@aws-cdk/assets'; // eslint-disable-next-line no-duplicate-imports, import/order import { Construct as CoreConstruct } from '@aws-cdk/core'; -export interface AssetOptions extends CopyOptions, cdk.AssetOptions { +export interface AssetOptions extends CopyOptions, cdk.FileCopyOptions, cdk.AssetOptions { /** * A list of principals that should be able to read this asset from S3. * You can use `asset.grantRead(principal)` to grant read permissions later. @@ -127,7 +127,7 @@ export class Asset extends CoreConstruct implements cdk.IAsset { const staging = new cdk.AssetStaging(this, 'Stage', { ...props, sourcePath: path.resolve(props.path), - follow: toSymlinkFollow(props.follow), + follow: props.followSymlinks ?? toSymlinkFollow(props.follow), assetHash: props.assetHash ?? props.sourceHash, }); diff --git a/packages/@aws-cdk/aws-s3-deployment/package.json b/packages/@aws-cdk/aws-s3-deployment/package.json index 1474657a73322..71dabe9caf6e0 100644 --- a/packages/@aws-cdk/aws-s3-deployment/package.json +++ b/packages/@aws-cdk/aws-s3-deployment/package.json @@ -79,7 +79,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@aws-cdk/cx-api": "0.0.0", - "@types/jest": "^26.0.21", + "@types/jest": "^26.0.22", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "jest": "^26.6.3", diff --git a/packages/@aws-cdk/aws-sam/package.json b/packages/@aws-cdk/aws-sam/package.json index 4b0b0c44b92e0..b6f36a639ba2a 100644 --- a/packages/@aws-cdk/aws-sam/package.json +++ b/packages/@aws-cdk/aws-sam/package.json @@ -73,7 +73,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/jest": "^26.0.21", + "@types/jest": "^26.0.22", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", "jest": "^26.6.3", diff --git a/packages/@aws-cdk/cdk-assets-schema/package.json b/packages/@aws-cdk/cdk-assets-schema/package.json index 80be64f4b67d3..1016f2d99ff1f 100644 --- a/packages/@aws-cdk/cdk-assets-schema/package.json +++ b/packages/@aws-cdk/cdk-assets-schema/package.json @@ -50,7 +50,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^26.0.21", + "@types/jest": "^26.0.22", "cdk-build-tools": "0.0.0", "jest": "^26.6.3", "pkglint": "0.0.0" diff --git a/packages/@aws-cdk/cloud-assembly-schema/package.json b/packages/@aws-cdk/cloud-assembly-schema/package.json index 9608d3fe6c8e0..08097a47c767d 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/package.json +++ b/packages/@aws-cdk/cloud-assembly-schema/package.json @@ -58,7 +58,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^26.0.21", + "@types/jest": "^26.0.22", "@types/mock-fs": "^4.13.0", "cdk-build-tools": "0.0.0", "jest": "^26.6.3", diff --git a/packages/@aws-cdk/cloudformation-diff/README.md b/packages/@aws-cdk/cloudformation-diff/README.md index 678bc2c9ba0d2..cc8a457769e9e 100644 --- a/packages/@aws-cdk/cloudformation-diff/README.md +++ b/packages/@aws-cdk/cloudformation-diff/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/cloudformation-diff/package.json b/packages/@aws-cdk/cloudformation-diff/package.json index f9b2c7342a84e..5b4afe8d0dcc4 100644 --- a/packages/@aws-cdk/cloudformation-diff/package.json +++ b/packages/@aws-cdk/cloudformation-diff/package.json @@ -26,10 +26,10 @@ "diff": "^5.0.0", "fast-deep-equal": "^3.1.3", "string-width": "^4.2.2", - "table": "^6.0.7" + "table": "^6.0.9" }, "devDependencies": { - "@types/jest": "^26.0.21", + "@types/jest": "^26.0.22", "@types/string-width": "^4.0.1", "@types/table": "^6.0.0", "cdk-build-tools": "0.0.0", @@ -51,8 +51,8 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, - "stability": "experimental", - "maturity": "experimental", + "stability": "stable", + "maturity": "stable", "cdk-build": { "jest": true }, diff --git a/packages/@aws-cdk/cloudformation-include/package.json b/packages/@aws-cdk/cloudformation-include/package.json index 6ec40598cac62..b9b82e67f55a3 100644 --- a/packages/@aws-cdk/cloudformation-include/package.json +++ b/packages/@aws-cdk/cloudformation-include/package.json @@ -368,7 +368,7 @@ }, "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/jest": "^26.0.21", + "@types/jest": "^26.0.22", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "jest": "^26.6.3", diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/yaml/year-month-date-as-strings.yaml b/packages/@aws-cdk/cloudformation-include/test/test-templates/yaml/year-month-date-as-strings.yaml new file mode 100644 index 0000000000000..f25f363879810 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/yaml/year-month-date-as-strings.yaml @@ -0,0 +1,14 @@ +AWSTemplateFormatVersion: 2010-09-09 +Resources: + Role: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Action: + - sts:AssumeRole diff --git a/packages/@aws-cdk/cloudformation-include/test/yaml-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/yaml-templates.test.ts index 5faf8bd1a5e1c..0724020f90f56 100644 --- a/packages/@aws-cdk/cloudformation-include/test/yaml-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/yaml-templates.test.ts @@ -23,6 +23,33 @@ describe('CDK Include', () => { ); }); + test('can ingest a template with year-month-date parsed as string instead of Date', () => { + includeTestTemplate(stack, 'year-month-date-as-strings.yaml'); + + expect(stack).toMatchTemplate({ + "AWSTemplateFormatVersion": "2010-09-09", + "Resources": { + "Role": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": ["ec2.amazonaws.com"], + }, + "Action": ["sts:AssumeRole"], + }, + ], + }, + }, + }, + }, + }); + }); + test('can ingest a template with the short form Base64 function', () => { includeTestTemplate(stack, 'short-form-base64.yaml'); diff --git a/packages/@aws-cdk/core/lib/arn.ts b/packages/@aws-cdk/core/lib/arn.ts index 1ffcc2da2b33c..fe6b5b12b934b 100644 --- a/packages/@aws-cdk/core/lib/arn.ts +++ b/packages/@aws-cdk/core/lib/arn.ts @@ -286,31 +286,39 @@ function parseToken(arnToken: string, sep: string = '/', hasName: boolean = true * Validate that a string is either unparseable or looks mostly like an ARN */ function parseArnShape(arn: string): 'token' | string[] { - const components = arn.split(':'); - const looksLikeArn = arn.startsWith('arn:') && components.length >= 6; + // assume anything that starts with 'arn:' is an ARN, + // so we can report better errors + const looksLikeArn = arn.startsWith('arn:'); if (!looksLikeArn) { - if (Token.isUnresolved(arn)) { return 'token'; } - throw new Error(`ARNs must start with "arn:" and have at least 6 components: ${arn}`); + if (Token.isUnresolved(arn)) { + return 'token'; + } else { + throw new Error(`ARNs must start with "arn:" and have at least 6 components: ${arn}`); + } } // If the ARN merely contains Tokens, but otherwise *looks* mostly like an ARN, - // it's a string of the form 'arn:${partition}:service:${region}:${account}:abc/xyz'. + // it's a string of the form 'arn:${partition}:service:${region}:${account}:resource/xyz'. // Parse fields out to the best of our ability. // Tokens won't contain ":", so this won't break them. + const components = arn.split(':'); - const [/* arn */, partition, service, /* region */ , /* account */ , resource] = components; + // const [/* arn */, partition, service, /* region */ , /* account */ , resource] = components; + const partition = components.length > 1 ? components[1] : undefined; if (!partition) { - throw new Error('The `partition` component (2nd component) is required: ' + arn); + throw new Error('The `partition` component (2nd component) of an ARN is required: ' + arn); } + const service = components.length > 2 ? components[2] : undefined; if (!service) { - throw new Error('The `service` component (3rd component) is required: ' + arn); + throw new Error('The `service` component (3rd component) of an ARN is required: ' + arn); } + const resource = components.length > 5 ? components[5] : undefined; if (!resource) { - throw new Error('The `resource` component (6th component) is required: ' + arn); + throw new Error('The `resource` component (6th component) of an ARN is required: ' + arn); } // Region can be missing in global ARNs (such as used by IAM) @@ -318,4 +326,4 @@ function parseArnShape(arn: string): 'token' | string[] { // Account can be missing in some ARN types (such as used for S3 buckets) return components; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/core/lib/bundling.ts b/packages/@aws-cdk/core/lib/bundling.ts index a9567fe6e464e..bd9299a5a990d 100644 --- a/packages/@aws-cdk/core/lib/bundling.ts +++ b/packages/@aws-cdk/core/lib/bundling.ts @@ -12,7 +12,7 @@ export interface BundlingOptions { /** * The Docker image where the command will run. */ - readonly image: BundlingDockerImage; + readonly image: DockerImage; /** * The entrypoint to run in the Docker container. @@ -157,34 +157,8 @@ export class BundlingDockerImage { * * @deprecated use DockerImage.fromBuild() */ - public static fromAsset(path: string, options: DockerBuildOptions = {}) { - const buildArgs = options.buildArgs || {}; - - if (options.file && isAbsolute(options.file)) { - throw new Error(`"file" must be relative to the docker build directory. Got ${options.file}`); - } - - // Image tag derived from path and build options - const input = JSON.stringify({ path, ...options }); - const tagHash = crypto.createHash('sha256').update(input).digest('hex'); - const tag = `cdk-${tagHash}`; - - const dockerArgs: string[] = [ - 'build', '-t', tag, - ...(options.file ? ['-f', join(path, options.file)] : []), - ...flatten(Object.entries(buildArgs).map(([k, v]) => ['--build-arg', `${k}=${v}`])), - path, - ]; - - dockerExec(dockerArgs); - - // Fingerprints the directory containing the Dockerfile we're building and - // differentiates the fingerprint based on build arguments. We do this so - // we can provide a stable image hash. Otherwise, the image ID will be - // different every time the Docker layer cache is cleared, due primarily to - // timestamps. - const hash = FileSystem.fingerprint(path, { extraHash: JSON.stringify(options) }); - return new BundlingDockerImage(tag, hash); + public static fromAsset(path: string, options: DockerBuildOptions = {}): BundlingDockerImage { + return DockerImage.fromBuild(path, options); } /** @param image The Docker image */ @@ -276,7 +250,33 @@ export class DockerImage extends BundlingDockerImage { * @param options Docker build options */ public static fromBuild(path: string, options: DockerBuildOptions = {}) { - return BundlingDockerImage.fromAsset(path, options); + const buildArgs = options.buildArgs || {}; + + if (options.file && isAbsolute(options.file)) { + throw new Error(`"file" must be relative to the docker build directory. Got ${options.file}`); + } + + // Image tag derived from path and build options + const input = JSON.stringify({ path, ...options }); + const tagHash = crypto.createHash('sha256').update(input).digest('hex'); + const tag = `cdk-${tagHash}`; + + const dockerArgs: string[] = [ + 'build', '-t', tag, + ...(options.file ? ['-f', join(path, options.file)] : []), + ...flatten(Object.entries(buildArgs).map(([k, v]) => ['--build-arg', `${k}=${v}`])), + path, + ]; + + dockerExec(dockerArgs); + + // Fingerprints the directory containing the Dockerfile we're building and + // differentiates the fingerprint based on build arguments. We do this so + // we can provide a stable image hash. Otherwise, the image ID will be + // different every time the Docker layer cache is cleared, due primarily to + // timestamps. + const hash = FileSystem.fingerprint(path, { extraHash: JSON.stringify(options) }); + return new DockerImage(tag, hash); } /** diff --git a/packages/@aws-cdk/core/lib/construct-compat.ts b/packages/@aws-cdk/core/lib/construct-compat.ts index ac7873a837dde..09a6fbd929496 100644 --- a/packages/@aws-cdk/core/lib/construct-compat.ts +++ b/packages/@aws-cdk/core/lib/construct-compat.ts @@ -412,11 +412,17 @@ export class ConstructNode { return this._actualNode.tryGetContext(key); } + /** + * DEPRECATED + * @deprecated use `metadataEntry` + */ + public get metadata() { return this._actualNode.metadata as cxapi.MetadataEntry[]; } + /** * An immutable array of metadata objects associated with this construct. * This can be used, for example, to implement support for deprecation notices, source mapping, etc. */ - public get metadata() { return this._actualNode.metadata as cxapi.MetadataEntry[]; } + public get metadataEntry() { return this._actualNode.metadata; } /** * Adds a metadata entry to this construct. diff --git a/packages/@aws-cdk/core/lib/fs/options.ts b/packages/@aws-cdk/core/lib/fs/options.ts index 3ea836a24e831..baf73bd7ffd30 100644 --- a/packages/@aws-cdk/core/lib/fs/options.ts +++ b/packages/@aws-cdk/core/lib/fs/options.ts @@ -56,19 +56,9 @@ export enum IgnoreMode { * context flag is set. */ DOCKER = 'docker' -}; - -/** - * Obtains applied when copying directories into the staging location. - */ -export interface CopyOptions { - /** - * A strategy for how to handle symlinks. - * - * @default SymlinkFollowMode.NEVER - */ - readonly follow?: SymlinkFollowMode; +} +interface FileOptions { /** * Glob patterns to exclude from the copy. * @@ -85,9 +75,30 @@ export interface CopyOptions { } /** - * Options related to calculating source hash. + * Options applied when copying directories + */ +export interface CopyOptions extends FileOptions { + /** + * A strategy for how to handle symlinks. + * + * @default SymlinkFollowMode.NEVER + */ + readonly follow?: SymlinkFollowMode; +} + +/** + * Options applied when copying directories into the staging location. */ -export interface FingerprintOptions extends CopyOptions { +export interface FileCopyOptions extends FileOptions { + /** + * A strategy for how to handle symlinks. + * + * @default SymlinkFollowMode.NEVER + */ + readonly followSymlinks?: SymlinkFollowMode; +} + +interface ExtraHashOptions { /** * Extra information to encode into the fingerprint (e.g. build instructions * and other inputs) @@ -96,3 +107,15 @@ export interface FingerprintOptions extends CopyOptions { */ readonly extraHash?: string; } + +/** + * Options related to calculating source hash. + */ +export interface FingerprintOptions extends CopyOptions, ExtraHashOptions { +} + +/** + * Options related to calculating source hash. + */ +export interface FileFingerprintOptions extends FileCopyOptions, ExtraHashOptions { +} diff --git a/packages/@aws-cdk/core/lib/resource.ts b/packages/@aws-cdk/core/lib/resource.ts index a828f0d6cec98..79d5542acc56e 100644 --- a/packages/@aws-cdk/core/lib/resource.ts +++ b/packages/@aws-cdk/core/lib/resource.ts @@ -90,6 +90,18 @@ export interface ResourceProps { * @default - the resource is in the same region as the stack it belongs to */ readonly region?: string; + + /** + * ARN to deduce region and account from + * + * The ARN is parsed and the account and region are taken from the ARN. + * This should be used for imported resources. + * + * Cannot be supplied together with either `account` or `region`. + * + * @default - take environment from `account`, `region` parameters, or use Stack environment. + */ + readonly environmentFromArn?: string; } /** @@ -126,12 +138,18 @@ export abstract class Resource extends CoreConstruct implements IResource { constructor(scope: Construct, id: string, props: ResourceProps = {}) { super(scope, id); + if ((props.account !== undefined || props.region !== undefined) && props.environmentFromArn !== undefined) { + throw new Error(`Supply at most one of 'account'/'region' (${props.account}/${props.region}) and 'environmentFromArn' (${props.environmentFromArn})`); + } + Object.defineProperty(this, RESOURCE_SYMBOL, { value: true }); this.stack = Stack.of(this); + + const parsedArn = props.environmentFromArn ? this.stack.parseArn(props.environmentFromArn) : undefined; this.env = { - account: props.account ?? this.stack.account, - region: props.region ?? this.stack.region, + account: props.account ?? parsedArn?.account ?? this.stack.account, + region: props.region ?? parsedArn?.region ?? this.stack.region, }; let physicalName = props.physicalName; diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index 2284ddedc203f..bd6f77c0da2f9 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -424,6 +424,17 @@ export class Stack extends CoreConstruct implements ITaggable { return CloudFormationLang.toJSON(obj, space).toString(); } + /** + * DEPRECATED + * @deprecated use `reportMissingContextKey()` + */ + public reportMissingContext(report: cxapi.MissingContext) { + if (!Object.values(cxschema.ContextProvider).includes(report.provider as cxschema.ContextProvider)) { + throw new Error(`Unknown context provider requested in: ${JSON.stringify(report)}`); + } + this.reportMissingContextKey(report as cxschema.MissingContext); + } + /** * Indicate that a context key was expected * @@ -432,11 +443,8 @@ export class Stack extends CoreConstruct implements ITaggable { * * @param report The set of parameters needed to obtain the context */ - public reportMissingContext(report: cxapi.MissingContext) { - if (!Object.values(cxschema.ContextProvider).includes(report.provider as cxschema.ContextProvider)) { - throw new Error(`Unknown context provider requested in: ${JSON.stringify(report)}`); - } - this._missingContext.push(report as cxschema.MissingContext); + public reportMissingContextKey(report: cxschema.MissingContext) { + this._missingContext.push(report); } /** diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index 26be28dcd023d..fb821664abaf5 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -175,8 +175,8 @@ "license": "Apache-2.0", "devDependencies": { "@types/lodash": "^4.14.168", - "@types/minimatch": "^3.0.3", - "@types/node": "^10.17.55", + "@types/minimatch": "^3.0.4", + "@types/node": "^10.17.56", "@types/sinon": "^9.0.11", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/core/test/arn.test.ts b/packages/@aws-cdk/core/test/arn.test.ts index b4b135fff94e6..febbcc407c2b7 100644 --- a/packages/@aws-cdk/core/test/arn.test.ts +++ b/packages/@aws-cdk/core/test/arn.test.ts @@ -110,19 +110,19 @@ nodeunitShim({ 'if the ARN doesnt have enough components'(test: Test) { const stack = new Stack(); - test.throws(() => stack.parseArn('arn:is:too:short'), /ARNs must.*have at least 6 components.*arn:is:too:short/); + test.throws(() => stack.parseArn('arn:is:too:short'), /The `resource` component \(6th component\) of an ARN is required/); test.done(); }, 'if "service" is not specified'(test: Test) { const stack = new Stack(); - test.throws(() => stack.parseArn('arn:aws::4:5:6'), /The `service` component \(3rd component\) is required/); + test.throws(() => stack.parseArn('arn:aws::4:5:6'), /The `service` component \(3rd component\) of an ARN is required/); test.done(); }, 'if "resource" is not specified'(test: Test) { const stack = new Stack(); - test.throws(() => stack.parseArn('arn:aws:service:::'), /The `resource` component \(6th component\) is required/); + test.throws(() => stack.parseArn('arn:aws:service:::'), /The `resource` component \(6th component\) of an ARN is required/); test.done(); }, }, diff --git a/packages/@aws-cdk/core/test/bundling.test.ts b/packages/@aws-cdk/core/test/bundling.test.ts index 99548030c011a..97b9d5969c2b3 100644 --- a/packages/@aws-cdk/core/test/bundling.test.ts +++ b/packages/@aws-cdk/core/test/bundling.test.ts @@ -60,7 +60,7 @@ nodeunitShim({ const fingerprintStub = sinon.stub(FileSystem, 'fingerprint'); fingerprintStub.callsFake(() => imageHash); - const image = BundlingDockerImage.fromAsset('docker-path', { + const image = DockerImage.fromBuild('docker-path', { buildArgs: { TEST_ARG: 'cdk-test', }, @@ -139,7 +139,7 @@ nodeunitShim({ const fingerprintStub = sinon.stub(FileSystem, 'fingerprint'); fingerprintStub.callsFake(() => imageHash); - const image = BundlingDockerImage.fromAsset('docker-path'); + const image = DockerImage.fromBuild('docker-path'); const tagHash = crypto.createHash('sha256').update(JSON.stringify({ path: 'docker-path', @@ -173,6 +173,25 @@ nodeunitShim({ test.done(); }, + 'fromAsset'(test: Test) { + sinon.stub(child_process, 'spawnSync').returns({ + status: 0, + stderr: Buffer.from('stderr'), + stdout: Buffer.from('sha256:1234567890abcdef'), + pid: 123, + output: ['stdout', 'stderr'], + signal: null, + }); + + const imagePath = path.join(__dirname, 'fs/fixtures/test1'); + const image = BundlingDockerImage.fromAsset(imagePath, { + file: 'my-dockerfile', + }); + test.ok(image); + test.ok(image.image); + test.done(); + }, + 'custom entrypoint is passed through to docker exec'(test: Test) { const spawnSyncStub = sinon.stub(child_process, 'spawnSync').returns({ status: 0, diff --git a/packages/@aws-cdk/core/test/resource.test.ts b/packages/@aws-cdk/core/test/resource.test.ts index b883d11f8d752..aa8fbe74575fc 100644 --- a/packages/@aws-cdk/core/test/resource.test.ts +++ b/packages/@aws-cdk/core/test/resource.test.ts @@ -3,7 +3,7 @@ import { nodeunitShim, Test } from 'nodeunit-shim'; import { App, App as Root, CfnCondition, CfnDeletionPolicy, CfnResource, Construct, - Fn, RemovalPolicy, Stack, + Fn, RemovalPolicy, Resource, Stack, } from '../lib'; import { synthesize } from '../lib/private/synthesis'; import { toCloudFormation } from './util'; @@ -818,6 +818,19 @@ nodeunitShim({ }, }); +test('Resource can get account and Region from ARN', () => { + const stack = new Stack(); + + // WHEN + const resource = new TestResource(stack, 'Resource', { + environmentFromArn: 'arn:partition:service:region:account:relative-id', + }); + + // THEN + expect(resource.env.account).toEqual('account'); + expect(resource.env.region).toEqual('region'); +}); + interface CounterProps { Count: number; } @@ -887,3 +900,8 @@ class CustomizableResource extends CfnResource { return cleanProps; } } + +/** + * Because Resource is abstract + */ +class TestResource extends Resource {} \ 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 cd26828aa92a5..9ba8ada806d57 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -75,7 +75,7 @@ "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-ssm": "0.0.0", - "@types/aws-lambda": "^8.10.72", + "@types/aws-lambda": "^8.10.73", "@types/fs-extra": "^8.1.1", "@types/sinon": "^9.0.11", "aws-sdk": "^2.848.0", diff --git a/packages/@aws-cdk/cx-api/package.json b/packages/@aws-cdk/cx-api/package.json index e054eeb2a3766..5b3f241e06afe 100644 --- a/packages/@aws-cdk/cx-api/package.json +++ b/packages/@aws-cdk/cx-api/package.json @@ -64,7 +64,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/jest": "^26.0.21", + "@types/jest": "^26.0.22", "@types/mock-fs": "^4.13.0", "@types/semver": "^7.3.4", "cdk-build-tools": "0.0.0", diff --git a/packages/@aws-cdk/yaml-cfn/lib/yaml.ts b/packages/@aws-cdk/yaml-cfn/lib/yaml.ts index eca37db0ed048..e46c8f8d6bf72 100644 --- a/packages/@aws-cdk/yaml-cfn/lib/yaml.ts +++ b/packages/@aws-cdk/yaml-cfn/lib/yaml.ts @@ -54,6 +54,6 @@ const shortForms: yaml_types.Schema.CustomTag[] = [ function parseYamlStrWithCfnTags(text: string): any { return yaml.parse(text, { customTags: shortForms, - schema: 'yaml-1.1', + schema: 'core', }); } diff --git a/packages/@aws-cdk/yaml-cfn/package.json b/packages/@aws-cdk/yaml-cfn/package.json index 7fc68d44a23af..f10beb171366c 100644 --- a/packages/@aws-cdk/yaml-cfn/package.json +++ b/packages/@aws-cdk/yaml-cfn/package.json @@ -67,7 +67,7 @@ }, "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/jest": "^26.0.21", + "@types/jest": "^26.0.22", "@types/yaml": "^1.9.7", "cdk-build-tools": "0.0.0", "jest": "^26.6.3", diff --git a/packages/@aws-cdk/yaml-cfn/test/deserialization.test.ts b/packages/@aws-cdk/yaml-cfn/test/deserialization.test.ts new file mode 100644 index 0000000000000..e2a3688393b61 --- /dev/null +++ b/packages/@aws-cdk/yaml-cfn/test/deserialization.test.ts @@ -0,0 +1,34 @@ +import '@aws-cdk/assert/jest'; +import * as yaml_cfn from '../lib'; + +test('Unquoted year-month-day is treated as a string, not a Date', () => { + const value = yaml_cfn.deserialize('Key: 2020-12-31'); + + expect(value).toEqual({ + Key: '2020-12-31', + }); +}); + +test("Unquoted 'No' is treated as a string, not a boolean", () => { + const value = yaml_cfn.deserialize('Key: No'); + + expect(value).toEqual({ + Key: 'No', + }); +}); + +test("Short-form 'Ref' is deserialized correctly", () => { + const value = yaml_cfn.deserialize('!Ref Resource'); + + expect(value).toEqual({ + Ref: 'Resource', + }); +}); + +test("Short-form 'Fn::GetAtt' is deserialized correctly", () => { + const value = yaml_cfn.deserialize('!GetAtt Resource.Attribute'); + + expect(value).toEqual({ + 'Fn::GetAtt': 'Resource.Attribute', + }); +}); diff --git a/packages/@aws-cdk/yaml-cfn/test/serialization.test.ts b/packages/@aws-cdk/yaml-cfn/test/serialization.test.ts new file mode 100644 index 0000000000000..bbc3a45d6ac26 --- /dev/null +++ b/packages/@aws-cdk/yaml-cfn/test/serialization.test.ts @@ -0,0 +1,8 @@ +import '@aws-cdk/assert/jest'; +import * as yaml_cfn from '../lib'; + +test('An object with a single string value is serialized as a simple string', () => { + const value = yaml_cfn.serialize({ key: 'some string' }); + + expect(value).toEqual('key: some string\n'); +}); diff --git a/packages/@aws-cdk/yaml-cfn/test/yaml.test.ts b/packages/@aws-cdk/yaml-cfn/test/yaml.test.ts deleted file mode 100644 index f8ce8131c4de7..0000000000000 --- a/packages/@aws-cdk/yaml-cfn/test/yaml.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -import '@aws-cdk/assert/jest'; - -test('No tests are specified for this package', () => { - expect(true).toBe(true); -}); diff --git a/packages/@monocdk-experiment/assert/package.json b/packages/@monocdk-experiment/assert/package.json index 8f028a635dba0..ea6e9e6a43059 100644 --- a/packages/@monocdk-experiment/assert/package.json +++ b/packages/@monocdk-experiment/assert/package.json @@ -34,8 +34,8 @@ "license": "Apache-2.0", "devDependencies": { "@monocdk-experiment/rewrite-imports": "0.0.0", - "@types/jest": "^26.0.21", - "@types/node": "^10.17.55", + "@types/jest": "^26.0.22", + "@types/node": "^10.17.56", "cdk-build-tools": "0.0.0", "constructs": "^3.3.69", "jest": "^26.6.3", diff --git a/packages/@monocdk-experiment/rewrite-imports/package.json b/packages/@monocdk-experiment/rewrite-imports/package.json index 6c8a2788e1f53..17db78f91896d 100644 --- a/packages/@monocdk-experiment/rewrite-imports/package.json +++ b/packages/@monocdk-experiment/rewrite-imports/package.json @@ -37,8 +37,8 @@ }, "devDependencies": { "@types/glob": "^7.1.3", - "@types/jest": "^26.0.21", - "@types/node": "^10.17.55", + "@types/jest": "^26.0.22", + "@types/node": "^10.17.56", "cdk-build-tools": "0.0.0", "pkglint": "0.0.0" }, diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index bc1a4b73f08c2..cef21953bf34a 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -33,7 +33,8 @@ "cdk-build": { "eslint": { "disable": true - } + }, + "stripDeprecated": true }, "pkglint": { "exclude": [ @@ -190,6 +191,7 @@ "@aws-cdk/aws-fsx": "0.0.0", "@aws-cdk/aws-gamelift": "0.0.0", "@aws-cdk/aws-globalaccelerator": "0.0.0", + "@aws-cdk/aws-globalaccelerator-endpoints": "0.0.0", "@aws-cdk/aws-glue": "0.0.0", "@aws-cdk/aws-greengrass": "0.0.0", "@aws-cdk/aws-greengrassv2": "0.0.0", @@ -290,7 +292,7 @@ "@aws-cdk/region-info": "0.0.0", "@aws-cdk/yaml-cfn": "0.0.0", "@types/fs-extra": "^8.1.1", - "@types/node": "^10.17.55", + "@types/node": "^10.17.56", "cdk-build-tools": "0.0.0", "constructs": "^3.3.69", "fs-extra": "^9.1.0", diff --git a/packages/aws-cdk/lib/init-templates/v1/app/go/%name%.template.go b/packages/aws-cdk/lib/init-templates/v1/app/go/%name%.template.go new file mode 100644 index 0000000000000..24a2b0e90b616 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/app/go/%name%.template.go @@ -0,0 +1,68 @@ +package main + +import ( + "github.com/aws/aws-cdk-go/awscdk" + "github.com/aws/aws-cdk-go/awscdk/awssns" + "github.com/aws/constructs-go/constructs/v3" + "github.com/aws/jsii-runtime-go" +) + +type %name.PascalCased%StackProps struct { + awscdk.StackProps +} + +func New%name.PascalCased%Stack(scope constructs.Construct, id string, props *%name.PascalCased%StackProps) awscdk.Stack { + var sprops awscdk.StackProps + if props != nil { + sprops = props.StackProps + } + stack := awscdk.NewStack(scope, &id, &sprops) + + // The code that defines your stack goes here + + // as an example, here's how you would define an AWS SNS topic: + awssns.NewTopic(stack, jsii.String("MyTopic"), &awssns.TopicProps{ + DisplayName: jsii.String("MyCoolTopic"), + }) + + return stack +} + +func main() { + app := awscdk.NewApp(nil) + + New%name.PascalCased%Stack(app, "%name.PascalCased%Stack", &%name.PascalCased%StackProps{ + awscdk.StackProps{ + Env: env(), + }, + }) + + app.Synth(nil) +} + +// env determines the AWS environment (account+region) in which our stack is to +// be deployed. For more information see: https://docs.aws.amazon.com/cdk/latest/guide/environments.html +func env() *awscdk.Environment { + // If unspecified, this stack will be "environment-agnostic". + // Account/Region-dependent features and context lookups will not work, but a + // single synthesized template can be deployed anywhere. + //--------------------------------------------------------------------------- + return nil + + // Uncomment if you know exactly what account and region you want to deploy + // the stack to. This is the recommendation for production stacks. + //--------------------------------------------------------------------------- + // return &awscdk.Environment{ + // Account: jsii.String("123456789012"), + // Region: jsii.String("us-east-1"), + // } + + // Uncomment to specialize this stack for the AWS Account and Region that are + // implied by the current CLI configuration. This is recommended for dev + // stacks. + //--------------------------------------------------------------------------- + // return &awscdk.Environment{ + // Account: jsii.String(os.Getenv("CDK_DEFAULT_ACCOUNT")), + // Region: jsii.String(os.Getenv("CDK_DEFAULT_REGION")), + // } +} diff --git a/packages/aws-cdk/lib/init-templates/v1/app/go/%name%_test.template.go b/packages/aws-cdk/lib/init-templates/v1/app/go/%name%_test.template.go new file mode 100644 index 0000000000000..c0534249b6282 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/app/go/%name%_test.template.go @@ -0,0 +1,28 @@ +package main + +import ( + "encoding/json" + "testing" + + "github.com/aws/aws-cdk-go/awscdk" + "github.com/stretchr/testify/assert" + "github.com/tidwall/gjson" +) + +func Test%name.PascalCased%Stack(t *testing.T) { + // GIVEN + app := awscdk.NewApp(nil) + + // WHEN + stack := New%name.PascalCased%Stack(app, "MyStack", nil) + + // THEN + bytes, err := json.Marshal(app.Synth(nil).GetStackArtifact(stack.ArtifactId()).Template()) + if err != nil { + t.Error(err) + } + + template := gjson.ParseBytes(bytes) + displayName := template.Get("Resources.MyTopic86869434.Properties.DisplayName").String() + assert.Equal(t, "MyCoolTopic", displayName) +} diff --git a/packages/aws-cdk/lib/init-templates/v1/app/go/.template.gitignore b/packages/aws-cdk/lib/init-templates/v1/app/go/.template.gitignore new file mode 100644 index 0000000000000..92fe1ec34b4b6 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/app/go/.template.gitignore @@ -0,0 +1,19 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# go.sum should be committed +!go.sum + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/packages/aws-cdk/lib/init-templates/v1/app/go/README.md b/packages/aws-cdk/lib/init-templates/v1/app/go/README.md new file mode 100644 index 0000000000000..9b8d4b29f26e3 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/app/go/README.md @@ -0,0 +1,14 @@ +# Welcome to your CDK Go project! + +This is a blank project for Go development with CDK. + +**NOTICE**: Go support is still in Developer Preview. This implies that APIs may +change while we address early feedback from the community. We would love to hear +about your experience through GitHub issues. + +## Useful commands + + * `cdk deploy` deploy this stack to your default AWS account/region + * `cdk diff` compare deployed stack with current state + * `cdk synth` emits the synthesized CloudFormation template + * `go test` run unit tests diff --git a/packages/aws-cdk/lib/init-templates/v1/app/go/cdk.template.json b/packages/aws-cdk/lib/init-templates/v1/app/go/cdk.template.json new file mode 100644 index 0000000000000..ad88cd7ef75f3 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/app/go/cdk.template.json @@ -0,0 +1,3 @@ +{ + "app": "go mod download && go run %name%.go" +} \ No newline at end of file diff --git a/packages/aws-cdk/lib/init-templates/v1/app/go/go.template.mod b/packages/aws-cdk/lib/init-templates/v1/app/go/go.template.mod new file mode 100644 index 0000000000000..a1dcb391c1614 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/v1/app/go/go.template.mod @@ -0,0 +1,13 @@ +module %name% + +go 1.16 + +require ( + github.com/aws/aws-cdk-go/awscdk v%cdk-version%-devpreview + github.com/aws/constructs-go/constructs/v3 v3.3.71 + github.com/aws/jsii-runtime-go v1.26.0 + + // for testing + github.com/tidwall/gjson v1.7.4 + github.com/stretchr/testify v1.7.0 +) diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 58d3863cc454b..29da596ef45bd 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -39,14 +39,14 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/core": "0.0.0", - "@octokit/rest": "^18.3.5", + "@octokit/rest": "^18.5.2", "@types/archiver": "^5.1.0", "@types/fs-extra": "^8.1.1", "@types/glob": "^7.1.3", - "@types/jest": "^26.0.21", - "@types/minimatch": "^3.0.3", + "@types/jest": "^26.0.22", + "@types/minimatch": "^3.0.4", "@types/mockery": "^1.4.29", - "@types/node": "^10.17.55", + "@types/node": "^10.17.56", "@types/promptly": "^3.0.1", "@types/semver": "^7.3.4", "@types/sinon": "^9.0.11", @@ -86,7 +86,7 @@ "proxy-agent": "^4.0.1", "semver": "^7.3.5", "source-map-support": "^0.5.19", - "table": "^6.0.7", + "table": "^6.0.9", "uuid": "^8.3.2", "wrap-ansi": "^7.0.0", "yargs": "^16.2.0" diff --git a/packages/aws-cdk/test/integ/init/test-all.sh b/packages/aws-cdk/test/integ/init/test-all.sh index cbca0f859feb2..f191d37bf4296 100755 --- a/packages/aws-cdk/test/integ/init/test-all.sh +++ b/packages/aws-cdk/test/integ/init/test-all.sh @@ -7,5 +7,6 @@ $scriptdir/test-java.sh $scriptdir/test-javascript.sh $scriptdir/test-python.sh $scriptdir/test-typescript.sh +$scriptdir/test-go.sh echo "SUCCESS" diff --git a/packages/aws-cdk/test/integ/init/test-go.sh b/packages/aws-cdk/test/integ/init/test-go.sh new file mode 100755 index 0000000000000..6b461f3e25ef5 --- /dev/null +++ b/packages/aws-cdk/test/integ/init/test-go.sh @@ -0,0 +1,26 @@ +#!/bin/bash +#------------------------------------------------------------------ +# setup +#------------------------------------------------------------------ +set -eu +scriptdir=$(cd $(dirname $0) && pwd) +source ${scriptdir}/common.bash + +header Go + +#------------------------------------------------------------------ + +if [[ "${1:-}" == "" ]]; then + templates="app" +else + templates="$@" +fi + +for template in $templates; do + echo "Trying Go template $template" + + setup + + cdk init -l go $template + cdk synth +done diff --git a/packages/cdk-assets/package.json b/packages/cdk-assets/package.json index 6df6c69730a29..5d685e1d8c8b0 100644 --- a/packages/cdk-assets/package.json +++ b/packages/cdk-assets/package.json @@ -32,10 +32,10 @@ "devDependencies": { "@types/archiver": "^5.1.0", "@types/glob": "^7.1.3", - "@types/jest": "^26.0.21", + "@types/jest": "^26.0.22", "@types/jszip": "^3.4.1", "@types/mock-fs": "^4.13.0", - "@types/node": "^10.17.55", + "@types/node": "^10.17.56", "@types/yargs": "^15.0.13", "cdk-build-tools": "0.0.0", "jest": "^26.6.3", diff --git a/packages/cdk-dasm/package.json b/packages/cdk-dasm/package.json index bb3fe012382df..2d0a216158e44 100644 --- a/packages/cdk-dasm/package.json +++ b/packages/cdk-dasm/package.json @@ -30,7 +30,7 @@ "yaml": "1.10.2" }, "devDependencies": { - "@types/jest": "^26.0.21", + "@types/jest": "^26.0.22", "@types/yaml": "1.9.7", "jest": "^26.6.3" }, diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 27978f6c4d451..055a1e1af08b2 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -112,6 +112,7 @@ "@aws-cdk/aws-fsx": "0.0.0", "@aws-cdk/aws-gamelift": "0.0.0", "@aws-cdk/aws-globalaccelerator": "0.0.0", + "@aws-cdk/aws-globalaccelerator-endpoints": "0.0.0", "@aws-cdk/aws-glue": "0.0.0", "@aws-cdk/aws-greengrass": "0.0.0", "@aws-cdk/aws-greengrassv2": "0.0.0", @@ -220,7 +221,7 @@ }, "devDependencies": { "@types/fs-extra": "^8.1.1", - "@types/jest": "^26.0.21", + "@types/jest": "^26.0.22", "@types/yaml": "1.9.7", "@types/yargs": "^15.0.13", "jest": "^26.6.3", diff --git a/packages/monocdk/package.json b/packages/monocdk/package.json index 7f1f6da5555b1..ce9057690ff00 100644 --- a/packages/monocdk/package.json +++ b/packages/monocdk/package.json @@ -196,6 +196,7 @@ "@aws-cdk/aws-fsx": "0.0.0", "@aws-cdk/aws-gamelift": "0.0.0", "@aws-cdk/aws-globalaccelerator": "0.0.0", + "@aws-cdk/aws-globalaccelerator-endpoints": "0.0.0", "@aws-cdk/aws-glue": "0.0.0", "@aws-cdk/aws-greengrass": "0.0.0", "@aws-cdk/aws-greengrassv2": "0.0.0", @@ -296,7 +297,7 @@ "@aws-cdk/region-info": "0.0.0", "@aws-cdk/yaml-cfn": "0.0.0", "@types/fs-extra": "^8.1.1", - "@types/node": "^10.17.55", + "@types/node": "^10.17.56", "cdk-build-tools": "0.0.0", "constructs": "^3.3.69", "fs-extra": "^9.1.0", diff --git a/tools/cdk-build-tools/lib/compile.ts b/tools/cdk-build-tools/lib/compile.ts index fe63d0c8f346e..173fea93685b5 100644 --- a/tools/cdk-build-tools/lib/compile.ts +++ b/tools/cdk-build-tools/lib/compile.ts @@ -7,7 +7,7 @@ import { Timers } from './timer'; */ export async function compileCurrentPackage(options: CDKBuildOptions, timers: Timers, compilers: CompilerOverrides = {}): Promise { const env = options.env; - await shell(packageCompiler(compilers), { timers, env }); + await shell(packageCompiler(compilers, options), { timers, env }); // Find files in bin/ that look like they should be executable, and make them so. const scripts = currentPackageJson().bin || {}; diff --git a/tools/cdk-build-tools/lib/package-info.ts b/tools/cdk-build-tools/lib/package-info.ts index 9e12b86b80580..b9d5b7614eb30 100644 --- a/tools/cdk-build-tools/lib/package-info.ts +++ b/tools/cdk-build-tools/lib/package-info.ts @@ -81,9 +81,13 @@ export interface CompilerOverrides { /** * Return the compiler for this package (either tsc or jsii) */ -export function packageCompiler(compilers: CompilerOverrides): string[] { +export function packageCompiler(compilers: CompilerOverrides, options?: CDKBuildOptions): string[] { if (isJsii()) { - return [compilers.jsii || require.resolve('jsii/bin/jsii'), '--silence-warnings=reserved-word']; + const args = ['--silence-warnings=reserved-word']; + if (options?.stripDeprecated) { + args.push('--strip-deprecated'); + } + return [compilers.jsii || require.resolve('jsii/bin/jsii'), ...args]; } else { return [compilers.tsc || require.resolve('typescript/bin/tsc'), '--build']; } @@ -148,6 +152,12 @@ export interface CDKBuildOptions { * Environment variables to be passed to 'cdk-build' and all of its child processes. */ env?: NodeJS.ProcessEnv; + + /** + * Whether deprecated symbols should be stripped from the jsii assembly and typescript declaration files. + * @see https://aws.github.io/jsii/user-guides/lib-author/toolchain/jsii/#-strip-deprecated + */ + stripDeprecated?: boolean; } /** diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index e77b0bd3e2cdb..f061a6249a738 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -34,16 +34,16 @@ "license": "Apache-2.0", "devDependencies": { "@types/fs-extra": "^8.1.1", - "@types/jest": "^26.0.21", + "@types/jest": "^26.0.22", "@types/yargs": "^15.0.13", "pkglint": "0.0.0" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^4.19.0", - "@typescript-eslint/parser": "^4.19.0", + "@typescript-eslint/eslint-plugin": "^4.20.0", + "@typescript-eslint/parser": "^4.20.0", "awslint": "0.0.0", "colors": "^1.4.0", - "eslint": "^7.22.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", diff --git a/tools/cfn2ts/package.json b/tools/cfn2ts/package.json index ad6f7eff24004..847f25acebeb5 100644 --- a/tools/cfn2ts/package.json +++ b/tools/cfn2ts/package.json @@ -37,7 +37,7 @@ }, "devDependencies": { "@types/fs-extra": "^8.1.1", - "@types/jest": "^26.0.21", + "@types/jest": "^26.0.22", "@types/yargs": "^15.0.13", "cdk-build-tools": "0.0.0", "jest": "^26.6.3", diff --git a/tools/eslint-plugin-cdk/package.json b/tools/eslint-plugin-cdk/package.json index b96543cec5c2c..78b9364bebf39 100644 --- a/tools/eslint-plugin-cdk/package.json +++ b/tools/eslint-plugin-cdk/package.json @@ -12,17 +12,17 @@ "build+test": "npm run build && npm test" }, "devDependencies": { - "@types/eslint": "^7.2.7", + "@types/eslint": "^7.2.8", "@types/fs-extra": "^8.1.1", - "@types/jest": "^26.0.21", - "@types/node": "^10.17.55", + "@types/jest": "^26.0.22", + "@types/node": "^10.17.56", "eslint-plugin-rulesdir": "^0.2.0", "jest": "^26.6.3", "typescript": "~3.9.9" }, "dependencies": { - "@typescript-eslint/parser": "^4.19.0", - "eslint": "^7.22.0", + "@typescript-eslint/parser": "^4.20.0", + "eslint": "^7.23.0", "fs-extra": "^9.1.0" }, "jest": { diff --git a/tools/nodeunit-shim/package.json b/tools/nodeunit-shim/package.json index c576c1271a271..58424e094b085 100644 --- a/tools/nodeunit-shim/package.json +++ b/tools/nodeunit-shim/package.json @@ -12,8 +12,8 @@ "build+test": "npm run build && npm test" }, "devDependencies": { - "@types/jest": "^26.0.21", - "@types/node": "^10.17.55", + "@types/jest": "^26.0.22", + "@types/node": "^10.17.56", "typescript": "~3.9.9" }, "dependencies": { diff --git a/tools/yarn-cling/lib/hoisting.ts b/tools/yarn-cling/lib/hoisting.ts index 5f920fe3ce7f1..5f486c0828355 100644 --- a/tools/yarn-cling/lib/hoisting.ts +++ b/tools/yarn-cling/lib/hoisting.ts @@ -1,4 +1,4 @@ -import { PackageLockPackage } from "./types"; +import { PackageLockPackage } from './types'; /** * Hoist package-lock dependencies in-place diff --git a/tools/yarn-cling/lib/index.ts b/tools/yarn-cling/lib/index.ts index 38a766c27d7e5..75a3605166255 100644 --- a/tools/yarn-cling/lib/index.ts +++ b/tools/yarn-cling/lib/index.ts @@ -1,6 +1,7 @@ import { promises as fs, exists } from 'fs'; import * as path from 'path'; import * as lockfile from '@yarnpkg/lockfile'; +import * as semver from 'semver'; import { hoistDependencies } from './hoisting'; import { PackageJson, PackageLock, PackageLockEntry, PackageLockPackage, YarnLock } from './types'; @@ -49,13 +50,17 @@ export async function generateShrinkwrap(options: ShrinkwrapOptions): Promise { - return { + const lockFile = { name: pkgJson.name, version: pkgJson.version, lockfileVersion: 1, requires: true, dependencies: await dependenciesFor(pkgJson.dependencies || {}, yarnLock, rootDir), }; + + checkRequiredVersions(lockFile); + + return lockFile; } // eslint-disable-next-line max-len @@ -186,4 +191,77 @@ async function findPackageDir(depName: string, rootDir: string) { } throw new Error(`Did not find '${depName}' upwards of '${rootDir}'`); +} + +/** + * We may sometimes try to adjust a package version to a version that's incompatible with the declared requirement. + * + * For example, this recently happened for 'netmask', where the package we + * depend on has `{ requires: { netmask: '^1.0.6', } }`, but we need to force-substitute in version `2.0.1`. + * + * If NPM processes the shrinkwrap and encounters the following situation: + * + * ``` + * { + * netmask: { version: '2.0.1' }, + * resolver: { + * requires: { + * netmask: '^1.0.6' + * } + * } + * } + * ``` + * + * NPM is going to disregard the swhinkrwap and still give `resolver` its own private + * copy of netmask `^1.0.6`. + * + * We tried overriding the `requires` version, and that works for `npm install` (yay) + * but if anyone runs `npm ls` afterwards, `npm ls` is going to check the actual source + * `package.jsons` against the actual `node_modules` file tree, and complain that the + * versions don't match. + * + * We run `npm ls` in our tests to make sure our dependency tree is sane, and our customers + * might too, so this is not a great solution. + * + * To cut any discussion short in the future, we're going to detect this situation and + * tell our future selves that is cannot and will not work, and we should find another + * solution. + */ +export function checkRequiredVersions(root: PackageLock | PackageLockPackage) { + recurse(root, []); + + function recurse(entry: PackageLock | PackageLockPackage, parentChain: PackageLockEntry[]) { + // On the root, 'requires' is the value 'true', for God knows what reason. Don't care about those. + if (typeof entry.requires === 'object') { + + // For every 'requires' dependency, find the version it actually got resolved to and compare. + for (const [name, range] of Object.entries(entry.requires)) { + const resolvedPackage = findResolved(name, [entry, ...parentChain]); + if (!resolvedPackage) { continue; } + + if (!semver.satisfies(resolvedPackage.version, range)) { + // Ruh-roh. + throw new Error(`Looks like we're trying to force '${name}' to version '${resolvedPackage.version}', but the dependency ` + + `is specified as '${range}'. This can never properly work via shrinkwrapping. Try vendoring a patched ` + + 'version of the intermediary dependencies instead.'); + } + } + } + + for (const dep of Object.values(entry.dependencies ?? {})) { + recurse(dep, [entry, ...parentChain]); + } + } + + /** + * Find a package name in a package lock tree. + */ + function findResolved(name: string, chain: PackageLockEntry[]) { + for (const level of chain) { + if (level.dependencies?.[name]) { + return level.dependencies?.[name]; + } + } + return undefined; + } } \ No newline at end of file diff --git a/tools/yarn-cling/lib/types.ts b/tools/yarn-cling/lib/types.ts index 0f800cc50de52..2af924f90cb6f 100644 --- a/tools/yarn-cling/lib/types.ts +++ b/tools/yarn-cling/lib/types.ts @@ -29,8 +29,8 @@ export interface ResolvedYarnPackage { export interface PackageLock extends PackageLockEntry { name: string; - lockfileVersion: 1; - requires: true; + lockfileVersion: number; + requires: boolean; } export interface PackageLockEntry { diff --git a/tools/yarn-cling/package.json b/tools/yarn-cling/package.json index ceeacb4544b08..134064d892405 100644 --- a/tools/yarn-cling/package.json +++ b/tools/yarn-cling/package.json @@ -38,15 +38,16 @@ ] }, "devDependencies": { - "@types/jest": "^26.0.21", - "@types/node": "^10.17.55", + "@types/jest": "^26.0.22", + "@types/node": "^10.17.56", "@types/yarnpkg__lockfile": "^1.1.4", "jest": "^26.6.3", "pkglint": "0.0.0", "typescript": "~3.9.9" }, "dependencies": { - "@yarnpkg/lockfile": "^1.1.0" + "@yarnpkg/lockfile": "^1.1.0", + "semver": "^7.3.5" }, "keywords": [ "aws", diff --git a/tools/yarn-cling/test/cling.test.ts b/tools/yarn-cling/test/cling.test.ts index 6b5854db688ed..1628d2415a1ae 100644 --- a/tools/yarn-cling/test/cling.test.ts +++ b/tools/yarn-cling/test/cling.test.ts @@ -1,5 +1,5 @@ import * as path from 'path'; -import { generateShrinkwrap } from "../lib"; +import { checkRequiredVersions, generateShrinkwrap } from '../lib'; test('generate lock for fixture directory', async () => { const lockFile = await generateShrinkwrap({ @@ -9,27 +9,27 @@ test('generate lock for fixture directory', async () => { expect(lockFile).toEqual({ lockfileVersion: 1, - name: "package1", + name: 'package1', requires: true, - version: "1.1.1", + version: '1.1.1', dependencies: { package2: { - version: "2.2.2", + version: '2.2.2', }, registrydependency1: { dependencies: { registrydependency2: { - integrity: "sha512-pineapple", - resolved: "https://registry.bla.com/stuff", - version: "2.3.999", + integrity: 'sha512-pineapple', + resolved: 'https://registry.bla.com/stuff', + version: '2.3.999', }, }, - integrity: "sha512-banana", + integrity: 'sha512-banana', requires: { - registrydependency2: "^2.3.4", + registrydependency2: '^2.3.4', }, - resolved: "https://registry.bla.com/stuff", - version: "1.2.999", + resolved: 'https://registry.bla.com/stuff', + version: '1.2.999', }, }, }); @@ -43,26 +43,48 @@ test('generate hoisted lock for fixture directory', async () => { expect(lockFile).toEqual({ lockfileVersion: 1, - name: "package1", + name: 'package1', requires: true, - version: "1.1.1", + version: '1.1.1', dependencies: { package2: { - version: "2.2.2", + version: '2.2.2', }, registrydependency1: { - integrity: "sha512-banana", + integrity: 'sha512-banana', requires: { - registrydependency2: "^2.3.4", + registrydependency2: '^2.3.4', }, - resolved: "https://registry.bla.com/stuff", - version: "1.2.999", + resolved: 'https://registry.bla.com/stuff', + version: '1.2.999', }, registrydependency2: { - integrity: "sha512-pineapple", - resolved: "https://registry.bla.com/stuff", - version: "2.3.999", + integrity: 'sha512-pineapple', + resolved: 'https://registry.bla.com/stuff', + version: '2.3.999', }, }, }); -}); \ No newline at end of file +}); + +test('fail when requires cannot be satisfied', async () => { + const lockFile = { + lockfileVersion: 1, + name: 'package1', + requires: true, + version: '1.1.1', + dependencies: { + package1: { + version: '2.2.2', + requires: { + package2: '^3.3.3', // <- this needs to be adjusted + }, + }, + package2: { + version: '4.4.4', + }, + }, + }; + + expect(() => checkRequiredVersions(lockFile)).toThrow(/This can never/); +}); diff --git a/version.v1.json b/version.v1.json index 80981e652a8d5..708b23a0d165c 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.95.2" + "version": "1.96.0" } diff --git a/yarn.lock b/yarn.lock index fcfc5e004e0e7..925d810fe1e4e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1314,6 +1314,11 @@ resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-5.3.2.tgz#b8ac43c5c3d00aef61a34cf744e315110c78deb4" integrity sha512-NxF1yfYOUO92rCx3dwvA2onF30Vdlg7YUkMVXkeptqpzA3tRLplThhFleV/UKWFgh7rpKu1yYRbvNDUtzSopKA== +"@octokit/openapi-types@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-6.0.0.tgz#7da8d7d5a72d3282c1a3ff9f951c8133a707480d" + integrity sha512-CnDdK7ivHkBtJYzWzZm7gEkanA7gKH6a09Eguz7flHw//GacPJLmkHA3f3N++MJmlxD1Fl+mB7B32EEpSCwztQ== + "@octokit/plugin-enterprise-rest@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" @@ -1339,6 +1344,14 @@ "@octokit/types" "^6.12.2" deprecation "^2.3.1" +"@octokit/plugin-rest-endpoint-methods@5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.0.tgz#cf2cdeb24ea829c31688216a5b165010b61f9a98" + integrity sha512-Jc7CLNUueIshXT+HWt6T+M0sySPjF32mSFQAK7UfAg8qGeRI6OM1GSBxDLwbXjkqy2NVdnqCedJcP1nC785JYg== + dependencies: + "@octokit/types" "^6.13.0" + deprecation "^2.3.1" + "@octokit/request-error@^2.0.0", "@octokit/request-error@^2.0.5": version "2.0.5" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.5.tgz#72cc91edc870281ad583a42619256b380c600143" @@ -1362,7 +1375,7 @@ once "^1.4.0" universal-user-agent "^6.0.0" -"@octokit/rest@^18.1.0", "@octokit/rest@^18.3.5": +"@octokit/rest@^18.1.0": version "18.3.5" resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.3.5.tgz#a89903d46e0b4273bd3234674ec2777a651d68ab" integrity sha512-ZPeRms3WhWxQBEvoIh0zzf8xdU2FX0Capa7+lTca8YHmRsO3QNJzf1H3PcuKKsfgp91/xVDRtX91sTe1kexlbw== @@ -1372,6 +1385,16 @@ "@octokit/plugin-request-log" "^1.0.2" "@octokit/plugin-rest-endpoint-methods" "4.13.5" +"@octokit/rest@^18.5.2": + version "18.5.2" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.5.2.tgz#0369e554b7076e3749005147be94c661c7a5a74b" + integrity sha512-Kz03XYfKS0yYdi61BkL9/aJ0pP2A/WK5vF/syhu9/kY30J8He3P68hv9GRpn8bULFx2K0A9MEErn4v3QEdbZcw== + dependencies: + "@octokit/core" "^3.2.3" + "@octokit/plugin-paginate-rest" "^2.6.2" + "@octokit/plugin-request-log" "^1.0.2" + "@octokit/plugin-rest-endpoint-methods" "5.0.0" + "@octokit/types@^6.0.3", "@octokit/types@^6.11.0", "@octokit/types@^6.12.2", "@octokit/types@^6.7.1": version "6.12.2" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.12.2.tgz#5b44add079a478b8eb27d78cf384cc47e4411362" @@ -1379,6 +1402,13 @@ dependencies: "@octokit/openapi-types" "^5.3.2" +"@octokit/types@^6.13.0": + version "6.13.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.13.0.tgz#779e5b7566c8dde68f2f6273861dd2f0409480d0" + integrity sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA== + dependencies: + "@octokit/openapi-types" "^6.0.0" + "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1": version "1.8.2" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.2.tgz#858f5c4b48d80778fde4b9d541f27edc0d56488b" @@ -1419,10 +1449,10 @@ dependencies: "@types/glob" "*" -"@types/aws-lambda@^8.10.72": - version "8.10.72" - resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.72.tgz#af2a6eeaf39be9674e3856f1870d9d15cf75e2e0" - integrity sha512-jOrTwAhSiUtBIN/QsWNKlI4+4aDtpZ0sr2BRvKW6XQZdspgHUSHPcuzxbzCRiHUiDQ+0026u5TSE38VyIhNnfA== +"@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/babel__core@^7.0.0", "@types/babel__core@^7.1.7": version "7.1.13" @@ -1457,10 +1487,10 @@ dependencies: "@babel/types" "^7.3.0" -"@types/eslint@^7.2.7": - version "7.2.7" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.7.tgz#f7ef1cf0dceab0ae6f9a976a0a9af14ab1baca26" - integrity sha512-EHXbc1z2GoQRqHaAT7+grxlTJ3WE2YNeD6jlpPoRc83cCoThRY+NUWjCUZaYmk51OICkPXn2hhphcWcWXgNW0Q== +"@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== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -1511,10 +1541,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^26.0.21": - version "26.0.21" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.21.tgz#3a73c2731e7e4f0fbaea56ce7ff8c79cf812bd24" - integrity sha512-ab9TyM/69yg7eew9eOwKMUmvIZAKEGZYlq/dhe5/0IMUd/QLJv5ldRMdddSn+u22N13FP3s5jYyktxuBwY0kDA== +"@types/jest@^26.0.22": + version "26.0.22" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.22.tgz#8308a1debdf1b807aa47be2838acdcd91e88fbe6" + integrity sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw== dependencies: jest-diff "^26.0.0" pretty-format "^26.0.0" @@ -1553,6 +1583,11 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== +"@types/minimatch@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" + integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== + "@types/minimist@^1.2.0": version "1.2.1" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256" @@ -1575,10 +1610,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.35.tgz#42c953a4e2b18ab931f72477e7012172f4ffa313" integrity sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag== -"@types/node@^10.17.55": - version "10.17.55" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.55.tgz#a147f282edec679b894d4694edb5abeb595fecbd" - integrity sha512-koZJ89uLZufDvToeWO5BrC4CR4OUfHnUz2qoPs/daQH6qq3IN62QFxCTZ+bKaCE0xaoCAJYE4AXre8AbghCrhg== +"@types/node@^10.17.56": + version "10.17.56" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.56.tgz#010c9e047c3ff09ddcd11cbb6cf5912725cdc2b3" + integrity sha512-LuAa6t1t0Bfw4CuSR0UITsm1hP17YL+u82kfHGrHUWdhlBtH7sa7jGY5z7glGaIj/WDYDkRtgGd+KCjCzxBW1w== "@types/nodeunit@^0.0.31": version "0.0.31" @@ -1692,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.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.19.0.tgz#56f8da9ee118fe9763af34d6a526967234f6a7f0" - integrity sha512-CRQNQ0mC2Pa7VLwKFbrGVTArfdVDdefS+gTw0oC98vSI98IX5A8EVH4BzJ2FOB0YlCmm8Im36Elad/Jgtvveaw== +"@typescript-eslint/eslint-plugin@^4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.20.0.tgz#9d8794bd99aad9153092ad13c96164e3082e9a92" + integrity sha512-sw+3HO5aehYqn5w177z2D82ZQlqHCwcKSMboueo7oE4KU9QiC0SAgfS/D4z9xXvpTc8Bt41Raa9fBR8T2tIhoQ== dependencies: - "@typescript-eslint/experimental-utils" "4.19.0" - "@typescript-eslint/scope-manager" "4.19.0" + "@typescript-eslint/experimental-utils" "4.20.0" + "@typescript-eslint/scope-manager" "4.20.0" debug "^4.1.1" functional-red-black-tree "^1.0.1" lodash "^4.17.15" @@ -1706,15 +1741,15 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.19.0.tgz#9ca379919906dc72cb0fcd817d6cb5aa2d2054c6" - integrity sha512-9/23F1nnyzbHKuoTqFN1iXwN3bvOm/PRIXSBR3qFAYotK/0LveEOHr5JT1WZSzcD6BESl8kPOG3OoDRKO84bHA== +"@typescript-eslint/experimental-utils@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.20.0.tgz#a8ab2d7b61924f99042b7d77372996d5f41dc44b" + integrity sha512-sQNlf6rjLq2yB5lELl3gOE7OuoA/6IVXJUJ+Vs7emrQMva14CkOwyQwD7CW+TkmOJ4Q/YGmoDLmbfFrpGmbKng== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.19.0" - "@typescript-eslint/types" "4.19.0" - "@typescript-eslint/typescript-estree" "4.19.0" + "@typescript-eslint/scope-manager" "4.20.0" + "@typescript-eslint/types" "4.20.0" + "@typescript-eslint/typescript-estree" "4.20.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" @@ -1730,14 +1765,14 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.19.0.tgz#4ae77513b39f164f1751f21f348d2e6cb2d11128" - integrity sha512-/uabZjo2ZZhm66rdAu21HA8nQebl3lAIDcybUoOxoI7VbZBYavLIwtOOmykKCJy+Xq6Vw6ugkiwn8Js7D6wieA== +"@typescript-eslint/parser@^4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.20.0.tgz#8dd403c8b4258b99194972d9799e201b8d083bdd" + integrity sha512-m6vDtgL9EABdjMtKVw5rr6DdeMCH3OA1vFb0dAyuZSa3e5yw1YRzlwFnm9knma9Lz6b2GPvoNSa8vOXrqsaglA== dependencies: - "@typescript-eslint/scope-manager" "4.19.0" - "@typescript-eslint/types" "4.19.0" - "@typescript-eslint/typescript-estree" "4.19.0" + "@typescript-eslint/scope-manager" "4.20.0" + "@typescript-eslint/types" "4.20.0" + "@typescript-eslint/typescript-estree" "4.20.0" debug "^4.1.1" "@typescript-eslint/scope-manager@4.18.0": @@ -1748,23 +1783,23 @@ "@typescript-eslint/types" "4.18.0" "@typescript-eslint/visitor-keys" "4.18.0" -"@typescript-eslint/scope-manager@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.19.0.tgz#5e0b49eca4df7684205d957c9856f4e720717a4f" - integrity sha512-GGy4Ba/hLXwJXygkXqMzduqOMc+Na6LrJTZXJWVhRrSuZeXmu8TAnniQVKgj8uTRKe4igO2ysYzH+Np879G75g== +"@typescript-eslint/scope-manager@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.20.0.tgz#953ecbf3b00845ece7be66246608be9d126d05ca" + integrity sha512-/zm6WR6iclD5HhGpcwl/GOYDTzrTHmvf8LLLkwKqqPKG6+KZt/CfSgPCiybshmck66M2L5fWSF/MKNuCwtKQSQ== dependencies: - "@typescript-eslint/types" "4.19.0" - "@typescript-eslint/visitor-keys" "4.19.0" + "@typescript-eslint/types" "4.20.0" + "@typescript-eslint/visitor-keys" "4.20.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.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.19.0.tgz#5181d5d2afd02e5b8f149ebb37ffc8bd7b07a568" - integrity sha512-A4iAlexVvd4IBsSTNxdvdepW0D4uR/fwxDrKUa+iEY9UWvGREu2ZyB8ylTENM1SH8F7bVC9ac9+si3LWNxcBuA== +"@typescript-eslint/types@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.20.0.tgz#c6cf5ef3c9b1c8f699a9bbdafb7a1da1ca781225" + integrity sha512-cYY+1PIjei1nk49JAPnH1VEnu7OYdWRdJhYI5wiKOUMhLTG1qsx5cQxCUTuwWCmQoyriadz3Ni8HZmGSofeC+w== "@typescript-eslint/typescript-estree@4.18.0": version "4.18.0" @@ -1779,13 +1814,13 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.19.0.tgz#8a709ffa400284ab72df33376df085e2e2f61147" - integrity sha512-3xqArJ/A62smaQYRv2ZFyTA+XxGGWmlDYrsfZG68zJeNbeqRScnhf81rUVa6QG4UgzHnXw5VnMT5cg75dQGDkA== +"@typescript-eslint/typescript-estree@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.20.0.tgz#8b3b08f85f18a8da5d88f65cb400f013e88ab7be" + integrity sha512-Knpp0reOd4ZsyoEJdW8i/sK3mtZ47Ls7ZHvD8WVABNx5Xnn7KhenMTRGegoyMTx6TiXlOVgMz9r0pDgXTEEIHA== dependencies: - "@typescript-eslint/types" "4.19.0" - "@typescript-eslint/visitor-keys" "4.19.0" + "@typescript-eslint/types" "4.20.0" + "@typescript-eslint/visitor-keys" "4.20.0" debug "^4.1.1" globby "^11.0.1" is-glob "^4.0.1" @@ -1800,12 +1835,12 @@ "@typescript-eslint/types" "4.18.0" eslint-visitor-keys "^2.0.0" -"@typescript-eslint/visitor-keys@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.19.0.tgz#cbea35109cbd9b26e597644556be4546465d8f7f" - integrity sha512-aGPS6kz//j7XLSlgpzU2SeTqHPsmRYxFztj2vPuMMFJXZudpRSehE3WCV+BaxwZFvfAqMoSd86TEuM0PQ59E/A== +"@typescript-eslint/visitor-keys@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.20.0.tgz#1e84db034da13f208325e6bfc995c3b75f7dbd62" + integrity sha512-NXKRM3oOVQL8yNFDNCZuieRIwZ5UtjNLYtmMx2PacEAGmbaEYtGgVHUHVyZvU/0rYZcizdrWjDo+WBtRPSgq+A== dependencies: - "@typescript-eslint/types" "4.19.0" + "@typescript-eslint/types" "4.20.0" eslint-visitor-keys "^2.0.0" "@yarnpkg/lockfile@^1.1.0": @@ -1903,6 +1938,16 @@ ajv@^7.0.2: require-from-string "^2.0.2" uri-js "^4.2.2" +ajv@^8.0.1: + version "8.0.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.0.2.tgz#1396e27f208ed56dd5638ab5a251edeb1c91d402" + integrity sha512-V0HGxJd0PiDF0ecHYIesTOqfd1gJguwQUOYfMfAWnRsWQEXfc5ifbUFhD3Wjc+O+y7VAqL+g07prq9gHQ/JOZQ== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + ansi-colors@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" @@ -3697,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.9.6: - version "0.9.6" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.9.6.tgz#2cae519e7ce2328ecf57ae738090d07ce7245850" - integrity sha512-F6vASxU0wT/Davt9aj2qtDwDNSkQxh9VbyO56M7PDWD+D/Vgq/rmUDGDQo7te76W5auauVojjnQr/wTu3vpaUA== +esbuild@^0.11.2: + version "0.11.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.11.2.tgz#3b995e107f2054d9090402b98a3b79ceffd05eb6" + integrity sha512-8d5FCQrR+juXC2u9zjTQ3+IYiuFuaWyKYwmApFJLquTrYNbk36H/+MkRQeTuOJg7IjUchRX2Ulwo1zRYXZ1pUg== escalade@^3.1.1: version "3.1.1" @@ -3852,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.22.0: - version "7.22.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.22.0.tgz#07ecc61052fec63661a2cab6bd507127c07adc6f" - integrity sha512-3VawOtjSJUQiiqac8MQc+w457iGLfuNGLFn8JmF051tTKbh5/x/0vlcEj8OgDCaw7Ysa2Jn8paGshV7x2abKXg== +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== dependencies: "@babel/code-frame" "7.12.11" "@eslint/eslintrc" "^0.4.0" @@ -6339,6 +6384,11 @@ lodash._reinterpolate@^3.0.0: resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" @@ -6404,6 +6454,11 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "^3.0.0" +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= + lodash.union@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" @@ -9141,7 +9196,7 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -table@^6.0.4, table@^6.0.7: +table@^6.0.4: version "6.0.7" resolved "https://registry.yarnpkg.com/table/-/table-6.0.7.tgz#e45897ffbcc1bcf9e8a87bf420f2c9e5a7a52a34" integrity sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g== @@ -9151,6 +9206,21 @@ table@^6.0.4, table@^6.0.7: 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== + dependencies: + ajv "^8.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + lodash.clonedeep "^4.5.0" + lodash.flatten "^4.4.0" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.0" + tap-mocha-reporter@^3.0.9, tap-mocha-reporter@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/tap-mocha-reporter/-/tap-mocha-reporter-5.0.1.tgz#74f00be2ddd2a380adad45e085795137bc39497a"