From f6dab8d8c5aa4cf56d6846e2d13c1d5641136f72 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 21 Jan 2022 02:38:08 +0100 Subject: [PATCH 01/12] fix(pipelines): CodeBuild projects are hard to tell apart (#18492) CDK Pipelines generates a number of CodeBuild projects, and they are hard to tell apart some times. Add `Description` fields to each of them. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/codepipeline/_codebuild-factory.ts | 1 + .../test/codepipeline/codebuild-step.test.ts | 22 +++++++++ .../integ.newpipeline-with-vpc.expected.json | 48 +++++++++++++++++++ .../test/integ.newpipeline.expected.json | 24 ++++++++++ .../pipelines/test/testhelpers/matchers.ts | 2 +- 5 files changed, 96 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/pipelines/lib/codepipeline/_codebuild-factory.ts b/packages/@aws-cdk/pipelines/lib/codepipeline/_codebuild-factory.ts index 294a8844b81bb..5d73e23784314 100644 --- a/packages/@aws-cdk/pipelines/lib/codepipeline/_codebuild-factory.ts +++ b/packages/@aws-cdk/pipelines/lib/codepipeline/_codebuild-factory.ts @@ -273,6 +273,7 @@ export class CodeBuildFactory implements ICodePipelineActionFactory { const project = new codebuild.PipelineProject(projectScope, this.constructId, { projectName: this.props.projectName, + description: `Pipeline step ${options.pipeline.pipeline.pipelineName}/${stage.stageName}/${actionName}`, environment, vpc: projectOptions.vpc, subnetSelection: projectOptions.subnetSelection, diff --git a/packages/@aws-cdk/pipelines/test/codepipeline/codebuild-step.test.ts b/packages/@aws-cdk/pipelines/test/codepipeline/codebuild-step.test.ts index c905c3cbc4cfa..01069ee7adee0 100644 --- a/packages/@aws-cdk/pipelines/test/codepipeline/codebuild-step.test.ts +++ b/packages/@aws-cdk/pipelines/test/codepipeline/codebuild-step.test.ts @@ -43,6 +43,28 @@ test('additionalinputs creates the right commands', () => { }); }); +test('CodeBuild projects have a description', () => { + new cdkp.CodePipeline(pipelineStack, 'Pipeline', { + synth: new cdkp.CodeBuildStep('Synth', { + commands: ['/bin/true'], + input: cdkp.CodePipelineSource.gitHub('test/test', 'main'), + }), + }); + + // THEN + Template.fromStack(pipelineStack).hasResourceProperties( + 'AWS::CodeBuild::Project', + { + Description: { + 'Fn::Join': [ + '', + ['Pipeline step ', { Ref: 'Pipeline9850B417' }, '/Build/Synth'], + ], + }, + }, + ); +}); + test('long duration steps are supported', () => { // WHEN new cdkp.CodePipeline(pipelineStack, 'Pipeline', { diff --git a/packages/@aws-cdk/pipelines/test/integ.newpipeline-with-vpc.expected.json b/packages/@aws-cdk/pipelines/test/integ.newpipeline-with-vpc.expected.json index c0d89b2bf135a..1b6de60c8e589 100644 --- a/packages/@aws-cdk/pipelines/test/integ.newpipeline-with-vpc.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.newpipeline-with-vpc.expected.json @@ -1355,6 +1355,18 @@ "Cache": { "Type": "NO_CACHE" }, + "Description": { + "Fn::Join": [ + "", + [ + "Pipeline step ", + { + "Ref": "Pipeline9850B417" + }, + "/Build/Synth" + ] + ] + }, "EncryptionKey": "alias/aws/s3", "VpcConfig": { "SecurityGroupIds": [ @@ -1942,6 +1954,18 @@ "Cache": { "Type": "NO_CACHE" }, + "Description": { + "Fn::Join": [ + "", + [ + "Pipeline step ", + { + "Ref": "Pipeline9850B417" + }, + "/UpdatePipeline/SelfMutate" + ] + ] + }, "EncryptionKey": "alias/aws/s3", "VpcConfig": { "SecurityGroupIds": [ @@ -2284,6 +2308,18 @@ "Cache": { "Type": "NO_CACHE" }, + "Description": { + "Fn::Join": [ + "", + [ + "Pipeline step ", + { + "Ref": "Pipeline9850B417" + }, + "/Assets/FileAsset1" + ] + ] + }, "EncryptionKey": "alias/aws/s3", "VpcConfig": { "SecurityGroupIds": [ @@ -2385,6 +2421,18 @@ "Cache": { "Type": "NO_CACHE" }, + "Description": { + "Fn::Join": [ + "", + [ + "Pipeline step ", + { + "Ref": "Pipeline9850B417" + }, + "/Assets/FileAsset2" + ] + ] + }, "EncryptionKey": "alias/aws/s3", "VpcConfig": { "SecurityGroupIds": [ diff --git a/packages/@aws-cdk/pipelines/test/integ.newpipeline.expected.json b/packages/@aws-cdk/pipelines/test/integ.newpipeline.expected.json index 6e12dae20df4d..6e8c76b176b7f 100644 --- a/packages/@aws-cdk/pipelines/test/integ.newpipeline.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.newpipeline.expected.json @@ -2036,6 +2036,18 @@ "Cache": { "Type": "NO_CACHE" }, + "Description": { + "Fn::Join": [ + "", + [ + "Pipeline step ", + { + "Ref": "Pipeline9850B417" + }, + "/Build/Synth" + ] + ] + }, "EncryptionKey": "alias/aws/s3" } }, @@ -2335,6 +2347,18 @@ "Cache": { "Type": "NO_CACHE" }, + "Description": { + "Fn::Join": [ + "", + [ + "Pipeline step ", + { + "Ref": "Pipeline9850B417" + }, + "/UpdatePipeline/SelfMutate" + ] + ] + }, "EncryptionKey": "alias/aws/s3" } } diff --git a/packages/@aws-cdk/pipelines/test/testhelpers/matchers.ts b/packages/@aws-cdk/pipelines/test/testhelpers/matchers.ts index 97a02fc1dc10d..f7ba6458f7449 100644 --- a/packages/@aws-cdk/pipelines/test/testhelpers/matchers.ts +++ b/packages/@aws-cdk/pipelines/test/testhelpers/matchers.ts @@ -26,7 +26,7 @@ class StringLike extends Matcher { public test(actual: any): MatchResult { if (typeof(actual) !== 'string') { - throw new Error(`Expected string but found ${typeof(actual)}`); + throw new Error(`Expected string but found ${typeof(actual)} ${JSON.stringify(actual)}`); } const re = new RegExp(`^${this.pattern.split('*').map(escapeRegex).join('.*')}$`); From b2b234a82da805227b084a4e818a69d8a6024882 Mon Sep 17 00:00:00 2001 From: Pat Myron Date: Thu, 20 Jan 2022 18:25:21 -0800 Subject: [PATCH 02/12] chore(region-info): ap-southeast-3 (Jakarta) ELBV2_ACCOUNT (#18300) prev: https://github.com/hashicorp/terraform-provider-aws/pull/22453, https://github.com/aws/aws-cdk/pull/18110 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/region-info/build-tools/fact-tables.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts index e641f654ba532..b36ff4818838d 100644 --- a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts +++ b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts @@ -102,6 +102,7 @@ export const PARTITION_MAP: { [region: string]: Region } = { }; // https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-logging-bucket-permissions +// https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/enable-access-logs.html#attach-bucket-policy export const ELBV2_ACCOUNTS: { [region: string]: string } = { 'af-south-1': '098369216593', 'ap-east-1': '754344448648', @@ -111,6 +112,7 @@ export const ELBV2_ACCOUNTS: { [region: string]: string } = { 'ap-south-1': '718504428378', 'ap-southeast-1': '114774131450', 'ap-southeast-2': '783225319266', + 'ap-southeast-3': '589379963580', 'ca-central-1': '985666609251', 'cn-north-1': '638102146993', 'cn-northwest-1': '037604701340', From 13937299596d0b858d56e9116bf7a7dbe039d4b4 Mon Sep 17 00:00:00 2001 From: Lukas Fruntke Date: Fri, 21 Jan 2022 04:14:18 +0100 Subject: [PATCH 03/12] fix(ecs-patterns): Fix Network Load Balancer Port assignments in ECS Patterns (#18157) This PR introduces changeable ports as a regression fix for the hardcoded port 80 in both NLB constructs bases. Closes #18073 Additionally it seems like the regression reported in the linked issue was not spotted in the integration tests either. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../network-load-balanced-service-base.ts | 2 +- ...ork-multiple-target-groups-service-base.ts | 2 +- ...oad-balanced-fargate-service.expected.json | 2 +- .../integ.special-listener.expected.json | 2 +- .../load-balanced-fargate-service-v2.test.ts | 74 ++++++++++++++++++- 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts index 0857b99ee8cfd..942f13e3439aa 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts @@ -347,7 +347,7 @@ export abstract class NetworkLoadBalancedServiceBase extends CoreConstruct { const loadBalancer = props.loadBalancer ?? new NetworkLoadBalancer(this, 'LB', lbProps); const listenerPort = props.listenerPort ?? 80; const targetProps = { - port: 80, + port: props.taskImageOptions?.containerPort ?? 80, }; this.listener = loadBalancer.addListener('PublicListener', { port: listenerPort }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts index 8eb751b07d13e..677caf8c2df9f 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts @@ -374,7 +374,7 @@ export abstract class NetworkMultipleTargetGroupsServiceBase extends CoreConstru protected registerECSTargets(service: BaseService, container: ContainerDefinition, targets: NetworkTargetProps[]): NetworkTargetGroup { for (const targetProps of targets) { const targetGroup = this.findListener(targetProps.listener).addTargets(`ECSTargetGroup${container.containerName}${targetProps.containerPort}`, { - port: 80, + port: targetProps.containerPort ?? 80, targets: [ service.loadBalancerTarget({ containerName: container.containerName, diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.multiple-network-load-balanced-fargate-service.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.multiple-network-load-balanced-fargate-service.expected.json index 643ff38905d69..91413b0f0fd6f 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.multiple-network-load-balanced-fargate-service.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.multiple-network-load-balanced-fargate-service.expected.json @@ -458,7 +458,7 @@ "myServicelb2listener2ECSTargetGroupweb90Group6841F924": { "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", "Properties": { - "Port": 80, + "Port": 90, "Protocol": "TCP", "TargetType": "ip", "VpcId": { diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.special-listener.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.special-listener.expected.json index 91c1f4e575e23..1af60f7eaf55c 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.special-listener.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.special-listener.expected.json @@ -404,7 +404,7 @@ "FargateNlbServiceLBPublicListenerECSGroup7501571D": { "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", "Properties": { - "Port": 80, + "Port": 2015, "Protocol": "TCP", "TargetType": "ip", "VpcId": { diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts index 191dbd4430236..b196f4b0616b1 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts @@ -1,9 +1,10 @@ import { Match, Template } from '@aws-cdk/assertions'; import { Vpc } from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; +import { ContainerImage } from '@aws-cdk/aws-ecs'; import { CompositePrincipal, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import { Duration, Stack } from '@aws-cdk/core'; -import { ApplicationMultipleTargetGroupsFargateService, NetworkMultipleTargetGroupsFargateService, ApplicationLoadBalancedFargateService } from '../../lib'; +import { ApplicationLoadBalancedFargateService, ApplicationMultipleTargetGroupsFargateService, NetworkLoadBalancedFargateService, NetworkMultipleTargetGroupsFargateService } from '../../lib'; describe('When Application Load Balancer', () => { test('test Fargate loadbalanced construct with default settings', () => { @@ -661,4 +662,75 @@ describe('When Network Load Balancer', () => { }); }).toThrow(/You must specify one of: taskDefinition or image/); }); + + test('test Fargate networkloadbalanced construct with custom Port', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + new NetworkLoadBalancedFargateService(stack, 'NLBService', { + cluster: cluster, + memoryLimitMiB: 1024, + cpu: 512, + taskImageOptions: { + image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + containerPort: 81, + }, + listenerPort: 8181, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { + Port: 81, + Protocol: 'TCP', + TargetType: 'ip', + VpcId: { + Ref: 'VPCB9E5F0B4', + }, + }); + }); + + test('test Fargate multinetworkloadbalanced construct with custom Port', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + }, + }); + + + new NetworkMultipleTargetGroupsFargateService(stack, 'NLBService', { + cluster: cluster, + memoryLimitMiB: 1024, + cpu: 512, + taskImageOptions: { + image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + loadBalancers: [ + { + name: 'lb1', + listeners: [ + { name: 'listener1', port: 8181 }, + ], + }, + ], + targetGroups: [{ + containerPort: 81, + }], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { + Port: 81, + Protocol: 'TCP', + TargetType: 'ip', + VpcId: { + Ref: 'VPCB9E5F0B4', + }, + }); + }); }); From 55ff1b2e69f1b42bbbecd9dc95e17f2ffc35f94e Mon Sep 17 00:00:00 2001 From: Robert Djurasaj Date: Fri, 21 Jan 2022 03:34:40 -0700 Subject: [PATCH 04/12] feat(assertions): support for conditions (#18577) Add conditions matcher to assertion package. Required by #18560. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/assertions/README.md | 2 +- .../assertions/lib/private/conditions.ts | 30 ++++ .../assertions/lib/private/template.ts | 7 +- packages/@aws-cdk/assertions/lib/template.ts | 26 ++++ .../@aws-cdk/assertions/test/template.test.ts | 146 +++++++++++++++++- 5 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 packages/@aws-cdk/assertions/lib/private/conditions.ts diff --git a/packages/@aws-cdk/assertions/README.md b/packages/@aws-cdk/assertions/README.md index cfa09352b2469..3d1275d2b4a3e 100644 --- a/packages/@aws-cdk/assertions/README.md +++ b/packages/@aws-cdk/assertions/README.md @@ -139,7 +139,7 @@ expect(result.Foo).toEqual({ Value: 'Fred', Description: 'FooFred' }); expect(result.Bar).toEqual({ Value: 'Fred', Description: 'BarFred' }); ``` -The APIs `hasMapping()` and `findMappings()` provide similar functionalities. +The APIs `hasMapping()`, `findMappings()`, `hasCondition()`, and `hasCondtions()` provide similar functionalities. ## Special Matchers diff --git a/packages/@aws-cdk/assertions/lib/private/conditions.ts b/packages/@aws-cdk/assertions/lib/private/conditions.ts new file mode 100644 index 0000000000000..e7c4665dee219 --- /dev/null +++ b/packages/@aws-cdk/assertions/lib/private/conditions.ts @@ -0,0 +1,30 @@ +import { filterLogicalId, formatFailure, matchSection } from './section'; +import { Template } from './template'; + +export function findConditions(template: Template, logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } { + const section: { [key: string] : {} } = template.Conditions; + const result = matchSection(filterLogicalId(section, logicalId), props); + + if (!result.match) { + return {}; + } + + return result.matches; +} + +export function hasCondition(template: Template, logicalId: string, props: any): string | void { + const section: { [key: string] : {} } = template.Conditions; + const result = matchSection(filterLogicalId(section, logicalId), props); + if (result.match) { + return; + } + + if (result.closestResult === undefined) { + return 'No conditions found in the template'; + } + + return [ + `Template has ${result.analyzedCount} conditions, but none match as expected.`, + formatFailure(result.closestResult), + ].join('\n'); +} diff --git a/packages/@aws-cdk/assertions/lib/private/template.ts b/packages/@aws-cdk/assertions/lib/private/template.ts index 72dbeb8b64661..fc5d0cb6b1e01 100644 --- a/packages/@aws-cdk/assertions/lib/private/template.ts +++ b/packages/@aws-cdk/assertions/lib/private/template.ts @@ -4,7 +4,8 @@ export type Template = { Resources: { [logicalId: string]: Resource }, Outputs: { [logicalId: string]: Output }, Mappings: { [logicalId: string]: Mapping }, - Parameters: { [logicalId: string]: Parameter } + Parameters: { [logicalId: string]: Parameter }, + Conditions: { [logicalId: string]: Condition }, } export type Resource = { @@ -19,4 +20,6 @@ export type Mapping = { [key: string]: any }; export type Parameter = { Type: string; [key: string]: any; -} \ No newline at end of file +} + +export type Condition = { [key: string]: any }; \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/lib/template.ts b/packages/@aws-cdk/assertions/lib/template.ts index 2de687096bf7b..8875a91d0ac9c 100644 --- a/packages/@aws-cdk/assertions/lib/template.ts +++ b/packages/@aws-cdk/assertions/lib/template.ts @@ -3,6 +3,7 @@ import { Stack, Stage } from '@aws-cdk/core'; import * as fs from 'fs-extra'; import { Match } from './match'; import { Matcher } from './matcher'; +import { findConditions, hasCondition } from './private/conditions'; import { findMappings, hasMapping } from './private/mappings'; import { findOutputs, hasOutput } from './private/outputs'; import { findParameters, hasParameter } from './private/parameters'; @@ -183,6 +184,31 @@ export class Template { return findMappings(this.template, logicalId, props); } + /** + * Assert that a Condition with the given properties exists in the CloudFormation template. + * By default, performs partial matching on the resource, via the `Match.objectLike()`. + * To configure different behavour, use other matchers in the `Match` class. + * @param logicalId the name of the mapping. Provide `'*'` to match all conditions in the template. + * @param props the output as should be expected in the template. + */ + public hasCondition(logicalId: string, props: any): void { + const matchError = hasCondition(this.template, logicalId, props); + if (matchError) { + throw new Error(matchError); + } + } + + /** + * Get the set of matching Conditions that match the given properties in the CloudFormation template. + * @param logicalId the name of the condition. Provide `'*'` to match all conditions in the template. + * @param props by default, matches all Conditions in the template. + * When a literal object is provided, performs a partial match via `Match.objectLike()`. + * Use the `Match` APIs to configure a different behaviour. + */ + public findConditions(logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } { + return findConditions(this.template, logicalId, props); + } + /** * Assert that the CloudFormation template matches the given value * @param expected the expected CloudFormation template as key-value pairs. diff --git a/packages/@aws-cdk/assertions/test/template.test.ts b/packages/@aws-cdk/assertions/test/template.test.ts index dd8377892f405..92bdb405ab9ce 100644 --- a/packages/@aws-cdk/assertions/test/template.test.ts +++ b/packages/@aws-cdk/assertions/test/template.test.ts @@ -1,4 +1,4 @@ -import { App, CfnMapping, CfnOutput, CfnParameter, CfnResource, NestedStack, Stack } from '@aws-cdk/core'; +import { App, CfnCondition, CfnMapping, CfnOutput, CfnParameter, CfnResource, Fn, NestedStack, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { Capture, Match, Template } from '../lib'; @@ -940,6 +940,150 @@ describe('Template', () => { expect(Object.keys(result).length).toEqual(0); }); }); + + describe('hasCondition', () => { + test('matching', () => { + const stack = new Stack(); + new CfnCondition(stack, 'Foo', { + expression: Fn.conditionEquals('Bar', 'Baz'), + }); + + const inspect = Template.fromStack(stack); + expect(() => inspect.hasCondition('*', { 'Fn::Equals': ['Bar', 'Baz'] })).not.toThrow(); + }); + + test('not matching', (done) => { + const stack = new Stack(); + new CfnCondition(stack, 'Foo', { + expression: Fn.conditionEquals('Bar', 'Baz'), + }); + + new CfnCondition(stack, 'Qux', { + expression: Fn.conditionNot(Fn.conditionEquals('Quux', 'Quuz')), + }); + + const inspect = Template.fromStack(stack); + expectToThrow( + () => inspect.hasCondition('*', { + 'Fn::Equals': ['Baz', 'Bar'], + }), + [ + /2 conditions/, + /Missing key/, + ], + done, + ); + done(); + }); + + test('matching specific outputName', () => { + const stack = new Stack(); + new CfnCondition(stack, 'Foo', { + expression: Fn.conditionEquals('Bar', 'Baz'), + }); + + const inspect = Template.fromStack(stack); + expect(() => inspect.hasCondition('Foo', { 'Fn::Equals': ['Bar', 'Baz'] })).not.toThrow(); + }); + + test('not matching specific outputName', (done) => { + const stack = new Stack(); + new CfnCondition(stack, 'Foo', { + expression: Fn.conditionEquals('Baz', 'Bar'), + }); + + const inspect = Template.fromStack(stack); + expectToThrow( + () => inspect.hasCondition('Foo', { + 'Fn::Equals': ['Bar', 'Baz'], + }), + [ + /1 conditions/, + /Expected Baz but received Bar/, + ], + done, + ); + done(); + }); + }); + + describe('findConditions', () => { + test('matching', () => { + const stack = new Stack(); + new CfnCondition(stack, 'Foo', { + expression: Fn.conditionEquals('Bar', 'Baz'), + }); + + new CfnCondition(stack, 'Qux', { + expression: Fn.conditionNot(Fn.conditionEquals('Quux', 'Quuz')), + }); + + const inspect = Template.fromStack(stack); + const firstCondition = inspect.findConditions('Foo'); + expect(firstCondition).toEqual({ + Foo: { + 'Fn::Equals': [ + 'Bar', + 'Baz', + ], + }, + }); + + const secondCondition = inspect.findConditions('Qux'); + expect(secondCondition).toEqual({ + Qux: { + 'Fn::Not': [ + { + 'Fn::Equals': [ + 'Quux', + 'Quuz', + ], + }, + ], + }, + }); + }); + + test('not matching', () => { + const stack = new Stack(); + new CfnCondition(stack, 'Foo', { + expression: Fn.conditionEquals('Bar', 'Baz'), + }); + + const inspect = Template.fromStack(stack); + const result = inspect.findMappings('Bar'); + expect(Object.keys(result).length).toEqual(0); + }); + + test('matching with specific outputName', () => { + const stack = new Stack(); + new CfnCondition(stack, 'Foo', { + expression: Fn.conditionEquals('Bar', 'Baz'), + }); + + const inspect = Template.fromStack(stack); + const result = inspect.findConditions('Foo', { 'Fn::Equals': ['Bar', 'Baz'] }); + expect(result).toEqual({ + Foo: { + 'Fn::Equals': [ + 'Bar', + 'Baz', + ], + }, + }); + }); + + test('not matching specific output name', () => { + const stack = new Stack(); + new CfnCondition(stack, 'Foo', { + expression: Fn.conditionEquals('Bar', 'Baz'), + }); + + const inspect = Template.fromStack(stack); + const result = inspect.findConditions('Foo', { 'Fn::Equals': ['Bar', 'Qux'] }); + expect(Object.keys(result).length).toEqual(0); + }); + }); }); function expectToThrow(fn: () => void, msgs: (RegExp | string)[], done: jest.DoneCallback): void { From 56fc11b7e1efbfa7ae98d1db074715b0d0edc13f Mon Sep 17 00:00:00 2001 From: AWS CDK Automation <43080478+aws-cdk-automation@users.noreply.github.com> Date: Fri, 21 Jan 2022 03:22:35 -0800 Subject: [PATCH 05/12] docs(cfnspec): update CloudFormation documentation (#18587) Co-authored-by: AWS CDK Team Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../spec-source/cfn-docs/cfn-docs.json | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json b/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json index 9b9671adcf8ae..584d3f0218535 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json +++ b/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json @@ -1735,7 +1735,7 @@ }, "AWS::AppConfig::ConfigurationProfile.Validators": { "attributes": {}, - "description": "A validator provides a syntactic or semantic check to ensure the configuration that you want to deploy functions as intended. To validate your application configuration data, you provide a schema or a Lambda function that runs against the configuration. The configuration deployment or update can only proceed when the configuration data is valid.", + "description": "A validator provides a syntactic or semantic check to ensure the configuration that you want to deploy functions as intended. To validate your application configuration data, you provide a schema or an AWS Lambda function that runs against the configuration. The configuration deployment or update can only proceed when the configuration data is valid.", "properties": { "Content": "Either the JSON Schema content or the Amazon Resource Name (ARN) of an Lambda function.", "Type": "AWS AppConfig supports validators of type `JSON_SCHEMA` and `LAMBDA`" @@ -14145,13 +14145,13 @@ "attributes": { "AccessPointId": "The ID of the EFS access point.", "Arn": "The Amazon Resource Name (ARN) of the access point.", - "Ref": "`Ref` returns the resource ID. For example:\n\n`{\"Ref\":\"fsap-0123456789abcdef0\"}` .\n\nRef returns the access point ID." + "Ref": "`Ref` returns the AccessPoint ID. For example:\n\n`{\"Ref\":\"access_point-logical_id\"}` returns\n\n`fsap-0123456789abcdef0`" }, "description": "The `AWS::EFS::AccessPoint` resource creates an EFS access point. An access point is an application-specific view into an EFS file system that applies an operating system user and group, and a file system path, to any file system request made through the access point. The operating system user and group override any identity information provided by the NFS client. The file system path is exposed as the access point's root directory. Applications using the access point can only access data in its own directory and below. To learn more, see [Mounting a file system using EFS access points](https://docs.aws.amazon.com/efs/latest/ug/efs-access-points.html) .\n\nThis operation requires permissions for the `elasticfilesystem:CreateAccessPoint` action.", "properties": { "AccessPointTags": "An array of key-value pairs to apply to this resource.\n\nFor more information, see [Tag](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html) .", "ClientToken": "The opaque string specified in the request to ensure idempotent creation.", - "FileSystemId": "The ID of the EFS file system that the access point applies to.", + "FileSystemId": "The ID of the EFS file system that the access point applies to. Accepts only the ID format for input when specifying a file system, for example `fs-0123456789abcedf2` .", "PosixUser": "The full POSIX identity, including the user ID, group ID, and secondary group IDs on the access point that is used for all file operations by NFS clients using the access point.", "RootDirectory": "The directory on the Amazon EFS file system that the access point exposes as the root directory to NFS clients using the access point." } @@ -14192,15 +14192,15 @@ }, "AWS::EFS::FileSystem": { "attributes": { - "Arn": "The Amazon Resource Name (ARN) of the EFS file system. For example: `arn:aws:elasticfilesystem:us-west-2:1111333322228888:file-system/fs-12345678`", + "Arn": "The Amazon Resource Name (ARN) of the EFS file system.\n\nExample: `arn:aws:elasticfilesystem:us-west-2:1111333322228888:file-system/fs-0123456789abcdef8`", "FileSystemId": "The ID of the EFS file system. For example: `fs-12345678`", - "Ref": "`Ref` returns the resource ID. For example:\n\n`{\"Ref\":\"fs-12345678\"}` .\n\nFor the Amazon EFS file system `fs-12345678` , Ref returns the file system ID." + "Ref": "`Ref` returns the FileSystem ID. For example:\n\n`{\"Ref\":\"file_system-logical_id\"}` returns\n\n`fs-0123456789abcdef2`" }, "description": "The `AWS::EFS::FileSystem` resource creates a new, empty file system in Amazon Elastic File System ( Amazon EFS ). You must create a mount target ( [AWS::EFS::MountTarget](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-mounttarget.html) ) to mount your EFS file system on an Amazon EC2 or other AWS cloud compute resource.", "properties": { "AvailabilityZoneName": "Used to create a file system that uses One Zone storage classes. It specifies the AWS Availability Zone in which to create the file system. Use the format `us-east-1a` to specify the Availability Zone. For more information about One Zone storage classes, see [Using EFS storage classes](https://docs.aws.amazon.com/efs/latest/ug/storage-classes.html) in the *Amazon EFS User Guide* .\n\n> One Zone storage classes are not available in all Availability Zones in AWS Regions where Amazon EFS is available.", "BackupPolicy": "Use the `BackupPolicy` to turn automatic backups on or off for the file system.", - "BypassPolicyLockoutSafetyCheck": "", + "BypassPolicyLockoutSafetyCheck": "(Optional) Use this boolean to use or bypass the `FileSystemPolicy` lockout safety check. The policy lockout safety check determines if the `FileSystemPolicy` in the request will lock out the IAM principal making the request, preventing them from making future `PutFileSystemPolicy` requests on the file system. Set `BypassPolicyLockoutSafetyCheck` to `True` only when you intend to prevent the IAM principal that is making the request from making a subsequent `PutFileSystemPolicy` request on the file system. The default value is `False` .", "Encrypted": "A Boolean value that, if true, creates an encrypted file system. When creating an encrypted file system, you have the option of specifying a KmsKeyId for an existing AWS KMS key . If you don't specify a KMS key , then the default KMS key for Amazon EFS , `/aws/elasticfilesystem` , is used to protect the encrypted file system.", "FileSystemPolicy": "The `FileSystemPolicy` for the EFS file system. A file system policy is an IAM resource policy used to control NFS access to an EFS file system. For more information, see [Using IAM to control NFS access to Amazon EFS](https://docs.aws.amazon.com/efs/latest/ug/iam-access-control-nfs-efs.html) in the *Amazon EFS User Guide* .", "FileSystemTags": "Use to create one or more tags associated with the file system. Each tag is a user-defined key-value pair. Name your file system on creation by including a `\"Key\":\"Name\",\"Value\":\"{value}\"` key-value pair. Each key must be unique. For more information, see [Tagging AWS resources](https://docs.aws.amazon.com/general/latest/gr/aws_tagging.html) in the *AWS General Reference Guide* .", @@ -14236,9 +14236,9 @@ }, "AWS::EFS::MountTarget": { "attributes": { - "Id": "", - "IpAddress": "The IPv4 address of the mount target.", - "Ref": "`Ref` returns the resource ID. For example:\n\n`{\"Ref\":\"fsmt-12345678\"}` .\n\nFor the Amazon EFS file system mount target `fsmt-12345678` , Ref returns the mount target ID." + "Id": "The ID of the Amazon EFS file system that the mount target provides access to.\n\nExample: `fs-0123456789111222a`", + "IpAddress": "The IPv4 address of the mount target.\n\nExample: 192.0.2.0", + "Ref": "`Ref` returns the MountTarget ID. For example:\n\n`{\"Ref\":\"logical_mount_target_id\"}` returns\n\n`fsmt-0123456789abcdef8` ." }, "description": "The `AWS::EFS::MountTarget` resource is an Amazon EFS resource that creates a mount target for an EFS file system. You can then mount the file system on Amazon EC2 instances or other resources by using the mount target.", "properties": { @@ -16795,12 +16795,12 @@ }, "AWS::FSx::FileSystem": { "attributes": { - "DNSName": "Use the DNSName value to access the DNS name of your Amazon FSx file system. The DNS name identifies the file system.", - "LustreMountName": "Use the LustreMountName value when mounting an Amazon FSx for Lustre file system. For SCRATCH_1 deployment types, this value is always \"fsx\". For SCRATCH_2 and PERSISTENT_1 deployment types, this value is a string that is unique within an AWS Region . For more information, see [Mounting from an Amazon EC2 Instance](https://docs.aws.amazon.com/fsx/latest/LustreGuide/mounting-ec2-instance.html) .", - "Ref": "`Ref` returns the function returns the file system resource ID. For example:\n\n`{\"Ref\":\"fs-01234567890123456\"}`\n\nFor the Amazon FSx file system `fs-01234567890123456` , Ref returns the file system ID.", - "RootVolumeId": "" + "DNSName": "Returns the FSx for Windows file system's DNSName.\n\nExample: `amznfsxp1honlek.corp.example.com`", + "LustreMountName": "Returns the file system's LustreMountName.\n\nExample for SCRATCH_1 deployment types: This value is always `fsx` .\n\nExample for SCRATCH_2 and PERSISTENT deployment types: `2p3fhbmv`", + "Ref": "`Ref` returns the file system resource ID. For example:\n\n`{\"Ref\":\"file_system_logical_id\"}`\n\nReturns `fs-0123456789abcdef6` .", + "RootVolumeId": "Returns the root volume ID of the FSx for OpenZFS file system.\n\nExample: `fsvol-0123456789abcdefa`" }, - "description": "The `AWS::FSx::FileSystem` resource is an Amazon FSx resource type that creates either an Amazon FSx for Windows File Server file system or an Amazon FSx for Lustre file system.", + "description": "The `AWS::FSx::FileSystem` resource is an Amazon FSx resource type that creates an Amazon FSx file system. You can create any of the following supported file system types:\n\n- Amazon FSx for Lustre\n- Amazon FSx for NetApp ONTAP\n- Amazon FSx for OpenZFS\n- Amazon FSx for Windows File Server", "properties": { "BackupId": "The ID of the source backup. Specifies the backup that you are copying.", "FileSystemType": "The type of Amazon FSx file system, which can be `LUSTRE` , `WINDOWS` , `ONTAP` , or `OPENZFS` .", @@ -16936,7 +16936,7 @@ "properties": { "ActiveDirectoryId": "The ID for an existing AWS Managed Microsoft Active Directory (AD) instance that the file system should join when it's created.", "Aliases": "An array of one or more DNS alias names that you want to associate with the Amazon FSx file system. Aliases allow you to use existing DNS names to access the data in your Amazon FSx file system. You can associate up to 50 aliases with a file system at any time.\n\nFor more information, see [Working with DNS Aliases](https://docs.aws.amazon.com/fsx/latest/WindowsGuide/managing-dns-aliases.html) and [Walkthrough 5: Using DNS aliases to access your file system](https://docs.aws.amazon.com/fsx/latest/WindowsGuide/walkthrough05-file-system-custom-CNAME.html) , including additional steps you must take to be able to access your file system using a DNS alias.\n\nAn alias name has to meet the following requirements:\n\n- Formatted as a fully-qualified domain name (FQDN), `hostname.domain` , for example, `accounting.example.com` .\n- Can contain alphanumeric characters, the underscore (_), and the hyphen (-).\n- Cannot start or end with a hyphen.\n- Can start with a numeric.\n\nFor DNS alias names, Amazon FSx stores alphabetical characters as lowercase letters (a-z), regardless of how you specify them: as uppercase letters, lowercase letters, or the corresponding letters in escape codes.", - "AuditLogConfiguration": "", + "AuditLogConfiguration": "The configuration that Amazon FSx for Windows File Server uses to audit and log user accesses of files, folders, and file shares on the Amazon FSx for Windows File Server file system.", "AutomaticBackupRetentionDays": "The number of days to retain automatic backups. The default is to retain backups for 7 days. Setting this value to 0 disables the creation of automatic backups. The maximum retention period for backups is 90 days.", "CopyTagsToBackups": "A Boolean flag indicating whether tags for the file system should be copied to backups. This value defaults to false. If it's set to true, all tags for the file system are copied to all automatic and user-initiated backups where the user doesn't specify tags. If this value is true, and you specify one or more tags, only the specified tags are copied to backups. If you specify one or more tags when creating a user-initiated backup, no tags are copied from the file system, regardless of this value.", "DailyAutomaticBackupStartTime": "The preferred time to take daily automatic backups, formatted HH:MM in the UTC time zone.", @@ -16949,8 +16949,8 @@ }, "AWS::FSx::Snapshot": { "attributes": { - "Ref": "", - "ResourceARN": "`Ref` returns the Amazon Resource Name (ARN)." + "Ref": "`Ref` returns the ID of the snapshot. For example:\n\n`{\"Ref\":\"logical_snapshot_id\"}`\n\nReturns `fsvolsnap-0123456789abcedf5` .", + "ResourceARN": "Returns the snapshot's Amazon Resource Name (ARN).\n\nExample: `arn:aws:fsx:us-east-2:111133334444:snapshot/fsvol-01234567890123456/fsvolsnap-0123456789abcedf5`" }, "description": "A snapshot of an Amazon FSx for OpenZFS volume.", "properties": { @@ -16961,10 +16961,10 @@ }, "AWS::FSx::StorageVirtualMachine": { "attributes": { - "Ref": "", - "ResourceARN": "`Ref` returns the Amazon Resource Name (ARN).", - "StorageVirtualMachineId": "`Ref` returns the SVM's system generated unique ID.", - "UUID": "`Ref` returns the volume's universally unique identifier (UUID)." + "Ref": "`Ref` returns the resource ID, such as `svm-01234567890123456` . For example:\n\n`{\"Ref\": \"svm_logical_id\"}` returns\n\n`svm-01234567890123456`", + "ResourceARN": "Returns the storage virtual machine's Amazon Resource Name (ARN).\n\nExample: `arn:aws:fsx:us-east-2:111111111111:storage-virtual-machine/fs-0123456789abcdef1/svm-01234567890123456`", + "StorageVirtualMachineId": "Returns the storgage virtual machine's system generated ID.\n\nExample: `svm-0123456789abcedf1`", + "UUID": "Returns the storage virtual machine's system generated unique identifier (UUID).\n\nExample: `abcd0123-cd45-ef67-11aa-1111aaaa23bc`" }, "description": "Creates a storage virtual machine (SVM) for an Amazon FSx for ONTAP file system.", "properties": { @@ -16998,10 +16998,10 @@ }, "AWS::FSx::Volume": { "attributes": { - "Ref": "", - "ResourceARN": "`Ref` returns the Amazon Resource Name (ARN).", - "UUID": "`Ref` returns the volume's universally unique identifier (UUID).", - "VolumeId": "`Ref` returns the system-generated, unique ID of the volume." + "Ref": "`Ref` returns the ID for the volume. For example:\n\n`{\"Ref\":\"vol_logical_id\"}`\n\nReturns `fsvol-0123456789abcdef6` .", + "ResourceARN": "Returns the volume's Amazon Resource Name (ARN).\n\nExample: `arn:aws:fsx:us-east-2:111122223333:volume/fs-0123456789abcdef9/fsvol-01234567891112223`", + "UUID": "Returns the volume's universally unique identifier (UUID).\n\nExample: `abcd0123-cd45-ef67-11aa-1111aaaa23bc`", + "VolumeId": "Returns the volume's ID.\n\nExample: `fsvol-0123456789abcdefa`" }, "description": "Creates an Amazon FSx for NetApp ONTAP or Amazon FSx for OpenZFS storage volume.", "properties": { @@ -23023,7 +23023,7 @@ "SslCertificateS3Path": "Information required to find a specific file in an Amazon S3 bucket.", "Urls": "The URLs of the Microsoft SharePoint site that contains the documents that should be indexed.", "UseChangeLog": "Set to `TRUE` to use the Microsoft SharePoint change log to determine the documents that need to be updated in the index. Depending on the size of the SharePoint change log, it may take longer for Amazon Kendra to use the change log than it takes it to determine the changed documents using the Amazon Kendra document crawler.", - "VpcConfiguration": "" + "VpcConfiguration": "Provides information for connecting to an Amazon VPC." } }, "AWS::Kendra::DataSource.SqlConfiguration": { @@ -23053,7 +23053,7 @@ "attributes": {}, "description": "", "properties": { - "AuthenticationConfiguration": "", + "AuthenticationConfiguration": "Provides configuration information required to connect to websites using authentication.\n\nYou can connect to websites using basic authentication of user name and password.\n\nYou must provide the website host name and port number. For example, the host name of https://a.example.com/page1.html is \"a.example.com\" and the port is 443, the standard port for HTTPS. You use a secret in [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html) to store your authentication credentials.", "CrawlDepth": "Specifies the number of levels in a website that you want to crawl.\n\nThe first level begins from the website seed or starting point URL. For example, if a website has 3 levels \u2013 index level (i.e. seed in this example), sections level, and subsections level \u2013 and you are only interested in crawling information up to the sections level (i.e. levels 0-1), you can set your depth to 1.\n\nThe default crawl depth is set to 2.", "MaxContentSizePerPageInMegaBytes": "The maximum size (in MB) of a webpage or attachment to crawl.\n\nFiles larger than this size (in MB) are skipped/not crawled.\n\nThe default maximum size of a webpage or attachment is set to 50 MB.", "MaxLinksPerPage": "The maximum number of URLs on a webpage to include when crawling a website. This number is per webpage.\n\nAs a website\u2019s webpages are crawled, any URLs the webpages link to are also crawled. URLs on a webpage are crawled in order of appearance.\n\nThe default maximum links per page is 100.", @@ -24547,7 +24547,7 @@ "FilterCriteria": "(Streams and Amazon SQS) An object that defines the filter criteria that determine whether Lambda should process an event. For more information, see [Lambda event filtering](https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventfiltering.html) .", "FunctionName": "The name of the Lambda function.\n\n**Name formats** - *Function name* - `MyFunction` .\n- *Function ARN* - `arn:aws:lambda:us-west-2:123456789012:function:MyFunction` .\n- *Version or Alias ARN* - `arn:aws:lambda:us-west-2:123456789012:function:MyFunction:PROD` .\n- *Partial ARN* - `123456789012:function:MyFunction` .\n\nThe length constraint applies only to the full ARN. If you specify only the function name, it's limited to 64 characters in length.", "FunctionResponseTypes": "(Streams and SQS) A list of current response type enums applied to the event source mapping.\n\nValid Values: `ReportBatchItemFailures`", - "MaximumBatchingWindowInSeconds": "(Streams and Amazon SQS standard queues) The maximum amount of time, in seconds, that Lambda spends gathering records before invoking the function.\n\nDefault: 0", + "MaximumBatchingWindowInSeconds": "The maximum amount of time, in seconds, that Lambda spends gathering records before invoking the function.\n\n*Default ( Kinesis , DynamoDB , Amazon SQS event sources)* : 0\n\n*Default ( Amazon MSK , Kafka, Amazon MQ event sources)* : 500 ms", "MaximumRecordAgeInSeconds": "(Streams only) Discard records older than the specified age. The default value is -1,\nwhich sets the maximum age to infinite. When the value is set to infinite, Lambda never discards old records.", "MaximumRetryAttempts": "(Streams only) Discard records after the specified number of retries. The default value is -1,\nwhich sets the maximum number of retries to infinite. When MaximumRetryAttempts is infinite, Lambda retries failed records until the record expires in the event source.", "ParallelizationFactor": "(Streams only) The number of batches to process concurrently from each shard. The default value is 1.", @@ -32158,7 +32158,7 @@ "AllowMajorVersionUpgrade": "A value that indicates whether major version upgrades are allowed. Changing this parameter doesn't result in an outage and the change is asynchronously applied as soon as possible.\n\nConstraints: Major version upgrades must be allowed when specifying a value for the `EngineVersion` parameter that is a different major version than the DB instance's current version.", "AssociatedRoles": "The AWS Identity and Access Management (IAM) roles associated with the DB instance.", "AutoMinorVersionUpgrade": "A value that indicates whether minor engine upgrades are applied automatically to the DB instance during the maintenance window. By default, minor engine upgrades are applied automatically.", - "AvailabilityZone": "The Availability Zone that the database instance will be created in.\n\nDefault: A random, system-chosen Availability Zone in the endpoint's region.\n\nExample: `us-east-1d`\n\nConstraint: The AvailabilityZone parameter cannot be specified if the MultiAZ parameter is set to `true` . The specified Availability Zone must be in the same region as the current endpoint.", + "AvailabilityZone": "The Availability Zone (AZ) where the database will be created. For information on AWS Regions and Availability Zones, see [Regions and Availability Zones](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html) .\n\n*Amazon Aurora*\n\nNot applicable. Availability Zones are managed by the DB cluster.\n\nDefault: A random, system-chosen Availability Zone in the endpoint's AWS Region.\n\nExample: `us-east-1d`\n\nConstraint: The `AvailabilityZone` parameter can't be specified if the DB instance is a Multi-AZ deployment. The specified Availability Zone must be in the same AWS Region as the current endpoint.\n\n> If you're creating a DB instance in an RDS on VMware environment, specify the identifier of the custom Availability Zone to create the DB instance in.\n> \n> For more information about RDS on VMware, see the [RDS on VMware User Guide.](https://docs.aws.amazon.com/AmazonRDS/latest/RDSonVMwareUserGuide/rds-on-vmware.html)", "BackupRetentionPeriod": "The number of days for which automated backups are retained. Setting this parameter to a positive number enables backups. Setting this parameter to 0 disables automated backups.\n\n*Amazon Aurora*\n\nNot applicable. The retention period for automated backups is managed by the DB cluster.\n\nDefault: 1\n\nConstraints:\n\n- Must be a value from 0 to 35\n- Can't be set to 0 if the DB instance is a source to read replicas", "CACertificateIdentifier": "The identifier of the CA certificate for this DB instance.\n\n> Specifying or updating this property triggers a reboot. \n\nFor more information about CA certificate identifiers for RDS DB engines, see [Rotating Your SSL/TLS Certificate](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL-certificate-rotation.html) in the *Amazon RDS User Guide* .\n\nFor more information about CA certificate identifiers for Aurora DB engines, see [Rotating Your SSL/TLS Certificate](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.SSL-certificate-rotation.html) in the *Amazon Aurora User Guide* .", "CharacterSetName": "For supported engines, indicates that the DB instance should be associated with the specified character set.\n\n*Amazon Aurora*\n\nNot applicable. The character set is managed by the DB cluster. For more information, see [AWS::RDS::DBCluster](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbcluster.html) .", @@ -32230,7 +32230,7 @@ }, "description": "The `AWS::RDS::DBParameterGroup` resource creates a custom parameter group for an RDS database family.\n\nThis type can be declared in a template and referenced in the `DBParameterGroupName` property of an `[AWS::RDS::DBInstance](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-database-instance.html)` resource.\n\nFor information about configuring parameters for Amazon RDS DB instances, see [Working with DB parameter groups](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_WorkingWithParamGroups.html) in the *Amazon RDS User Guide* .\n\nFor information about configuring parameters for Amazon Aurora DB instances, see [Working with DB parameter groups and DB cluster parameter groups](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/USER_WorkingWithParamGroups.html) in the *Amazon Aurora User Guide* .\n\n> Applying a parameter group to a DB instance may require the DB instance to reboot, resulting in a database outage for the duration of the reboot.", "properties": { - "Description": "Provides the customer-specified description for this DB Parameter Group.", + "Description": "Provides the customer-specified description for this DB parameter group.", "Family": "The DB parameter group family name. A DB parameter group can be associated with one and only one DB parameter group family, and can be applied only to a DB instance running a DB engine and engine version compatible with that DB parameter group family.\n\n> The DB parameter group family can't be changed when updating a DB parameter group. \n\nTo list all of the available parameter group families, use the following command:\n\n`aws rds describe-db-engine-versions --query \"DBEngineVersions[].DBParameterGroupFamily\"`\n\nThe output contains duplicates.\n\nFor more information, see `[CreateDBParameterGroup](https://docs.aws.amazon.com//AmazonRDS/latest/APIReference/API_CreateDBParameterGroup.html)` .", "Parameters": "An array of parameter names and values for the parameter update. At least one parameter name and value must be supplied. Subsequent arguments are optional.\n\nFor more information about DB parameters and DB parameter groups for Amazon RDS DB engines, see [Working with DB Parameter Groups](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_WorkingWithParamGroups.html) in the *Amazon RDS User Guide* .\n\nFor more information about DB cluster and DB instance parameters and parameter groups for Amazon Aurora DB engines, see [Working with DB Parameter Groups and DB Cluster Parameter Groups](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/USER_WorkingWithParamGroups.html) in the *Amazon Aurora User Guide* .\n\n> AWS CloudFormation doesn't support specifying an apply method for each individual parameter. The default apply method for each parameter is used.", "Tags": "Tags to assign to the DB parameter group." @@ -32334,7 +32334,7 @@ "properties": { "DBSecurityGroupIngress": "Ingress rules to be applied to the DB security group.", "EC2VpcId": "The identifier of an Amazon VPC. This property indicates the VPC that this DB security group belongs to.\n\n> The `EC2VpcId` property is for backward compatibility with older regions, and is no longer recommended for providing security information to an RDS DB instance.", - "GroupDescription": "Provides the description of the DB Security Group.", + "GroupDescription": "Provides the description of the DB security group.", "Tags": "Tags to assign to the DB security group." } }, @@ -32343,9 +32343,9 @@ "description": "The `Ingress` property type specifies an individual ingress rule within an `AWS::RDS::DBSecurityGroup` resource.", "properties": { "CIDRIP": "The IP range to authorize.", - "EC2SecurityGroupId": "Id of the EC2 Security Group to authorize. For VPC DB Security Groups, `EC2SecurityGroupId` must be provided. Otherwise, EC2SecurityGroupOwnerId and either `EC2SecurityGroupName` or `EC2SecurityGroupId` must be provided.", - "EC2SecurityGroupName": "Name of the EC2 Security Group to authorize. For VPC DB Security Groups, `EC2SecurityGroupId` must be provided. Otherwise, EC2SecurityGroupOwnerId and either `EC2SecurityGroupName` or `EC2SecurityGroupId` must be provided.", - "EC2SecurityGroupOwnerId": "AWS Account Number of the owner of the EC2 Security Group specified in the EC2SecurityGroupName parameter. The AWS Access Key ID is not an acceptable value. For VPC DB Security Groups, `EC2SecurityGroupId` must be provided. Otherwise, EC2SecurityGroupOwnerId and either `EC2SecurityGroupName` or `EC2SecurityGroupId` must be provided." + "EC2SecurityGroupId": "Id of the EC2 security group to authorize. For VPC DB security groups, `EC2SecurityGroupId` must be provided. Otherwise, `EC2SecurityGroupOwnerId` and either `EC2SecurityGroupName` or `EC2SecurityGroupId` must be provided.", + "EC2SecurityGroupName": "Name of the EC2 security group to authorize. For VPC DB security groups, `EC2SecurityGroupId` must be provided. Otherwise, `EC2SecurityGroupOwnerId` and either `EC2SecurityGroupName` or `EC2SecurityGroupId` must be provided.", + "EC2SecurityGroupOwnerId": "AWS account number of the owner of the EC2 security group specified in the `EC2SecurityGroupName` parameter. The AWS access key ID isn't an acceptable value. For VPC DB security groups, `EC2SecurityGroupId` must be provided. Otherwise, `EC2SecurityGroupOwnerId` and either `EC2SecurityGroupName` or `EC2SecurityGroupId` must be provided." } }, "AWS::RDS::DBSecurityGroupIngress": { @@ -32355,10 +32355,10 @@ "description": "The `AWS::RDS::DBSecurityGroupIngress` resource enables ingress to a DB security group using one of two forms of authorization. First, you can add EC2 or VPC security groups to the DB security group if the application using the database is running on EC2 or VPC instances. Second, IP ranges are available if the application accessing your database is running on the Internet.\n\nThis type supports updates. For more information about updating stacks, see [AWS CloudFormation Stacks Updates](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks.html) .\n\nFor details about the settings for DB security group ingress, see [AuthorizeDBSecurityGroupIngress](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_AuthorizeDBSecurityGroupIngress.html) .", "properties": { "CIDRIP": "The IP range to authorize.", - "DBSecurityGroupName": "The name of the DB Security Group to add authorization to.", - "EC2SecurityGroupId": "Id of the EC2 Security Group to authorize. For VPC DB Security Groups, `EC2SecurityGroupId` must be provided. Otherwise, EC2SecurityGroupOwnerId and either `EC2SecurityGroupName` or `EC2SecurityGroupId` must be provided.", - "EC2SecurityGroupName": "Name of the EC2 Security Group to authorize. For VPC DB Security Groups, `EC2SecurityGroupId` must be provided. Otherwise, EC2SecurityGroupOwnerId and either `EC2SecurityGroupName` or `EC2SecurityGroupId` must be provided.", - "EC2SecurityGroupOwnerId": "AWS Account Number of the owner of the EC2 Security Group specified in the EC2SecurityGroupName parameter. The AWS Access Key ID is not an acceptable value. For VPC DB Security Groups, `EC2SecurityGroupId` must be provided. Otherwise, EC2SecurityGroupOwnerId and either `EC2SecurityGroupName` or `EC2SecurityGroupId` must be provided." + "DBSecurityGroupName": "The name of the DB security group to add authorization to.", + "EC2SecurityGroupId": "Id of the EC2 security group to authorize. For VPC DB security groups, `EC2SecurityGroupId` must be provided. Otherwise, `EC2SecurityGroupOwnerId` and either `EC2SecurityGroupName` or `EC2SecurityGroupId` must be provided.", + "EC2SecurityGroupName": "Name of the EC2 security group to authorize. For VPC DB security groups, `EC2SecurityGroupId` must be provided. Otherwise, `EC2SecurityGroupOwnerId` and either `EC2SecurityGroupName` or `EC2SecurityGroupId` must be provided.", + "EC2SecurityGroupOwnerId": "AWS account number of the owner of the EC2 security group specified in the `EC2SecurityGroupName` parameter. The AWS access key ID isn't an acceptable value. For VPC DB security groups, `EC2SecurityGroupId` must be provided. Otherwise, `EC2SecurityGroupOwnerId` and either `EC2SecurityGroupName` or `EC2SecurityGroupId` must be provided." } }, "AWS::RDS::DBSubnetGroup": { @@ -32367,9 +32367,9 @@ }, "description": "The `AWS::RDS::DBSubnetGroup` resource creates a database subnet group. Subnet groups must contain at least two subnets in two different Availability Zones in the same region.\n\nFor more information, see [Working with DB subnet groups](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_VPC.WorkingWithRDSInstanceinaVPC.html#USER_VPC.Subnets) in the *Amazon RDS User Guide* .", "properties": { - "DBSubnetGroupDescription": "The description for the DB Subnet Group.", + "DBSubnetGroupDescription": "The description for the DB subnet group.", "DBSubnetGroupName": "The name for the DB subnet group. This value is stored as a lowercase string.\n\nConstraints: Must contain no more than 255 lowercase alphanumeric characters or hyphens. Must not be \"Default\".\n\nExample: `mysubnetgroup`", - "SubnetIds": "The EC2 Subnet IDs for the DB Subnet Group.", + "SubnetIds": "The EC2 Subnet IDs for the DB subnet group.", "Tags": "Tags to assign to the DB subnet group." } }, @@ -32379,8 +32379,8 @@ }, "description": "The `AWS::RDS::EventSubscription` resource allows you to receive notifications for Amazon Relational Database Service events through the Amazon Simple Notification Service (Amazon SNS). For more information, see [Using Amazon RDS Event Notification](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Events.html) in the *Amazon RDS User Guide* .", "properties": { - "Enabled": "A Boolean value; set to *true* to activate the subscription, set to *false* to create the subscription but not active it.", - "EventCategories": "A list of event categories for a SourceType that you want to subscribe to. You can see a list of the categories for a given SourceType in the [Events](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Events.html) topic in the Amazon RDS User Guide or by using the *DescribeEventCategories* action.", + "Enabled": "A value that indicates whether to activate the subscription. If the event notification subscription isn't activated, the subscription is created but not active.", + "EventCategories": "A list of event categories for a particular source type ( `SourceType` ) that you want to subscribe to. You can see a list of the categories for a given source type in the \"Amazon RDS event categories and event messages\" section of the [*Amazon RDS User Guide*](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Events.Messages.html) or the [*Amazon Aurora User Guide*](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/USER_Events.Messages.html) . You can also see this list by using the `DescribeEventCategories` operation.", "SnsTopicArn": "The Amazon Resource Name (ARN) of the SNS topic created for event notification. The ARN is created by Amazon SNS when you create a topic and subscribe to it.", "SourceIds": "The list of identifiers of the event sources for which events are returned. If not specified, then all sources are included in the response. An identifier must begin with a letter and must contain only ASCII letters, digits, and hyphens. It can't end with a hyphen or contain two consecutive hyphens.\n\nConstraints:\n\n- If a `SourceIds` value is supplied, `SourceType` must also be provided.\n- If the source type is a DB instance, a `DBInstanceIdentifier` value must be supplied.\n- If the source type is a DB cluster, a `DBClusterIdentifier` value must be supplied.\n- If the source type is a DB parameter group, a `DBParameterGroupName` value must be supplied.\n- If the source type is a DB security group, a `DBSecurityGroupName` value must be supplied.\n- If the source type is a DB snapshot, a `DBSnapshotIdentifier` value must be supplied.\n- If the source type is a DB cluster snapshot, a `DBClusterSnapshotIdentifier` value must be supplied.", "SourceType": "The type of source that is generating the events. For example, if you want to be notified of events generated by a DB instance, set this parameter to `db-instance` . If this value isn't specified, all events are returned.\n\nValid values: `db-instance` | `db-cluster` | `db-parameter-group` | `db-security-group` | `db-snapshot` | `db-cluster-snapshot`" @@ -36859,7 +36859,7 @@ "description": "The `AWS::SecretsManager::SecretTargetAttachment` resource completes the final link between a Secrets Manager secret and the associated database. This is required because each has a dependency on the other. No matter which one you create first, the other doesn't exist yet. To resolve this, you must create the resources in the following order:\n\n- Define the secret without referencing the service or database. You can't reference the service or database because it doesn't exist yet. The secret must contain a user name and password.\n- Next, define the service or database. Include the reference to the secret to use stored credentials to define the database admin user and password.\n- Finally, define a `SecretTargetAttachment` resource type to finish configuring the secret with the required database engine type and the connection details of the service or database. The rotation function requires the details, if you attach one later by defining a [AWS::SecretsManager::RotationSchedule](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-rotationschedule.html) resource type.", "properties": { "SecretId": "The ARN or name of the secret. To reference a secret also created in this template, use the see [Ref](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html) function with the secret's logical ID.", - "TargetId": "The ARN of the database or cluster.", + "TargetId": "The ID of the database or cluster.", "TargetType": "A string that defines the type of service or database associated with the secret. This value instructs Secrets Manager how to update the secret with the details of the service or database. This value must be one of the following:\n\n- AWS::RDS::DBInstance\n- AWS::RDS::DBCluster\n- AWS::Redshift::Cluster\n- AWS::DocDB::DBInstance\n- AWS::DocDB::DBCluster" } }, From 0e78aeb9ad62226e67f72f23c0008ba749b3a73b Mon Sep 17 00:00:00 2001 From: Mischa Spiegelmock Date: Fri, 21 Jan 2022 16:09:45 +0200 Subject: [PATCH 06/12] feat(lambda-nodejs): Allow setting mainFields for esbuild (#18569) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’m trying to use the new format=esm bundling with NodejsFunction I noticed that it bundles the “CJS” version of dependencies not the ESM version (if available). esbuild has some wack defaults - https://esbuild.github.io/api/#main-fields This causes problems because the CJS versions usually have stuff like require() and they don’t work for tree-shaking. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-lambda-nodejs/README.md | 1 + packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts | 1 + packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts | 8 ++++++++ packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts | 3 ++- 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index 21f0c02004903..e7080c9ab72a0 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -192,6 +192,7 @@ new lambda.NodejsFunction(this, 'my-handler', { footer: '/* comments */', // requires esbuild >= 0.9.0, defaults to none charset: lambda.Charset.UTF8, // do not escape non-ASCII characters, defaults to Charset.ASCII format: lambda.OutputFormat.ESM, // ECMAScript module output format, defaults to OutputFormat.CJS (OutputFormat.ESM requires Node.js 14.x) + mainFields: ['module', 'main'], // prefer ECMAScript versions of dependencies }, }); ``` diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 62b5d56a0bc1d..d0cae32489818 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -211,6 +211,7 @@ export class Bundling implements cdk.BundlingOptions { ...this.props.banner ? [`--banner:js=${JSON.stringify(this.props.banner)}`] : [], ...this.props.footer ? [`--footer:js=${JSON.stringify(this.props.footer)}`] : [], ...this.props.charset ? [`--charset=${this.props.charset}`] : [], + ...this.props.mainFields ? [`--main-fields=${this.props.mainFields.join(',')}`] : [], ]; let depsCommand = ''; diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts index b74ac1df3b74a..e43dc6d41be1e 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts @@ -270,6 +270,14 @@ export interface BundlingOptions { * @default OutputFormat.CJS */ readonly format?: OutputFormat; + + /** + * How to determine the entry point for modules. + * Try ['module', 'main'] to default to ES module versions. + * + * @default ['main', 'module'] + */ + readonly mainFields?: string[]; } /** diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index 5941ce880a987..f9b8301eacacc 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -201,6 +201,7 @@ test('esbuild bundling with esbuild options', () => { footer: '/* comments */', charset: Charset.UTF8, forceDockerBundling: true, + mainFields: ['module', 'main'], define: { 'process.env.KEY': JSON.stringify('VALUE'), 'process.env.BOOL': 'true', @@ -224,7 +225,7 @@ test('esbuild bundling with esbuild options', () => { defineInstructions, '--log-level=silent --keep-names --tsconfig=/asset-input/lib/custom-tsconfig.ts', '--metafile=/asset-output/index.meta.json --banner:js="/* comments */" --footer:js="/* comments */"', - '--charset=utf8', + '--charset=utf8 --main-fields=module,main', ].join(' '), ], }), From 0e0a0922ba1d538abdfeb61a260c262109115038 Mon Sep 17 00:00:00 2001 From: Sam Hewitt Date: Fri, 21 Jan 2022 10:14:23 -0500 Subject: [PATCH 07/12] fix(aws-apigateway): cross region authorizer ref (#18444) Fixes: https://github.com/aws/aws-cdk/issues/18443 *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-apigateway/lib/authorizers/lambda.ts | 5 +- ...integ.request-authorizer.lit.expected.json | 30 +++- ...eg.token-authorizer-iam-role.expected.json | 30 +++- .../integ.token-authorizer.lit.expected.json | 30 +++- .../test/authorizers/lambda.test.ts | 144 ++++++++++++++++-- 5 files changed, 219 insertions(+), 20 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts index a354c8a4b3196..f7be4f954d7e8 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Duration, Lazy, Names, Stack } from '@aws-cdk/core'; +import { Arn, ArnFormat, Duration, Lazy, Names, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnAuthorizer } from '../apigateway.generated'; import { Authorizer, IAuthorizer } from '../authorizer'; @@ -254,5 +254,6 @@ export class RequestAuthorizer extends LambdaAuthorizer { * constructs the authorizerURIArn. */ function lambdaAuthorizerArn(handler: lambda.IFunction) { - return `arn:${Stack.of(handler).partition}:apigateway:${Stack.of(handler).region}:lambda:path/2015-03-31/functions/${handler.functionArn}/invocations`; + const { region, partition } = Arn.split( handler.functionArn, ArnFormat.COLON_RESOURCE_NAME); + return `arn:${partition}:apigateway:${region}:lambda:path/2015-03-31/functions/${handler.functionArn}/invocations`; } diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json index 9768e9692a548..981f02ebed888 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.request-authorizer.lit.expected.json @@ -253,11 +253,37 @@ [ "arn:", { - "Ref": "AWS::Partition" + "Fn::Select": [ + 1, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "MyAuthorizerFunction70F1223E", + "Arn" + ] + } + ] + } + ] }, ":apigateway:", { - "Ref": "AWS::Region" + "Fn::Select": [ + 3, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "MyAuthorizerFunction70F1223E", + "Arn" + ] + } + ] + } + ] }, ":lambda:path/2015-03-31/functions/", { diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json index b3d35baa2e42c..eda922f948d66 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer-iam-role.expected.json @@ -122,11 +122,37 @@ [ "arn:", { - "Ref": "AWS::Partition" + "Fn::Select": [ + 1, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "MyAuthorizerFunction70F1223E", + "Arn" + ] + } + ] + } + ] }, ":apigateway:", { - "Ref": "AWS::Region" + "Fn::Select": [ + 3, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "MyAuthorizerFunction70F1223E", + "Arn" + ] + } + ] + } + ] }, ":lambda:path/2015-03-31/functions/", { diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json index f36705d28f193..237a238eefcaa 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.token-authorizer.lit.expected.json @@ -253,11 +253,37 @@ [ "arn:", { - "Ref": "AWS::Partition" + "Fn::Select": [ + 1, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "MyAuthorizerFunction70F1223E", + "Arn" + ] + } + ] + } + ] }, ":apigateway:", { - "Ref": "AWS::Region" + "Fn::Select": [ + 3, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "MyAuthorizerFunction70F1223E", + "Arn" + ] + } + ] + } + ] }, ":lambda:path/2015-03-31/functions/", { diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/lambda.test.ts b/packages/@aws-cdk/aws-apigateway/test/authorizers/lambda.test.ts index fdaa20af10cd3..6728e0204ed37 100644 --- a/packages/@aws-cdk/aws-apigateway/test/authorizers/lambda.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/lambda.test.ts @@ -35,11 +35,31 @@ describe('lambda authorizer', () => { [ 'arn:', { - Ref: 'AWS::Partition', + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':apigateway:', { - Ref: 'AWS::Region', + 'Fn::Select': [ + 3, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':lambda:path/2015-03-31/functions/', { @@ -89,11 +109,31 @@ describe('lambda authorizer', () => { [ 'arn:', { - Ref: 'AWS::Partition', + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':apigateway:', { - Ref: 'AWS::Region', + 'Fn::Select': [ + 3, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':lambda:path/2015-03-31/functions/', { @@ -167,11 +207,31 @@ describe('lambda authorizer', () => { [ 'arn:', { - Ref: 'AWS::Partition', + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':apigateway:', { - Ref: 'AWS::Region', + 'Fn::Select': [ + 3, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':lambda:path/2015-03-31/functions/', { @@ -218,11 +278,31 @@ describe('lambda authorizer', () => { [ 'arn:', { - Ref: 'AWS::Partition', + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':apigateway:', { - Ref: 'AWS::Region', + 'Fn::Select': [ + 3, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':lambda:path/2015-03-31/functions/', { @@ -269,11 +349,31 @@ describe('lambda authorizer', () => { [ 'arn:', { - Ref: 'AWS::Partition', + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':apigateway:', { - Ref: 'AWS::Region', + 'Fn::Select': [ + 3, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':lambda:path/2015-03-31/functions/', { @@ -341,11 +441,31 @@ describe('lambda authorizer', () => { [ 'arn:', { - Ref: 'AWS::Partition', + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':apigateway:', { - Ref: 'AWS::Region', + 'Fn::Select': [ + 3, + { + 'Fn::Split': [ + ':', + { + 'Fn::GetAtt': ['myfunction9B95E948', 'Arn'], + }, + ], + }, + ], }, ':lambda:path/2015-03-31/functions/', { From 66f8bb0d94303177a19234c5d1a92d8ffccf30d5 Mon Sep 17 00:00:00 2001 From: Robert Djurasaj Date: Fri, 21 Jan 2022 09:03:10 -0700 Subject: [PATCH 08/12] chore(dynamodb): migrate tests to assertions (#18560) Depends on #18577 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-dynamodb/package.json | 2 +- .../aws-dynamodb/test/dynamodb.test.ts | 308 +++++++++--------- 2 files changed, 154 insertions(+), 156 deletions(-) diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index a4953cd3fb355..146ec9cc2d741 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -79,7 +79,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert-internal": "0.0.0", + "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts index 3c125c1bc0061..448aa7119eee6 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts @@ -1,5 +1,4 @@ -import { arrayWith, ABSENT, ResourcePart, SynthUtils } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as iam from '@aws-cdk/aws-iam'; import * as kinesis from '@aws-cdk/aws-kinesis'; @@ -84,20 +83,20 @@ describe('default properties', () => { test('hash key only', () => { new Table(stack, CONSTRUCT_NAME, { partitionKey: TABLE_PARTITION_KEY }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', { + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [{ AttributeName: 'hashKey', AttributeType: 'S' }], KeySchema: [{ AttributeName: 'hashKey', KeyType: 'HASH' }], ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 }, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', { DeletionPolicy: CfnDeletionPolicy.RETAIN }, ResourcePart.CompleteDefinition); + Template.fromStack(stack).hasResource('AWS::DynamoDB::Table', { DeletionPolicy: CfnDeletionPolicy.RETAIN }); }); test('removalPolicy is DESTROY', () => { new Table(stack, CONSTRUCT_NAME, { partitionKey: TABLE_PARTITION_KEY, removalPolicy: RemovalPolicy.DESTROY }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', { DeletionPolicy: CfnDeletionPolicy.DELETE }, ResourcePart.CompleteDefinition); + Template.fromStack(stack).hasResource('AWS::DynamoDB::Table', { DeletionPolicy: CfnDeletionPolicy.DELETE }); }); @@ -107,7 +106,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', { + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, { AttributeName: 'sortKey', AttributeType: 'N' }, @@ -126,7 +125,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -146,7 +145,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -167,7 +166,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -188,7 +187,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -209,7 +208,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -234,7 +233,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -261,7 +260,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { KeySchema: [ { AttributeName: 'hashKey', KeyType: 'HASH' }, @@ -288,7 +287,7 @@ describe('default properties', () => { sortKey: TABLE_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { KeySchema: [ { AttributeName: 'hashKey', KeyType: 'HASH' }, @@ -313,8 +312,8 @@ describe('default properties', () => { // since the resource has not been used in a cross-environment manner, // so the name should not be filled - expect(stack).toHaveResourceLike('AWS::DynamoDB::Table', { - TableName: ABSENT, + Template.fromStack(stack).hasResource('AWS::DynamoDB::Table', { + TableName: Match.absent(), }); }); }); @@ -338,7 +337,7 @@ testDeprecated('when specifying every property', () => { }); Tags.of(table).add('Environment', 'Production'); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -377,7 +376,7 @@ test('when specifying sse with customer managed CMK', () => { }); Tags.of(table).add('Environment', 'Production'); - expect(stack).toHaveResource('AWS::DynamoDB::Table', { + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { 'SSESpecification': { 'KMSMasterKeyId': { 'Fn::GetAtt': [ @@ -403,7 +402,7 @@ test('when specifying only encryptionKey', () => { }); Tags.of(table).add('Environment', 'Production'); - expect(stack).toHaveResource('AWS::DynamoDB::Table', { + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { 'SSESpecification': { 'KMSMasterKeyId': { 'Fn::GetAtt': [ @@ -430,7 +429,7 @@ test('when specifying sse with customer managed CMK with encryptionKey provided }); Tags.of(table).add('Environment', 'Production'); - expect(stack).toHaveResource('AWS::DynamoDB::Table', { + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { 'SSESpecification': { 'KMSMasterKeyId': { 'Fn::GetAtt': [ @@ -514,15 +513,15 @@ testLegacyBehavior('if an encryption key is included, encrypt/decrypt permission }); const user = new iam.User(stack, 'MyUser'); table.grantReadWriteData(user); - expect(stack).toMatchTemplate({ - 'Resources': { - 'TableAKey07CC09EC': { - 'Type': 'AWS::KMS::Key', - 'Properties': { - 'KeyPolicy': { - 'Statement': [ + Template.fromStack(stack).templateMatches({ + Resources: { + TableAKey07CC09EC: { + Type: 'AWS::KMS::Key', + Properties: { + KeyPolicy: { + Statement: [ { - 'Action': [ + Action: [ 'kms:Create*', 'kms:Describe*', 'kms:Enable*', @@ -539,99 +538,99 @@ testLegacyBehavior('if an encryption key is included, encrypt/decrypt permission 'kms:TagResource', 'kms:UntagResource', ], - 'Effect': 'Allow', - 'Principal': { - 'AWS': { + Effect: 'Allow', + Principal: { + AWS: { 'Fn::Join': [ '', [ 'arn:', { - 'Ref': 'AWS::Partition', + Ref: 'AWS::Partition', }, ':iam::', { - 'Ref': 'AWS::AccountId', + Ref: 'AWS::AccountId', }, ':root', ], ], }, }, - 'Resource': '*', + Resource: '*', }, { - 'Action': [ + Action: [ 'kms:Decrypt', 'kms:DescribeKey', 'kms:Encrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', ], - 'Effect': 'Allow', - 'Principal': { - 'AWS': { + Effect: 'Allow', + Principal: { + AWS: { 'Fn::GetAtt': [ 'MyUserDC45028B', 'Arn', ], }, }, - 'Resource': '*', + Resource: '*', }, ], - 'Version': '2012-10-17', + Version: '2012-10-17', }, - 'Description': 'Customer-managed key auto-created for encrypting DynamoDB table at Default/Table A', - 'EnableKeyRotation': true, + Description: 'Customer-managed key auto-created for encrypting DynamoDB table at Default/Table A', + EnableKeyRotation: true, }, - 'UpdateReplacePolicy': 'Retain', - 'DeletionPolicy': 'Retain', + UpdateReplacePolicy: 'Retain', + DeletionPolicy: 'Retain', }, - 'TableA3D7B5AFA': { - 'Type': 'AWS::DynamoDB::Table', - 'Properties': { - 'KeySchema': [ + TableA3D7B5AFA: { + Type: 'AWS::DynamoDB::Table', + Properties: { + KeySchema: [ { - 'AttributeName': 'hashKey', - 'KeyType': 'HASH', + AttributeName: 'hashKey', + KeyType: 'HASH', }, ], - 'AttributeDefinitions': [ + AttributeDefinitions: [ { - 'AttributeName': 'hashKey', - 'AttributeType': 'S', + AttributeName: 'hashKey', + AttributeType: 'S', }, ], - 'ProvisionedThroughput': { - 'ReadCapacityUnits': 5, - 'WriteCapacityUnits': 5, + ProvisionedThroughput: { + ReadCapacityUnits: 5, + WriteCapacityUnits: 5, }, - 'SSESpecification': { - 'KMSMasterKeyId': { + SSESpecification: { + KMSMasterKeyId: { 'Fn::GetAtt': [ 'TableAKey07CC09EC', 'Arn', ], }, - 'SSEEnabled': true, - 'SSEType': 'KMS', + SSEEnabled: true, + SSEType: 'KMS', }, - 'TableName': 'MyTable', + TableName: 'MyTable', }, - 'UpdateReplacePolicy': 'Retain', - 'DeletionPolicy': 'Retain', + UpdateReplacePolicy: 'Retain', + DeletionPolicy: 'Retain', }, - 'MyUserDC45028B': { - 'Type': 'AWS::IAM::User', + MyUserDC45028B: { + Type: 'AWS::IAM::User', }, - 'MyUserDefaultPolicy7B897426': { - 'Type': 'AWS::IAM::Policy', - 'Properties': { - 'PolicyDocument': { - 'Statement': [ + MyUserDefaultPolicy7B897426: { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyDocument: { + Statement: [ { - 'Action': [ + Action: [ 'dynamodb:BatchGetItem', 'dynamodb:GetRecords', 'dynamodb:GetShardIterator', @@ -644,8 +643,8 @@ testLegacyBehavior('if an encryption key is included, encrypt/decrypt permission 'dynamodb:UpdateItem', 'dynamodb:DeleteItem', ], - 'Effect': 'Allow', - 'Resource': [ + Effect: 'Allow', + Resource: [ { 'Fn::GetAtt': [ 'TableA3D7B5AFA', @@ -653,20 +652,20 @@ testLegacyBehavior('if an encryption key is included, encrypt/decrypt permission ], }, { - 'Ref': 'AWS::NoValue', + Ref: 'AWS::NoValue', }, ], }, { - 'Action': [ + Action: [ 'kms:Decrypt', 'kms:DescribeKey', 'kms:Encrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', ], - 'Effect': 'Allow', - 'Resource': { + Effect: 'Allow', + Resource: { 'Fn::GetAtt': [ 'TableAKey07CC09EC', 'Arn', @@ -674,12 +673,12 @@ testLegacyBehavior('if an encryption key is included, encrypt/decrypt permission }, }, ], - 'Version': '2012-10-17', + Version: '2012-10-17', }, - 'PolicyName': 'MyUserDefaultPolicy7B897426', - 'Users': [ + PolicyName: 'MyUserDefaultPolicy7B897426', + Users: [ { - 'Ref': 'MyUserDC45028B', + Ref: 'MyUserDC45028B', }, ], }, @@ -698,24 +697,24 @@ test('if an encryption key is included, encrypt/decrypt permissions are added to const user = new iam.User(stack, 'MyUser'); table.grantReadWriteData(user); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { - 'PolicyDocument': { - 'Statement': arrayWith({ - 'Action': [ + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: Match.arrayWith([{ + Action: [ 'kms:Decrypt', 'kms:DescribeKey', 'kms:Encrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', ], - 'Effect': 'Allow', - 'Resource': { + Effect: 'Allow', + Resource: { 'Fn::GetAtt': [ 'TableAKey07CC09EC', 'Arn', ], }, - }), + }]), }, }); }); @@ -728,7 +727,7 @@ test('when specifying PAY_PER_REQUEST billing mode', () => { partitionKey: TABLE_PARTITION_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { KeySchema: [ { AttributeName: 'hashKey', KeyType: 'HASH' }, @@ -891,7 +890,7 @@ test('when adding a global secondary index with hash key only', () => { writeCapacity: 1337, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -933,7 +932,7 @@ test('when adding a global secondary index with hash + range key', () => { writeCapacity: 1337, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -975,7 +974,7 @@ test('when adding a global secondary index with projection type KEYS_ONLY', () = projectionType: ProjectionType.KEYS_ONLY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1017,7 +1016,7 @@ test('when adding a global secondary index with projection type INCLUDE', () => writeCapacity: 1337, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1056,7 +1055,7 @@ test('when adding a global secondary index on a table with PAY_PER_REQUEST billi partitionKey: GSI_PARTITION_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1171,7 +1170,7 @@ test('when adding multiple global secondary indexes', () => { table.addGlobalSecondaryIndex(gsiGenerator.next().value); } - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1242,7 +1241,7 @@ test('when adding a global secondary index without specifying read and write cap partitionKey: GSI_PARTITION_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1277,7 +1276,7 @@ test('when adding a local secondary index with hash + range key', () => { sortKey: LSI_SORT_KEY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1312,7 +1311,7 @@ test('when adding a local secondary index with projection type KEYS_ONLY', () => projectionType: ProjectionType.KEYS_ONLY, }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1349,7 +1348,7 @@ test('when adding a local secondary index with projection type INCLUDE', () => { nonKeyAttributes: [lsiNonKeyAttributeGenerator.next().value, lsiNonKeyAttributeGenerator.next().value], }); - expect(stack).toHaveResource('AWS::DynamoDB::Table', + Template.fromStack(stack).hasResourceProperties('AWS::DynamoDB::Table', { AttributeDefinitions: [ { AttributeName: 'hashKey', AttributeType: 'S' }, @@ -1426,13 +1425,13 @@ test('can enable Read AutoScaling', () => { table.autoScaleReadCapacity({ minCapacity: 50, maxCapacity: 500 }).scaleOnUtilization({ targetUtilizationPercent: 75 }); // THEN - expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalableTarget', { + Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalableTarget', { MaxCapacity: 500, MinCapacity: 50, ScalableDimension: 'dynamodb:table:ReadCapacityUnits', ServiceNamespace: 'dynamodb', }); - expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { + Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalingPolicy', { PolicyType: 'TargetTrackingScaling', TargetTrackingScalingPolicyConfiguration: { PredefinedMetricSpecification: { PredefinedMetricType: 'DynamoDBReadCapacityUtilization' }, @@ -1450,13 +1449,13 @@ test('can enable Write AutoScaling', () => { table.autoScaleWriteCapacity({ minCapacity: 50, maxCapacity: 500 }).scaleOnUtilization({ targetUtilizationPercent: 75 }); // THEN - expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalableTarget', { + Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalableTarget', { MaxCapacity: 500, MinCapacity: 50, ScalableDimension: 'dynamodb:table:WriteCapacityUnits', ServiceNamespace: 'dynamodb', }); - expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { + Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalingPolicy', { PolicyType: 'TargetTrackingScaling', TargetTrackingScalingPolicyConfiguration: { PredefinedMetricSpecification: { PredefinedMetricType: 'DynamoDBWriteCapacityUtilization' }, @@ -1537,7 +1536,7 @@ test('can autoscale on a schedule', () => { }); // THEN - expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalableTarget', { + Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalableTarget', { ScheduledActions: [ { ScalableTargetAction: { 'MaxCapacity': 10 }, @@ -1806,7 +1805,7 @@ describe('grants', () => { table.grant(user, 'dynamodb:action1', 'dynamodb:action2'); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -1874,7 +1873,7 @@ describe('grants', () => { Table.grantListStreams(user); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -1920,7 +1919,7 @@ describe('grants', () => { table.grantTableListStreams(user); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -1966,7 +1965,7 @@ describe('grants', () => { table.grantStreamRead(user); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -2007,7 +2006,7 @@ describe('grants', () => { table.grantReadData(user); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -2066,7 +2065,7 @@ describe('grants', () => { table.grant(user, 'dynamodb:*'); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2159,7 +2158,7 @@ describe('import', () => { table.grantReadData(role); // it is possible to obtain a permission statement for a ref - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -2201,7 +2200,7 @@ describe('import', () => { table.grantReadWriteData(role); // it is possible to obtain a permission statement for a ref - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -2284,7 +2283,7 @@ describe('import', () => { expect(table.grantTableListStreams(role)).toBeDefined(); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2312,7 +2311,7 @@ describe('import', () => { expect(table.grantStreamRead(role)).toBeDefined(); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2347,7 +2346,7 @@ describe('import', () => { table.grantReadData(role); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2410,7 +2409,7 @@ describe('global', () => { }); // THEN - expect(stack).toHaveResource('Custom::DynamoDBReplica', { + Template.fromStack(stack).hasResource('Custom::DynamoDBReplica', { Properties: { TableName: { Ref: 'TableCD117FA1', @@ -2418,9 +2417,9 @@ describe('global', () => { Region: 'eu-west-2', }, Condition: 'TableStackRegionNotEqualseuwest2A03859E7', - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResource('Custom::DynamoDBReplica', { + Template.fromStack(stack).hasResource('Custom::DynamoDBReplica', { Properties: { TableName: { Ref: 'TableCD117FA1', @@ -2428,19 +2427,18 @@ describe('global', () => { Region: 'eu-central-1', }, Condition: 'TableStackRegionNotEqualseucentral199D46FC0', - }, ResourcePart.CompleteDefinition); + }); - expect(SynthUtils.toCloudFormation(stack).Conditions).toEqual({ - TableStackRegionNotEqualseuwest2A03859E7: { - 'Fn::Not': [ - { 'Fn::Equals': ['eu-west-2', { Ref: 'AWS::Region' }] }, - ], - }, - TableStackRegionNotEqualseucentral199D46FC0: { - 'Fn::Not': [ - { 'Fn::Equals': ['eu-central-1', { Ref: 'AWS::Region' }] }, - ], - }, + Template.fromStack(stack).hasCondition('TableStackRegionNotEqualseuwest2A03859E7', { + 'Fn::Not': [ + { 'Fn::Equals': ['eu-west-2', { Ref: 'AWS::Region' }] }, + ], + }); + + Template.fromStack(stack).hasCondition('TableStackRegionNotEqualseucentral199D46FC0', { + 'Fn::Not': [ + { 'Fn::Equals': ['eu-central-1', { Ref: 'AWS::Region' }] }, + ], }); }); @@ -2462,7 +2460,7 @@ describe('global', () => { }); // THEN - expect(stack).toHaveResource('Custom::DynamoDBReplica', { + Template.fromStack(stack).hasResource('Custom::DynamoDBReplica', { Properties: { TableName: { Ref: 'TableCD117FA1', @@ -2471,9 +2469,9 @@ describe('global', () => { SkipReplicationCompletedWait: 'true', }, Condition: 'TableStackRegionNotEqualseuwest2A03859E7', - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResource('Custom::DynamoDBReplica', { + Template.fromStack(stack).hasResource('Custom::DynamoDBReplica', { Properties: { TableName: { Ref: 'TableCD117FA1', @@ -2482,19 +2480,18 @@ describe('global', () => { SkipReplicationCompletedWait: 'true', }, Condition: 'TableStackRegionNotEqualseucentral199D46FC0', - }, ResourcePart.CompleteDefinition); + }); - expect(SynthUtils.toCloudFormation(stack).Conditions).toEqual({ - TableStackRegionNotEqualseuwest2A03859E7: { - 'Fn::Not': [ - { 'Fn::Equals': ['eu-west-2', { Ref: 'AWS::Region' }] }, - ], - }, - TableStackRegionNotEqualseucentral199D46FC0: { - 'Fn::Not': [ - { 'Fn::Equals': ['eu-central-1', { Ref: 'AWS::Region' }] }, - ], - }, + Template.fromStack(stack).hasCondition('TableStackRegionNotEqualseuwest2A03859E7', { + 'Fn::Not': [ + { 'Fn::Equals': ['eu-west-2', { Ref: 'AWS::Region' }] }, + ], + }); + + Template.fromStack(stack).hasCondition('TableStackRegionNotEqualseucentral199D46FC0', { + 'Fn::Not': [ + { 'Fn::Equals': ['eu-central-1', { Ref: 'AWS::Region' }] }, + ], }); }); @@ -2523,7 +2520,7 @@ describe('global', () => { table.grantReadData(user); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2677,7 +2674,7 @@ describe('global', () => { table.grantReadData(user); // THEN - expect(stack2).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack2).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2822,7 +2819,7 @@ describe('global', () => { table.grantTableListStreams(user); // THEN - expect(stack2).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack2).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2855,7 +2852,7 @@ describe('global', () => { // THEN expect(() => { - SynthUtils.synthesize(stack); + Template.fromStack(stack); }).toThrow(/A global Table that uses PROVISIONED as the billing mode needs auto-scaled write capacity/); }); @@ -2882,7 +2879,7 @@ describe('global', () => { // THEN expect(() => { - SynthUtils.synthesize(stack); + Template.fromStack(stack); }).toThrow(/A global Table that uses PROVISIONED as the billing mode needs auto-scaled write capacity with a policy/); }); @@ -2907,8 +2904,8 @@ describe('global', () => { maxCapacity: 10, }).scaleOnUtilization({ targetUtilizationPercent: 75 }); - expect(stack).toHaveResourceLike('AWS::DynamoDB::Table', { - BillingMode: ABSENT, // PROVISIONED is the default + Template.fromStack(stack).hasResource('AWS::DynamoDB::Table', { + BillingMode: Match.absent(), // PROVISIONED is the default }); }); @@ -2971,7 +2968,8 @@ describe('global', () => { }); // THEN - expect(SynthUtils.toCloudFormation(stack).Conditions).toBeUndefined(); + const conditions = Template.fromStack(stack).findConditions('*'); + expect(Object.keys(conditions).length).toEqual(0); }); test('can configure timeout', () => { @@ -3014,7 +3012,7 @@ test('L1 inside L2 expects removalpolicy to have been set', () => { new FakeTableL2(stack, 'Table'); expect(() => { - SynthUtils.toCloudFormation(stack); + Template.fromStack(stack); }).toThrow(/is a stateful resource type/); }); @@ -3029,7 +3027,7 @@ function testGrant(expectedActions: string[], invocation: (user: iam.IPrincipal, // THEN const action = expectedActions.length > 1 ? expectedActions.map(a => `dynamodb:${a}`) : `dynamodb:${expectedActions[0]}`; - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { From 41c8a3fa6b50a94affb65286d862056050d02e84 Mon Sep 17 00:00:00 2001 From: Lee Steakley <97981757+leestkly@users.noreply.github.com> Date: Fri, 21 Jan 2022 11:50:17 -0500 Subject: [PATCH 09/12] fix(apigatewayv2): websocket api: allow all methods in grant manage connections (#18544) Current code only grants POST method, but GET and DELETE methods are also needed for full connection management. closes #18410 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts | 2 +- packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts | 2 +- packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts | 2 +- packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts index 19bede1303437..da740d582bbad 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts @@ -150,7 +150,7 @@ export class WebSocketApi extends ApiBase implements IWebSocketApi { return Grant.addToPrincipal({ grantee: identity, actions: ['execute-api:ManageConnections'], - resourceArns: [`${arn}/*/POST/@connections/*`], + resourceArns: [`${arn}/*/*/@connections/*`], }); } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts index 6d5cc8527fef0..685850a746f4e 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/websocket/stage.ts @@ -131,7 +131,7 @@ export class WebSocketStage extends StageBase implements IWebSocketStage { return Grant.addToPrincipal({ grantee: identity, actions: ['execute-api:ManageConnections'], - resourceArns: [`${arn}/${this.stageName}/POST/@connections/*`], + resourceArns: [`${arn}/${this.stageName}/*/@connections/*`], }); } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts index ba687a79a9afe..1ac6cfbae315f 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts @@ -141,7 +141,7 @@ describe('WebSocketApi', () => { { Ref: 'apiC8550315', }, - '/*/POST/@connections/*', + '/*/*/@connections/*', ]], }, }]), diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts index b873f7fa74efa..b1af6af2e59bc 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/websocket/stage.test.ts @@ -99,7 +99,7 @@ describe('WebSocketStage', () => { { Ref: 'ApiF70053CD', }, - `/${defaultStage.stageName}/POST/@connections/*`, + `/${defaultStage.stageName}/*/@connections/*`, ]], }, }]), From 0e08eebd2f13ab0da6cac7b91288845cad530192 Mon Sep 17 00:00:00 2001 From: Cory Hall <43035978+corymhall@users.noreply.github.com> Date: Fri, 21 Jan 2022 12:36:51 -0500 Subject: [PATCH 10/12] fix(cli): hotswap should wait for lambda's `updateFunctionCode` to complete (#18536) There are [upcoming changes](https://aws.amazon.com/blogs/compute/coming-soon-expansion-of-aws-lambda-states-to-all-functions/) that will rollout Lambda states to all Lambda Functions. Prior to this update (current functionality) when you made an `updateFunctionCode` request the function was immediately available for both invocation and future updates. Once this change is rolled out this will no longer be the case. With Lambda states, when you make an update to a Lambda Function, it will not be available for future updates until the `LastUpdateStatus` returns `Successful`. This PR introduces a custom waiter that will wait for the update to complete before proceeding. The waiter will wait until the `State=Active` and the `LastUpdateStatus=Successful`. The `State` controls whether or not the function can be invoked, and the `LastUpdateStatus` controls whether the function can be updated. Based on this, I am not considering a deployment complete until both are successful. To see a more in depth analysis of the different values see #18386. In my testing I found that the time it took for a function to go from `LastUpdateStatus=InProgress` to `LastUpdateStatus=Successful` was: - ~1 second for a zip Function not in a VPC - ~25 seconds for a container Function or a Function in a VPC - ~2 minutes to deploy a VPC function (best proxy for StateReasonCode=Restoring) There are a couple of built in waiters that could have been used for this, namely [functionUpdated](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#functionUpdated-waiter). This waiter uses `getFunctionConfiguration` which has a quota of 15 requests/second. In addition the waiter polls every 5 seconds and this cannot be configured. Because hotswapping is sensitive to any latency that is introduced, I created a custom waiter that uses `getFunction`. `getFunction` has a quota of 100 requests/second and the custom waiter can be configured to poll every 1 second or every 5 seconds depending on what type of function is being updated. fixes #18386 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/api/hotswap/lambda-functions.ts | 63 ++++-- .../api/hotswap/hotswap-deployments.test.ts | 2 +- .../test/api/hotswap/hotswap-test-setup.ts | 25 ++- ...nctions-docker-hotswap-deployments.test.ts | 62 +++++- ...mbda-functions-hotswap-deployments.test.ts | 184 +++++++++++++++++- ...nctions-inline-hotswap-deployments.test.ts | 2 +- ...rsions-aliases-hotswap-deployments.test.ts | 2 +- packages/aws-cdk/test/util/mock-sdk.ts | 4 +- 8 files changed, 323 insertions(+), 21 deletions(-) diff --git a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts index d2966e756b69d..ac87e25c29655 100644 --- a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts +++ b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts @@ -1,5 +1,6 @@ import { Writable } from 'stream'; import * as archiver from 'archiver'; +import * as AWS from 'aws-sdk'; import { flatMap } from '../../util'; import { ISDK } from '../aws-auth'; import { CfnEvaluationException, EvaluateCloudFormationTemplate } from '../evaluate-cloudformation-template'; @@ -232,7 +233,7 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { const operations: Promise[] = []; if (resource.code !== undefined) { - const updateFunctionCodePromise = lambda.updateFunctionCode({ + const updateFunctionCodeResponse = await lambda.updateFunctionCode({ FunctionName: this.lambdaFunctionResource.physicalName, S3Bucket: resource.code.s3Bucket, S3Key: resource.code.s3Key, @@ -240,17 +241,10 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { ZipFile: resource.code.functionCodeZip, }).promise(); + await this.waitForLambdasCodeUpdateToFinish(updateFunctionCodeResponse, lambda); + // only if the code changed is there any point in publishing a new Version if (this.lambdaFunctionResource.publishVersion) { - // we need to wait for the code update to be done before publishing a new Version - await updateFunctionCodePromise; - // if we don't wait for the Function to finish updating, - // we can get a "The operation cannot be performed at this time. An update is in progress for resource:" - // error when publishing a new Version - await lambda.waitFor('functionUpdated', { - FunctionName: this.lambdaFunctionResource.physicalName, - }).promise(); - const publishVersionPromise = lambda.publishVersion({ FunctionName: this.lambdaFunctionResource.physicalName, }).promise(); @@ -269,8 +263,6 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { } else { operations.push(publishVersionPromise); } - } else { - operations.push(updateFunctionCodePromise); } } @@ -304,6 +296,53 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { // run all of our updates in parallel return Promise.all(operations); } + + /** + * After a Lambda Function is updated, it cannot be updated again until the + * `State=Active` and the `LastUpdateStatus=Successful`. + * + * Depending on the configuration of the Lambda Function this could happen relatively quickly + * or very slowly. For example, Zip based functions _not_ in a VPC can take ~1 second whereas VPC + * or Container functions can take ~25 seconds (and 'idle' VPC functions can take minutes). + */ + private async waitForLambdasCodeUpdateToFinish(currentFunctionConfiguration: AWS.Lambda.FunctionConfiguration, lambda: AWS.Lambda): Promise { + const functionIsInVpcOrUsesDockerForCode = currentFunctionConfiguration.VpcConfig?.VpcId || + currentFunctionConfiguration.PackageType === 'Image'; + + // if the function is deployed in a VPC or if it is a container image function + // then the update will take much longer and we can wait longer between checks + // otherwise, the update will be quick, so a 1-second delay is fine + const delaySeconds = functionIsInVpcOrUsesDockerForCode ? 5 : 1; + + // configure a custom waiter to wait for the function update to complete + (lambda as any).api.waiters.updateFunctionCodeToFinish = { + name: 'UpdateFunctionCodeToFinish', + operation: 'getFunction', + // equates to 1 minute for zip function not in a VPC and + // 5 minutes for container functions or function in a VPC + maxAttempts: 60, + delay: delaySeconds, + acceptors: [ + { + matcher: 'path', + argument: "Configuration.LastUpdateStatus == 'Successful' && Configuration.State == 'Active'", + expected: true, + state: 'success', + }, + { + matcher: 'path', + argument: 'Configuration.LastUpdateStatus', + expected: 'Failed', + state: 'failure', + }, + ], + }; + + const updateFunctionCodeWaiter = new (AWS as any).ResourceWaiter(lambda, 'updateFunctionCodeToFinish'); + await updateFunctionCodeWaiter.wait({ + FunctionName: this.lambdaFunctionResource.physicalName, + }).promise(); + } } /** diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts index 00a6b2095a569..67b0117a8d7ae 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts @@ -8,7 +8,7 @@ let mockGetEndpointSuffix: () => string; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn(); + mockUpdateLambdaCode = jest.fn().mockReturnValue({}); mockUpdateMachineDefinition = jest.fn(); mockGetEndpointSuffix = jest.fn(() => 'amazonaws.com'); hotswapMockSdkProvider.stubLambda({ diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts index e47b127e68766..7b6aebb9a81ee 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts @@ -83,8 +83,29 @@ export class HotswapMockSdkProvider { }); } - public stubLambda(stubs: SyncHandlerSubsetOf) { - this.mockSdkProvider.stubLambda(stubs); + public stubLambda( + stubs: SyncHandlerSubsetOf, + serviceStubs?: SyncHandlerSubsetOf, + additionalProperties: { [key: string]: any } = {}, + ): void { + this.mockSdkProvider.stubLambda(stubs, { + api: { + waiters: {}, + }, + makeRequest() { + return { + promise: () => Promise.resolve({}), + response: {}, + addListeners: () => {}, + }; + }, + ...serviceStubs, + ...additionalProperties, + }); + } + + public getLambdaApiWaiters(): { [key: string]: any } { + return (this.mockSdkProvider.sdk.lambda() as any).api.waiters; } public setUpdateProjectMock(mockUpdateProject: (input: codebuild.UpdateProjectInput) => codebuild.UpdateProjectOutput) { diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts index 9aef8b8778cf0..3ed53e5fd908b 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts @@ -5,16 +5,26 @@ let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => La let mockTagResource: (params: Lambda.Types.TagResourceRequest) => {}; let mockUntagResource: (params: Lambda.Types.UntagResourceRequest) => {}; let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; +let mockMakeRequest: (operation: string, params: any) => AWS.Request; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn(); + mockUpdateLambdaCode = jest.fn().mockReturnValue({ + PackageType: 'Image', + }); mockTagResource = jest.fn(); mockUntagResource = jest.fn(); + mockMakeRequest = jest.fn().mockReturnValue({ + promise: () => Promise.resolve({}), + response: {}, + addListeners: () => {}, + }); hotswapMockSdkProvider.stubLambda({ updateFunctionCode: mockUpdateLambdaCode, tagResource: mockTagResource, untagResource: mockUntagResource, + }, { + makeRequest: mockMakeRequest, }); }); @@ -65,3 +75,53 @@ test('calls the updateLambdaCode() API when it receives only a code difference i ImageUri: 'new-image', }); }); + +test('calls the getFunction() API with a delay of 5', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + ImageUri: 'current-image', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + ImageUri: 'new-image', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // WHEN + await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(mockMakeRequest).toHaveBeenCalledWith('getFunction', { FunctionName: 'my-function' }); + expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ + updateFunctionCodeToFinish: expect.objectContaining({ + name: 'UpdateFunctionCodeToFinish', + delay: 5, + }), + })); +}); diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts index 0b43563f233d9..d96b8530bce88 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts @@ -4,17 +4,25 @@ import * as setup from './hotswap-test-setup'; let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => Lambda.Types.FunctionConfiguration; let mockTagResource: (params: Lambda.Types.TagResourceRequest) => {}; let mockUntagResource: (params: Lambda.Types.UntagResourceRequest) => {}; +let mockMakeRequest: (operation: string, params: any) => AWS.Request; let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn(); + mockUpdateLambdaCode = jest.fn().mockReturnValue({}); mockTagResource = jest.fn(); mockUntagResource = jest.fn(); + mockMakeRequest = jest.fn().mockReturnValue({ + promise: () => Promise.resolve({}), + response: {}, + addListeners: () => {}, + }); hotswapMockSdkProvider.stubLambda({ updateFunctionCode: mockUpdateLambdaCode, tagResource: mockTagResource, untagResource: mockUntagResource, + }, { + makeRequest: mockMakeRequest, }); }); @@ -539,3 +547,177 @@ test('does not call the updateLambdaCode() API when a resource with type that is expect(deployStackResult).toBeUndefined(); expect(mockUpdateLambdaCode).not.toHaveBeenCalled(); }); + +test('calls getFunction() after function code is updated with delay 1', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // WHEN + await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(mockMakeRequest).toHaveBeenCalledWith('getFunction', { FunctionName: 'my-function' }); + expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ + updateFunctionCodeToFinish: expect.objectContaining({ + name: 'UpdateFunctionCodeToFinish', + delay: 1, + }), + })); +}); + +test('calls getFunction() after function code is updated and VpcId is empty string with delay 1', async () => { + // GIVEN + mockUpdateLambdaCode = jest.fn().mockReturnValue({ + VpcConfig: { + VpcId: '', + }, + }); + hotswapMockSdkProvider.stubLambda({ + updateFunctionCode: mockUpdateLambdaCode, + tagResource: mockTagResource, + untagResource: mockUntagResource, + }); + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // WHEN + await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ + updateFunctionCodeToFinish: expect.objectContaining({ + name: 'UpdateFunctionCodeToFinish', + delay: 1, + }), + })); +}); + +test('calls getFunction() after function code is updated on a VPC function with delay 5', async () => { + // GIVEN + mockUpdateLambdaCode = jest.fn().mockReturnValue({ + VpcConfig: { + VpcId: 'abc', + }, + }); + hotswapMockSdkProvider.stubLambda({ + updateFunctionCode: mockUpdateLambdaCode, + tagResource: mockTagResource, + untagResource: mockUntagResource, + }); + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // WHEN + await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ + updateFunctionCodeToFinish: expect.objectContaining({ + name: 'UpdateFunctionCodeToFinish', + delay: 5, + }), + })); +}); diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts index 13554cc655dcb..478fd80b538bf 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts @@ -8,7 +8,7 @@ let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn(); + mockUpdateLambdaCode = jest.fn().mockReturnValue({}); mockTagResource = jest.fn(); mockUntagResource = jest.fn(); hotswapMockSdkProvider.stubLambda({ diff --git a/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts index 4596bb2c96ac0..f937be7c5a5a0 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts @@ -8,7 +8,7 @@ let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn(); + mockUpdateLambdaCode = jest.fn().mockReturnValue({}); mockPublishVersion = jest.fn(); mockUpdateAlias = jest.fn(); hotswapMockSdkProvider.stubLambda({ diff --git a/packages/aws-cdk/test/util/mock-sdk.ts b/packages/aws-cdk/test/util/mock-sdk.ts index a1ed61a431366..bb8eaae251c47 100644 --- a/packages/aws-cdk/test/util/mock-sdk.ts +++ b/packages/aws-cdk/test/util/mock-sdk.ts @@ -102,8 +102,8 @@ export class MockSdkProvider extends SdkProvider { (this.sdk as any).ssm = jest.fn().mockReturnValue(partialAwsService(stubs)); } - public stubLambda(stubs: SyncHandlerSubsetOf) { - (this.sdk as any).lambda = jest.fn().mockReturnValue(partialAwsService(stubs)); + public stubLambda(stubs: SyncHandlerSubsetOf, additionalProperties: { [key: string]: any } = {}) { + (this.sdk as any).lambda = jest.fn().mockReturnValue(partialAwsService(stubs, additionalProperties)); } public stubStepFunctions(stubs: SyncHandlerSubsetOf) { From 7a20f29ec11cf0668a804c41308cff01170c4a57 Mon Sep 17 00:00:00 2001 From: Otavio Macedo Date: Fri, 21 Jan 2022 20:39:50 +0000 Subject: [PATCH 11/12] chore(eks-legacy): migrate tests to `assertions` (#18596) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-eks-legacy/package.json | 2 +- .../aws-eks-legacy/test/awsauth.test.ts | 12 +++--- .../aws-eks-legacy/test/cluster.test.ts | 42 +++++++++---------- .../aws-eks-legacy/test/helm-chart.test.ts | 10 ++--- .../aws-eks-legacy/test/manifest.test.ts | 4 +- 5 files changed, 35 insertions(+), 35 deletions(-) diff --git a/packages/@aws-cdk/aws-eks-legacy/package.json b/packages/@aws-cdk/aws-eks-legacy/package.json index 685633b13c976..c05d56e6b3ddb 100644 --- a/packages/@aws-cdk/aws-eks-legacy/package.json +++ b/packages/@aws-cdk/aws-eks-legacy/package.json @@ -77,7 +77,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert-internal": "0.0.0", + "@aws-cdk/assertions": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts index 3a5f28f648441..f642836ded6e2 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/awsauth.test.ts @@ -1,6 +1,6 @@ -import '@aws-cdk/assert-internal/jest'; -import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; +import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; +import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Cluster, KubernetesResource } from '../lib'; import { AwsAuth } from '../lib/aws-auth'; import { testFixtureNoVpc } from './util'; @@ -17,7 +17,7 @@ describeDeprecated('awsauth', () => { new AwsAuth(stack, 'AwsAuth', { cluster }); // THEN - expect(stack).toHaveResource(KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesResource.RESOURCE_TYPE, { Manifest: JSON.stringify([{ apiVersion: 'v1', kind: 'ConfigMap', @@ -44,8 +44,8 @@ describeDeprecated('awsauth', () => { cluster.awsAuth.addAccount('5566776655'); // THEN - expect(stack).toCountResources(KubernetesResource.RESOURCE_TYPE, 1); - expect(stack).toHaveResource(KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).resourceCountIs(KubernetesResource.RESOURCE_TYPE, 1); + Template.fromStack(stack).hasResourceProperties(KubernetesResource.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -106,7 +106,7 @@ describeDeprecated('awsauth', () => { cluster.awsAuth.addUserMapping(user, { groups: ['group2'] }); // THEN - expect(stack).toHaveResource(KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesResource.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', diff --git a/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts index b7f1666fb79b0..61188e858110a 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/cluster.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; @@ -18,7 +18,7 @@ describeDeprecated('cluster', () => { new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Cluster', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Cluster', { ResourcesVpcConfig: { SubnetIds: [ { Ref: 'VPCPublicSubnet1SubnetB4246D30' }, @@ -40,7 +40,7 @@ describeDeprecated('cluster', () => { new eks.Cluster(stack, 'cluster'); // THEN - expect(stack).toHaveResource('AWS::EC2::VPC'); + Template.fromStack(stack).resourceCountIs('AWS::EC2::VPC', 1); }); @@ -55,8 +55,8 @@ describeDeprecated('cluster', () => { // THEN expect(cluster.defaultCapacity).toBeDefined(); - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { DesiredCapacity: '2' }); - expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { InstanceType: 'm5.large' }); + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { DesiredCapacity: '2' }); + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { InstanceType: 'm5.large' }); }); @@ -72,8 +72,8 @@ describeDeprecated('cluster', () => { // THEN expect(cluster.defaultCapacity).toBeDefined(); - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { DesiredCapacity: '10' }); - expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { InstanceType: 'm2.xlarge' }); + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { DesiredCapacity: '10' }); + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { InstanceType: 'm2.xlarge' }); }); @@ -86,8 +86,8 @@ describeDeprecated('cluster', () => { // THEN expect(cluster.defaultCapacity).toBeUndefined(); - expect(stack).not.toHaveResource('AWS::AutoScaling::AutoScalingGroup'); - expect(stack).not.toHaveResource('AWS::AutoScaling::LaunchConfiguration'); + Template.fromStack(stack).resourceCountIs('AWS::AutoScaling::AutoScalingGroup', 0); + Template.fromStack(stack).resourceCountIs('AWS::AutoScaling::LaunchConfiguration', 0); }); }); @@ -100,7 +100,7 @@ describeDeprecated('cluster', () => { new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); // THEN - expect(stack).toHaveResource('AWS::EC2::Subnet', { + Template.fromStack(stack).hasResourceProperties('AWS::EC2::Subnet', { Tags: [ { Key: 'aws-cdk:subnet-name', Value: 'Private' }, { Key: 'aws-cdk:subnet-type', Value: 'Private' }, @@ -120,7 +120,7 @@ describeDeprecated('cluster', () => { new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); // THEN - expect(stack).toHaveResource('AWS::EC2::Subnet', { + Template.fromStack(stack).hasResourceProperties('AWS::EC2::Subnet', { MapPublicIpOnLaunch: true, Tags: [ { Key: 'aws-cdk:subnet-name', Value: 'Public' }, @@ -144,7 +144,7 @@ describeDeprecated('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { Tags: [ { Key: { 'Fn::Join': ['', ['kubernetes.io/cluster/', { Ref: 'ClusterEB0386A7' }]] }, @@ -182,7 +182,7 @@ describeDeprecated('cluster', () => { new cdk.CfnOutput(stack2, 'ClusterARN', { value: imported.clusterArn }); // THEN - expect(stack2).toMatchTemplate({ + Template.fromStack(stack2).templateMatches({ Outputs: { ClusterARN: { Value: { @@ -216,7 +216,7 @@ describeDeprecated('cluster', () => { new eks.Cluster(stack, 'Cluster', { vpc, mastersRole: role, defaultCapacity: 0 }); // THEN - expect(stack).toHaveResource(eks.KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesResource.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -247,11 +247,11 @@ describeDeprecated('cluster', () => { cluster.addResource('manifest2', { bar: 123 }, { boor: [1, 2, 3] }); // THEN - expect(stack).toHaveResource(eks.KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesResource.RESOURCE_TYPE, { Manifest: '[{"foo":123}]', }); - expect(stack).toHaveResource(eks.KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesResource.RESOURCE_TYPE, { Manifest: '[{"bar":123},{"boor":[1,2,3]}]', }); @@ -269,7 +269,7 @@ describeDeprecated('cluster', () => { }); // THEN - expect(stack).toHaveResource(eks.KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesResource.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -302,7 +302,7 @@ describeDeprecated('cluster', () => { }); // THEN - expect(stack).not.toHaveResource(eks.KubernetesResource.RESOURCE_TYPE); + Template.fromStack(stack).resourceCountIs(eks.KubernetesResource.RESOURCE_TYPE, 0); }); @@ -317,7 +317,7 @@ describeDeprecated('cluster', () => { }); // THEN - expect(stack).not.toHaveResource(eks.KubernetesResource.RESOURCE_TYPE); + Template.fromStack(stack).resourceCountIs(eks.KubernetesResource.RESOURCE_TYPE, 0); }); @@ -524,7 +524,7 @@ describeDeprecated('cluster', () => { }); // THEN - expect(stack).toHaveResource(eks.KubernetesResource.RESOURCE_TYPE, { Manifest: JSON.stringify(spotInterruptHandler()) }); + Template.fromStack(stack).hasResourceProperties(eks.KubernetesResource.RESOURCE_TYPE, { Manifest: JSON.stringify(spotInterruptHandler()) }); }); @@ -540,7 +540,7 @@ describeDeprecated('cluster', () => { }); // THEN - expect(stack).not.toHaveResource(eks.KubernetesResource.RESOURCE_TYPE); + Template.fromStack(stack).resourceCountIs(eks.KubernetesResource.RESOURCE_TYPE, 0); }); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts index 9606f892d5f1c..90b814a8bc0b3 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/helm-chart.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as eks from '../lib'; import { testFixtureCluster } from './util'; @@ -15,7 +15,7 @@ describeDeprecated('helm chart', () => { new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart' }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { Namespace: 'default' }); + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { Namespace: 'default' }); }); test('should have a lowercase default release name', () => { @@ -26,7 +26,7 @@ describeDeprecated('helm chart', () => { new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart' }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { Release: 'stackmychartff398361' }); + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { Release: 'stackmychartff398361' }); }); test('should trim the last 63 of the default release name', () => { @@ -37,7 +37,7 @@ describeDeprecated('helm chart', () => { new eks.HelmChart(stack, 'MyChartNameWhichISMostProbablyLongerThenSixtyThreeCharacters', { cluster, chart: 'chart' }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { Release: 'rtnamewhichismostprobablylongerthensixtythreecharactersb800614d' }); + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { Release: 'rtnamewhichismostprobablylongerthensixtythreecharactersb800614d' }); }); test('with values', () => { @@ -48,7 +48,7 @@ describeDeprecated('helm chart', () => { new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart', values: { foo: 123 } }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { Values: '{\"foo\":123}' }); + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { Values: '{\"foo\":123}' }); }); }); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts b/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts index 9a3de8f587f4c..2558945d61e94 100644 --- a/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts +++ b/packages/@aws-cdk/aws-eks-legacy/test/manifest.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import { Cluster, KubernetesResource } from '../lib'; import { testFixtureNoVpc } from './util'; @@ -69,7 +69,7 @@ describeDeprecated('manifest', () => { manifest, }); - expect(stack).toHaveResource(KubernetesResource.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesResource.RESOURCE_TYPE, { Manifest: JSON.stringify(manifest), }); From c62651305e197c9a1b84a33bcfb74a8e4ce41f02 Mon Sep 17 00:00:00 2001 From: Kaizen Conroy <36202692+kaizen3031593@users.noreply.github.com> Date: Fri, 21 Jan 2022 16:27:40 -0500 Subject: [PATCH 12/12] docs(stepfunctions): fix typo (#18583) Closes #18508. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts index fb5e52d2831b2..bff92d431cbab 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts @@ -116,7 +116,7 @@ export interface PassProps { /** * Define a Pass in the state machine * - * A Pass state can be used to transform the current exeuction's state. + * A Pass state can be used to transform the current execution's state. */ export class Pass extends State implements INextable { public readonly endStates: INextable[];