diff --git a/packages/@aws-cdk/assertions/README.md b/packages/@aws-cdk/assertions/README.md index 3d1275d2b4a3e..5370b1d094c8e 100644 --- a/packages/@aws-cdk/assertions/README.md +++ b/packages/@aws-cdk/assertions/README.md @@ -251,7 +251,7 @@ This matcher can be combined with any of the other matchers. // The following will NOT throw an assertion error template.hasResourceProperties('Foo::Bar', { Fred: { - Wobble: [ Match.anyValue(), "Flip" ], + Wobble: [ Match.anyValue(), Match.anyValue() ], }, }); @@ -400,7 +400,7 @@ template.hasResourceProperties('Foo::Bar', { ## Capturing Values -This matcher APIs documented above allow capturing values in the matching entry +The matcher APIs documented above allow capturing values in the matching entry (Resource, Output, Mapping, etc.). The following code captures a string from a matching resource. @@ -492,3 +492,74 @@ fredCapture.asString(); // returns "Flob" fredCapture.next(); // returns true fredCapture.asString(); // returns "Quib" ``` + +## Asserting Annotations + +In addition to template matching, we provide an API for annotation matching. +[Annotations](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.Annotations.html) +can be added via the [Aspects](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.Aspects.html) +API. You can learn more about Aspects [here](https://docs.aws.amazon.com/cdk/v2/guide/aspects.html). + +Say you have a `MyAspect` and a `MyStack` that uses `MyAspect`: + +```ts nofixture +import * as cdk from '@aws-cdk/core'; +import { Construct, IConstruct } from 'constructs'; + +class MyAspect implements cdk.IAspect { + public visit(node: IConstruct): void { + if (node instanceof cdk.CfnResource && node.cfnResourceType === 'Foo::Bar') { + this.error(node, 'we do not want a Foo::Bar resource'); + } + } + + protected error(node: IConstruct, message: string): void { + cdk.Annotations.of(node).addError(message); + } +} + +class MyStack extends cdk.Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + const stack = new cdk.Stack(); + new cdk.CfnResource(stack, 'Foo', { + type: 'Foo::Bar', + properties: { + Fred: 'Thud', + }, + }); + cdk.Aspects.of(stack).add(new MyAspect()); + } +} +``` + +We can then assert that the stack contains the expected Error: + +```ts +// import { Annotations } from '@aws-cdk/assertions'; + +Annotations.fromStack(stack).hasError( + '/Default/Foo', + 'we do not want a Foo::Bar resource', +); +``` + +Here are the available APIs for `Annotations`: + +- `hasError()` and `findError()` +- `hasWarning()` and `findWarning()` +- `hasInfo()` and `findInfo()` + +The corresponding `findXxx()` API is complementary to the `hasXxx()` API, except instead +of asserting its presence, it returns the set of matching messages. + +In addition, this suite of APIs is compatable with `Matchers` for more fine-grained control. +For example, the following assertion works as well: + +```ts +Annotations.fromStack(stack).hasError( + '/Default/Foo', + Match.stringLikeRegexp('.*Foo::Bar.*'), +); +``` diff --git a/packages/@aws-cdk/assertions/lib/annotations.ts b/packages/@aws-cdk/assertions/lib/annotations.ts new file mode 100644 index 0000000000000..c656b15d6bab8 --- /dev/null +++ b/packages/@aws-cdk/assertions/lib/annotations.ts @@ -0,0 +1,129 @@ +import { Stack, Stage } from '@aws-cdk/core'; +import { SynthesisMessage } from '@aws-cdk/cx-api'; +import { Messages } from './private/message'; +import { findMessage, hasMessage } from './private/messages'; + +/** + * Suite of assertions that can be run on a CDK Stack. + * Focused on asserting annotations. + */ +export class Annotations { + /** + * Base your assertions on the messages returned by a synthesized CDK `Stack`. + * @param stack the CDK Stack to run assertions on + */ + public static fromStack(stack: Stack): Annotations { + return new Annotations(toMessages(stack)); + } + + private readonly _messages: Messages; + + private constructor(messages: SynthesisMessage[]) { + this._messages = convertArrayToMessagesType(messages); + } + + /** + * Assert that an error with the given message exists in the synthesized CDK `Stack`. + * + * @param constructPath the construct path to the error. Provide `'*'` to match all errors in the template. + * @param message the error message as should be expected. This should be a string or Matcher object. + */ + public hasError(constructPath: string, message: any): void { + const matchError = hasMessage(this._messages, constructPath, constructMessage('error', message)); + if (matchError) { + throw new Error(matchError); + } + } + + /** + * Get the set of matching errors of a given construct path and message. + * + * @param constructPath the construct path to the error. Provide `'*'` to match all errors in the template. + * @param message the error message as should be expected. This should be a string or Matcher object. + */ + public findError(constructPath: string, message: any): SynthesisMessage[] { + return convertMessagesTypeToArray(findMessage(this._messages, constructPath, constructMessage('error', message)) as Messages); + } + + /** + * Assert that an warning with the given message exists in the synthesized CDK `Stack`. + * + * @param constructPath the construct path to the warning. Provide `'*'` to match all warnings in the template. + * @param message the warning message as should be expected. This should be a string or Matcher object. + */ + public hasWarning(constructPath: string, message: any): void { + const matchError = hasMessage(this._messages, constructPath, constructMessage('warning', message)); + if (matchError) { + throw new Error(matchError); + } + } + + /** + * Get the set of matching warning of a given construct path and message. + * + * @param constructPath the construct path to the warning. Provide `'*'` to match all warnings in the template. + * @param message the warning message as should be expected. This should be a string or Matcher object. + */ + public findWarning(constructPath: string, message: any): SynthesisMessage[] { + return convertMessagesTypeToArray(findMessage(this._messages, constructPath, constructMessage('warning', message)) as Messages); + } + + /** + * Assert that an info with the given message exists in the synthesized CDK `Stack`. + * + * @param constructPath the construct path to the info. Provide `'*'` to match all info in the template. + * @param message the info message as should be expected. This should be a string or Matcher object. + */ + public hasInfo(constructPath: string, message: any): void { + const matchError = hasMessage(this._messages, constructPath, constructMessage('info', message)); + if (matchError) { + throw new Error(matchError); + } + } + + /** + * Get the set of matching infos of a given construct path and message. + * + * @param constructPath the construct path to the info. Provide `'*'` to match all infos in the template. + * @param message the info message as should be expected. This should be a string or Matcher object. + */ + public findInfo(constructPath: string, message: any): SynthesisMessage[] { + return convertMessagesTypeToArray(findMessage(this._messages, constructPath, constructMessage('info', message)) as Messages); + } +} + +function constructMessage(type: 'info' | 'warning' | 'error', message: any): {[key:string]: any } { + return { + level: type, + entry: { + data: message, + }, + }; +} + +function convertArrayToMessagesType(messages: SynthesisMessage[]): Messages { + return messages.reduce((obj, item) => { + return { + ...obj, + [item.id]: item, + }; + }, {}) as Messages; +} + +function convertMessagesTypeToArray(messages: Messages): SynthesisMessage[] { + return Object.values(messages) as SynthesisMessage[]; +} + +function toMessages(stack: Stack): any { + const root = stack.node.root; + if (!Stage.isStage(root)) { + throw new Error('unexpected: all stacks must be part of a Stage or an App'); + } + + // to support incremental assertions (i.e. "expect(stack).toNotContainSomething(); doSomething(); expect(stack).toContainSomthing()") + const force = true; + + const assembly = root.synth({ force }); + + return assembly.getStackArtifact(stack.artifactId).messages; +} diff --git a/packages/@aws-cdk/assertions/lib/index.ts b/packages/@aws-cdk/assertions/lib/index.ts index 492fad1227af3..eccbfac38637f 100644 --- a/packages/@aws-cdk/assertions/lib/index.ts +++ b/packages/@aws-cdk/assertions/lib/index.ts @@ -1,4 +1,5 @@ export * from './capture'; export * from './template'; export * from './match'; -export * from './matcher'; \ No newline at end of file +export * from './matcher'; +export * from './annotations'; \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/lib/private/message.ts b/packages/@aws-cdk/assertions/lib/private/message.ts new file mode 100644 index 0000000000000..9657a5d90ad99 --- /dev/null +++ b/packages/@aws-cdk/assertions/lib/private/message.ts @@ -0,0 +1,5 @@ +import { SynthesisMessage } from '@aws-cdk/cx-api'; + +export type Messages = { + [logicalId: string]: SynthesisMessage; +} diff --git a/packages/@aws-cdk/assertions/lib/private/messages.ts b/packages/@aws-cdk/assertions/lib/private/messages.ts new file mode 100644 index 0000000000000..75c6fe3ae50b1 --- /dev/null +++ b/packages/@aws-cdk/assertions/lib/private/messages.ts @@ -0,0 +1,41 @@ +import { MatchResult } from '../matcher'; +import { Messages } from './message'; +import { filterLogicalId, formatFailure, matchSection } from './section'; + +export function findMessage(messages: Messages, logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } { + const section: { [key: string]: {} } = messages; + const result = matchSection(filterLogicalId(section, logicalId), props); + + if (!result.match) { + return {}; + } + + return result.matches; +} + +export function hasMessage(messages: Messages, logicalId: string, props: any): string | void { + const section: { [key: string]: {} } = messages; + const result = matchSection(filterLogicalId(section, logicalId), props); + + if (result.match) { + return; + } + + if (result.closestResult === undefined) { + return 'No messages found in the stack'; + } + + return [ + `Stack has ${result.analyzedCount} messages, but none match as expected.`, + formatFailure(formatMessage(result.closestResult)), + ].join('\n'); +} + +// We redact the stack trace by default because it is unnecessarily long and unintelligible. +// If there is a use case for rendering the trace, we can add it later. +function formatMessage(match: MatchResult, renderTrace: boolean = false): MatchResult { + if (!renderTrace) { + match.target.entry.trace = 'redacted'; + } + return match; +} diff --git a/packages/@aws-cdk/assertions/lib/template.ts b/packages/@aws-cdk/assertions/lib/template.ts index 8875a91d0ac9c..ec23538eaf4aa 100644 --- a/packages/@aws-cdk/assertions/lib/template.ts +++ b/packages/@aws-cdk/assertions/lib/template.ts @@ -129,7 +129,8 @@ export class Template { * @param logicalId the name of the parameter. Provide `'*'` to match all parameters in the template. * @param props by default, matches all Parameters 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. */ + * Use the `Match` APIs to configure a different behaviour. + */ public findParameters(logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } { return findParameters(this.template, logicalId, props); } diff --git a/packages/@aws-cdk/assertions/rosetta/default.ts-fixture b/packages/@aws-cdk/assertions/rosetta/default.ts-fixture index fa862da5c609a..32d751f8c38fe 100644 --- a/packages/@aws-cdk/assertions/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/assertions/rosetta/default.ts-fixture @@ -1,7 +1,7 @@ // Fixture with packages imported, but nothing else import { Construct } from 'constructs'; -import { Stack } from '@aws-cdk/core'; -import { Capture, Match, Template } from '@aws-cdk/assertions'; +import { Aspects, CfnResource, Stack } from '@aws-cdk/core'; +import { Annotations, Capture, Match, Template } from '@aws-cdk/assertions'; class Fixture extends Stack { constructor(scope: Construct, id: string) { diff --git a/packages/@aws-cdk/assertions/test/annotations.ts b/packages/@aws-cdk/assertions/test/annotations.ts new file mode 100644 index 0000000000000..8275e52d39dff --- /dev/null +++ b/packages/@aws-cdk/assertions/test/annotations.ts @@ -0,0 +1,150 @@ +import { Annotations, Aspects, CfnResource, IAspect, Stack } from '@aws-cdk/core'; +import { IConstruct } from 'constructs'; +import { Annotations as _Annotations, Match } from '../lib'; + +describe('Messages', () => { + let stack: Stack; + let annotations: _Annotations; + beforeAll(() => { + stack = new Stack(); + new CfnResource(stack, 'Foo', { + type: 'Foo::Bar', + properties: { + Fred: 'Thud', + }, + }); + + new CfnResource(stack, 'Bar', { + type: 'Foo::Bar', + properties: { + Baz: 'Qux', + }, + }); + + new CfnResource(stack, 'Fred', { + type: 'Baz::Qux', + properties: { + Foo: 'Bar', + }, + }); + + new CfnResource(stack, 'Qux', { + type: 'Fred::Thud', + properties: { + Fred: 'Bar', + }, + }); + + Aspects.of(stack).add(new MyAspect()); + annotations = _Annotations.fromStack(stack); + }); + + describe('hasError', () => { + test('match', () => { + annotations.hasError('/Default/Foo', 'this is an error'); + }); + + test('no match', () => { + expect(() => annotations.hasError('/Default/Fred', Match.anyValue())) + .toThrowError(/Stack has 1 messages, but none match as expected./); + }); + }); + + describe('findError', () => { + test('match', () => { + const result = annotations.findError('*', Match.anyValue()); + expect(Object.keys(result).length).toEqual(2); + }); + + test('no match', () => { + const result = annotations.findError('*', 'no message looks like this'); + expect(Object.keys(result).length).toEqual(0); + }); + }); + + describe('hasWarning', () => { + test('match', () => { + annotations.hasWarning('/Default/Fred', 'this is a warning'); + }); + + test('no match', () => { + expect(() => annotations.hasWarning('/Default/Foo', Match.anyValue())).toThrowError(/Stack has 1 messages, but none match as expected./); + }); + }); + + describe('findWarning', () => { + test('match', () => { + const result = annotations.findWarning('*', Match.anyValue()); + expect(Object.keys(result).length).toEqual(1); + }); + + test('no match', () => { + const result = annotations.findWarning('*', 'no message looks like this'); + expect(Object.keys(result).length).toEqual(0); + }); + }); + + describe('hasInfo', () => { + test('match', () => { + annotations.hasInfo('/Default/Qux', 'this is an info'); + }); + + test('no match', () => { + expect(() => annotations.hasInfo('/Default/Qux', 'this info is incorrect')).toThrowError(/Stack has 1 messages, but none match as expected./); + }); + }); + + describe('findInfo', () => { + test('match', () => { + const result = annotations.findInfo('/Default/Qux', 'this is an info'); + expect(Object.keys(result).length).toEqual(1); + }); + + test('no match', () => { + const result = annotations.findInfo('*', 'no message looks like this'); + expect(Object.keys(result).length).toEqual(0); + }); + }); + + describe('with matchers', () => { + test('anyValue', () => { + const result = annotations.findError('*', Match.anyValue()); + expect(Object.keys(result).length).toEqual(2); + }); + + test('not', () => { + expect(() => annotations.hasError('/Default/Foo', Match.not('this is an error'))) + .toThrowError(/Found unexpected match: "this is an error" at \/entry\/data/); + }); + + test('stringLikeRegEx', () => { + annotations.hasError('/Default/Foo', Match.stringLikeRegexp('.*error')); + }); + }); +}); + +class MyAspect implements IAspect { + public visit(node: IConstruct): void { + if (node instanceof CfnResource) { + if (node.cfnResourceType === 'Foo::Bar') { + this.error(node, 'this is an error'); + } else if (node.cfnResourceType === 'Baz::Qux') { + this.warn(node, 'this is a warning'); + } else { + this.info(node, 'this is an info'); + } + } + }; + + protected warn(node: IConstruct, message: string): void { + Annotations.of(node).addWarning(message); + } + + protected error(node: IConstruct, message: string): void { + Annotations.of(node).addError(message); + } + + protected info(node: IConstruct, message: string): void { + Annotations.of(node).addInfo(message); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json b/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json index 89be3fc475a6b..d0e520c926fe7 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json @@ -64,7 +64,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert-internal": "0.0.0", + "@aws-cdk/assertions": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/test/hooks.test.ts b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/hooks.test.ts index 4615c45913b44..413142423f7b5 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/test/hooks.test.ts +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/hooks.test.ts @@ -1,5 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; -import { arrayWith } from '@aws-cdk/assert-internal'; +import { Match, Template } from '@aws-cdk/assertions'; import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; @@ -27,7 +26,7 @@ describe('given an AutoScalingGroup and no role', () => { }); afterEach(() => { - expect(stack).toHaveResource('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [ @@ -54,8 +53,8 @@ describe('given an AutoScalingGroup and no role', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { 'Fn::GetAtt': ['Queue4A7E3555', 'Arn'] } }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { 'Fn::GetAtt': ['Queue4A7E3555', 'Arn'] } }); + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -95,8 +94,8 @@ describe('given an AutoScalingGroup and no role', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'TopicBFC7AF6E' } }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'TopicBFC7AF6E' } }); + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -133,13 +132,13 @@ describe('given an AutoScalingGroup and no role', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' } }); - expect(stack).toHaveResource('AWS::SNS::Subscription', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' } }); + Template.fromStack(stack).hasResourceProperties('AWS::SNS::Subscription', { Protocol: 'lambda', TopicArn: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' }, Endpoint: { 'Fn::GetAtt': ['Fn9270CBC0', 'Arn'] }, }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -177,7 +176,7 @@ describe('given an AutoScalingGroup and no role', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::SNS::Topic', { + Template.fromStack(stack).hasResourceProperties('AWS::SNS::Topic', { KmsMasterKeyId: { 'Fn::GetAtt': [ 'keyFEDD6EC0', @@ -185,9 +184,9 @@ describe('given an AutoScalingGroup and no role', () => { ], }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith( + Statement: Match.arrayWith([ { Effect: 'Allow', Action: [ @@ -201,7 +200,7 @@ describe('given an AutoScalingGroup and no role', () => { ], }, }, - ), + ]), }, }); }); @@ -223,7 +222,7 @@ describe('given an AutoScalingGroup and a role', () => { }); afterEach(() => { - expect(stack).toHaveResource('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [ @@ -253,7 +252,7 @@ describe('given an AutoScalingGroup and a role', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { 'Fn::GetAtt': ['Queue4A7E3555', 'Arn'] } }); + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { 'Fn::GetAtt': ['Queue4A7E3555', 'Arn'] } }); }); test('can use topic as hook target with a role', () => { @@ -271,8 +270,8 @@ describe('given an AutoScalingGroup and a role', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'TopicBFC7AF6E' } }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'TopicBFC7AF6E' } }); + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -313,13 +312,13 @@ describe('given an AutoScalingGroup and a role', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' } }); - expect(stack).toHaveResource('AWS::SNS::Subscription', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' } }); + Template.fromStack(stack).hasResourceProperties('AWS::SNS::Subscription', { Protocol: 'lambda', TopicArn: { Ref: 'ASGLifecycleHookTransTopic9B0D4842' }, Endpoint: { 'Fn::GetAtt': ['Fn9270CBC0', 'Arn'] }, }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-autoscaling/package.json b/packages/@aws-cdk/aws-autoscaling/package.json index bd58a5c1c288b..cc9037521f731 100644 --- a/packages/@aws-cdk/aws-autoscaling/package.json +++ b/packages/@aws-cdk/aws-autoscaling/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-autoscaling/test/aspects/require-imdsv2-aspect.test.ts b/packages/@aws-cdk/aws-autoscaling/test/aspects/require-imdsv2-aspect.test.ts index 22a58f097a98b..5fa4210d2a6f5 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/aspects/require-imdsv2-aspect.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/aspects/require-imdsv2-aspect.test.ts @@ -1,8 +1,4 @@ -import { - expect as expectCDK, - haveResourceLike, -} from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import { @@ -37,11 +33,12 @@ describe('AutoScalingGroupRequireImdsv2Aspect', () => { cdk.Aspects.of(stack).add(aspect); // THEN - expectCDK(stack).notTo(haveResourceLike('AWS::AutoScaling::LaunchConfiguration', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', Match.not({ MetadataOptions: { HttpTokens: 'required', }, })); + expect(asg.node.metadataEntry).toContainEqual({ data: expect.stringContaining('CfnLaunchConfiguration.MetadataOptions field is a CDK token.'), type: 'aws:cdk:warning', @@ -62,11 +59,11 @@ describe('AutoScalingGroupRequireImdsv2Aspect', () => { cdk.Aspects.of(stack).add(aspect); // THEN - expectCDK(stack).to(haveResourceLike('AWS::AutoScaling::LaunchConfiguration', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { MetadataOptions: { HttpTokens: 'required', }, - })); + }); }); }); diff --git a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts index 8ca98c19deeb8..945339ca86f5b 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts @@ -1,5 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; -import { ABSENT, InspectionFailure, ResourcePart } from '@aws-cdk/assert-internal'; +import { Match, Template } from '@aws-cdk/assertions'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; @@ -22,7 +21,7 @@ describe('auto scaling group', () => { vpc, }); - expect(stack).toMatchTemplate({ + Template.fromStack(stack).templateMatches({ 'Parameters': { 'SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter': { 'Type': 'AWS::SSM::Parameter::Value', @@ -136,8 +135,6 @@ describe('auto scaling group', () => { }, }, }); - - }); test('can set minCapacity, maxCapacity, desiredCapacity to 0', () => { @@ -153,14 +150,11 @@ describe('auto scaling group', () => { desiredCapacity: 0, }); - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { MinSize: '0', MaxSize: '0', DesiredCapacity: '0', - }, - ); - - + }); }); test('validation is not performed when using Tokens', () => { @@ -177,14 +171,11 @@ describe('auto scaling group', () => { }); // THEN: no exception - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { MinSize: '5', MaxSize: '1', DesiredCapacity: '20', - }, - ); - - + }); }); test('userdata can be overridden by image', () => { @@ -206,8 +197,6 @@ describe('auto scaling group', () => { // THEN expect(asg.userData.render()).toEqual('#!/bin/bash\nit me!'); - - }); test('userdata can be overridden at ASG directly', () => { @@ -233,8 +222,6 @@ describe('auto scaling group', () => { // THEN expect(asg.userData.render()).toEqual('#!/bin/bash\nno me!'); - - }); test('can specify only min capacity', () => { @@ -251,13 +238,10 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { MinSize: '10', MaxSize: '10', - }, - ); - - + }); }); test('can specify only max capacity', () => { @@ -274,13 +258,10 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { MinSize: '1', MaxSize: '10', - }, - ); - - + }); }); test('can specify only desiredCount', () => { @@ -297,14 +278,11 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { MinSize: '1', MaxSize: '10', DesiredCapacity: '10', - }, - ); - - + }); }); test('addToRolePolicy can be used to add statements to the role policy', () => { @@ -322,7 +300,7 @@ describe('auto scaling group', () => { resources: ['*'], })); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Version: '2012-10-17', Statement: [ @@ -352,7 +330,7 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResource('AWS::AutoScaling::AutoScalingGroup', { UpdatePolicy: { AutoScalingReplacingUpdate: { WillReplace: true, @@ -363,9 +341,7 @@ describe('auto scaling group', () => { MinSuccessfulInstancesPercent: 50, }, }, - }, ResourcePart.CompleteDefinition); - - + }); }); testDeprecated('can configure rolling update', () => { @@ -386,7 +362,7 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResource('AWS::AutoScaling::AutoScalingGroup', { UpdatePolicy: { 'AutoScalingRollingUpdate': { 'MinSuccessfulInstancesPercent': 50, @@ -395,9 +371,7 @@ describe('auto scaling group', () => { 'SuspendProcesses': ['HealthCheck', 'ReplaceUnhealthy', 'AZRebalance', 'AlarmNotification', 'ScheduledActions'], }, }, - }, ResourcePart.CompleteDefinition); - - + }); }); testDeprecated('can configure resource signals', () => { @@ -415,16 +389,14 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResource('AWS::AutoScaling::AutoScalingGroup', { CreationPolicy: { ResourceSignal: { Count: 5, Timeout: 'PT11M6S', }, }, - }, ResourcePart.CompleteDefinition); - - + }); }); test('can configure EC2 health check', () => { @@ -441,11 +413,9 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { HealthCheckType: 'EC2', }); - - }); test('can configure EBS health check', () => { @@ -462,12 +432,10 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { HealthCheckType: 'ELB', HealthCheckGracePeriod: 900, }); - - }); test('can add Security Group to Fleet', () => { @@ -482,7 +450,7 @@ describe('auto scaling group', () => { vpc, }); asg.addSecurityGroup(mockSecurityGroup(stack)); - expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { SecurityGroups: [ { 'Fn::GetAtt': [ @@ -493,7 +461,6 @@ describe('auto scaling group', () => { 'most-secure', ], }); - }); test('can set tags', () => { @@ -514,7 +481,7 @@ describe('auto scaling group', () => { cdk.Tags.of(asg).add('notsuper', 'caramel', { applyToLaunchedInstances: false }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { Tags: [ { Key: 'Name', @@ -533,7 +500,6 @@ describe('auto scaling group', () => { }, ], }); - }); test('allows setting spot price', () => { @@ -552,11 +518,9 @@ describe('auto scaling group', () => { // THEN expect(asg.spotPrice).toEqual('0.05'); - expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { SpotPrice: '0.05', }); - - }); test('allows association of public IP address', () => { @@ -578,11 +542,9 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { AssociatePublicIpAddress: true, - }, - ); - + }); }); test('association of public IP address requires public subnet', () => { @@ -602,7 +564,6 @@ describe('auto scaling group', () => { associatePublicIpAddress: true, }); }).toThrow(); - }); test('allows disassociation of public IP address', () => { @@ -622,11 +583,9 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { AssociatePublicIpAddress: false, - }, - ); - + }); }); test('does not specify public IP address association by default', () => { @@ -645,16 +604,9 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', (resource: any, errors: InspectionFailure) => { - for (const key of Object.keys(resource)) { - if (key === 'AssociatePublicIpAddress') { - errors.failureReason = 'Has AssociatePublicIpAddress'; - return false; - } - } - return true; + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { + AssociatePublicIpAddress: Match.absent(), }); - }); test('an existing security group can be specified instead of auto-created', () => { @@ -672,11 +624,9 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { SecurityGroups: ['most-secure'], - }, - ); - + }); }); test('an existing role can be specified instead of auto-created', () => { @@ -695,10 +645,9 @@ describe('auto scaling group', () => { // THEN expect(asg.role).toEqual(importedRole); - expect(stack).toHaveResource('AWS::IAM::InstanceProfile', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::InstanceProfile', { 'Roles': ['HelloDude'], }); - }); test('defaultChild is available on an ASG', () => { @@ -713,8 +662,6 @@ describe('auto scaling group', () => { // THEN expect(asg.node.defaultChild instanceof autoscaling.CfnAutoScalingGroup).toEqual(true); - - }); test('can set blockDeviceMappings', () => { @@ -755,7 +702,7 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { BlockDeviceMappings: [ { DeviceName: 'ebs', @@ -766,7 +713,7 @@ describe('auto scaling group', () => { VolumeSize: 15, VolumeType: 'io1', }, - NoDevice: ABSENT, + NoDevice: Match.absent(), }, { DeviceName: 'ebs-snapshot', @@ -776,12 +723,12 @@ describe('auto scaling group', () => { VolumeSize: 500, VolumeType: 'sc1', }, - NoDevice: ABSENT, + NoDevice: Match.absent(), }, { DeviceName: 'ephemeral', VirtualName: 'ephemeral0', - NoDevice: ABSENT, + NoDevice: Match.absent(), }, { DeviceName: 'disabled', @@ -793,8 +740,6 @@ describe('auto scaling group', () => { }, ], }); - - }); test('can configure maxInstanceLifetime', () => { @@ -809,11 +754,9 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { 'MaxInstanceLifetime': 604800, }); - - }); test('throws if maxInstanceLifetime < 7 days', () => { @@ -830,8 +773,6 @@ describe('auto scaling group', () => { maxInstanceLifetime: cdk.Duration.days(6), }); }).toThrow(/maxInstanceLifetime must be between 7 and 365 days \(inclusive\)/); - - }); test('throws if maxInstanceLifetime > 365 days', () => { @@ -848,8 +789,6 @@ describe('auto scaling group', () => { maxInstanceLifetime: cdk.Duration.days(366), }); }).toThrow(/maxInstanceLifetime must be between 7 and 365 days \(inclusive\)/); - - }); test('can configure instance monitoring', () => { @@ -866,10 +805,9 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { InstanceMonitoring: false, }); - }); test('instance monitoring defaults to absent', () => { @@ -885,10 +823,9 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LaunchConfiguration', { - InstanceMonitoring: ABSENT, + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { + InstanceMonitoring: Match.absent(), }); - }); test('throws if ephemeral volumeIndex < 0', () => { @@ -908,8 +845,6 @@ describe('auto scaling group', () => { }], }); }).toThrow(/volumeIndex must be a number starting from 0/); - - }); test('throws if volumeType === IO1 without iops', () => { @@ -933,8 +868,6 @@ describe('auto scaling group', () => { }], }); }).toThrow(/ops property is required with volumeType: EbsDeviceVolumeType.IO1/); - - }); test('warning if iops without volumeType', () => { @@ -959,8 +892,6 @@ describe('auto scaling group', () => { // THEN expect(asg.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); expect(asg.node.metadataEntry[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); - - }); test('warning if iops and volumeType !== IO1', () => { @@ -986,8 +917,6 @@ describe('auto scaling group', () => { // THEN expect(asg.node.metadataEntry[0].type).toEqual(cxschema.ArtifactMetadataEntryType.WARN); expect(asg.node.metadataEntry[0].data).toEqual('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); - - }); test('step scaling on metric', () => { @@ -1015,15 +944,13 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::CloudWatch::Alarm', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { ComparisonOperator: 'LessThanOrEqualToThreshold', EvaluationPeriods: 1, MetricName: 'Metric', Namespace: 'Test', Period: 300, }); - - }); test('step scaling on MathExpression', () => { @@ -1056,11 +983,11 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).not.toHaveResource('AWS::CloudWatch::Alarm', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', Match.not({ Period: 60, - }); + })); - expect(stack).toHaveResource('AWS::CloudWatch::Alarm', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { 'ComparisonOperator': 'LessThanOrEqualToThreshold', 'EvaluationPeriods': 1, 'Metrics': [ @@ -1083,8 +1010,6 @@ describe('auto scaling group', () => { ], 'Threshold': 49, }); - - }); test('test GroupMetrics.all(), adds a single MetricsCollection with no Metrics specified', () => { @@ -1100,15 +1025,14 @@ describe('auto scaling group', () => { }); // Then - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { MetricsCollection: [ { Granularity: '1Minute', - Metrics: ABSENT, + Metrics: Match.absent(), }, ], }); - }); test('test can specify a subset of group metrics', () => { @@ -1135,7 +1059,7 @@ describe('auto scaling group', () => { }); // Then - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { MetricsCollection: [ { Granularity: '1Minute', @@ -1146,7 +1070,6 @@ describe('auto scaling group', () => { }, ], }); - }); test('test deduplication of group metrics ', () => { @@ -1165,7 +1088,7 @@ describe('auto scaling group', () => { }); // Then - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { MetricsCollection: [ { Granularity: '1Minute', @@ -1173,7 +1096,6 @@ describe('auto scaling group', () => { }, ], }); - }); test('allow configuring notifications', () => { @@ -1200,7 +1122,7 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { NotificationConfigurations: [ { TopicARN: { Ref: 'MyTopic86869434' }, @@ -1216,10 +1138,7 @@ describe('auto scaling group', () => { ], }, ], - }, - ); - - + }); }); testDeprecated('throw if notification and notificationsTopics are both configured', () => { @@ -1240,7 +1159,6 @@ describe('auto scaling group', () => { }], }); }).toThrow('Cannot set \'notificationsTopic\' and \'notifications\', \'notificationsTopic\' is deprecated use \'notifications\' instead'); - }); test('notificationTypes default includes all non test NotificationType', () => { @@ -1262,7 +1180,7 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { NotificationConfigurations: [ { TopicARN: { Ref: 'MyTopic86869434' }, @@ -1274,10 +1192,7 @@ describe('auto scaling group', () => { ], }, ], - }, - ); - - + }); }); testDeprecated('setting notificationTopic configures all non test NotificationType', () => { @@ -1295,7 +1210,7 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { NotificationConfigurations: [ { TopicARN: { Ref: 'MyTopic86869434' }, @@ -1307,10 +1222,7 @@ describe('auto scaling group', () => { ], }, ], - }, - ); - - + }); }); test('NotificationTypes.ALL includes all non test NotificationType', () => { @@ -1333,11 +1245,9 @@ describe('auto scaling group', () => { // THEN expect(asg.areNewInstancesProtectedFromScaleIn()).toEqual(true); - expect(stack).toHaveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { NewInstancesProtectedFromScaleIn: true, }); - - }); test('Can protect new instances from scale-in via setter', () => { @@ -1355,11 +1265,9 @@ describe('auto scaling group', () => { // THEN expect(asg.areNewInstancesProtectedFromScaleIn()).toEqual(true); - expect(stack).toHaveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { NewInstancesProtectedFromScaleIn: true, }); - - }); test('requires imdsv2', () => { @@ -1376,7 +1284,7 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::AutoScaling::LaunchConfiguration', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { MetadataOptions: { HttpTokens: 'required', }, @@ -1400,7 +1308,7 @@ describe('auto scaling group', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { TerminationPolicies: [ 'OldestInstance', 'Default', @@ -1433,7 +1341,7 @@ test('Can set autoScalingGroupName', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { AutoScalingGroupName: 'MyAsg', }); }); @@ -1470,7 +1378,7 @@ test('can use Vpc imported from unparseable list tokens', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { VPCZoneIdentifier: { 'Fn::Split': [',', { 'Fn::ImportValue': 'myPrivateSubnetIds' }], }, diff --git a/packages/@aws-cdk/aws-autoscaling/test/cfn-init.test.ts b/packages/@aws-cdk/aws-autoscaling/test/cfn-init.test.ts index 2fd252e5e459d..1e34f43260c62 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/cfn-init.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/cfn-init.test.ts @@ -1,5 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; -import { anything, arrayWith, expect, haveResourceLike, ResourcePart } from '@aws-cdk/assert-internal'; +import { Match, Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import { Duration, Stack } from '@aws-cdk/core'; import * as autoscaling from '../lib'; @@ -37,13 +36,13 @@ test('Signals.waitForAll uses desiredCapacity if available', () => { }); // THEN - expect(stack).to(haveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResource('AWS::AutoScaling::AutoScalingGroup', { CreationPolicy: { ResourceSignal: { Count: 5, }, }, - }, ResourcePart.CompleteDefinition)); + }); }); test('Signals.waitForAll uses minCapacity if desiredCapacity is not available', () => { @@ -55,13 +54,13 @@ test('Signals.waitForAll uses minCapacity if desiredCapacity is not available', }); // THEN - expect(stack).to(haveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResource('AWS::AutoScaling::AutoScalingGroup', { CreationPolicy: { ResourceSignal: { Count: 2, }, }, - }, ResourcePart.CompleteDefinition)); + }); }); test('Signals.waitForMinCapacity uses minCapacity', () => { @@ -72,13 +71,13 @@ test('Signals.waitForMinCapacity uses minCapacity', () => { }); // THEN - expect(stack).to(haveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResource('AWS::AutoScaling::AutoScalingGroup', { CreationPolicy: { ResourceSignal: { Count: 2, }, }, - }, ResourcePart.CompleteDefinition)); + }); }); test('Signals.waitForCount uses given number', () => { @@ -89,13 +88,13 @@ test('Signals.waitForCount uses given number', () => { }); // THEN - expect(stack).to(haveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResource('AWS::AutoScaling::AutoScalingGroup', { CreationPolicy: { ResourceSignal: { Count: 10, }, }, - }, ResourcePart.CompleteDefinition)); + }); }); test('When signals are given appropriate IAM policy is added', () => { @@ -106,15 +105,15 @@ test('When signals are given appropriate IAM policy is added', () => { }); // THEN - expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith({ + Statement: Match.arrayWith([{ Action: 'cloudformation:SignalResource', Effect: 'Allow', Resource: { Ref: 'AWS::StackId' }, - }), + }]), }, - })); + }); }); test('UpdatePolicy.rollingUpdate() still correctly inserts IgnoreUnmodifiedGroupSizeProperties', () => { @@ -125,13 +124,13 @@ test('UpdatePolicy.rollingUpdate() still correctly inserts IgnoreUnmodifiedGroup }); // THEN - expect(stack).to(haveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResource('AWS::AutoScaling::AutoScalingGroup', { UpdatePolicy: { AutoScalingScheduledAction: { IgnoreUnmodifiedGroupSizeProperties: true, }, }, - }, ResourcePart.CompleteDefinition)); + }); }); test('UpdatePolicy.rollingUpdate() with Signals uses those defaults', () => { @@ -146,7 +145,7 @@ test('UpdatePolicy.rollingUpdate() with Signals uses those defaults', () => { }); // THEN - expect(stack).to(haveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResource('AWS::AutoScaling::AutoScalingGroup', { CreationPolicy: { AutoScalingCreationPolicy: { MinSuccessfulInstancesPercent: 50, @@ -163,7 +162,7 @@ test('UpdatePolicy.rollingUpdate() with Signals uses those defaults', () => { WaitOnResourceSignals: true, }, }, - }, ResourcePart.CompleteDefinition)); + }); }); test('UpdatePolicy.rollingUpdate() without Signals', () => { @@ -174,12 +173,12 @@ test('UpdatePolicy.rollingUpdate() without Signals', () => { }); // THEN - expect(stack).to(haveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResource('AWS::AutoScaling::AutoScalingGroup', { UpdatePolicy: { AutoScalingRollingUpdate: { }, }, - }, ResourcePart.CompleteDefinition)); + }); }); test('UpdatePolicy.replacingUpdate() renders correct UpdatePolicy', () => { @@ -190,13 +189,13 @@ test('UpdatePolicy.replacingUpdate() renders correct UpdatePolicy', () => { }); // THEN - expect(stack).to(haveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResource('AWS::AutoScaling::AutoScalingGroup', { UpdatePolicy: { AutoScalingReplacingUpdate: { WillReplace: true, }, }, - }, ResourcePart.CompleteDefinition)); + }); }); test('Using init config in ASG leads to default updatepolicy', () => { @@ -210,11 +209,11 @@ test('Using init config in ASG leads to default updatepolicy', () => { }); // THEN - expect(stack).to(haveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResource('AWS::AutoScaling::AutoScalingGroup', { UpdatePolicy: { - AutoScalingRollingUpdate: anything(), + AutoScalingRollingUpdate: Match.anyValue(), }, - }, ResourcePart.CompleteDefinition)); + }); }); test('Using init config in ASG leads to correct UserData and permissions', () => { @@ -228,7 +227,7 @@ test('Using init config in ASG leads to correct UserData and permissions', () => }); // THEN - expect(stack).to(haveResourceLike('AWS::AutoScaling::LaunchConfiguration', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { UserData: { 'Fn::Base64': { 'Fn::Join': ['', [ @@ -244,14 +243,15 @@ test('Using init config in ASG leads to correct UserData and permissions', () => ]], }, }, - })); - expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith({ + Statement: Match.arrayWith([{ Action: ['cloudformation:DescribeStackResource', 'cloudformation:SignalResource'], Effect: 'Allow', Resource: { Ref: 'AWS::StackId' }, - }), + }]), }, - })); + }); }); diff --git a/packages/@aws-cdk/aws-autoscaling/test/lifecyclehooks.test.ts b/packages/@aws-cdk/aws-autoscaling/test/lifecyclehooks.test.ts index a1f3717f91042..2cecb8b227107 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/lifecyclehooks.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/lifecyclehooks.test.ts @@ -1,5 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; -import { ResourcePart } from '@aws-cdk/assert-internal'; +import { Match, Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; @@ -20,22 +19,22 @@ describe('lifecycle hooks', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LifecycleHook', { LifecycleTransition: 'autoscaling:EC2_INSTANCE_LAUNCHING', DefaultResult: 'ABANDON', NotificationTargetARN: 'target:arn', }); // Lifecycle Hook has a dependency on the policy object - expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { + Template.fromStack(stack).hasResource('AWS::AutoScaling::LifecycleHook', { DependsOn: [ 'ASGLifecycleHookTransitionRoleDefaultPolicy2E50C7DB', 'ASGLifecycleHookTransitionRole3AAA6BB7', ], - }, ResourcePart.CompleteDefinition); + }); // A default role is provided - expect(stack).toHaveResource('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [ @@ -51,7 +50,7 @@ describe('lifecycle hooks', () => { }); // FakeNotificationTarget.bind() was executed - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Version: '2012-10-17', Statement: [ @@ -78,13 +77,13 @@ test('we can add a lifecycle hook to an ASG with no role and with no notificatio }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LifecycleHook', { LifecycleTransition: 'autoscaling:EC2_INSTANCE_LAUNCHING', DefaultResult: 'ABANDON', }); // A default role is NOT provided - expect(stack).not.toHaveResource('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', Match.not({ AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [ @@ -97,21 +96,10 @@ test('we can add a lifecycle hook to an ASG with no role and with no notificatio }, ], }, - }); + })); // FakeNotificationTarget.bind() was NOT executed - expect(stack).not.toHaveResource('AWS::IAM::Policy', { - PolicyDocument: { - Version: '2012-10-17', - Statement: [ - { - Action: 'action:Work', - Effect: 'Allow', - Resource: '*', - }, - ], - }, - }); + Template.fromStack(stack).resourceCountIs('AWS::IAM::Policy', 0); }); test('we can add a lifecycle hook to an ASG with a role and with a notificationTargetArn', () => { @@ -131,14 +119,14 @@ test('we can add a lifecycle hook to an ASG with a role and with a notificationT }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::LifecycleHook', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LifecycleHook', { NotificationTargetARN: 'target:arn', LifecycleTransition: 'autoscaling:EC2_INSTANCE_LAUNCHING', DefaultResult: 'ABANDON', }); // the provided role (myrole), not the default role, is used - expect(stack).toHaveResource('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [ diff --git a/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts b/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts index a497efab73135..d427b6afa1ff8 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts @@ -1,5 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; -import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert-internal'; +import { Template } from '@aws-cdk/assertions'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; @@ -24,15 +23,13 @@ describe('scaling', () => { }); // THEN - expect(stack).to(haveResource('AWS::AutoScaling::ScalingPolicy', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::ScalingPolicy', { PolicyType: 'TargetTrackingScaling', TargetTrackingConfiguration: { PredefinedMetricSpecification: { PredefinedMetricType: 'ASGAverageCPUUtilization' }, TargetValue: 30, }, - })); - - + }); }); test('network ingress', () => { @@ -46,15 +43,13 @@ describe('scaling', () => { }); // THEN - expect(stack).to(haveResource('AWS::AutoScaling::ScalingPolicy', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::ScalingPolicy', { PolicyType: 'TargetTrackingScaling', TargetTrackingConfiguration: { PredefinedMetricSpecification: { PredefinedMetricType: 'ASGAverageNetworkIn' }, TargetValue: 100, }, - })); - - + }); }); test('network egress', () => { @@ -68,15 +63,13 @@ describe('scaling', () => { }); // THEN - expect(stack).to(haveResource('AWS::AutoScaling::ScalingPolicy', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::ScalingPolicy', { PolicyType: 'TargetTrackingScaling', TargetTrackingConfiguration: { PredefinedMetricSpecification: { PredefinedMetricType: 'ASGAverageNetworkOut' }, TargetValue: 100, }, - })); - - + }); }); test('request count per second', () => { @@ -103,7 +96,7 @@ describe('scaling', () => { ], }; - expect(stack).to(haveResource('AWS::AutoScaling::ScalingPolicy', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::ScalingPolicy', { PolicyType: 'TargetTrackingScaling', TargetTrackingConfiguration: { TargetValue: 600, @@ -122,9 +115,7 @@ describe('scaling', () => { }, }, }, - })); - - + }); }); test('request count per minute', () => { @@ -151,7 +142,7 @@ describe('scaling', () => { ], }; - expect(stack).to(haveResource('AWS::AutoScaling::ScalingPolicy', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::ScalingPolicy', { PolicyType: 'TargetTrackingScaling', TargetTrackingConfiguration: { TargetValue: 10, @@ -170,9 +161,7 @@ describe('scaling', () => { }, }, }, - })); - - + }); }); test('custom metric', () => { @@ -193,7 +182,7 @@ describe('scaling', () => { }); // THEN - expect(stack).to(haveResource('AWS::AutoScaling::ScalingPolicy', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::ScalingPolicy', { PolicyType: 'TargetTrackingScaling', TargetTrackingConfiguration: { CustomizedMetricSpecification: { @@ -204,9 +193,7 @@ describe('scaling', () => { }, TargetValue: 2, }, - })); - - + }); }); }); @@ -231,7 +218,7 @@ describe('scaling', () => { }); // THEN: scaling in policy - expect(stack).to(haveResource('AWS::AutoScaling::ScalingPolicy', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::ScalingPolicy', { MetricAggregationType: 'Average', PolicyType: 'StepScaling', StepAdjustments: [ @@ -245,17 +232,17 @@ describe('scaling', () => { ScalingAdjustment: -2, }, ], - })); + }); - expect(stack).to(haveResource('AWS::CloudWatch::Alarm', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { ComparisonOperator: 'GreaterThanOrEqualToThreshold', Threshold: 3, AlarmActions: [{ Ref: 'FixtureASGMetricUpperPolicyC464CAFB' }], AlarmDescription: 'Upper threshold scaling alarm', - })); + }); // THEN: scaling out policy - expect(stack).to(haveResource('AWS::AutoScaling::ScalingPolicy', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::ScalingPolicy', { MetricAggregationType: 'Average', PolicyType: 'StepScaling', StepAdjustments: [ @@ -264,16 +251,14 @@ describe('scaling', () => { ScalingAdjustment: 1, }, ], - })); + }); - expect(stack).to(haveResource('AWS::CloudWatch::Alarm', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { ComparisonOperator: 'LessThanOrEqualToThreshold', Threshold: 2, AlarmActions: [{ Ref: 'FixtureASGMetricLowerPolicy4A1CDE42' }], AlarmDescription: 'Lower threshold scaling alarm', - })); - - + }); }); }); @@ -293,11 +278,12 @@ test('step scaling from percentile metric', () => { }); // THEN - expect(stack).to(haveResourceLike('AWS::AutoScaling::ScalingPolicy', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::ScalingPolicy', { PolicyType: 'StepScaling', MetricAggregationType: 'Average', - })); - expect(stack).to(haveResource('AWS::CloudWatch::Alarm', { + }); + + Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { ComparisonOperator: 'GreaterThanOrEqualToThreshold', EvaluationPeriods: 1, AlarmActions: [ @@ -307,7 +293,7 @@ test('step scaling from percentile metric', () => { MetricName: 'Metric', Namespace: 'Test', Threshold: 100, - })); + }); }); test('step scaling with evaluation period configured', () => { @@ -328,18 +314,19 @@ test('step scaling with evaluation period configured', () => { }); // THEN - expect(stack).to(haveResourceLike('AWS::AutoScaling::ScalingPolicy', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::ScalingPolicy', { PolicyType: 'StepScaling', MetricAggregationType: 'Maximum', - })); - expect(stack).to(haveResource('AWS::CloudWatch::Alarm', { + }); + + Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { ComparisonOperator: 'GreaterThanOrEqualToThreshold', EvaluationPeriods: 10, ExtendedStatistic: 'p99', MetricName: 'Metric', Namespace: 'Test', Threshold: 100, - })); + }); }); class ASGFixture extends Construct { diff --git a/packages/@aws-cdk/aws-autoscaling/test/scheduled-action.test.ts b/packages/@aws-cdk/aws-autoscaling/test/scheduled-action.test.ts index 57789d5ae36fe..8579099ffd6cf 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/scheduled-action.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/scheduled-action.test.ts @@ -1,5 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; -import { expect, haveResource, MatchStyle } from '@aws-cdk/assert-internal'; +import { Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import { describeDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; @@ -19,12 +18,10 @@ describeDeprecated('scheduled action', () => { }); // THEN - expect(stack).to(haveResource('AWS::AutoScaling::ScheduledAction', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::ScheduledAction', { Recurrence: '0 8 * * *', MinSize: 10, - })); - - + }); }); test('correctly formats date objects', () => { @@ -40,11 +37,9 @@ describeDeprecated('scheduled action', () => { }); // THEN - expect(stack).to(haveResource('AWS::AutoScaling::ScheduledAction', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::ScheduledAction', { StartTime: '2033-09-10T12:00:00Z', - })); - - + }); }); test('have timezone property', () => { @@ -60,12 +55,11 @@ describeDeprecated('scheduled action', () => { }); // THEN - expect(stack).to(haveResource('AWS::AutoScaling::ScheduledAction', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::ScheduledAction', { MinSize: 12, Recurrence: '0 12 * * *', TimeZone: 'Asia/Seoul', - })); - + }); }); test('autoscaling group has recommended updatepolicy for scheduled actions', () => { @@ -80,7 +74,7 @@ describeDeprecated('scheduled action', () => { }); // THEN - expect(stack).toMatch({ + Template.fromStack(stack).templateMatches({ Resources: { ASG46ED3070: { Type: 'AWS::AutoScaling::AutoScalingGroup', @@ -124,9 +118,7 @@ describeDeprecated('scheduled action', () => { Default: '/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2', }, }, - }, MatchStyle.SUPERSET); - - + }); }); }); diff --git a/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts b/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts index 12cf079c5279e..76f6603652dcf 100644 --- a/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts +++ b/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts @@ -209,7 +209,7 @@ export class Trail extends Resource { const cloudTrailPrincipal = new iam.ServicePrincipal('cloudtrail.amazonaws.com'); - this.s3bucket = props.bucket || new s3.Bucket(this, 'S3', { encryption: s3.BucketEncryption.UNENCRYPTED }); + this.s3bucket = props.bucket || new s3.Bucket(this, 'S3', { encryption: s3.BucketEncryption.UNENCRYPTED, enforceSSL: true }); this.s3bucket.addToResourcePolicy(new iam.PolicyStatement({ resources: [this.s3bucket.bucketArn], diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index 6fb04cb8051e6..1d28c67dd7c28 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/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-cloudtrail/test/cloudtrail.test.ts b/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts index a53a45b2c97ce..1492a3c1377b1 100644 --- a/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts +++ b/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts @@ -1,5 +1,4 @@ -import { ABSENT, SynthUtils } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as lambda from '@aws-cdk/aws-lambda'; @@ -13,6 +12,36 @@ import { ManagementEventSources, ReadWriteType, Trail } from '../lib'; const ExpectedBucketPolicyProperties = { PolicyDocument: { Statement: [ + { + Action: 's3:*', + Condition: { + Bool: { 'aws:SecureTransport': 'false' }, + }, + Effect: 'Deny', + Principal: { + AWS: '*', + }, + Resource: [ + { + 'Fn::GetAtt': [ + 'MyAmazingCloudTrailS3A580FE27', + 'Arn', + ], + }, + { + 'Fn::Join': [ + '', + [{ + 'Fn::GetAtt': [ + 'MyAmazingCloudTrailS3A580FE27', + 'Arn', + ], + }, + '/*'], + ], + }, + ], + }, { Action: 's3:GetBucketAcl', Effect: 'Allow', @@ -69,11 +98,11 @@ describe('cloudtrail', () => { test('with no properties', () => { const stack = getTestStack(); new Trail(stack, 'MyAmazingCloudTrail'); - expect(stack).toHaveResource('AWS::CloudTrail::Trail'); - expect(stack).toHaveResource('AWS::S3::Bucket'); - expect(stack).toHaveResource('AWS::S3::BucketPolicy', ExpectedBucketPolicyProperties); - expect(stack).not.toHaveResource('AWS::Logs::LogGroup'); - const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; + Template.fromStack(stack).resourceCountIs('AWS::CloudTrail::Trail', 1); + Template.fromStack(stack).resourceCountIs('AWS::S3::Bucket', 1); + Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', ExpectedBucketPolicyProperties); + Template.fromStack(stack).resourceCountIs('AWS::Logs::LogGroup', 0); + const trail: any = Template.fromStack(stack).toJSON().Resources.MyAmazingCloudTrail54516E8D; expect(trail.DependsOn).toEqual(['MyAmazingCloudTrailS3Policy39C120B0']); }); @@ -98,10 +127,10 @@ describe('cloudtrail', () => { new Trail(stack, 'Trail', { bucket: Trailbucket }); - expect(stack).toHaveResource('AWS::CloudTrail::Trail'); - expect(stack).toHaveResource('AWS::S3::Bucket'); - expect(stack).toHaveResource('AWS::S3::BucketPolicy'); - expect(stack).not.toHaveResource('AWS::Logs::LogGroup'); + Template.fromStack(stack).resourceCountIs('AWS::CloudTrail::Trail', 1); + Template.fromStack(stack).resourceCountIs('AWS::S3::Bucket', 1); + Template.fromStack(stack).resourceCountIs('AWS::S3::BucketPolicy', 1); + Template.fromStack(stack).resourceCountIs('AWS::Logs::LogGroup', 0); }); test('with sns topic', () => { @@ -111,9 +140,9 @@ describe('cloudtrail', () => { new Trail(stack, 'Trail', { snsTopic: topic }); - expect(stack).toHaveResource('AWS::CloudTrail::Trail'); - expect(stack).not.toHaveResource('AWS::Logs::LogGroup'); - expect(stack).toHaveResource('AWS::SNS::TopicPolicy', { + Template.fromStack(stack).resourceCountIs('AWS::CloudTrail::Trail', 1); + Template.fromStack(stack).resourceCountIs('AWS::Logs::LogGroup', 0); + Template.fromStack(stack).hasResourceProperties('AWS::SNS::TopicPolicy', { PolicyDocument: { Statement: [ { @@ -137,7 +166,7 @@ describe('cloudtrail', () => { // WHEN new Trail(stack, 'Trail', { bucket }); - expect(stack).toHaveResource('AWS::CloudTrail::Trail', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', { S3BucketName: 'somebucket', }); }); @@ -149,12 +178,46 @@ describe('cloudtrail', () => { // WHEN new Trail(stack, 'Trail', { s3KeyPrefix: 'someprefix' }); - expect(stack).toHaveResource('AWS::CloudTrail::Trail'); - expect(stack).toHaveResource('AWS::S3::Bucket'); - expect(stack).toHaveResource('AWS::S3::BucketPolicy', { + Template.fromStack(stack).resourceCountIs('AWS::CloudTrail::Trail', 1); + Template.fromStack(stack).resourceCountIs('AWS::S3::Bucket', 1); + Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', { Bucket: { Ref: 'TrailS30071F172' }, PolicyDocument: { Statement: [ + { + Action: 's3:*', + Condition: { + Bool: { + 'aws:SecureTransport': 'false', + }, + }, + Effect: 'Deny', + Principal: { + AWS: '*', + }, + Resource: [ + { + 'Fn::GetAtt': [ + 'TrailS30071F172', + 'Arn', + ], + }, + { + 'Fn::Join': [ + '', + [ + { + 'Fn::GetAtt': [ + 'TrailS30071F172', + 'Arn', + ], + }, + '/*', + ], + ], + }, + ], + }, { Action: 's3:GetBucketAcl', Effect: 'Allow', @@ -199,21 +262,21 @@ describe('cloudtrail', () => { trailName: 'UnencryptedTrail', }); - expect(stack).toHaveResource('AWS::CloudTrail::Trail', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', { TrailName: 'EncryptionKeyTrail', KMSKeyId: { 'Fn::GetAtt': ['keyFEDD6EC0', 'Arn'], }, }); - expect(stack).toHaveResource('AWS::CloudTrail::Trail', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', { TrailName: 'KmsKeyTrail', KMSKeyId: { 'Fn::GetAtt': ['keyFEDD6EC0', 'Arn'], }, }); - expect(stack).toHaveResource('AWS::CloudTrail::Trail', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', { TrailName: 'UnencryptedTrail', - KMSKeyId: ABSENT, + KMSKeyId: Match.absent(), }); }); @@ -235,13 +298,13 @@ describe('cloudtrail', () => { sendToCloudWatchLogs: true, }); - expect(stack).toHaveResource('AWS::CloudTrail::Trail'); - expect(stack).toHaveResource('AWS::S3::Bucket'); - expect(stack).toHaveResource('AWS::S3::BucketPolicy', ExpectedBucketPolicyProperties); - expect(stack).toHaveResource('AWS::Logs::LogGroup'); - expect(stack).toHaveResource('AWS::IAM::Role'); - expect(stack).toHaveResource('AWS::Logs::LogGroup', { RetentionInDays: 365 }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).resourceCountIs('AWS::CloudTrail::Trail', 1); + Template.fromStack(stack).resourceCountIs('AWS::S3::Bucket', 1); + Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', ExpectedBucketPolicyProperties); + Template.fromStack(stack).resourceCountIs('AWS::Logs::LogGroup', 1); + Template.fromStack(stack).resourceCountIs('AWS::IAM::Role', 1); + Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { RetentionInDays: 365 }); + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Version: '2012-10-17', Statement: [{ @@ -255,7 +318,7 @@ describe('cloudtrail', () => { PolicyName: logsRolePolicyName, Roles: [{ Ref: 'MyAmazingCloudTrailLogsRoleF2CCF977' }], }); - const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; + const trail: any = Template.fromStack(stack).toJSON().Resources.MyAmazingCloudTrail54516E8D; expect(trail.DependsOn).toEqual([logsRolePolicyName, logsRoleName, 'MyAmazingCloudTrailS3Policy39C120B0']); }); @@ -266,15 +329,15 @@ describe('cloudtrail', () => { cloudWatchLogsRetention: RetentionDays.ONE_WEEK, }); - expect(stack).toHaveResource('AWS::CloudTrail::Trail'); - expect(stack).toHaveResource('AWS::S3::Bucket'); - expect(stack).toHaveResource('AWS::S3::BucketPolicy', ExpectedBucketPolicyProperties); - expect(stack).toHaveResource('AWS::Logs::LogGroup'); - expect(stack).toHaveResource('AWS::IAM::Role'); - expect(stack).toHaveResource('AWS::Logs::LogGroup', { + Template.fromStack(stack).resourceCountIs('AWS::CloudTrail::Trail', 1); + Template.fromStack(stack).resourceCountIs('AWS::S3::Bucket', 1); + Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', ExpectedBucketPolicyProperties); + Template.fromStack(stack).resourceCountIs('AWS::Logs::LogGroup', 1); + Template.fromStack(stack).resourceCountIs('AWS::IAM::Role', 1); + Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { RetentionInDays: 7, }); - const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; + const trail: any = Template.fromStack(stack).toJSON().Resources.MyAmazingCloudTrail54516E8D; expect(trail.DependsOn).toEqual([logsRolePolicyName, logsRoleName, 'MyAmazingCloudTrailS3Policy39C120B0']); }); @@ -289,19 +352,19 @@ describe('cloudtrail', () => { cloudWatchLogGroup, }); - expect(stack).toHaveResource('AWS::Logs::LogGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { RetentionInDays: 5, }); - expect(stack).toHaveResource('AWS::CloudTrail::Trail', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', { CloudWatchLogsLogGroupArn: stack.resolve(cloudWatchLogGroup.logGroupArn), }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: [{ + Statement: [Match.objectLike({ Resource: stack.resolve(cloudWatchLogGroup.logGroupArn), - }], + })], }, }); }); @@ -313,7 +376,7 @@ describe('cloudtrail', () => { cloudWatchLogsRetention: RetentionDays.ONE_WEEK, }); expect(t.logGroup).toBeUndefined(); - expect(stack).not.toHaveResource('AWS::Logs::LogGroup'); + Template.fromStack(stack).resourceCountIs('AWS::Logs::LogGroup', 0); }); }); @@ -324,7 +387,7 @@ describe('cloudtrail', () => { const cloudTrail = new Trail(stack, 'MyAmazingCloudTrail'); cloudTrail.logAllS3DataEvents(); - expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', { EventSelectors: [ { DataResources: [{ @@ -344,8 +407,8 @@ describe('cloudtrail', () => { }, ], }], - IncludeManagementEvents: ABSENT, - ReadWriteType: ABSENT, + IncludeManagementEvents: Match.absent(), + ReadWriteType: Match.absent(), }, ], }); @@ -362,7 +425,7 @@ describe('cloudtrail', () => { objectPrefix: 'prefix-1/prefix-2', }]); - expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', { EventSelectors: [ { DataResources: [{ @@ -400,7 +463,7 @@ describe('cloudtrail', () => { const stack = getTestStack(); const cloudTrail = new Trail(stack, 'MyAmazingCloudTrail'); cloudTrail.addS3EventSelector([]); - expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', { EventSelectors: [], }); }); @@ -411,7 +474,7 @@ describe('cloudtrail', () => { const cloudTrail = new Trail(stack, 'MyAmazingCloudTrail'); cloudTrail.logAllS3DataEvents({ includeManagementEvents: false, readWriteType: ReadWriteType.READ_ONLY }); - expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', { EventSelectors: [ { DataResources: [{ @@ -443,7 +506,7 @@ describe('cloudtrail', () => { new Trail(stack, 'MyAmazingCloudTrail', { managementEvents: ReadWriteType.WRITE_ONLY }); - expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', { EventSelectors: [ { IncludeManagementEvents: true, @@ -467,7 +530,7 @@ describe('cloudtrail', () => { excludeManagementEventSources: [], }); - expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', { EventSelectors: [ { DataResources: [{ @@ -517,7 +580,7 @@ describe('cloudtrail', () => { const cloudTrail = new Trail(stack, 'MyAmazingCloudTrail'); cloudTrail.addLambdaEventSelector([lambdaFunction]); - expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', { EventSelectors: [ { DataResources: [{ @@ -537,7 +600,7 @@ describe('cloudtrail', () => { const cloudTrail = new Trail(stack, 'MyAmazingCloudTrail'); cloudTrail.logAllLambdaDataEvents(); - expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', { EventSelectors: [ { DataResources: [{ @@ -569,7 +632,7 @@ describe('cloudtrail', () => { managementEvents: ReadWriteType.NONE, }); - expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + Template.fromStack(stack).hasResourceProperties('AWS::CloudTrail::Trail', { EventSelectors: [ { IncludeManagementEvents: false, @@ -596,7 +659,7 @@ describe('cloudtrail', () => { }); // THEN - expect(stack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { EventPattern: { 'detail-type': [ 'AWS API Call via CloudTrail', @@ -612,4 +675,4 @@ describe('cloudtrail', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail.lit.expected.json b/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail.lit.expected.json index 5b90761ade1ff..90c9dd9724771 100644 --- a/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail.lit.expected.json +++ b/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail.lit.expected.json @@ -97,6 +97,40 @@ }, "PolicyDocument": { "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "TrailS30071F172", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "TrailS30071F172", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, { "Action": "s3:GetBucketAcl", "Effect": "Allow", diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index 6bde99b6e2c63..0f16ed23d8b1d 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -83,7 +83,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert-internal": "0.0.0", + "@aws-cdk/assertions": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sqs": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-codebuild/test/codebuild.test.ts b/packages/@aws-cdk/aws-codebuild/test/codebuild.test.ts index 1cc66df6b236f..5a058625649c3 100644 --- a/packages/@aws-cdk/aws-codebuild/test/codebuild.test.ts +++ b/packages/@aws-cdk/aws-codebuild/test/codebuild.test.ts @@ -1,5 +1,4 @@ -import { ABSENT, SynthUtils } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as codecommit from '@aws-cdk/aws-codecommit'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; @@ -17,7 +16,7 @@ describe('default properties', () => { new codebuild.PipelineProject(stack, 'MyProject'); - expect(stack).toMatchTemplate({ + Template.fromStack(stack).templateMatches({ 'Resources': { 'MyProjectRole9BBE5233': { 'Type': 'AWS::IAM::Role', @@ -177,7 +176,7 @@ describe('default properties', () => { source, }); - expect(stack).toMatchTemplate({ + Template.fromStack(stack).templateMatches({ 'Resources': { 'MyRepoF4F48043': { 'Type': 'AWS::CodeCommit::Repository', @@ -361,7 +360,7 @@ describe('default properties', () => { }, }); - expect(stack).toMatchTemplate({ + Template.fromStack(stack).templateMatches({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -572,7 +571,7 @@ describe('default properties', () => { }), }); - expect(stack).toHaveResource('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Source: { Type: 'GITHUB', Location: 'https://github.com/testowner/testrepo.git', @@ -584,7 +583,7 @@ describe('default properties', () => { }, }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Triggers: { Webhook: true, FilterGroups: [ @@ -620,7 +619,7 @@ describe('default properties', () => { }), }); - expect(stack).toHaveResource('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Source: { Type: 'GITHUB_ENTERPRISE', InsecureSsl: true, @@ -630,7 +629,7 @@ describe('default properties', () => { }, }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Triggers: { Webhook: true, FilterGroups: [ @@ -672,7 +671,7 @@ describe('default properties', () => { }), }); - expect(stack).toHaveResource('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Source: { Type: 'BITBUCKET', Location: 'https://bitbucket.org/testowner/testrepo.git', @@ -681,7 +680,7 @@ describe('default properties', () => { }, }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Triggers: { Webhook: true, FilterGroups: [ @@ -710,7 +709,7 @@ describe('default properties', () => { }), }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Triggers: { Webhook: true, BuildType: 'BUILD_BATCH', @@ -725,7 +724,7 @@ describe('default properties', () => { }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [ { @@ -740,7 +739,7 @@ describe('default properties', () => { }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -809,7 +808,7 @@ describe('default properties', () => { securityGroups: [securityGroup], }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'VpcConfig': { 'SecurityGroupIds': [ { @@ -900,7 +899,7 @@ describe('default properties', () => { new codebuild.PipelineProject(stack, 'MyProject'); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { EncryptionKey: 'alias/aws/s3', }); }); @@ -915,7 +914,7 @@ describe('default properties', () => { grantReportGroupPermissions: false, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ {}, // CloudWatch logs @@ -960,7 +959,7 @@ test('using timeout and path in S3 artifacts sets it correctly', () => { timeout: cdk.Duration.minutes(123), }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Artifacts': { 'Path': 'some/path', 'Name': 'some_name', @@ -999,7 +998,7 @@ describe('secondary sources', () => { identifier: 'id', })); - expect(() => SynthUtils.synthesize(stack)).toThrow(/secondary sources/); + expect(() => Template.fromStack(stack)).toThrow(/secondary sources/); }); test('added with an identifer after the Project has been created are rendered in the template', () => { @@ -1018,7 +1017,7 @@ describe('secondary sources', () => { identifier: 'source1', })); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'SecondarySources': [ { 'SourceIdentifier': 'source1', @@ -1047,7 +1046,7 @@ describe('secondary source versions', () => { version: 'someversion', })); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'SecondarySources': [ { 'SourceIdentifier': 'source1', @@ -1079,7 +1078,7 @@ describe('secondary source versions', () => { identifier: 'source1', })); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'SecondarySources': [ { 'SourceIdentifier': 'source1', @@ -1105,7 +1104,7 @@ describe('fileSystemLocations', () => { })], }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'FileSystemLocations': [ { 'Identifier': 'myidentifier2', @@ -1132,7 +1131,7 @@ describe('fileSystemLocations', () => { mountOptions: 'opts', })); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'FileSystemLocations': [ { 'Identifier': 'myidentifier3', @@ -1177,7 +1176,7 @@ describe('secondary artifacts', () => { identifier: 'id', })); - expect(() => SynthUtils.synthesize(stack)).toThrow(/secondary artifacts/); + expect(() => Template.fromStack(stack)).toThrow(/secondary artifacts/); }); test('added with an identifier after the Project has been created are rendered in the template', () => { @@ -1197,7 +1196,7 @@ describe('secondary artifacts', () => { identifier: 'artifact1', })); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'SecondaryArtifacts': [ { 'ArtifactIdentifier': 'artifact1', @@ -1225,7 +1224,7 @@ describe('secondary artifacts', () => { encryption: false, })); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'SecondaryArtifacts': [ { 'ArtifactIdentifier': 'artifact1', @@ -1243,7 +1242,7 @@ describe('artifacts', () => { new codebuild.PipelineProject(stack, 'MyProject'); - expect(stack).toHaveResource('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Source': { 'Type': 'CODEPIPELINE', }, @@ -1283,9 +1282,9 @@ describe('artifacts', () => { }), }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Artifacts': { - 'Name': ABSENT, + 'Name': Match.absent(), 'ArtifactIdentifier': 'artifact1', 'OverrideArtifactName': true, }, @@ -1308,11 +1307,11 @@ describe('artifacts', () => { }), }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Artifacts': { 'ArtifactIdentifier': 'artifact1', 'Name': 'specificname', - 'OverrideArtifactName': ABSENT, + 'OverrideArtifactName': Match.absent(), }, }); }); @@ -1334,7 +1333,7 @@ test('events', () => { project.onStateChange('OnStateChange', { target: { bind: () => ({ arn: 'ARN', id: 'ID' }) } }); project.onBuildStarted('OnBuildStarted', { target: { bind: () => ({ arn: 'ARN', id: 'ID' }) } }); - expect(stack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'aws.codebuild', @@ -1356,7 +1355,7 @@ test('events', () => { 'State': 'ENABLED', }); - expect(stack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'aws.codebuild', @@ -1378,7 +1377,7 @@ test('events', () => { 'State': 'ENABLED', }); - expect(stack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'aws.codebuild', @@ -1397,7 +1396,7 @@ test('events', () => { 'State': 'ENABLED', }); - expect(stack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'aws.codebuild', @@ -1416,7 +1415,7 @@ test('events', () => { 'State': 'ENABLED', }); - expect(stack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'aws.codebuild', @@ -1455,7 +1454,7 @@ test('environment variables can be overridden at the project level', () => { }, }); - expect(stack).toHaveResource('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Source': { 'Type': 'CODEPIPELINE', }, @@ -1554,7 +1553,7 @@ test('fromCodebuildImage', () => { }, }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'Image': 'aws/codebuild/standard:4.0', }, @@ -1571,7 +1570,7 @@ describe('Windows2019 image', () => { }, }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'Type': 'WINDOWS_SERVER_2019_CONTAINER', 'ComputeType': 'BUILD_GENERAL1_MEDIUM', @@ -1591,7 +1590,7 @@ describe('ARM image', () => { }, }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'Type': 'ARM_CONTAINER', 'ComputeType': 'BUILD_GENERAL1_LARGE', @@ -1608,7 +1607,7 @@ describe('ARM image', () => { }, }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'Type': 'ARM_CONTAINER', 'ComputeType': 'BUILD_GENERAL1_SMALL', @@ -1638,7 +1637,7 @@ describe('ARM image', () => { }, }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'Type': 'ARM_CONTAINER', 'ComputeType': 'BUILD_GENERAL1_LARGE', @@ -1867,7 +1866,7 @@ test('enableBatchBuilds()', () => { throw new Error('Expecting return value with role'); } - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { BuildBatchConfig: { ServiceRole: { 'Fn::GetAtt': [ @@ -1878,7 +1877,7 @@ test('enableBatchBuilds()', () => { }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [ { @@ -1893,7 +1892,7 @@ test('enableBatchBuilds()', () => { }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-codebuild/test/linux-gpu-build-image.test.ts b/packages/@aws-cdk/aws-codebuild/test/linux-gpu-build-image.test.ts index d3b0d44dde984..106836acdac21 100644 --- a/packages/@aws-cdk/aws-codebuild/test/linux-gpu-build-image.test.ts +++ b/packages/@aws-cdk/aws-codebuild/test/linux-gpu-build-image.test.ts @@ -1,5 +1,4 @@ -import { arrayWith, objectLike } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as ecr from '@aws-cdk/aws-ecr'; import * as cdk from '@aws-cdk/core'; import * as codebuild from '../lib'; @@ -22,7 +21,7 @@ describe('Linux GPU build image', () => { }, }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { ComputeType: 'BUILD_GENERAL1_LARGE', Image: { @@ -37,9 +36,9 @@ describe('Linux GPU build image', () => { }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith(objectLike({ + Statement: Match.arrayWith([Match.objectLike({ Action: [ 'ecr:BatchCheckLayerAvailability', 'ecr:GetDownloadUrlForLayer', @@ -54,7 +53,7 @@ describe('Linux GPU build image', () => { ':123456789012:repository/my-repo', ]], }, - })), + })]), }, }); }); @@ -78,7 +77,7 @@ describe('Linux GPU build image', () => { }, }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { ComputeType: 'BUILD_GENERAL1_LARGE', Image: { @@ -96,9 +95,9 @@ describe('Linux GPU build image', () => { }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith(objectLike({ + Statement: Match.arrayWith([Match.objectLike({ Action: [ 'ecr:BatchCheckLayerAvailability', 'ecr:GetDownloadUrlForLayer', @@ -116,7 +115,7 @@ describe('Linux GPU build image', () => { { Ref: 'myrepo5DFA62E5' }, ]], }, - })), + })]), }, }); }); @@ -138,7 +137,7 @@ describe('Linux GPU build image', () => { }, }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { ComputeType: 'BUILD_GENERAL1_LARGE', Image: { @@ -154,9 +153,9 @@ describe('Linux GPU build image', () => { }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith(objectLike({ + Statement: Match.arrayWith([Match.objectLike({ Action: [ 'ecr:BatchCheckLayerAvailability', 'ecr:GetDownloadUrlForLayer', @@ -173,7 +172,7 @@ describe('Linux GPU build image', () => { ':repository/test-repo', ]], }, - })), + })]), }, }); }); @@ -195,7 +194,7 @@ describe('Linux GPU build image', () => { }, }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { ComputeType: 'BUILD_GENERAL1_LARGE', Image: { @@ -210,9 +209,9 @@ describe('Linux GPU build image', () => { }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith(objectLike({ + Statement: Match.arrayWith([Match.objectLike({ Action: [ 'ecr:BatchCheckLayerAvailability', 'ecr:GetDownloadUrlForLayer', @@ -227,7 +226,7 @@ describe('Linux GPU build image', () => { ':585695036304:repository/foo/bar/foo/fooo', ]], }, - })), + })]), }, }); }); diff --git a/packages/@aws-cdk/aws-codebuild/test/notification-rule.test.ts b/packages/@aws-cdk/aws-codebuild/test/notification-rule.test.ts index 45d6a65d76ce4..e0dc908185427 100644 --- a/packages/@aws-cdk/aws-codebuild/test/notification-rule.test.ts +++ b/packages/@aws-cdk/aws-codebuild/test/notification-rule.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; import * as codebuild from '../lib'; @@ -24,7 +24,7 @@ test('notifications rule', () => { project.notifyOnBuildFailed('NotifyOnBuildFailed', target); - expect(stack).toHaveResource('AWS::CodeStarNotifications::NotificationRule', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { Name: 'MyCodebuildProjectNotifyOnBuildSucceeded77719592', DetailType: 'FULL', EventTypeIds: [ @@ -46,7 +46,7 @@ test('notifications rule', () => { ], }); - expect(stack).toHaveResource('AWS::CodeStarNotifications::NotificationRule', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { Name: 'MyCodebuildProjectNotifyOnBuildFailedF680E310', DetailType: 'FULL', EventTypeIds: [ diff --git a/packages/@aws-cdk/aws-codebuild/test/project.test.ts b/packages/@aws-cdk/aws-codebuild/test/project.test.ts index bf93885378ac5..5c7a17d7eb40d 100644 --- a/packages/@aws-cdk/aws-codebuild/test/project.test.ts +++ b/packages/@aws-cdk/aws-codebuild/test/project.test.ts @@ -1,5 +1,4 @@ -import { ABSENT, objectLike, ResourcePart, arrayWith } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; @@ -24,7 +23,7 @@ test('can use filename as buildspec', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Source: { BuildSpec: 'hello.yml', }, @@ -41,7 +40,7 @@ test('can use buildspec literal', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Source: { BuildSpec: '{\n "phases": [\n "say hi"\n ]\n}', }, @@ -67,7 +66,7 @@ test('can use yamlbuildspec literal', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Source: { BuildSpec: 'text: text\ndecimal: 10\nlist:\n - say hi\nobj:\n text: text\n decimal: 10\n list:\n - say hi\n', }, @@ -110,7 +109,7 @@ describe('GitHub source', () => { }); // THEN - expect(stack).toHaveResource('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Source: { Type: 'GITHUB', Location: 'https://github.com/testowner/testrepo.git', @@ -134,7 +133,7 @@ describe('GitHub source', () => { }); // THEN - expect(stack).toHaveResource('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { SourceVersion: 'testbranch', }); }); @@ -153,7 +152,7 @@ describe('GitHub source', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Source: { ReportBuildStatus: false, }, @@ -174,7 +173,7 @@ describe('GitHub source', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Triggers: { Webhook: true, }, @@ -207,7 +206,7 @@ describe('GitHub source', () => { }); // THEN - expect(stack).toHaveResource('AWS::CodeBuild::SourceCredential', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::SourceCredential', { 'ServerType': 'GITHUB', 'AuthType': 'PERSONAL_ACCESS_TOKEN', 'Token': 'my-access-token', @@ -229,7 +228,7 @@ describe('GitHub Enterprise source', () => { }); // THEN - expect(stack).toHaveResource('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { SourceVersion: 'testbranch', }); }); @@ -244,7 +243,7 @@ describe('GitHub Enterprise source', () => { }); // THEN - expect(stack).toHaveResource('AWS::CodeBuild::SourceCredential', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::SourceCredential', { 'ServerType': 'GITHUB_ENTERPRISE', 'AuthType': 'PERSONAL_ACCESS_TOKEN', 'Token': 'my-access-token', @@ -267,7 +266,7 @@ describe('BitBucket source', () => { }); // THEN - expect(stack).toHaveResource('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { SourceVersion: 'testbranch', }); }); @@ -283,7 +282,7 @@ describe('BitBucket source', () => { }); // THEN - expect(stack).toHaveResource('AWS::CodeBuild::SourceCredential', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::SourceCredential', { 'ServerType': 'BITBUCKET', 'AuthType': 'BASIC_AUTH', 'Username': 'my-username', @@ -303,10 +302,10 @@ describe('caching', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Cache: { Type: 'NO_CACHE', - Location: ABSENT, + Location: Match.absent(), }, }); }); @@ -327,7 +326,7 @@ describe('caching', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Cache: { Type: 'S3', Location: { @@ -361,7 +360,7 @@ describe('caching', () => { }); // THEN - expect(stack).toHaveResource('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { SourceVersion: 's3version', }); }); @@ -381,7 +380,7 @@ describe('caching', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Cache: { Type: 'LOCAL', Modes: [ @@ -406,10 +405,10 @@ describe('caching', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { Cache: { Type: 'NO_CACHE', - Location: ABSENT, + Location: Match.absent(), }, }); }); @@ -434,18 +433,18 @@ test('if a role is shared between projects in a VPC, the VPC Policy is only atta // - 1 is for `ec2:CreateNetworkInterfacePermission`, deduplicated as they're part of a single policy // - 1 is for `ec2:CreateNetworkInterface`, this is the separate Policy we're deduplicating // We would have found 3 if the deduplication didn't work. - expect(stack).toCountResources('AWS::IAM::Policy', 2); + Template.fromStack(stack).resourceCountIs('AWS::IAM::Policy', 2); // THEN - both Projects have a DependsOn on the same policy - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResource('AWS::CodeBuild::Project', { Properties: { Name: 'P1' }, DependsOn: ['Project1PolicyDocumentF9761562'], - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResource('AWS::CodeBuild::Project', { Properties: { Name: 'P1' }, DependsOn: ['Project1PolicyDocumentF9761562'], - }, ResourcePart.CompleteDefinition); + }); }); test('can use an imported Role for a Project within a VPC', () => { @@ -462,7 +461,7 @@ test('can use an imported Role for a Project within a VPC', () => { vpc, }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { // no need to do any assertions }); }); @@ -484,15 +483,15 @@ test('can use an imported Role with mutable = false for a Project within a VPC', vpc, }); - expect(stack).toCountResources('AWS::IAM::Policy', 0); + Template.fromStack(stack).resourceCountIs('AWS::IAM::Policy', 0); // Check that the CodeBuild project does not have a DependsOn - expect(stack).toHaveResource('AWS::CodeBuild::Project', (res: any) => { + Template.fromStack(stack).hasResource('AWS::CodeBuild::Project', (res: any) => { if (res.DependsOn && res.DependsOn.length > 0) { throw new Error(`CodeBuild project should have no DependsOn, but got: ${JSON.stringify(res, undefined, 2)}`); } return true; - }, ResourcePart.CompleteDefinition); + }); }); test('can use an ImmutableRole for a Project within a VPC', () => { @@ -512,15 +511,15 @@ test('can use an ImmutableRole for a Project within a VPC', () => { vpc, }); - expect(stack).toCountResources('AWS::IAM::Policy', 0); + Template.fromStack(stack).resourceCountIs('AWS::IAM::Policy', 0); // Check that the CodeBuild project does not have a DependsOn - expect(stack).toHaveResource('AWS::CodeBuild::Project', (res: any) => { + Template.fromStack(stack).hasResource('AWS::CodeBuild::Project', (res: any) => { if (res.DependsOn && res.DependsOn.length > 0) { throw new Error(`CodeBuild project should have no DependsOn, but got: ${JSON.stringify(res, undefined, 2)}`); } return true; - }, ResourcePart.CompleteDefinition); + }); }); test('metric method generates a valid CloudWatch metric', () => { @@ -557,7 +556,7 @@ describe('CodeBuild test reports group', () => { }); reportGroup.grantWrite(project); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ {}, @@ -598,8 +597,8 @@ describe('Environment', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { - Environment: objectLike({ + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { + Environment: Match.objectLike({ RegistryCredential: { CredentialProvider: 'SECRETS_MANAGER', Credential: { 'Ref': 'SecretA720EF05' }, @@ -625,8 +624,8 @@ describe('Environment', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { - Environment: objectLike({ + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { + Environment: Match.objectLike({ RegistryCredential: { CredentialProvider: 'SECRETS_MANAGER', Credential: 'MySecretName', @@ -655,8 +654,8 @@ describe('Environment', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { - LogsConfig: objectLike({ + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { + LogsConfig: Match.objectLike({ CloudWatchLogs: { GroupName: 'MyLogGroupName', Status: 'ENABLED', @@ -684,8 +683,8 @@ describe('Environment', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { - LogsConfig: objectLike({ + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { + LogsConfig: Match.objectLike({ CloudWatchLogs: { Status: 'DISABLED', }, @@ -713,8 +712,8 @@ describe('Environment', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { - LogsConfig: objectLike({ + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { + LogsConfig: Match.objectLike({ S3Logs: { Location: 'mybucketname/my-logs', Status: 'ENABLED', @@ -746,8 +745,8 @@ describe('Environment', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { - LogsConfig: objectLike({ + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { + LogsConfig: Match.objectLike({ CloudWatchLogs: { GroupName: 'MyLogGroupName', Status: 'ENABLED', @@ -780,8 +779,8 @@ describe('Environment', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { - Environment: objectLike({ + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { + Environment: Match.objectLike({ Certificate: { 'Fn::Join': ['', [ 'arn:', @@ -818,8 +817,8 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { - Environment: objectLike({ + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { + Environment: Match.objectLike({ EnvironmentVariables: [{ Name: 'ENV_VAR1', Type: 'PARAMETER_STORE', @@ -855,9 +854,9 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith(objectLike({ + 'Statement': Match.arrayWith([Match.objectLike({ 'Action': 'ssm:GetParameters', 'Effect': 'Allow', 'Resource': [{ @@ -900,7 +899,7 @@ describe('EnvironmentVariables', () => { ], ], }], - })), + })]), }, }); }); @@ -928,14 +927,14 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).not.toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', Match.not({ 'PolicyDocument': { - 'Statement': arrayWith(objectLike({ + 'Statement': Match.arrayWith([Match.objectLike({ 'Action': 'ssm:GetParameters', 'Effect': 'Allow', - })), + })]), }, - }); + })); }); }); @@ -955,7 +954,7 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'EnvironmentVariables': [ { @@ -968,9 +967,9 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'secretsmanager:GetSecretValue', 'Effect': 'Allow', 'Resource': { @@ -984,7 +983,7 @@ describe('EnvironmentVariables', () => { ':secret:my-secret-??????', ]], }, - }), + }]), }, }); }); @@ -1004,7 +1003,7 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'EnvironmentVariables': [ { @@ -1017,9 +1016,9 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'secretsmanager:GetSecretValue', 'Effect': 'Allow', 'Resource': { @@ -1033,7 +1032,7 @@ describe('EnvironmentVariables', () => { ':secret:my-secret-??????', ]], }, - }), + }]), }, }); }); @@ -1053,7 +1052,7 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'EnvironmentVariables': [ { @@ -1066,26 +1065,26 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'secretsmanager:GetSecretValue', 'Effect': 'Allow', 'Resource': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:my-secret-123456*', - }), + }]), }, }); // THEN - expect(stack).not.toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', Match.not({ 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'kms:Decrypt', 'Effect': 'Allow', 'Resource': 'arn:aws:kms:us-west-2:123456789012:key/*', - }), + }]), }, - }); + })); }); test('can be provided as a verbatim partial secret ARN', () => { @@ -1103,7 +1102,7 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'EnvironmentVariables': [ { @@ -1116,26 +1115,26 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'secretsmanager:GetSecretValue', 'Effect': 'Allow', 'Resource': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret*', - }), + }]), }, }); // THEN - expect(stack).not.toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', Match.not({ 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'kms:Decrypt', 'Effect': 'Allow', 'Resource': 'arn:aws:kms:us-west-2:123456789012:key/*', - }), + }]), }, - }); + })); }); test("when provided as a verbatim partial secret ARN from another account, adds permission to decrypt keys in the Secret's account", () => { @@ -1156,13 +1155,13 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'kms:Decrypt', 'Effect': 'Allow', 'Resource': 'arn:aws:kms:us-west-2:901234567890:key/*', - }), + }]), }, }); }); @@ -1189,13 +1188,13 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'kms:Decrypt', 'Effect': 'Allow', 'Resource': 'arn:aws:kms:us-west-2:901234567890:key/*', - }), + }]), }, }); }); @@ -1216,7 +1215,7 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'EnvironmentVariables': [ { @@ -1229,13 +1228,13 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'secretsmanager:GetSecretValue', 'Effect': 'Allow', 'Resource': { 'Ref': 'SecretA720EF05' }, - }), + }]), }, }); }); @@ -1260,7 +1259,7 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'EnvironmentVariables': [ { @@ -1278,13 +1277,13 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'secretsmanager:GetSecretValue', 'Effect': 'Allow', 'Resource': { 'Ref': 'SecretA720EF05' }, - }), + }]), }, }); }); @@ -1305,7 +1304,7 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'EnvironmentVariables': [ { @@ -1323,13 +1322,13 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'secretsmanager:GetSecretValue', 'Effect': 'Allow', 'Resource': { 'Ref': 'SecretA720EF05' }, - }), + }]), }, }); }); @@ -1350,7 +1349,7 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'EnvironmentVariables': [ { @@ -1363,9 +1362,9 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'secretsmanager:GetSecretValue', 'Effect': 'Allow', 'Resource': { @@ -1379,7 +1378,7 @@ describe('EnvironmentVariables', () => { ':secret:mysecret-??????', ]], }, - }), + }]), }, }); }); @@ -1401,7 +1400,7 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'EnvironmentVariables': [ { @@ -1414,13 +1413,13 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'secretsmanager:GetSecretValue', 'Effect': 'Allow', 'Resource': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret*', - }), + }]), }, }); }); @@ -1442,7 +1441,7 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'EnvironmentVariables': [ { @@ -1455,13 +1454,13 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'secretsmanager:GetSecretValue', 'Effect': 'Allow', 'Resource': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret-123456*', - }), + }]), }, }); }); @@ -1488,7 +1487,7 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'EnvironmentVariables': [ { @@ -1508,9 +1507,9 @@ describe('EnvironmentVariables', () => { }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'secretsmanager:GetSecretValue', 'Effect': 'Allow', 'Resource': { @@ -1522,13 +1521,13 @@ describe('EnvironmentVariables', () => { ':012345678912:secret:secret-name-??????', ]], }, - }), + }]), }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'kms:Decrypt', 'Effect': 'Allow', 'Resource': { @@ -1540,7 +1539,7 @@ describe('EnvironmentVariables', () => { ':012345678912:key/*', ]], }, - }), + }]), }, }); }); @@ -1567,7 +1566,7 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'EnvironmentVariables': [ { @@ -1587,9 +1586,9 @@ describe('EnvironmentVariables', () => { }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'secretsmanager:GetSecretValue', 'Effect': 'Allow', 'Resource': { @@ -1601,13 +1600,13 @@ describe('EnvironmentVariables', () => { ':012345678912:secret:secret-name*', ]], }, - }), + }]), }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'kms:Decrypt', 'Effect': 'Allow', 'Resource': { @@ -1619,7 +1618,7 @@ describe('EnvironmentVariables', () => { ':012345678912:key/*', ]], }, - }), + }]), }, }); }); @@ -1644,7 +1643,7 @@ describe('EnvironmentVariables', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 'Environment': { 'EnvironmentVariables': [ { @@ -1656,23 +1655,23 @@ describe('EnvironmentVariables', () => { }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'secretsmanager:GetSecretValue', 'Effect': 'Allow', 'Resource': `${secretArn}*`, - }), + }]), }, }); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': arrayWith({ + 'Statement': Match.arrayWith([{ 'Action': 'kms:Decrypt', 'Effect': 'Allow', 'Resource': 'arn:aws:kms:us-west-2:901234567890:key/*', - }), + }]), }, }); }); @@ -1744,7 +1743,7 @@ describe('Timeouts', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { QueuedTimeoutInMinutes: 30, }); }); @@ -1763,7 +1762,7 @@ describe('Timeouts', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { TimeoutInMinutes: 30, }); }); @@ -1784,7 +1783,7 @@ describe('Maximum concurrency', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { ConcurrentBuildLimit: 1, }); }); diff --git a/packages/@aws-cdk/aws-codebuild/test/report-group.test.ts b/packages/@aws-cdk/aws-codebuild/test/report-group.test.ts index 4459ef4672f0c..6e653d891f3fb 100644 --- a/packages/@aws-cdk/aws-codebuild/test/report-group.test.ts +++ b/packages/@aws-cdk/aws-codebuild/test/report-group.test.ts @@ -1,5 +1,4 @@ -import { ABSENT, ResourcePart } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; @@ -15,11 +14,11 @@ describe('Test Reports Groups', () => { new codebuild.ReportGroup(stack, 'ReportGroup'); - expect(stack).toHaveResourceLike('AWS::CodeBuild::ReportGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::ReportGroup', { "Type": "TEST", "ExportConfig": { "ExportConfigType": "NO_EXPORT", - "S3Destination": ABSENT, + "S3Destination": Match.absent(), }, }); }); @@ -31,7 +30,7 @@ describe('Test Reports Groups', () => { reportGroupName: 'my-report-group', }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::ReportGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::ReportGroup', { "Name": 'my-report-group', }); }); @@ -51,7 +50,7 @@ describe('Test Reports Groups', () => { })); expect(reportGroup.reportGroupName).toEqual('my-report-group'); - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { "PolicyDocument": { "Statement": [ { @@ -80,15 +79,15 @@ describe('Test Reports Groups', () => { exportBucket: s3.Bucket.fromBucketName(stack, 'Bucket', 'my-bucket'), }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::ReportGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::ReportGroup', { "Type": "TEST", "ExportConfig": { "ExportConfigType": "S3", "S3Destination": { "Bucket": "my-bucket", - "EncryptionKey": ABSENT, - "EncryptionDisabled": ABSENT, - "Packaging": ABSENT, + "EncryptionKey": Match.absent(), + "EncryptionDisabled": Match.absent(), + "Packaging": Match.absent(), }, }, }); @@ -106,7 +105,7 @@ describe('Test Reports Groups', () => { zipExport: true, }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::ReportGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::ReportGroup', { "Type": "TEST", "ExportConfig": { "ExportConfigType": "S3", @@ -125,10 +124,10 @@ describe('Test Reports Groups', () => { new codebuild.ReportGroup(stack, 'ReportGroup'); - expect(stack).toHaveResourceLike('AWS::CodeBuild::ReportGroup', { + Template.fromStack(stack).hasResource('AWS::CodeBuild::ReportGroup', { "DeletionPolicy": "Retain", "UpdateReplacePolicy": "Retain", - }, ResourcePart.CompleteDefinition); + }); }); test('can be created with RemovalPolicy.DESTROY', () => { @@ -138,9 +137,9 @@ describe('Test Reports Groups', () => { removalPolicy: cdk.RemovalPolicy.DESTROY, }); - expect(stack).toHaveResourceLike('AWS::CodeBuild::ReportGroup', { + Template.fromStack(stack).hasResource('AWS::CodeBuild::ReportGroup', { "DeletionPolicy": "Delete", "UpdateReplacePolicy": "Delete", - }, ResourcePart.CompleteDefinition); + }); }); }); diff --git a/packages/@aws-cdk/aws-codebuild/test/untrusted-code-boundary.test.ts b/packages/@aws-cdk/aws-codebuild/test/untrusted-code-boundary.test.ts index 89933d11a7ab8..af1f4fdfb328f 100644 --- a/packages/@aws-cdk/aws-codebuild/test/untrusted-code-boundary.test.ts +++ b/packages/@aws-cdk/aws-codebuild/test/untrusted-code-boundary.test.ts @@ -1,5 +1,4 @@ -import { arrayWith } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as codebuild from '../lib'; @@ -15,7 +14,7 @@ test('can attach permissions boundary to Project', () => { iam.PermissionsBoundary.of(project).apply(new codebuild.UntrustedCodeBoundaryPolicy(stack, 'Boundary')); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { PermissionsBoundary: { Ref: 'BoundaryEA298153' }, }); }); @@ -38,13 +37,13 @@ test('can add additional statements Boundary', () => { })); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::ManagedPolicy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::ManagedPolicy', { PolicyDocument: { - Statement: arrayWith({ + Statement: Match.arrayWith([{ Effect: 'Allow', Action: 'a:a', Resource: 'b', - }), + }]), }, }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.expected.json index 53614fd854b19..583aecbc300b4 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.expected.json @@ -669,6 +669,40 @@ }, "PolicyDocument": { "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "CloudTrailS310CD22F2", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CloudTrailS310CD22F2", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, { "Action": "s3:GetBucketAcl", "Effect": "Allow", diff --git a/packages/@aws-cdk/aws-ec2/lib/client-vpn-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/client-vpn-endpoint.ts index 3ee8e8956d3d1..f28dd43462838 100644 --- a/packages/@aws-cdk/aws-ec2/lib/client-vpn-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/client-vpn-endpoint.ts @@ -150,6 +150,37 @@ export interface ClientVpnEndpointOptions { * @default true */ readonly authorizeAllUsersToVpcCidr?: boolean; + + /** + * The maximum VPN session duration time. + * + * @default ClientVpnSessionTimeout.TWENTY_FOUR_HOURS + */ + readonly sessionTimeout?: ClientVpnSessionTimeout; + + /** + * Customizable text that will be displayed in a banner on AWS provided clients + * when a VPN session is established. + * + * UTF-8 encoded characters only. Maximum of 1400 characters. + * + * @default - no banner is presented to the client + */ + readonly clientLoginBanner?: string; +} + +/** + * Maximum VPN session duration time + */ +export enum ClientVpnSessionTimeout { + /** 8 hours */ + EIGHT_HOURS = 8, + /** 10 hours */ + TEN_HOURS = 10, + /** 12 hours */ + TWELVE_HOURS = 12, + /** 24 hours */ + TWENTY_FOUR_HOURS = 24, } /** @@ -284,6 +315,12 @@ export class ClientVpnEndpoint extends Resource implements IClientVpnEndpoint { throw new Error('The name of the Lambda function must begin with the `AWSClientVPN-` prefix'); } + if (props.clientLoginBanner + && !Token.isUnresolved(props.clientLoginBanner) + && props.clientLoginBanner.length > 1400) { + throw new Error(`The maximum length for the client login banner is 1400, got ${props.clientLoginBanner.length}`); + } + const logging = props.logging ?? true; const logGroup = logging ? props.logGroup ?? new logs.LogGroup(this, 'LogGroup') @@ -317,6 +354,13 @@ export class ClientVpnEndpoint extends Resource implements IClientVpnEndpoint { transportProtocol: props.transportProtocol, vpcId: props.vpc.vpcId, vpnPort: props.port, + sessionTimeoutHours: props.sessionTimeout, + clientLoginBannerOptions: props.clientLoginBanner + ? { + enabled: true, + bannerText: props.clientLoginBanner, + } + : undefined, }); this.endpointId = endpoint.ref; diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index 5d51459d3b4d2..2eb5260bac1c4 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -693,7 +693,9 @@ "props-physical-name:@aws-cdk/aws-ec2.VpnGatewayProps", "props-physical-name:@aws-cdk/aws-ec2.ClientVpnEndpointProps", "props-physical-name:@aws-cdk/aws-ec2.ClientVpnAuthorizationRuleProps", - "props-physical-name:@aws-cdk/aws-ec2.ClientVpnRouteProps" + "props-physical-name:@aws-cdk/aws-ec2.ClientVpnRouteProps", + "duration-prop-type:@aws-cdk/aws-ec2.ClientVpnEndpointOptions.sessionTimeout", + "duration-prop-type:@aws-cdk/aws-ec2.ClientVpnEndpointProps.sessionTimeout" ] }, "stability": "stable", diff --git a/packages/@aws-cdk/aws-ec2/test/client-vpn-endpoint.test.ts b/packages/@aws-cdk/aws-ec2/test/client-vpn-endpoint.test.ts index 2bf2357f9c32f..2b087d6da1f10 100644 --- a/packages/@aws-cdk/aws-ec2/test/client-vpn-endpoint.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/client-vpn-endpoint.test.ts @@ -242,6 +242,35 @@ test('client vpn endpoint with custom route', () => { }); }); +test('client vpn endpoint with custom session timeout', () => { + vpc.addClientVpnEndpoint('Endpoint', { + cidr: '10.100.0.0/16', + serverCertificateArn: 'server-certificate-arn', + clientCertificateArn: 'client-certificate-arn', + sessionTimeout: ec2.ClientVpnSessionTimeout.TEN_HOURS, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::EC2::ClientVpnEndpoint', { + SessionTimeoutHours: 10, + }); +}); + +test('client vpn endpoint with client login banner', () => { + vpc.addClientVpnEndpoint('Endpoint', { + cidr: '10.100.0.0/16', + serverCertificateArn: 'server-certificate-arn', + clientCertificateArn: 'client-certificate-arn', + clientLoginBanner: 'Welcome!', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::EC2::ClientVpnEndpoint', { + ClientLoginBannerOptions: { + Enabled: true, + BannerText: 'Welcome!', + }, + }); +}); + test('throws with more than 2 dns servers', () => { expect(() => vpc.addClientVpnEndpoint('Endpoint', { cidr: '10.100.0.0/16', diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index 79ea6601f9fff..799f07066b7e7 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/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-eks/test/alb-controller.test.ts b/packages/@aws-cdk/aws-eks/test/alb-controller.test.ts index 7a1720a93f844..66d2d3f0ff1fd 100644 --- a/packages/@aws-cdk/aws-eks/test/alb-controller.test.ts +++ b/packages/@aws-cdk/aws-eks/test/alb-controller.test.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import * as path from 'path'; +import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; -import '@aws-cdk/assert-internal/jest'; import { Cluster, KubernetesVersion, AlbController, AlbControllerVersion, HelmChart } from '../lib'; import { testFixture } from './util'; @@ -40,7 +40,7 @@ test('can configure a custom repository', () => { repository: 'custom', }); - expect(stack).toHaveResource(HelmChart.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(HelmChart.RESOURCE_TYPE, { Values: { 'Fn::Join': [ '', diff --git a/packages/@aws-cdk/aws-eks/test/awsauth.test.ts b/packages/@aws-cdk/aws-eks/test/awsauth.test.ts index 0f6d3cc4b5891..c141965738084 100644 --- a/packages/@aws-cdk/aws-eks/test/awsauth.test.ts +++ b/packages/@aws-cdk/aws-eks/test/awsauth.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import { Cluster, KubernetesManifest, KubernetesVersion } from '../lib'; @@ -58,7 +58,7 @@ describe('aws auth', () => { new AwsAuth(stack, 'AwsAuth', { cluster }); // THEN - expect(stack).toHaveResource(KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesManifest.RESOURCE_TYPE, { Manifest: JSON.stringify([{ apiVersion: 'v1', kind: 'ConfigMap', @@ -85,8 +85,8 @@ describe('aws auth', () => { cluster.awsAuth.addAccount('5566776655'); // THEN - expect(stack).toCountResources(KubernetesManifest.RESOURCE_TYPE, 1); - expect(stack).toHaveResource(KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).resourceCountIs(KubernetesManifest.RESOURCE_TYPE, 1); + Template.fromStack(stack).hasResourceProperties(KubernetesManifest.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -175,7 +175,7 @@ describe('aws auth', () => { cluster.awsAuth.addUserMapping(user, { groups: ['group2'] }); // THEN - expect(stack).toHaveResource(KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesManifest.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -236,7 +236,7 @@ describe('aws auth', () => { cluster.awsAuth.addMastersRole(role); // THEN - expect(stack).toHaveResource(KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesManifest.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', diff --git a/packages/@aws-cdk/aws-eks/test/cluster.test.ts b/packages/@aws-cdk/aws-eks/test/cluster.test.ts index e3c32b1e345c2..77556af1a0da4 100644 --- a/packages/@aws-cdk/aws-eks/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-eks/test/cluster.test.ts @@ -1,7 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; -import '@aws-cdk/assert-internal/jest'; -import { SynthUtils } from '@aws-cdk/assert-internal'; +import { Match, Template } from '@aws-cdk/assertions'; import * as asg from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; @@ -32,7 +31,7 @@ describe('cluster', () => { }, }); - expect(stack).toHaveResource('Custom::AWSCDK-EKS-HelmChart', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-HelmChart', { Chart: 'aws-load-balancer-controller', }); expect(cluster.albController).toBeDefined(); @@ -51,8 +50,9 @@ describe('cluster', () => { const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.ClusterResourceProvider') as cdk.NestedStack; - const template = SynthUtils.toCloudFormation(nested); - expect(template.Resources.OnEventHandler42BEBAE0.Properties.Environment).toEqual({ Variables: { foo: 'bar' } }); + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { + Environment: { Variables: { foo: 'bar' } }, + }); }); test('can specify security group to cluster resource handler', () => { @@ -70,8 +70,11 @@ describe('cluster', () => { const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.ClusterResourceProvider') as cdk.NestedStack; - const template = SynthUtils.toCloudFormation(nested); - expect(template.Resources.OnEventHandler42BEBAE0.Properties.VpcConfig.SecurityGroupIds).toEqual([{ Ref: 'referencetoStackProxyInstanceSG80B79D87GroupId' }]); + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { + VpcConfig: { + SecurityGroupIds: [{ Ref: 'referencetoStackProxyInstanceSG80B79D87GroupId' }], + }, + }); }); test('throws when trying to place cluster handlers in a vpc with no private subnets', () => { @@ -150,7 +153,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('Custom::AWSCDK-EKS-Cluster', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-Cluster', { Config: { resourcesVpcConfig: { subnetIds: { @@ -162,8 +165,6 @@ describe('cluster', () => { }, }, }); - - }); }); @@ -176,8 +177,6 @@ describe('cluster', () => { }); expect(() => cluster.clusterSecurityGroup).toThrow(/"clusterSecurityGroup" is not defined for this imported cluster/); - - }); test('can place cluster handlers in the cluster vpc', () => { @@ -190,10 +189,11 @@ describe('cluster', () => { }); const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.ClusterResourceProvider') as cdk.NestedStack; - const template = SynthUtils.toCloudFormation(nested); + const template = Template.fromStack(nested); + const resources = template.findResources('AWS::Lambda::Function'); function assertFunctionPlacedInVpc(id: string) { - expect(template.Resources[id].Properties.VpcConfig.SubnetIds).toEqual([ + expect(resources[id].Properties.VpcConfig.SubnetIds).toEqual([ { Ref: 'referencetoStackClusterDefaultVpcPrivateSubnet1SubnetA64D1BF0Ref' }, { Ref: 'referencetoStackClusterDefaultVpcPrivateSubnet2Subnet32D85AB8Ref' }, ]); @@ -204,8 +204,6 @@ describe('cluster', () => { assertFunctionPlacedInVpc('ProviderframeworkonEvent83C1D0A7'); assertFunctionPlacedInVpc('ProviderframeworkisComplete26D7B0CB'); assertFunctionPlacedInVpc('ProviderframeworkonTimeout0B47CA38'); - - }); test('can access cluster security group for imported cluster with cluster security group id', () => { @@ -241,13 +239,12 @@ describe('cluster', () => { instanceType: new ec2.InstanceType('t2.medium'), }); - const template = SynthUtils.toCloudFormation(stack); - expect(template.Resources.ClusterselfmanagedLaunchConfigA5B57EF6.Properties.SecurityGroups).toEqual([ - { 'Fn::GetAtt': ['ClusterselfmanagedInstanceSecurityGroup64468C3A', 'GroupId'] }, - { 'Fn::GetAtt': ['Cluster9EE0221C', 'ClusterSecurityGroupId'] }, - ]); - - + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { + SecurityGroups: [ + { 'Fn::GetAtt': ['ClusterselfmanagedInstanceSecurityGroup64468C3A', 'GroupId'] }, + { 'Fn::GetAtt': ['Cluster9EE0221C', 'ClusterSecurityGroupId'] }, + ], + }); }); test('security group of self-managed asg is not tagged with owned', () => { @@ -264,11 +261,10 @@ describe('cluster', () => { instanceType: new ec2.InstanceType('t2.medium'), }); - const template = SynthUtils.toCloudFormation(stack); - // make sure the "kubernetes.io/cluster/: owned" tag isn't here. - expect(template.Resources.ClusterselfmanagedInstanceSecurityGroup64468C3A.Properties.Tags).toEqual([ - { Key: 'Name', Value: 'Stack/Cluster/self-managed' }, - ]); + let template = Template.fromStack(stack); + template.hasResourceProperties('AWS::EC2::SecurityGroup', { + Tags: [{ Key: 'Name', Value: 'Stack/Cluster/self-managed' }], + }); }); test('connect autoscaling group with imported cluster', () => { @@ -296,11 +292,12 @@ describe('cluster', () => { // WHEN importedCluster.connectAutoScalingGroupCapacity(selfManaged, {}); - const template = SynthUtils.toCloudFormation(stack); - expect(template.Resources.selfmanagedLaunchConfigD41289EB.Properties.SecurityGroups).toEqual([ - { 'Fn::GetAtt': ['selfmanagedInstanceSecurityGroupEA6D80C9', 'GroupId'] }, - { 'Fn::GetAtt': ['Cluster9EE0221C', 'ClusterSecurityGroupId'] }, - ]); + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { + SecurityGroups: [ + { 'Fn::GetAtt': ['selfmanagedInstanceSecurityGroupEA6D80C9', 'GroupId'] }, + { 'Fn::GetAtt': ['Cluster9EE0221C', 'ClusterSecurityGroupId'] }, + ], + }); }); test('cluster security group is attached when connecting self-managed nodes', () => { @@ -323,13 +320,12 @@ describe('cluster', () => { // WHEN cluster.connectAutoScalingGroupCapacity(selfManaged, {}); - const template = SynthUtils.toCloudFormation(stack); - expect(template.Resources.selfmanagedLaunchConfigD41289EB.Properties.SecurityGroups).toEqual([ - { 'Fn::GetAtt': ['selfmanagedInstanceSecurityGroupEA6D80C9', 'GroupId'] }, - { 'Fn::GetAtt': ['Cluster9EE0221C', 'ClusterSecurityGroupId'] }, - ]); - - + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', { + SecurityGroups: [ + { 'Fn::GetAtt': ['selfmanagedInstanceSecurityGroupEA6D80C9', 'GroupId'] }, + { 'Fn::GetAtt': ['Cluster9EE0221C', 'ClusterSecurityGroupId'] }, + ], + }); }); test('spot interrupt handler is not added if spotInterruptHandler is false when connecting self-managed nodes', () => { @@ -370,8 +366,6 @@ describe('cluster', () => { const someConstruct = new constructs.Construct(stack, 'SomeConstruct'); expect(() => cluster.addCdk8sChart('chart', someConstruct)).toThrow(/Invalid cdk8s chart. Must contain a \'toJson\' method, but found undefined/); - - }); test('throws when a core construct is added as cdk8s chart', () => { @@ -387,8 +381,6 @@ describe('cluster', () => { const someConstruct = new cdk.Construct(stack, 'SomeConstruct'); expect(() => cluster.addCdk8sChart('chart', someConstruct)).toThrow(/Invalid cdk8s chart. Must contain a \'toJson\' method, but found undefined/); - - }); test('cdk8s chart can be added to cluster', () => { @@ -417,7 +409,7 @@ describe('cluster', () => { cluster.addCdk8sChart('cdk8s-chart', chart); - expect(stack).toHaveResourceLike('Custom::AWSCDK-EKS-KubernetesResource', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-KubernetesResource', { Manifest: { 'Fn::Join': [ '', @@ -431,8 +423,6 @@ describe('cluster', () => { ], }, }); - - }); test('cluster connections include both control plane and cluster security group', () => { @@ -487,8 +477,6 @@ describe('cluster', () => { // make sure we can synth (no circular dependencies between the stacks) app.synth(); - - }); test('can declare a manifest with a token from a different stack than the cluster that depends on the cluster stack', () => { @@ -706,7 +694,7 @@ describe('cluster', () => { new eks.Cluster(stack, 'Cluster', { vpc, defaultCapacity: 0, version: CLUSTER_VERSION, prune: false }); // THEN - expect(stack).toHaveResourceLike('Custom::AWSCDK-EKS-Cluster', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-Cluster', { Config: { roleArn: { 'Fn::GetAtt': ['ClusterRoleFA261979', 'Arn'] }, version: '1.21', @@ -733,7 +721,7 @@ describe('cluster', () => { new eks.Cluster(stack, 'cluster', { version: CLUSTER_VERSION, prune: false }); // THEN - expect(stack).toHaveResource('AWS::EC2::VPC'); + Template.fromStack(stack).hasResourceProperties('AWS::EC2::VPC', Match.anyValue()); }); @@ -748,7 +736,7 @@ describe('cluster', () => { // THEN expect(cluster.defaultNodegroup).toBeDefined(); - expect(stack).toHaveResource('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { InstanceTypes: [ 'm5.large', ], @@ -775,7 +763,7 @@ describe('cluster', () => { // THEN expect(cluster.defaultNodegroup).toBeDefined(); - expect(stack).toHaveResource('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { ScalingConfig: { DesiredSize: 10, MaxSize: 10, @@ -795,8 +783,8 @@ describe('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); }); }); @@ -809,7 +797,7 @@ describe('cluster', () => { new eks.Cluster(stack, 'Cluster', { vpc, defaultCapacity: 0, version: CLUSTER_VERSION, prune: false }); // 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' }, @@ -829,7 +817,7 @@ describe('cluster', () => { new eks.Cluster(stack, 'Cluster', { vpc, defaultCapacity: 0, version: CLUSTER_VERSION, prune: false }); // THEN - expect(stack).toHaveResource('AWS::EC2::Subnet', { + Template.fromStack(stack).hasResourceProperties('AWS::EC2::Subnet', { MapPublicIpOnLaunch: true, Tags: [ { Key: 'aws-cdk:subnet-name', Value: 'Public' }, @@ -857,9 +845,9 @@ describe('cluster', () => { instanceType: new ec2.InstanceType('t2.medium'), }); - const template = SynthUtils.toCloudFormation(stack); - expect(template.Resources.ClusterASG0E4BA723.UpdatePolicy).toEqual({ AutoScalingScheduledAction: { IgnoreUnmodifiedGroupSizeProperties: true } }); - + Template.fromStack(stack).hasResource('AWS::AutoScaling::AutoScalingGroup', { + UpdatePolicy: { AutoScalingScheduledAction: { IgnoreUnmodifiedGroupSizeProperties: true } }, + }); }); test('adding capacity creates an ASG with tags', () => { @@ -878,7 +866,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { Tags: [ { Key: { 'Fn::Join': ['', ['kubernetes.io/cluster/', { Ref: 'Cluster9EE0221C' }]] }, @@ -919,7 +907,7 @@ describe('cluster', () => { // THEN expect(cluster.defaultNodegroup).toBeDefined(); - expect(stack).toHaveResource('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { ScalingConfig: { DesiredSize: 10, MaxSize: 10, @@ -946,7 +934,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::AutoScaling::AutoScalingGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', { Tags: [ { Key: { 'Fn::Join': ['', ['kubernetes.io/cluster/', { Ref: 'Cluster9EE0221C' }]] }, @@ -1000,67 +988,112 @@ describe('cluster', () => { expect(cluster.kubectlProvider).toEqual(kubectlProvider); }); - test('import cluster with existing kubectl provider function should work as expected with resources relying on kubectl getOrCreate', () => { + describe('import cluster with existing kubectl provider function should work as expected with resources relying on kubectl getOrCreate', () => { + test('creates helm chart', () => { + const { stack } = testFixture(); - const { stack } = testFixture(); + const handlerRole = iam.Role.fromRoleArn(stack, 'HandlerRole', 'arn:aws:iam::123456789012:role/lambda-role'); + const kubectlProvider = KubectlProvider.fromKubectlProviderAttributes(stack, 'KubectlProvider', { + functionArn: 'arn:aws:lambda:us-east-2:123456789012:function:my-function:1', + kubectlRoleArn: 'arn:aws:iam::123456789012:role/kubectl-role', + handlerRole: handlerRole, + }); - const handlerRole = iam.Role.fromRoleArn(stack, 'HandlerRole', 'arn:aws:iam::123456789012:role/lambda-role'); - const kubectlProvider = KubectlProvider.fromKubectlProviderAttributes(stack, 'KubectlProvider', { - functionArn: 'arn:aws:lambda:us-east-2:123456789012:function:my-function:1', - kubectlRoleArn: 'arn:aws:iam::123456789012:role/kubectl-role', - handlerRole: handlerRole, - }); + const cluster = eks.Cluster.fromClusterAttributes(stack, 'Cluster', { + clusterName: 'cluster', + kubectlProvider: kubectlProvider, + }); - const cluster = eks.Cluster.fromClusterAttributes(stack, 'Cluster', { - clusterName: 'cluster', - kubectlProvider: kubectlProvider, - }); + new eks.HelmChart(stack, 'Chart', { + cluster: cluster, + chart: 'chart', + }); - new eks.HelmChart(stack, 'Chart', { - cluster: cluster, - chart: 'chart', + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-HelmChart', { + ServiceToken: kubectlProvider.serviceToken, + RoleArn: kubectlProvider.roleArn, + }); }); - expect(stack).toHaveResourceLike('Custom::AWSCDK-EKS-HelmChart', { - ServiceToken: kubectlProvider.serviceToken, - RoleArn: kubectlProvider.roleArn, - }); + test('creates Kubernetes patch', () => { + const { stack } = testFixture(); - new eks.KubernetesPatch(stack, 'Patch', { - cluster: cluster, - applyPatch: {}, - restorePatch: {}, - resourceName: 'PatchResource', - }); + const handlerRole = iam.Role.fromRoleArn(stack, 'HandlerRole', 'arn:aws:iam::123456789012:role/lambda-role'); + const kubectlProvider = KubectlProvider.fromKubectlProviderAttributes(stack, 'KubectlProvider', { + functionArn: 'arn:aws:lambda:us-east-2:123456789012:function:my-function:1', + kubectlRoleArn: 'arn:aws:iam::123456789012:role/kubectl-role', + handlerRole: handlerRole, + }); - expect(stack).toHaveResourceLike('Custom::AWSCDK-EKS-KubernetesPatch', { - ServiceToken: kubectlProvider.serviceToken, - RoleArn: kubectlProvider.roleArn, - }); + const cluster = eks.Cluster.fromClusterAttributes(stack, 'Cluster', { + clusterName: 'cluster', + kubectlProvider: kubectlProvider, + }); - new eks.KubernetesManifest(stack, 'Manifest', { - cluster: cluster, - manifest: [], - }); + new eks.HelmChart(stack, 'Chart', { + cluster: cluster, + chart: 'chart', + }); - expect(stack).toHaveResourceLike('Custom::AWSCDK-EKS-KubernetesResource', { - ServiceToken: kubectlProvider.serviceToken, - RoleArn: kubectlProvider.roleArn, - }); + new eks.KubernetesPatch(stack, 'Patch', { + cluster: cluster, + applyPatch: {}, + restorePatch: {}, + resourceName: 'PatchResource', + }); - new eks.KubernetesObjectValue(stack, 'ObjectValue', { - cluster: cluster, - jsonPath: '', - objectName: 'name', - objectType: 'type', + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-KubernetesPatch', { + ServiceToken: kubectlProvider.serviceToken, + RoleArn: kubectlProvider.roleArn, + }); }); - expect(stack).toHaveResourceLike('Custom::AWSCDK-EKS-KubernetesObjectValue', { - ServiceToken: kubectlProvider.serviceToken, - RoleArn: kubectlProvider.roleArn, - }); + test('creates Kubernetes object value', () => { + const { stack } = testFixture(); + + const handlerRole = iam.Role.fromRoleArn(stack, 'HandlerRole', 'arn:aws:iam::123456789012:role/lambda-role'); + const kubectlProvider = KubectlProvider.fromKubectlProviderAttributes(stack, 'KubectlProvider', { + functionArn: 'arn:aws:lambda:us-east-2:123456789012:function:my-function:1', + kubectlRoleArn: 'arn:aws:iam::123456789012:role/kubectl-role', + handlerRole: handlerRole, + }); + + const cluster = eks.Cluster.fromClusterAttributes(stack, 'Cluster', { + clusterName: 'cluster', + kubectlProvider: kubectlProvider, + }); + + new eks.HelmChart(stack, 'Chart', { + cluster: cluster, + chart: 'chart', + }); + + new eks.KubernetesPatch(stack, 'Patch', { + cluster: cluster, + applyPatch: {}, + restorePatch: {}, + resourceName: 'PatchResource', + }); + + new eks.KubernetesManifest(stack, 'Manifest', { + cluster: cluster, + manifest: [], + }); + + new eks.KubernetesObjectValue(stack, 'ObjectValue', { + cluster: cluster, + jsonPath: '', + objectName: 'name', + objectType: 'type', + }); - expect(cluster.kubectlProvider).not.toBeInstanceOf(eks.KubectlProvider); + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-KubernetesObjectValue', { + ServiceToken: kubectlProvider.serviceToken, + RoleArn: kubectlProvider.roleArn, + }); + + expect(cluster.kubectlProvider).not.toBeInstanceOf(eks.KubectlProvider); + }); }); test('import cluster with new kubectl private subnets', () => { @@ -1111,7 +1144,7 @@ describe('cluster', () => { new cdk.CfnOutput(stack2, 'ClusterARN', { value: imported.clusterArn }); // THEN - expect(stack2).toMatchTemplate({ + Template.fromStack(stack2).templateMatches({ Outputs: { ClusterARN: { Value: { @@ -1154,7 +1187,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource(eks.KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesManifest.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -1197,11 +1230,11 @@ describe('cluster', () => { cluster.addManifest('manifest2', { bar: 123 }, { boor: [1, 2, 3] }); // THEN - expect(stack).toHaveResource(eks.KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesManifest.RESOURCE_TYPE, { Manifest: '[{"foo":123}]', }); - expect(stack).toHaveResource(eks.KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesManifest.RESOURCE_TYPE, { Manifest: '[{"bar":123},{"boor":[1,2,3]}]', }); @@ -1224,7 +1257,7 @@ describe('cluster', () => { app.synth(); // no cyclic dependency (see https://github.com/aws/aws-cdk/issues/7231) // expect a single resource in the 2nd stack - expect(stack2).toMatchTemplate({ + Template.fromStack(stack2).templateMatches({ Resources: { myresource49C6D325: { Type: 'Custom::AWSCDK-EKS-KubernetesResource', @@ -1261,7 +1294,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource(eks.KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesManifest.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -1313,7 +1346,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource(eks.KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesManifest.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -1531,7 +1564,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { Release: 'stackclusterchartspotinterrupthandlerdec62e07', Chart: 'aws-node-termination-handler', Values: '{\"nodeSelector\":{\"lifecycle\":\"Ec2Spot\"}}', @@ -1575,7 +1608,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toCountResources(eks.HelmChart.RESOURCE_TYPE, 1); + Template.fromStack(stack).resourceCountIs(eks.HelmChart.RESOURCE_TYPE, 1); }); @@ -1653,7 +1686,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { AmiType: 'AL2_ARM_64', }); @@ -1674,7 +1707,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { AmiType: 'AL2_ARM_64', }); @@ -1695,7 +1728,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { AmiType: 'AL2_ARM_64', }); @@ -1801,7 +1834,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('Custom::AWSCDK-EKS-Cluster', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-Cluster', { Config: { name: 'my-cluster-name', roleArn: { 'Fn::GetAtt': ['MyClusterRoleBA20FE72', 'Arn'] }, @@ -1823,7 +1856,7 @@ describe('cluster', () => { }); // role can be assumed by 3 lambda handlers (2 for the cluster resource and 1 for the kubernetes resource) - expect(stack).toHaveResource('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [ { @@ -1844,7 +1877,7 @@ describe('cluster', () => { }); // policy allows creation role to pass the cluster role and to interact with the cluster (given we know the explicit cluster name) - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -1963,7 +1996,7 @@ describe('cluster', () => { new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION, prune: false }); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2046,7 +2079,7 @@ describe('cluster', () => { // THEN const providerStack = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - expect(providerStack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(providerStack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2089,7 +2122,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('Custom::AWSCDK-EKS-KubernetesPatch', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-KubernetesPatch', { ResourceName: 'deployment/coredns', ResourceNamespace: 'kube-system', ApplyPatchJson: '{"spec":{"template":{"metadata":{"annotations":{"eks.amazonaws.com/compute-type":"fargate"}}}}}', @@ -2116,7 +2149,7 @@ describe('cluster', () => { // THEN expect(provider).toEqual(cluster.openIdConnectProvider); - expect(stack).toHaveResource('Custom::AWSCDKOpenIdConnectProvider', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDKOpenIdConnectProvider', { ServiceToken: { 'Fn::GetAtt': [ 'CustomAWSCDKOpenIdConnectProviderCustomResourceProviderHandlerF2C543E0', @@ -2152,7 +2185,7 @@ describe('cluster', () => { const sanitized = YAML.parse(fileContents); // THEN - expect(stack).toHaveResource(eks.KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesManifest.RESOURCE_TYPE, { Manifest: JSON.stringify([sanitized]), }); @@ -2219,7 +2252,7 @@ describe('cluster', () => { // THEN const providerStack = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - expect(providerStack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(providerStack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -2260,11 +2293,11 @@ describe('cluster', () => { // the kubectl provider is inside a nested stack. const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - const template = SynthUtils.toCloudFormation(nested); - expect(template.Resources.ProviderframeworkonEvent83C1D0A7.Properties.VpcConfig.SecurityGroupIds).toEqual( - [{ Ref: 'referencetoStackCluster18DFEAC17ClusterSecurityGroupId' }]); - - + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { + VpcConfig: { + SecurityGroupIds: [{ Ref: 'referencetoStackCluster18DFEAC17ClusterSecurityGroupId' }], + }, + }); }); test('kubectl provider passes environment to lambda', () => { @@ -2293,7 +2326,7 @@ describe('cluster', () => { // the kubectl provider is inside a nested stack. const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - expect(nested).toHaveResource('AWS::Lambda::Function', { + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { Environment: { Variables: { Foo: 'Bar', @@ -2332,7 +2365,7 @@ describe('cluster', () => { // the kubectl provider is inside a nested stack. const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - expect(nested).toHaveResourceLike('AWS::Lambda::Function', { + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { Role: { Ref: 'referencetoStackKubectlIamRole02F8947EArn', }, @@ -2362,12 +2395,12 @@ describe('cluster', () => { }); const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - const template = SynthUtils.toCloudFormation(nested); // we don't attach vpc config in case endpoint is public only, regardless of whether // the vpc has private subnets or not. - expect(template.Resources.Handler886CB40B.Properties.VpcConfig).toEqual(undefined); - + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { + VpcConfig: Match.absent(), + }); }); @@ -2382,13 +2415,12 @@ describe('cluster', () => { }); const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - const template = SynthUtils.toCloudFormation(nested); // we don't attach vpc config in case endpoint is public only, regardless of whether // the vpc has private subnets or not. - expect(template.Resources.Handler886CB40B.Properties.VpcConfig).toEqual(undefined); - - + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { + VpcConfig: Match.absent(), + }); }); test('private without private subnets', () => { @@ -2417,13 +2449,10 @@ describe('cluster', () => { }); const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - const template = SynthUtils.toCloudFormation(nested); - - // handler should have vpc config - expect(template.Resources.Handler886CB40B.Properties.VpcConfig.SubnetIds.length).not.toEqual(0); - expect(template.Resources.Handler886CB40B.Properties.VpcConfig.SecurityGroupIds.length).not.toEqual(0); - + const functions = Template.fromStack(nested).findResources('AWS::Lambda::Function'); + expect(functions.Handler886CB40B.Properties.VpcConfig.SubnetIds.length).not.toEqual(0); + expect(functions.Handler886CB40B.Properties.VpcConfig.SecurityGroupIds.length).not.toEqual(0); }); test('private and non restricted public without private subnets', () => { @@ -2437,12 +2466,12 @@ describe('cluster', () => { }); const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - const template = SynthUtils.toCloudFormation(nested); // we don't have private subnets, but we don't need them since public access // is not restricted. - expect(template.Resources.Handler886CB40B.Properties.VpcConfig).toEqual(undefined); - + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { + VpcConfig: Match.absent(), + }); }); @@ -2456,13 +2485,11 @@ describe('cluster', () => { }); const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - const template = SynthUtils.toCloudFormation(nested); // we have private subnets so we should use them. - expect(template.Resources.Handler886CB40B.Properties.VpcConfig.SubnetIds.length).not.toEqual(0); - expect(template.Resources.Handler886CB40B.Properties.VpcConfig.SecurityGroupIds.length).not.toEqual(0); - - + const functions = Template.fromStack(nested).findResources('AWS::Lambda::Function'); + expect(functions.Handler886CB40B.Properties.VpcConfig.SubnetIds.length).not.toEqual(0); + expect(functions.Handler886CB40B.Properties.VpcConfig.SecurityGroupIds.length).not.toEqual(0); }); test('private and restricted public without private subnets', () => { @@ -2490,12 +2517,11 @@ describe('cluster', () => { }); const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - const template = SynthUtils.toCloudFormation(nested); // we have private subnets so we should use them. - expect(template.Resources.Handler886CB40B.Properties.VpcConfig.SubnetIds.length).not.toEqual(0); - expect(template.Resources.Handler886CB40B.Properties.VpcConfig.SecurityGroupIds.length).not.toEqual(0); - + const functions = Template.fromStack(nested).findResources('AWS::Lambda::Function'); + expect(functions.Handler886CB40B.Properties.VpcConfig.SubnetIds.length).not.toEqual(0); + expect(functions.Handler886CB40B.Properties.VpcConfig.SecurityGroupIds.length).not.toEqual(0); }); @@ -2552,13 +2578,9 @@ describe('cluster', () => { }); const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - const template = SynthUtils.toCloudFormation(nested); - - expect(template.Resources.Handler886CB40B.Properties.VpcConfig.SubnetIds).toEqual([ - 'subnet-private-in-us-east-1a', - ]); - - + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { + VpcConfig: { SubnetIds: ['subnet-private-in-us-east-1a'] }, + }); }); test('private endpoint access selects only private subnets from looked up vpc with concrete subnet selection', () => { @@ -2620,13 +2642,9 @@ describe('cluster', () => { }); const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - const template = SynthUtils.toCloudFormation(nested); - - expect(template.Resources.Handler886CB40B.Properties.VpcConfig.SubnetIds).toEqual([ - 'subnet-private-in-us-east-1a', - ]); - - + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { + VpcConfig: { SubnetIds: ['subnet-private-in-us-east-1a'] }, + }); }); test('private endpoint access selects only private subnets from managed vpc with concrete subnet selection', () => { @@ -2650,14 +2668,14 @@ describe('cluster', () => { }); const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - const template = SynthUtils.toCloudFormation(nested); - - expect(template.Resources.Handler886CB40B.Properties.VpcConfig.SubnetIds).toEqual([ - { Ref: 'referencetoStackVpcPrivateSubnet1Subnet8E6A14CBRef' }, - 'subnet-unknown', - ]); - - + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { + VpcConfig: { + SubnetIds: [ + { Ref: 'referencetoStackVpcPrivateSubnet1Subnet8E6A14CBRef' }, + 'subnet-unknown', + ], + }, + }); }); test('private endpoint access considers specific subnet selection', () => { @@ -2676,13 +2694,9 @@ describe('cluster', () => { }); const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - const template = SynthUtils.toCloudFormation(nested); - - expect(template.Resources.Handler886CB40B.Properties.VpcConfig.SubnetIds).toEqual([ - 'subnet1', - ]); - - + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { + VpcConfig: { SubnetIds: ['subnet1'] }, + }); }); test('can configure private endpoint access', () => { @@ -2737,7 +2751,7 @@ describe('cluster', () => { // the kubectl provider is inside a nested stack. const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - expect(nested).toHaveResource('AWS::Lambda::Function', { + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { VpcConfig: { SecurityGroupIds: [ { @@ -2803,10 +2817,8 @@ describe('cluster', () => { // the kubectl provider is inside a nested stack. const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - const template = SynthUtils.toCloudFormation(nested); - expect(16).toEqual(template.Resources.Handler886CB40B.Properties.VpcConfig.SubnetIds.length); - - + const functions = Template.fromStack(nested).findResources('AWS::Lambda::Function'); + expect(functions.Handler886CB40B.Properties.VpcConfig.SubnetIds.length).toEqual(16); }); test('kubectl provider considers vpc subnet selection', () => { @@ -2855,7 +2867,7 @@ describe('cluster', () => { // the kubectl provider is inside a nested stack. const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - expect(nested).toHaveResource('AWS::Lambda::Function', { + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { VpcConfig: { SecurityGroupIds: [ { @@ -2937,10 +2949,11 @@ describe('cluster', () => { const expectedKubernetesGetId = 'Cluster1myserviceLoadBalancerAddress198CCB03'; - const template = SynthUtils.toCloudFormation(stack); + let template = Template.fromStack(stack); + const resources = template.findResources('Custom::AWSCDK-EKS-KubernetesObjectValue'); // make sure the custom resource is created correctly - expect(template.Resources[expectedKubernetesGetId].Properties).toEqual({ + expect(resources[expectedKubernetesGetId].Properties).toEqual({ ServiceToken: { 'Fn::GetAtt': [ 'awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B', @@ -2964,8 +2977,9 @@ describe('cluster', () => { }); // make sure the attribute points to the expected custom resource and extracts the correct attribute - expect(template.Outputs.LoadBalancerAddress.Value).toEqual({ 'Fn::GetAtt': [expectedKubernetesGetId, 'Value'] }); - + template.hasOutput('LoadBalancerAddress', { + Value: { 'Fn::GetAtt': [expectedKubernetesGetId, 'Value'] }, + }); }); test('custom kubectl layer can be provided', () => { @@ -2982,7 +2996,7 @@ describe('cluster', () => { // THEN const providerStack = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - expect(providerStack).toHaveResource('AWS::Lambda::Function', { + Template.fromStack(providerStack).hasResourceProperties('AWS::Lambda::Function', { Layers: ['arn:of:layer'], }); @@ -3002,7 +3016,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('Custom::AWSCDK-EKS-Cluster', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-Cluster', { Config: { encryptionConfig: [{ provider: { @@ -3070,7 +3084,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('Custom::AWSCDK-EKS-Cluster', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-Cluster', { Config: { kubernetesNetworkConfig: { serviceIpv4Cidr: customCidr, diff --git a/packages/@aws-cdk/aws-eks/test/fargate.test.ts b/packages/@aws-cdk/aws-eks/test/fargate.test.ts index 776da61d1561a..ddd195c7574d4 100644 --- a/packages/@aws-cdk/aws-eks/test/fargate.test.ts +++ b/packages/@aws-cdk/aws-eks/test/fargate.test.ts @@ -1,5 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; -import { ResourcePart } from '@aws-cdk/assert-internal'; +import { Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; @@ -21,7 +20,7 @@ describe('fargate', () => { }); // THEN - expect(stack).toHaveResource('Custom::AWSCDK-EKS-FargateProfile', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-FargateProfile', { Config: { clusterName: { Ref: 'MyCluster8AD82BF8' }, podExecutionRoleArn: { 'Fn::GetAtt': ['MyClusterfargateprofileMyProfilePodExecutionRole4795C054', 'Arn'] }, @@ -43,7 +42,7 @@ describe('fargate', () => { }); // THEN - expect(stack).toHaveResource('Custom::AWSCDK-EKS-FargateProfile', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-FargateProfile', { Config: { clusterName: { Ref: 'MyCluster8AD82BF8' }, podExecutionRoleArn: { 'Fn::GetAtt': ['MyClusterfargateprofileMyProfilePodExecutionRole4795C054', 'Arn'] }, @@ -67,7 +66,7 @@ describe('fargate', () => { }); // THEN - expect(stack).toHaveResource('Custom::AWSCDK-EKS-FargateProfile', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-FargateProfile', { Config: { clusterName: { Ref: 'MyCluster8AD82BF8' }, podExecutionRoleArn: { 'Fn::GetAtt': ['MyRoleF48FFE04', 'Arn'] }, @@ -91,7 +90,7 @@ describe('fargate', () => { Tags.of(cluster).add('propTag', '123'); // THEN - expect(stack).toHaveResource('Custom::AWSCDK-EKS-FargateProfile', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-FargateProfile', { Config: { selectors: [{ namespace: 'default' }], clusterName: { Ref: 'MyCluster8AD82BF8' }, @@ -122,7 +121,7 @@ describe('fargate', () => { }); // THEN - expect(stack).toHaveResource('Custom::AWSCDK-EKS-FargateProfile', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-FargateProfile', { Config: { clusterName: { Ref: 'MyCluster8AD82BF8' }, podExecutionRoleArn: { 'Fn::GetAtt': ['MyClusterfargateprofileMyProfilePodExecutionRole4795C054', 'Arn'] }, @@ -161,7 +160,7 @@ describe('fargate', () => { new eks.FargateCluster(stack, 'FargateCluster', { version: CLUSTER_VERSION }); // THEN - expect(stack).toHaveResource('Custom::AWSCDK-EKS-KubernetesPatch', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-KubernetesPatch', { ResourceName: 'deployment/coredns', ResourceNamespace: 'kube-system', ApplyPatchJson: '{"spec":{"template":{"metadata":{"annotations":{"eks.amazonaws.com/compute-type":"fargate"}}}}}', @@ -171,7 +170,7 @@ describe('fargate', () => { }, }); - expect(stack).toHaveResource('Custom::AWSCDK-EKS-FargateProfile', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-FargateProfile', { Config: { clusterName: { Ref: 'FargateCluster019F03E8', @@ -204,7 +203,7 @@ describe('fargate', () => { }); // THEN - expect(stack).toHaveResource('Custom::AWSCDK-EKS-FargateProfile', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-FargateProfile', { Config: { clusterName: { Ref: 'FargateCluster019F03E8', @@ -238,7 +237,7 @@ describe('fargate', () => { }); // THEN - expect(stack).toHaveResource('Custom::AWSCDK-EKS-FargateProfile', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-FargateProfile', { Config: { clusterName: { Ref: 'FargateCluster019F03E8', @@ -272,14 +271,14 @@ describe('fargate', () => { }); // THEN - expect(stack).toHaveResource('Custom::AWSCDK-EKS-FargateProfile', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-FargateProfile', { Config: { clusterName: { Ref: 'MyCluster8AD82BF8' }, podExecutionRoleArn: { 'Fn::GetAtt': ['MyClusterfargateprofileMyProfile1PodExecutionRole794E9E37', 'Arn'] }, selectors: [{ namespace: 'namespace1' }], }, }); - expect(stack).toHaveResource('Custom::AWSCDK-EKS-FargateProfile', { + Template.fromStack(stack).hasResource('Custom::AWSCDK-EKS-FargateProfile', { Properties: { ServiceToken: { 'Fn::GetAtt': [ @@ -298,7 +297,7 @@ describe('fargate', () => { 'MyClusterfargateprofileMyProfile1PodExecutionRole794E9E37', 'MyClusterfargateprofileMyProfile1879D501A', ], - }, ResourcePart.CompleteDefinition); + }); }); @@ -311,7 +310,7 @@ describe('fargate', () => { new eks.FargateCluster(stack, 'FargateCluster', { version: CLUSTER_VERSION }); // THEN - expect(stack).toHaveResource('Custom::AWSCDK-EKS-KubernetesResource', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-KubernetesResource', { Manifest: { 'Fn::Join': [ '', @@ -353,7 +352,7 @@ describe('fargate', () => { new eks.FargateCluster(stack, 'FargateCluster', { version: CLUSTER_VERSION }); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -442,7 +441,7 @@ describe('fargate', () => { }); // THEN - expect(stack).toHaveResourceLike('Custom::AWSCDK-EKS-Cluster', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-Cluster', { Config: { encryptionConfig: [{ provider: { diff --git a/packages/@aws-cdk/aws-eks/test/helm-chart.test.ts b/packages/@aws-cdk/aws-eks/test/helm-chart.test.ts index cfc7961f5e5e5..5f0c7536a0872 100644 --- a/packages/@aws-cdk/aws-eks/test/helm-chart.test.ts +++ b/packages/@aws-cdk/aws-eks/test/helm-chart.test.ts @@ -1,5 +1,5 @@ -import '@aws-cdk/assert-internal/jest'; import * as path from 'path'; +import { Template } from '@aws-cdk/assertions'; import { Asset } from '@aws-cdk/aws-s3-assets'; import { Duration } from '@aws-cdk/core'; import * as eks from '../lib'; @@ -17,7 +17,7 @@ describe('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', () => { // GIVEN @@ -27,7 +27,7 @@ describe('helm chart', () => { new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart' }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { Release: 'stackmychartff398361', }); }); @@ -92,7 +92,7 @@ describe('helm chart', () => { new eks.HelmChart(stack, 'MyChart', { cluster, chartAsset }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { ChartAssetURL: { 'Fn::Join': [ '', @@ -144,7 +144,7 @@ describe('helm chart', () => { }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { Release: 'hismostprobablylongerthanfiftythreecharacterscaf15d09', }); }); @@ -156,7 +156,7 @@ describe('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}' }); }); test('should support create namespaces by default', () => { // GIVEN @@ -166,7 +166,7 @@ describe('helm chart', () => { new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart' }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { CreateNamespace: true }); + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { CreateNamespace: true }); }); test('should support create namespaces when explicitly specified', () => { // GIVEN @@ -176,7 +176,7 @@ describe('helm chart', () => { new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart', createNamespace: true }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { CreateNamespace: true }); + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { CreateNamespace: true }); }); test('should not create namespaces when disabled', () => { // GIVEN @@ -186,7 +186,8 @@ describe('helm chart', () => { new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart', createNamespace: false }); // THEN - expect(stack).not.toHaveResource(eks.HelmChart.RESOURCE_TYPE, { CreateNamespace: true }); + const charts = Template.fromStack(stack).findResources(eks.HelmChart.RESOURCE_TYPE, { CreateNamespace: true }); + expect(Object.keys(charts).length).toEqual(0); }); test('should support waiting until everything is completed before marking release as successful', () => { // GIVEN @@ -196,7 +197,7 @@ describe('helm chart', () => { new eks.HelmChart(stack, 'MyWaitingChart', { cluster, chart: 'chart', wait: true }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { Wait: true }); + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { Wait: true }); }); test('should default to not waiting before marking release as successful', () => { // GIVEN @@ -206,7 +207,9 @@ describe('helm chart', () => { new eks.HelmChart(stack, 'MyWaitingChart', { cluster, chart: 'chart' }); // THEN - expect(stack).not.toHaveResource(eks.HelmChart.RESOURCE_TYPE, { Wait: true }); + const charts = Template.fromStack(stack).findResources(eks.HelmChart.RESOURCE_TYPE, { Wait: true }); + expect(Object.keys(charts).length).toEqual(0); + }); test('should enable waiting when specified', () => { // GIVEN @@ -216,7 +219,7 @@ describe('helm chart', () => { new eks.HelmChart(stack, 'MyWaitingChart', { cluster, chart: 'chart', wait: true }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { Wait: true }); + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { Wait: true }); }); test('should disable waiting when specified as false', () => { // GIVEN @@ -226,7 +229,8 @@ describe('helm chart', () => { new eks.HelmChart(stack, 'MyWaitingChart', { cluster, chart: 'chart', wait: false }); // THEN - expect(stack).not.toHaveResource(eks.HelmChart.RESOURCE_TYPE, { Wait: true }); + const charts = Template.fromStack(stack).findResources(eks.HelmChart.RESOURCE_TYPE, { Wait: true }); + expect(Object.keys(charts).length).toEqual(0); }); test('should timeout only after 10 minutes', () => { @@ -241,7 +245,7 @@ describe('helm chart', () => { }); // THEN - expect(stack).toHaveResource(eks.HelmChart.RESOURCE_TYPE, { Timeout: '600s' }); + Template.fromStack(stack).hasResourceProperties(eks.HelmChart.RESOURCE_TYPE, { Timeout: '600s' }); }); }); }); diff --git a/packages/@aws-cdk/aws-eks/test/k8s-manifest.test.ts b/packages/@aws-cdk/aws-eks/test/k8s-manifest.test.ts index 9b0295c23efff..34b2a47cc6d14 100644 --- a/packages/@aws-cdk/aws-eks/test/k8s-manifest.test.ts +++ b/packages/@aws-cdk/aws-eks/test/k8s-manifest.test.ts @@ -1,5 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; -import { SynthUtils } from '@aws-cdk/assert-internal'; +import { Template } from '@aws-cdk/assertions'; import { CfnResource, Stack } from '@aws-cdk/core'; import { Cluster, KubernetesManifest, KubernetesVersion, HelmChart } from '../lib'; import { testFixtureNoVpc, testFixtureCluster } from './util'; @@ -72,7 +71,7 @@ describe('k8s manifest', () => { manifest, }); - expect(stack).toHaveResource(KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesManifest.RESOURCE_TYPE, { Manifest: JSON.stringify(manifest), }); @@ -91,13 +90,13 @@ describe('k8s manifest', () => { cluster.addHelmChart('helm', { chart: 'hello-world' }); // THEN - expect(stack).toHaveResource(KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesManifest.RESOURCE_TYPE, { Manifest: '[{"bar":2334}]', ClusterName: 'my-cluster-name', RoleArn: 'arn:aws:iam::1111111:role/iam-role-that-has-masters-access', }); - expect(stack).toHaveResource(HelmChart.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(HelmChart.RESOURCE_TYPE, { ClusterName: 'my-cluster-name', RoleArn: 'arn:aws:iam::1111111:role/iam-role-that-has-masters-access', Release: 'myclustercharthelm78d2c26a', @@ -140,7 +139,7 @@ describe('k8s manifest', () => { }); // THEN - expect(stack).toHaveResource(KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesManifest.RESOURCE_TYPE, { Manifest: JSON.stringify([{ apiVersion: 'v1beta1', kind: 'Foo', @@ -182,7 +181,7 @@ describe('k8s manifest', () => { ); // THEN - expect(stack).toHaveResource(KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesManifest.RESOURCE_TYPE, { Manifest: JSON.stringify([ { apiVersion: 'v1beta', @@ -244,7 +243,7 @@ describe('k8s manifest', () => { }); // THEN - expect(stack).toHaveResource(KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesManifest.RESOURCE_TYPE, { Manifest: JSON.stringify([ { apiVersion: 'v1beta', @@ -259,7 +258,7 @@ describe('k8s manifest', () => { PruneLabel: 'aws.cdk.eks/prune-c89a5983505f58231ac2a9a86fd82735ccf2308eac', }); - expect(stack).toHaveResource(KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesManifest.RESOURCE_TYPE, { Manifest: JSON.stringify([ { apiVersion: 'v1', @@ -297,7 +296,7 @@ describe('k8s manifest', () => { }); // THEN - expect(stack).toHaveResource(KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesManifest.RESOURCE_TYPE, { Manifest: JSON.stringify([{ malformed: { resource: 'yes' } }]), PruneLabel: 'aws.cdk.eks/prune-c89a5983505f58231ac2a9a86fd82735ccf2308eac', }); @@ -314,7 +313,7 @@ describe('k8s manifest', () => { cluster.addManifest('m1', ['foo']); // THEN - expect(stack).toHaveResource(KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(KubernetesManifest.RESOURCE_TYPE, { Manifest: JSON.stringify([['foo']]), PruneLabel: 'aws.cdk.eks/prune-c89a5983505f58231ac2a9a86fd82735ccf2308eac', }); @@ -347,7 +346,7 @@ describe('k8s manifest', () => { }); // THEN - const template = SynthUtils.synthesize(stack).template; + const template = Template.fromStack(stack).toJSON(); const m1 = template.Resources.Clustermanifestm1E5FBE3C1.Properties; const m2 = template.Resources.m201F909C5.Properties; diff --git a/packages/@aws-cdk/aws-eks/test/k8s-object-value.test.ts b/packages/@aws-cdk/aws-eks/test/k8s-object-value.test.ts index 8bc87ba294763..8d29fa19d6471 100644 --- a/packages/@aws-cdk/aws-eks/test/k8s-object-value.test.ts +++ b/packages/@aws-cdk/aws-eks/test/k8s-object-value.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import { App, Stack, Duration } from '@aws-cdk/core'; import * as eks from '../lib'; import { KubernetesObjectValue } from '../lib/k8s-object-value'; diff --git a/packages/@aws-cdk/aws-eks/test/k8s-patch.test.ts b/packages/@aws-cdk/aws-eks/test/k8s-patch.test.ts index c59851500eff0..4040a070c15a0 100644 --- a/packages/@aws-cdk/aws-eks/test/k8s-patch.test.ts +++ b/packages/@aws-cdk/aws-eks/test/k8s-patch.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import { Names, Stack } from '@aws-cdk/core'; import * as eks from '../lib'; import { KubernetesPatch, PatchType } from '../lib/k8s-patch'; @@ -20,7 +20,7 @@ describe('k8s patch', () => { }); // THEN - expect(stack).toHaveResource('Custom::AWSCDK-EKS-KubernetesPatch', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-KubernetesPatch', { ServiceToken: { 'Fn::GetAtt': [ 'awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B', @@ -59,7 +59,7 @@ describe('k8s patch', () => { restorePatch: { restore: { patch: 123 } }, resourceName: 'myResourceName', }); - expect(stack).toHaveResource('Custom::AWSCDK-EKS-KubernetesPatch', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-KubernetesPatch', { PatchType: 'strategic', }); @@ -92,15 +92,15 @@ describe('k8s patch', () => { patchType: PatchType.STRATEGIC, }); - expect(stack).toHaveResource('Custom::AWSCDK-EKS-KubernetesPatch', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-KubernetesPatch', { ResourceName: 'jsonPatchResource', PatchType: 'json', }); - expect(stack).toHaveResource('Custom::AWSCDK-EKS-KubernetesPatch', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-KubernetesPatch', { ResourceName: 'mergePatchResource', PatchType: 'merge', }); - expect(stack).toHaveResource('Custom::AWSCDK-EKS-KubernetesPatch', { + Template.fromStack(stack).hasResourceProperties('Custom::AWSCDK-EKS-KubernetesPatch', { ResourceName: 'strategicPatchResource', PatchType: 'strategic', }); diff --git a/packages/@aws-cdk/aws-eks/test/nodegroup.test.ts b/packages/@aws-cdk/aws-eks/test/nodegroup.test.ts index 717b091acf2aa..dd8d0aa1274cd 100644 --- a/packages/@aws-cdk/aws-eks/test/nodegroup.test.ts +++ b/packages/@aws-cdk/aws-eks/test/nodegroup.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 { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; @@ -95,7 +95,7 @@ describe('node group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { AmiType: 'AL2_x86_64_GPU', }); @@ -114,7 +114,7 @@ describe('node group', () => { new eks.Nodegroup(stack, 'Nodegroup', { cluster }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { ClusterName: { Ref: 'Cluster9EE0221C', }, @@ -159,7 +159,7 @@ describe('node group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { ClusterName: { Ref: 'Cluster9EE0221C', }, @@ -204,7 +204,7 @@ describe('node group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { ClusterName: { Ref: 'Cluster9EE0221C', }, @@ -254,7 +254,7 @@ describe('node group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { AmiType: 'AL2_x86_64', }); }); @@ -281,7 +281,7 @@ describe('node group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { AmiType: 'AL2_ARM_64', }); }); @@ -309,7 +309,7 @@ describe('node group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { AmiType: 'AL2_x86_64_GPU', }); }); @@ -463,9 +463,9 @@ describe('node group', () => { }); /** - * BOTTOEROCKET_X86_64 with defined instance types w/o launchTemplateSpec should deploy correctly. + * BOTTLEROCKET_X86_64 with defined instance types w/o launchTemplateSpec should deploy correctly. */ - test('BOTTOEROCKET_X86_64 with defined instance types w/o launchTemplateSpec should deploy correctly', () => { + test('BOTTLEROCKET_X86_64 with defined instance types w/o launchTemplateSpec should deploy correctly', () => { // GIVEN const { stack, vpc } = testFixture(); const cluster = new eks.Cluster(stack, 'Cluster', { @@ -480,15 +480,15 @@ describe('node group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { AmiType: 'BOTTLEROCKET_x86_64', }); }); /** - * BOTTOEROCKET_ARM_64 with defined instance types w/o launchTemplateSpec should deploy correctly. + * BOTTLEROCKET_ARM_64 with defined instance types w/o launchTemplateSpec should deploy correctly. */ - test('BOTTOEROCKET_ARM_64 with defined instance types w/o launchTemplateSpec should deploy correctly', () => { + test('BOTTLEROCKET_ARM_64 with defined instance types w/o launchTemplateSpec should deploy correctly', () => { // GIVEN const { stack, vpc } = testFixture(); const cluster = new eks.Cluster(stack, 'Cluster', { @@ -503,7 +503,7 @@ describe('node group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { AmiType: 'BOTTLEROCKET_ARM_64', }); }); @@ -521,7 +521,7 @@ describe('node group', () => { new eks.Nodegroup(stack, 'Nodegroup', { cluster }); // THEN - expect(stack).toHaveResource(eks.KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesManifest.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -582,7 +582,7 @@ describe('node group', () => { }, }); // THEN - expect(stack).toHaveResource('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { RemoteAccess: { Ec2SshKey: 'foo', SourceSecurityGroups: [ @@ -611,7 +611,7 @@ describe('node group', () => { new eks.Nodegroup(stack, 'Nodegroup', { cluster, forceUpdate: false }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { ForceUpdateEnabled: false, }); @@ -633,7 +633,7 @@ describe('node group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { InstanceTypes: [ 'm5.large', ], @@ -658,7 +658,7 @@ describe('node group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { InstanceTypes: [ 'm5.large', ], @@ -687,7 +687,7 @@ describe('node group', () => { capacityType: eks.CapacityType.SPOT, }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { InstanceTypes: [ 'm5.large', 't3.large', @@ -718,7 +718,7 @@ describe('node group', () => { capacityType: eks.CapacityType.ON_DEMAND, }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { InstanceTypes: [ 'm5.large', 't3.large', @@ -765,7 +765,7 @@ describe('node group', () => { capacityType: eks.CapacityType.SPOT, }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { CapacityType: 'SPOT', }); @@ -830,7 +830,7 @@ describe('node group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { RemoteAccess: { Ec2SshKey: 'foo', }, @@ -856,7 +856,7 @@ describe('node group', () => { new cdk.CfnOutput(stack2, 'NodegroupName', { value: imported.nodegroupName }); // THEN - expect(stack2).toMatchTemplate({ + Template.fromStack(stack2).templateMatches({ Outputs: { NodegroupName: { Value: { @@ -880,7 +880,7 @@ describe('node group', () => { cluster.addNodegroupCapacity('ng'); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { ClusterName: { Ref: 'Cluster9EE0221C', }, @@ -929,7 +929,7 @@ describe('node group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { ClusterName: { Ref: 'Cluster9EE0221C', }, @@ -984,7 +984,7 @@ describe('node group', () => { desiredSize: 4, }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { ScalingConfig: { MinSize: 2, MaxSize: 6, @@ -1010,7 +1010,7 @@ describe('node group', () => { desiredSize: cdk.Lazy.number({ produce: () => 20 }), }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { ScalingConfig: { MinSize: 5, MaxSize: 1, @@ -1050,7 +1050,7 @@ describe('node group', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::EKS::Nodegroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EKS::Nodegroup', { LaunchTemplate: { Id: { Ref: 'LaunchTemplate', diff --git a/packages/@aws-cdk/aws-eks/test/service-account.test.ts b/packages/@aws-cdk/aws-eks/test/service-account.test.ts index e457598ccaa4f..e4db2f6680a97 100644 --- a/packages/@aws-cdk/aws-eks/test/service-account.test.ts +++ b/packages/@aws-cdk/aws-eks/test/service-account.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as eks from '../lib'; import { testFixture, testFixtureCluster } from './util'; @@ -15,7 +15,7 @@ describe('service account', () => { new eks.ServiceAccount(stack, 'MyServiceAccount', { cluster }); // THEN - expect(stack).toHaveResource(eks.KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesManifest.RESOURCE_TYPE, { ServiceToken: { 'Fn::GetAtt': [ 'awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B', @@ -38,7 +38,7 @@ describe('service account', () => { ], }, }); - expect(stack).toHaveResource(iam.CfnRole.CFN_RESOURCE_TYPE_NAME, { + Template.fromStack(stack).hasResourceProperties(iam.CfnRole.CFN_RESOURCE_TYPE_NAME, { AssumeRolePolicyDocument: { Statement: [ { @@ -73,7 +73,7 @@ describe('service account', () => { cluster.addServiceAccount('MyOtherServiceAccount'); // THEN - expect(stack).toHaveResource(eks.KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesManifest.RESOURCE_TYPE, { ServiceToken: { 'Fn::GetAtt': [ 'awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B', @@ -122,7 +122,7 @@ describe('service account', () => { cluster.addServiceAccount('MyServiceAccount'); - expect(stack).toHaveResource(eks.KubernetesManifest.RESOURCE_TYPE, { + Template.fromStack(stack).hasResourceProperties(eks.KubernetesManifest.RESOURCE_TYPE, { ServiceToken: { 'Fn::GetAtt': [ 'StackClusterF0EB02FAKubectlProviderNestedStackStackClusterF0EB02FAKubectlProviderNestedStackResource739D12C4', @@ -147,7 +147,7 @@ describe('service account', () => { }, }); - expect(stack).toHaveResource(iam.CfnRole.CFN_RESOURCE_TYPE_NAME, { + Template.fromStack(stack).hasResourceProperties(iam.CfnRole.CFN_RESOURCE_TYPE_NAME, { AssumeRolePolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts index b54ae026b86fb..93e4e7cdd9b7a 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts @@ -1,7 +1,5 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; -import { PolicyStatement, ServicePrincipal } from '@aws-cdk/aws-iam'; -import { IBucket } from '@aws-cdk/aws-s3'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { Resource } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; @@ -125,41 +123,6 @@ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoa }); } - /** - * Enable access logging for this load balancer. - * - * A region must be specified on the stack containing the load balancer; you cannot enable logging on - * environment-agnostic stacks. See https://docs.aws.amazon.com/cdk/latest/guide/environments.html - * - * This is extending the BaseLoadBalancer.logAccessLogs method to match the bucket permissions described - * at https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-access-logs.html#access-logging-bucket-requirements - */ - public logAccessLogs(bucket: IBucket, prefix?: string) { - super.logAccessLogs(bucket, prefix); - - const logsDeliveryServicePrincipal = new ServicePrincipal('delivery.logs.amazonaws.com'); - - bucket.addToResourcePolicy( - new PolicyStatement({ - actions: ['s3:PutObject'], - principals: [logsDeliveryServicePrincipal], - resources: [ - bucket.arnForObjects(`${prefix ? prefix + '/' : ''}AWSLogs/${this.stack.account}/*`), - ], - conditions: { - StringEquals: { 's3:x-amz-acl': 'bucket-owner-full-control' }, - }, - }), - ); - bucket.addToResourcePolicy( - new PolicyStatement({ - actions: ['s3:GetBucketAcl'], - principals: [logsDeliveryServicePrincipal], - resources: [bucket.bucketArn], - }), - ); - } - /** * Return the given named metric for this Network Load Balancer * @@ -326,4 +289,4 @@ class LookedUpNetworkLoadBalancer extends Resource implements INetworkLoadBalanc ...props, }); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index 345583bf9e5f8..0fcb0f4916c87 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -1,5 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; +import { PolicyStatement, ServicePrincipal } from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { ContextProvider, IResource, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; @@ -258,7 +259,27 @@ export abstract class BaseLoadBalancer extends Resource { throw new Error(`Cannot enable access logging; don't know ELBv2 account for region ${region}`); } + const logsDeliveryServicePrincipal = new ServicePrincipal('delivery.logs.amazonaws.com'); bucket.grantPut(new iam.AccountPrincipal(account), `${(prefix ? prefix + '/' : '')}AWSLogs/${Stack.of(this).account}/*`); + bucket.addToResourcePolicy( + new PolicyStatement({ + actions: ['s3:PutObject'], + principals: [logsDeliveryServicePrincipal], + resources: [ + bucket.arnForObjects(`${prefix ? prefix + '/' : ''}AWSLogs/${this.stack.account}/*`), + ], + conditions: { + StringEquals: { 's3:x-amz-acl': 'bucket-owner-full-control' }, + }, + }), + ); + bucket.addToResourcePolicy( + new PolicyStatement({ + actions: ['s3:GetBucketAcl'], + principals: [logsDeliveryServicePrincipal], + resources: [bucket.bucketArn], + }), + ); // make sure the bucket's policy is created before the ALB (see https://github.com/aws/aws-cdk/issues/1633) this.node.addDependency(bucket); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts index 4b89f79b75d31..4502b0c1d32e0 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/load-balancer.test.ts @@ -2,7 +2,7 @@ import { Match, Template } from '@aws-cdk/assertions'; import { Metric } from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as s3 from '@aws-cdk/aws-s3'; -import { testFutureBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; +import { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools/lib/feature-flag'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import * as elbv2 from '../../lib'; @@ -146,105 +146,208 @@ describe('tests', () => { expect(loadBalancer.listeners).toContain(listener); }); - testFutureBehavior('Access logging', s3GrantWriteCtx, cdk.App, (app) => { - // GIVEN - const stack = new cdk.Stack(app, undefined, { env: { region: 'us-east-1' } }); - const vpc = new ec2.Vpc(stack, 'Stack'); - const bucket = new s3.Bucket(stack, 'AccessLoggingBucket'); - const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + describe('logAccessLogs', () => { - // WHEN - lb.logAccessLogs(bucket); + function loggingSetup(app: cdk.App): { stack: cdk.Stack, bucket: s3.Bucket, lb: elbv2.ApplicationLoadBalancer } { + const stack = new cdk.Stack(app, undefined, { env: { region: 'us-east-1' } }); + const vpc = new ec2.Vpc(stack, 'Stack'); + const bucket = new s3.Bucket(stack, 'AccessLoggingBucket'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + return { stack, bucket, lb }; + } - // THEN + test('sets load balancer attributes', () => { + // GIVEN + const app = new cdk.App(); + const { stack, bucket, lb } = loggingSetup(app); - // verify that the LB attributes reference the bucket - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { - LoadBalancerAttributes: Match.arrayWith([ - { - Key: 'access_logs.s3.enabled', - Value: 'true', - }, - { - Key: 'access_logs.s3.bucket', - Value: { Ref: 'AccessLoggingBucketA6D88F29' }, - }, - { - Key: 'access_logs.s3.prefix', - Value: '', - }, - ]), - }); + // WHEN + lb.logAccessLogs(bucket); - // verify the bucket policy allows the ALB to put objects in the bucket - Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', { - PolicyDocument: { - Version: '2012-10-17', - Statement: [ + //THEN + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { + LoadBalancerAttributes: Match.arrayWith([ { - Action: ['s3:PutObject', 's3:Abort*'], - Effect: 'Allow', - Principal: { AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::127311923021:root']] } }, - Resource: { - 'Fn::Join': ['', [{ 'Fn::GetAtt': ['AccessLoggingBucketA6D88F29', 'Arn'] }, '/AWSLogs/', - { Ref: 'AWS::AccountId' }, '/*']], - }, + Key: 'access_logs.s3.enabled', + Value: 'true', }, - ], - }, + { + Key: 'access_logs.s3.bucket', + Value: { Ref: 'AccessLoggingBucketA6D88F29' }, + }, + { + Key: 'access_logs.s3.prefix', + Value: '', + }, + ]), + }); }); - // verify the ALB depends on the bucket *and* the bucket policy - Template.fromStack(stack).hasResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { - DependsOn: ['AccessLoggingBucketPolicy700D7CC6', 'AccessLoggingBucketA6D88F29'], + test('adds a dependency on the bucket', () => { + // GIVEN + const app = new cdk.App(); + const { stack, bucket, lb } = loggingSetup(app); + + // WHEN + lb.logAccessLogs(bucket); + + // THEN + // verify the ALB depends on the bucket *and* the bucket policy + Template.fromStack(stack).hasResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + DependsOn: ['AccessLoggingBucketPolicy700D7CC6', 'AccessLoggingBucketA6D88F29'], + }); }); - }); - testFutureBehavior('access logging with prefix', s3GrantWriteCtx, cdk.App, (app) => { - // GIVEN - const stack = new cdk.Stack(app, undefined, { env: { region: 'us-east-1' } }); - const vpc = new ec2.Vpc(stack, 'Stack'); - const bucket = new s3.Bucket(stack, 'AccessLoggingBucket'); - const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + testLegacyBehavior('legacy bucket permissions', cdk.App, (app) => { + const { stack, bucket, lb } = loggingSetup(app); - // WHEN - lb.logAccessLogs(bucket, 'prefix-of-access-logs'); + // WHEN + lb.logAccessLogs(bucket); - // THEN - // verify that the LB attributes reference the bucket - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { - LoadBalancerAttributes: Match.arrayWith([ - { - Key: 'access_logs.s3.enabled', - Value: 'true', - }, - { - Key: 'access_logs.s3.bucket', - Value: { Ref: 'AccessLoggingBucketA6D88F29' }, + // THEN + // verify the bucket policy allows the ALB to put objects in the bucket + Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: ['s3:PutObject*', 's3:Abort*'], + Effect: 'Allow', + Principal: { AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::127311923021:root']] } }, + Resource: { + 'Fn::Join': ['', [{ 'Fn::GetAtt': ['AccessLoggingBucketA6D88F29', 'Arn'] }, '/AWSLogs/', + { Ref: 'AWS::AccountId' }, '/*']], + }, + }, + { + Action: 's3:PutObject', + Effect: 'Allow', + Principal: { Service: 'delivery.logs.amazonaws.com' }, + Resource: { + 'Fn::Join': ['', [{ 'Fn::GetAtt': ['AccessLoggingBucketA6D88F29', 'Arn'] }, '/AWSLogs/', + { Ref: 'AWS::AccountId' }, '/*']], + }, + Condition: { StringEquals: { 's3:x-amz-acl': 'bucket-owner-full-control' } }, + }, + { + Action: 's3:GetBucketAcl', + Effect: 'Allow', + Principal: { Service: 'delivery.logs.amazonaws.com' }, + Resource: { + 'Fn::GetAtt': ['AccessLoggingBucketA6D88F29', 'Arn'], + }, + }, + ], }, - { - Key: 'access_logs.s3.prefix', - Value: 'prefix-of-access-logs', + }); + }); + + testFutureBehavior('logging bucket permissions', s3GrantWriteCtx, cdk.App, (app) => { + // GIVEN + const { stack, bucket, lb } = loggingSetup(app); + + // WHEN + lb.logAccessLogs(bucket); + + // THEN + // verify the bucket policy allows the ALB to put objects in the bucket + Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: ['s3:PutObject', 's3:Abort*'], + Effect: 'Allow', + Principal: { AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::127311923021:root']] } }, + Resource: { + 'Fn::Join': ['', [{ 'Fn::GetAtt': ['AccessLoggingBucketA6D88F29', 'Arn'] }, '/AWSLogs/', + { Ref: 'AWS::AccountId' }, '/*']], + }, + }, + { + Action: 's3:PutObject', + Effect: 'Allow', + Principal: { Service: 'delivery.logs.amazonaws.com' }, + Resource: { + 'Fn::Join': ['', [{ 'Fn::GetAtt': ['AccessLoggingBucketA6D88F29', 'Arn'] }, '/AWSLogs/', + { Ref: 'AWS::AccountId' }, '/*']], + }, + Condition: { StringEquals: { 's3:x-amz-acl': 'bucket-owner-full-control' } }, + }, + { + Action: 's3:GetBucketAcl', + Effect: 'Allow', + Principal: { Service: 'delivery.logs.amazonaws.com' }, + Resource: { + 'Fn::GetAtt': ['AccessLoggingBucketA6D88F29', 'Arn'], + }, + }, + ], }, - ]), + }); }); - // verify the bucket policy allows the ALB to put objects in the bucket - Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', { - PolicyDocument: { - Version: '2012-10-17', - Statement: [ + testFutureBehavior('access logging with prefix', s3GrantWriteCtx, cdk.App, (app) => { + // GIVEN + const { stack, bucket, lb } = loggingSetup(app); + + // WHEN + lb.logAccessLogs(bucket, 'prefix-of-access-logs'); + + // THEN + // verify that the LB attributes reference the bucket + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { + LoadBalancerAttributes: Match.arrayWith([ { - Action: ['s3:PutObject', 's3:Abort*'], - Effect: 'Allow', - Principal: { AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::127311923021:root']] } }, - Resource: { - 'Fn::Join': ['', [{ 'Fn::GetAtt': ['AccessLoggingBucketA6D88F29', 'Arn'] }, '/prefix-of-access-logs/AWSLogs/', - { Ref: 'AWS::AccountId' }, '/*']], - }, + Key: 'access_logs.s3.enabled', + Value: 'true', + }, + { + Key: 'access_logs.s3.bucket', + Value: { Ref: 'AccessLoggingBucketA6D88F29' }, + }, + { + Key: 'access_logs.s3.prefix', + Value: 'prefix-of-access-logs', }, - ], - }, + ]), + }); + + // verify the bucket policy allows the ALB to put objects in the bucket + Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: ['s3:PutObject', 's3:Abort*'], + Effect: 'Allow', + Principal: { AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::127311923021:root']] } }, + Resource: { + 'Fn::Join': ['', [{ 'Fn::GetAtt': ['AccessLoggingBucketA6D88F29', 'Arn'] }, '/prefix-of-access-logs/AWSLogs/', + { Ref: 'AWS::AccountId' }, '/*']], + }, + }, + { + Action: 's3:PutObject', + Effect: 'Allow', + Principal: { Service: 'delivery.logs.amazonaws.com' }, + Resource: { + 'Fn::Join': ['', [{ 'Fn::GetAtt': ['AccessLoggingBucketA6D88F29', 'Arn'] }, '/prefix-of-access-logs/AWSLogs/', + { Ref: 'AWS::AccountId' }, '/*']], + }, + Condition: { StringEquals: { 's3:x-amz-acl': 'bucket-owner-full-control' } }, + }, + { + Action: 's3:GetBucketAcl', + Effect: 'Allow', + Principal: { Service: 'delivery.logs.amazonaws.com' }, + Resource: { + 'Fn::GetAtt': ['AccessLoggingBucketA6D88F29', 'Arn'], + }, + }, + ], + }, + }); }); }); diff --git a/packages/@aws-cdk/aws-events/package.json b/packages/@aws-cdk/aws-events/package.json index f871d4ec7b6a4..57af72c704d52 100644 --- a/packages/@aws-cdk/aws-events/package.json +++ b/packages/@aws-cdk/aws-events/package.json @@ -81,7 +81,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/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", diff --git a/packages/@aws-cdk/aws-events/test/archive.test.ts b/packages/@aws-cdk/aws-events/test/archive.test.ts index 0c37a0ec4ae39..8119961738cf1 100644 --- a/packages/@aws-cdk/aws-events/test/archive.test.ts +++ b/packages/@aws-cdk/aws-events/test/archive.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import { Duration, Stack } from '@aws-cdk/core'; import { EventBus } from '../lib'; import { Archive } from '../lib/archive'; @@ -9,7 +9,7 @@ describe('archive', () => { const stack = new Stack(); // WHEN - let eventBus = new EventBus(stack, 'Bus'); + const eventBus = new EventBus(stack, 'Bus'); new Archive(stack, 'Archive', { sourceEventBus: eventBus, @@ -20,11 +20,11 @@ describe('archive', () => { }); // THEN - expect(stack).toHaveResource('AWS::Events::EventBus', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { Name: 'Bus', }); - expect(stack).toHaveResource('AWS::Events::Archive', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Archive', { EventPattern: { account: [{ Ref: 'AWS::AccountId', @@ -38,15 +38,14 @@ describe('archive', () => { ], }, }); - - }); + test('creates an archive for an EventBus with a pattern including a detailType property', () => { // GIVEN const stack = new Stack(); // WHEN - let eventBus = new EventBus(stack, 'Bus'); + const eventBus = new EventBus(stack, 'Bus'); new Archive(stack, 'Archive', { sourceEventBus: eventBus, @@ -58,11 +57,11 @@ describe('archive', () => { }); // THEN - expect(stack).toHaveResource('AWS::Events::EventBus', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { Name: 'Bus', }); - expect(stack).toHaveResource('AWS::Events::Archive', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Archive', { EventPattern: { 'account': [{ Ref: 'AWS::AccountId', @@ -77,7 +76,5 @@ describe('archive', () => { ], }, }); - - }); }); diff --git a/packages/@aws-cdk/aws-events/test/event-bus.test.ts b/packages/@aws-cdk/aws-events/test/event-bus.test.ts index 71f089a58b23c..34406986c608e 100644 --- a/packages/@aws-cdk/aws-events/test/event-bus.test.ts +++ b/packages/@aws-cdk/aws-events/test/event-bus.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Aws, CfnResource, Stack, Arn, App, PhysicalName, CfnOutput } from '@aws-cdk/core'; @@ -13,7 +13,7 @@ describe('event bus', () => { new EventBus(stack, 'Bus'); // THEN - expect(stack).toHaveResource('AWS::Events::EventBus', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { Name: 'Bus', }); }); @@ -26,7 +26,7 @@ describe('event bus', () => { new EventBus(stack, 'Bus', {}); // THEN - expect(stack).toHaveResource('AWS::Events::EventBus', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { Name: 'Bus', }); }); @@ -41,11 +41,9 @@ describe('event bus', () => { }); // THEN - expect(stack).toHaveResource('AWS::Events::EventBus', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { Name: 'myEventBus', }); - - }); test('partner event bus', () => { @@ -58,12 +56,10 @@ describe('event bus', () => { }); // THEN - expect(stack).toHaveResource('AWS::Events::EventBus', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { Name: 'aws.partner/PartnerName/acct1/repo1', EventSourceName: 'aws.partner/PartnerName/acct1/repo1', }); - - }); test('imported event bus', () => { @@ -82,12 +78,10 @@ describe('event bus', () => { }, }); - expect(stack).toHaveResource('Test::Resource', { + Template.fromStack(stack).hasResourceProperties('Test::Resource', { EventBusArn1: { 'Fn::GetAtt': ['BusEA82B648', 'Arn'] }, EventBusArn2: { 'Fn::GetAtt': ['BusEA82B648', 'Arn'] }, }); - - }); test('imported event bus from name', () => { @@ -99,8 +93,6 @@ describe('event bus', () => { // WHEN expect(stack.resolve(eventBus.eventBusName)).toEqual(stack.resolve(importEB.eventBusName)); - - }); test('same account imported event bus has right resource env', () => { @@ -113,8 +105,6 @@ describe('event bus', () => { // WHEN expect(stack.resolve(importEB.env.account)).toEqual({ 'Fn::Select': [4, { 'Fn::Split': [':', { 'Fn::GetAtt': ['BusEA82B648', 'Arn'] }] }] }); expect(stack.resolve(importEB.env.region)).toEqual({ 'Fn::Select': [3, { 'Fn::Split': [':', { 'Fn::GetAtt': ['BusEA82B648', 'Arn'] }] }] }); - - }); test('cross account imported event bus has right resource env', () => { @@ -134,8 +124,6 @@ describe('event bus', () => { // WHEN expect(importEB.env.account).toEqual(arnParts.account); expect(importEB.env.region).toEqual(arnParts.region); - - }); test('can get bus name', () => { @@ -154,11 +142,9 @@ describe('event bus', () => { }); // THEN - expect(stack).toHaveResource('Test::Resource', { + Template.fromStack(stack).hasResourceProperties('Test::Resource', { EventBusName: { Ref: 'BusEA82B648' }, }); - - }); test('can get bus arn', () => { @@ -177,11 +163,9 @@ describe('event bus', () => { }); // THEN - expect(stack).toHaveResource('Test::Resource', { + Template.fromStack(stack).hasResourceProperties('Test::Resource', { EventBusArn: { 'Fn::GetAtt': ['BusEA82B648', 'Arn'] }, }); - - }); test('event bus name cannot be default', () => { @@ -197,8 +181,6 @@ describe('event bus', () => { expect(() => { createInvalidBus(); }).toThrow(/'eventBusName' must not be 'default'/); - - }); test('event bus name cannot contain slash', () => { @@ -214,8 +196,6 @@ describe('event bus', () => { expect(() => { createInvalidBus(); }).toThrow(/'eventBusName' must not contain '\/'/); - - }); test('event bus cannot have name and source name', () => { @@ -232,8 +212,6 @@ describe('event bus', () => { expect(() => { createInvalidBus(); }).toThrow(/'eventBusName' and 'eventSourceName' cannot both be provided/); - - }); test('event bus name cannot be empty string', () => { @@ -249,8 +227,6 @@ describe('event bus', () => { expect(() => { createInvalidBus(); }).toThrow(/'eventBusName' must satisfy: /); - - }); test('does not throw if eventBusName is a token', () => { @@ -261,8 +237,6 @@ describe('event bus', () => { expect(() => new EventBus(stack, 'EventBus', { eventBusName: Aws.STACK_NAME, })).not.toThrow(); - - }); test('event bus source name must follow pattern', () => { @@ -278,8 +252,6 @@ describe('event bus', () => { expect(() => { createInvalidBus(); }).toThrow(/'eventSourceName' must satisfy: \/\^aws/); - - }); test('event bus source name cannot be empty string', () => { @@ -295,8 +267,6 @@ describe('event bus', () => { expect(() => { createInvalidBus(); }).toThrow(/'eventSourceName' must satisfy: /); - - }); testDeprecated('can grant PutEvents', () => { @@ -310,7 +280,7 @@ describe('event bus', () => { EventBus.grantPutEvents(role); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -327,8 +297,6 @@ describe('event bus', () => { }, ], }); - - }); test('can grant PutEvents using grantAllPutEvents', () => { @@ -342,7 +310,7 @@ describe('event bus', () => { EventBus.grantAllPutEvents(role); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -359,9 +327,8 @@ describe('event bus', () => { }, ], }); - - }); + test('can grant PutEvents to a specific event bus', () => { // GIVEN const stack = new Stack(); @@ -375,7 +342,7 @@ describe('event bus', () => { eventBus.grantPutEventsTo(role); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -397,9 +364,8 @@ describe('event bus', () => { }, ], }); - - }); + test('can archive events', () => { // GIVEN const stack = new Stack(); @@ -415,11 +381,11 @@ describe('event bus', () => { }); // THEN - expect(stack).toHaveResource('AWS::Events::EventBus', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { Name: 'Bus', }); - expect(stack).toHaveResource('AWS::Events::Archive', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Archive', { SourceArn: { 'Fn::GetAtt': [ 'BusEA82B648', @@ -448,9 +414,8 @@ describe('event bus', () => { RetentionDays: 0, ArchiveName: 'MyArchive', }); - - }); + test('can archive events from an imported EventBus', () => { // GIVEN const stack = new Stack(); @@ -468,11 +433,11 @@ describe('event bus', () => { }); // THEN - expect(stack).toHaveResource('AWS::Events::EventBus', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { Name: 'Bus', }); - expect(stack).toHaveResource('AWS::Events::Archive', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Archive', { SourceArn: { 'Fn::GetAtt': [ 'BusEA82B648', @@ -524,9 +489,8 @@ describe('event bus', () => { RetentionDays: 0, ArchiveName: 'MyArchive', }); - - }); + test('cross account event bus uses generated physical name', () => { // GIVEN const app = new App(); @@ -551,7 +515,7 @@ describe('event bus', () => { new CfnOutput(stack2, 'BusName', { value: bus1.eventBusName }); // THEN - expect(stack1).toHaveResource('AWS::Events::EventBus', { + Template.fromStack(stack1).hasResourceProperties('AWS::Events::EventBus', { Name: 'stack1stack1busca19bdf8ab2e51b62a5a', }); }); diff --git a/packages/@aws-cdk/aws-events/test/input.test.ts b/packages/@aws-cdk/aws-events/test/input.test.ts index 76ec2cd2d2bd9..2c5536620a9fb 100644 --- a/packages/@aws-cdk/aws-events/test/input.test.ts +++ b/packages/@aws-cdk/aws-events/test/input.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import { User } from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import { EventField, IRuleTarget, RuleTargetInput, Schedule } from '../lib'; @@ -17,14 +17,13 @@ describe('input', () => { rule.addTarget(new SomeTarget(RuleTargetInput.fromObject({ SomeObject: 'withAValue' }))); // THEN - expect(stack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { Input: '{"SomeObject":"withAValue"}', }, ], }); - }); test('can use joined JSON containing refs in JSON object', () => { @@ -41,7 +40,7 @@ describe('input', () => { }))); // THEN - expect(stack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { InputTransformer: { @@ -62,8 +61,6 @@ describe('input', () => { }, ], }); - - }); test('can use joined JSON containing refs in JSON object with tricky inputs', () => { @@ -80,7 +77,7 @@ describe('input', () => { }))); // THEN - expect(stack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { InputTransformer: { @@ -101,8 +98,6 @@ describe('input', () => { }, ], }); - - }); test('can use joined JSON containing refs in JSON object and concat', () => { @@ -119,7 +114,7 @@ describe('input', () => { }))); // THEN - expect(stack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { InputTransformer: { @@ -140,8 +135,6 @@ describe('input', () => { }, ], }); - - }); test('can use joined JSON containing refs in JSON object and quotes', () => { @@ -158,7 +151,7 @@ describe('input', () => { }))); // THEN - expect(stack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { InputTransformer: { @@ -179,8 +172,6 @@ describe('input', () => { }, ], }); - - }); test('can use joined JSON containing refs in JSON object and multiple keys', () => { @@ -197,7 +188,7 @@ describe('input', () => { }))); // THEN - expect(stack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { InputTransformer: { @@ -218,8 +209,6 @@ describe('input', () => { }, ], }); - - }); test('can use token', () => { @@ -234,7 +223,7 @@ describe('input', () => { rule.addTarget(new SomeTarget(RuleTargetInput.fromObject({ userArn: user.userArn }))); // THEN - expect(stack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { Input: { @@ -255,7 +244,6 @@ describe('input', () => { }, ], }); - }); }); @@ -271,15 +259,13 @@ describe('input', () => { rule.addTarget(new SomeTarget(RuleTargetInput.fromMultilineText('I have\nmultiple lines'))); // THEN - expect(stack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { Input: '"I have"\n"multiple lines"', }, ], }); - - }); test('escaped newlines are not interpreted as newlines', () => { @@ -290,18 +276,16 @@ describe('input', () => { }); // WHEN - rule.addTarget(new SomeTarget(RuleTargetInput.fromMultilineText('this is not\\na real newline'))), + rule.addTarget(new SomeTarget(RuleTargetInput.fromMultilineText('this is not\\na real newline'))); // THEN - expect(stack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { Input: '"this is not\\\\na real newline"', }, ], }); - - }); test('can use Tokens in text templates', () => { @@ -317,15 +301,13 @@ describe('input', () => { rule.addTarget(new SomeTarget(RuleTargetInput.fromText(`hello ${world}`))); // THEN - expect(stack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { Input: '"hello world"', }, ], }); - - }); }); }); diff --git a/packages/@aws-cdk/aws-events/test/rule.test.ts b/packages/@aws-cdk/aws-events/test/rule.test.ts index 21782c089b40c..1cbd776441fce 100644 --- a/packages/@aws-cdk/aws-events/test/rule.test.ts +++ b/packages/@aws-cdk/aws-events/test/rule.test.ts @@ -1,5 +1,5 @@ /* eslint-disable object-curly-newline */ -import '@aws-cdk/assert-internal/jest'; +import { Match, Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import { EventBus, EventField, IRule, IRuleTarget, RuleTargetConfig, RuleTargetInput, Schedule } from '../lib'; @@ -15,7 +15,7 @@ describe('rule', () => { schedule: Schedule.rate(cdk.Duration.minutes(10)), }); - expect(stack).toMatchTemplate({ + Template.fromStack(stack).templateMatches({ 'Resources': { 'MyRuleA44AB831': { 'Type': 'AWS::Events::Rule', @@ -26,7 +26,6 @@ describe('rule', () => { }, }, }); - }); test('can get rule name', () => { @@ -42,11 +41,9 @@ describe('rule', () => { }, }); - expect(stack).toHaveResource('Test::Resource', { + Template.fromStack(stack).hasResourceProperties('Test::Resource', { RuleName: { Ref: 'MyRuleA44AB831' }, }); - - }); test('get rate as token', () => { @@ -60,18 +57,15 @@ describe('rule', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { 'Name': 'rateInMinutes', 'ScheduleExpression': 'rate(5 minutes)', }); - - }); test('Seconds is not an allowed value for Schedule rate', () => { const lazyDuration = cdk.Duration.seconds(cdk.Lazy.number({ produce: () => 5 })); expect(() => Schedule.rate(lazyDuration)).toThrow(/Allowed units for scheduling/i); - }); test('Millis is not an allowed value for Schedule rate', () => { @@ -79,7 +73,6 @@ describe('rule', () => { // THEN expect(() => Schedule.rate(lazyDuration)).toThrow(/Allowed units for scheduling/i); - }); test('rule with physical name', () => { @@ -93,11 +86,9 @@ describe('rule', () => { }); // THEN - expect(stack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { Name: 'PhysicalName', }); - - }); test('eventPattern is rendered properly', () => { @@ -119,7 +110,7 @@ describe('rule', () => { }, }); - expect(stack).toMatchTemplate({ + Template.fromStack(stack).templateMatches({ 'Resources': { 'MyRuleA44AB831': { 'Type': 'AWS::Events::Rule', @@ -140,8 +131,6 @@ describe('rule', () => { }, }, }); - - }); test('fails synthesis if neither eventPattern nor scheudleExpression are specified', () => { @@ -149,7 +138,6 @@ describe('rule', () => { const stack = new cdk.Stack(app, 'MyStack'); new Rule(stack, 'Rule'); expect(() => app.synth()).toThrow(/Either 'eventPattern' or 'schedule' must be defined/); - }); test('addEventPattern can be used to add filters', () => { @@ -173,7 +161,7 @@ describe('rule', () => { }, }); - expect(stack).toMatchTemplate({ + Template.fromStack(stack).templateMatches({ 'Resources': { 'MyRuleA44AB831': { 'Type': 'AWS::Events::Rule', @@ -202,7 +190,6 @@ describe('rule', () => { }, }, }); - }); test('addEventPattern can de-duplicate filters and keep the order', () => { @@ -217,7 +204,7 @@ describe('rule', () => { detailType: ['EC2 Instance State-change Notification', 'AWS API Call via CloudTrail'], }); - expect(stack).toMatchTemplate({ + Template.fromStack(stack).templateMatches({ 'Resources': { 'MyRuleA44AB831': { 'Type': 'AWS::Events::Rule', @@ -233,7 +220,6 @@ describe('rule', () => { }, }, }); - }); test('targets can be added via props or addTarget with input transformer', () => { @@ -261,7 +247,7 @@ describe('rule', () => { rule.addTarget(t2); - expect(stack).toMatchTemplate({ + Template.fromStack(stack).templateMatches({ 'Resources': { 'EventRule5A491D2C': { 'Type': 'AWS::Events::Rule', @@ -291,7 +277,6 @@ describe('rule', () => { }, }, }); - }); test('input template can contain tokens', () => { @@ -337,7 +322,7 @@ describe('rule', () => { }), }); - expect(stack).toMatchTemplate({ + Template.fromStack(stack).templateMatches({ 'Resources': { 'EventRule5A491D2C': { 'Type': 'AWS::Events::Rule', @@ -378,8 +363,6 @@ describe('rule', () => { }, }, }); - - }); test('target can declare role which will be used', () => { @@ -404,7 +387,7 @@ describe('rule', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { 'Targets': [ { 'Arn': 'ARN2', @@ -413,8 +396,6 @@ describe('rule', () => { }, ], }); - - }); test('in cross-account scenario, target role is only used in target account', () => { @@ -442,7 +423,7 @@ describe('rule', () => { }); // THEN - expect(ruleStack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(ruleStack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { Arn: { 'Fn::Join': ['', [ @@ -453,7 +434,7 @@ describe('rule', () => { }, ], }); - expect(targetStack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(targetStack).hasResourceProperties('AWS::Events::Rule', { 'Targets': [ { 'Arn': 'ARN2', @@ -462,8 +443,6 @@ describe('rule', () => { }, ], }); - - }); test('asEventRuleTarget can use the ruleArn and a uniqueId of the rule', () => { @@ -490,7 +469,6 @@ describe('rule', () => { expect(stack.resolve(receivedRuleArn)).toEqual(stack.resolve(rule.ruleArn)); expect(receivedRuleId).toEqual(cdk.Names.uniqueId(rule)); - }); test('fromEventRuleArn', () => { @@ -503,7 +481,6 @@ describe('rule', () => { // THEN expect(importedRule.ruleArn).toEqual('arn:aws:events:us-east-2:123456789012:rule/example'); expect(importedRule.ruleName).toEqual('example'); - }); test('rule can be disabled', () => { @@ -517,11 +494,9 @@ describe('rule', () => { }); // THEN - expect(stack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { 'State': 'DISABLED', }); - - }); test('can add multiple targets with the same id', () => { @@ -535,7 +510,7 @@ describe('rule', () => { rule.addTarget(new SomeTarget()); // THEN - expect(stack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { 'Arn': 'ARN1', @@ -553,8 +528,6 @@ describe('rule', () => { }, ], }); - - }); test('sqsParameters are generated when they are specified in target props', () => { @@ -572,7 +545,7 @@ describe('rule', () => { targets: [t1], }); - expect(stack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { 'Arn': 'ARN1', @@ -583,7 +556,6 @@ describe('rule', () => { }, ], }); - }); test('associate rule with event bus', () => { @@ -600,13 +572,11 @@ describe('rule', () => { }); // THEN - expect(stack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { EventBusName: { Ref: 'EventBus7B8748AA', }, }); - - }); test('throws with eventBus and schedule', () => { @@ -620,7 +590,6 @@ describe('rule', () => { schedule: Schedule.rate(cdk.Duration.minutes(10)), eventBus, })).toThrow(/Cannot associate rule with 'eventBus' when using 'schedule'/); - }); test('allow an imported target if is in the same account and region', () => { @@ -639,7 +608,7 @@ describe('rule', () => { rule.addTarget(new SomeTarget('T', resource)); - expect(sourceStack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(sourceStack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { 'Arn': 'ARN1', @@ -650,8 +619,6 @@ describe('rule', () => { }, ], }); - - }); describe('for cross-account and/or cross-region targets', () => { @@ -668,8 +635,6 @@ describe('rule', () => { expect(() => { rule.addTarget(new SomeTarget('T', resource)); }).toThrow(/You need to provide a concrete region/); - - }); test('requires that the target stack specify a concrete account', () => { @@ -685,8 +650,6 @@ describe('rule', () => { expect(() => { rule.addTarget(new SomeTarget('T', resource)); }).toThrow(/You need to provide a concrete account for the target stack when using cross-account or cross-region events/); - - }); test('requires that the target stack specify a concrete region', () => { @@ -703,8 +666,6 @@ describe('rule', () => { expect(() => { rule.addTarget(new SomeTarget('T', resource)); }).toThrow(/You need to provide a concrete region for the target stack when using cross-account or cross-region events/); - - }); test('creates cross-account targets if in the same region', () => { @@ -726,7 +687,7 @@ describe('rule', () => { rule.addTarget(new SomeTarget('T', resource)); - expect(sourceStack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(sourceStack).hasResourceProperties('AWS::Events::Rule', { 'State': 'ENABLED', 'Targets': [ { @@ -745,7 +706,7 @@ describe('rule', () => { ], }); - expect(targetStack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(targetStack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { 'Arn': 'ARN1', @@ -756,8 +717,6 @@ describe('rule', () => { }, ], }); - - }); test('creates cross-region targets', () => { @@ -779,7 +738,7 @@ describe('rule', () => { rule.addTarget(new SomeTarget('T', resource)); - expect(sourceStack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(sourceStack).hasResourceProperties('AWS::Events::Rule', { 'State': 'ENABLED', 'Targets': [ { @@ -798,7 +757,7 @@ describe('rule', () => { ], }); - expect(targetStack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(targetStack).hasResourceProperties('AWS::Events::Rule', { Targets: [ { 'Arn': 'ARN1', @@ -809,8 +768,6 @@ describe('rule', () => { }, ], }); - - }); test('do not create duplicated targets', () => { @@ -834,7 +791,7 @@ describe('rule', () => { // same target should be skipped rule.addTarget(new SomeTarget('T1', resource)); - expect(sourceStack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(sourceStack).hasResourceProperties('AWS::Events::Rule', { 'State': 'ENABLED', 'Targets': [ { @@ -853,7 +810,7 @@ describe('rule', () => { ], }); - expect(sourceStack).not.toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(sourceStack).hasResourceProperties('AWS::Events::Rule', Match.not({ 'State': 'ENABLED', 'Targets': [ { @@ -870,9 +827,7 @@ describe('rule', () => { }, }, ], - }); - - + })); }); test('requires that the target is not imported', () => { @@ -893,8 +848,6 @@ describe('rule', () => { expect(() => { rule.addTarget(new SomeTarget('T', resource)); }).toThrow(/Cannot create a cross-account or cross-region rule for an imported resource/); - - }); test('requires that the source and target stacks be part of the same App', () => { @@ -911,8 +864,6 @@ describe('rule', () => { expect(() => { rule.addTarget(new SomeTarget('T', resource)); }).toThrow(/Event stack and target stack must belong to the same CDK app/); - - }); test('generates the correct rules in the source and target stacks when eventPattern is passed in the constructor', () => { @@ -944,7 +895,7 @@ describe('rule', () => { rule.addTarget(new SomeTarget('T1', resource1)); rule.addTarget(new SomeTarget('T2', resource2)); - expect(sourceStack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(sourceStack).hasResourceProperties('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'some-event', @@ -968,7 +919,7 @@ describe('rule', () => { ], }); - expect(targetStack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(targetStack).hasResourceProperties('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'some-event', @@ -982,7 +933,7 @@ describe('rule', () => { }, ], }); - expect(targetStack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(targetStack).hasResourceProperties('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'some-event', @@ -998,13 +949,11 @@ describe('rule', () => { }); const eventBusPolicyStack = app.node.findChild(`EventBusPolicy-${sourceAccount}-us-west-2-${targetAccount}`) as cdk.Stack; - expect(eventBusPolicyStack).toHaveResourceLike('AWS::Events::EventBusPolicy', { + Template.fromStack(eventBusPolicyStack).hasResourceProperties('AWS::Events::EventBusPolicy', { 'Action': 'events:PutEvents', 'StatementId': `Allow-account-${sourceAccount}`, 'Principal': sourceAccount, }); - - }); test('generates the correct rule in the target stack when addEventPattern in the source rule is used', () => { @@ -1034,7 +983,7 @@ describe('rule', () => { source: ['some-event'], }); - expect(targetStack).toHaveResourceLike('AWS::Events::Rule', { + Template.fromStack(targetStack).hasResourceProperties('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'some-event', @@ -1048,8 +997,6 @@ describe('rule', () => { }, ], }); - - }); }); }); diff --git a/packages/@aws-cdk/aws-events/test/schedule.test.ts b/packages/@aws-cdk/aws-events/test/schedule.test.ts index d853da9ba6c30..9dea322c62d0a 100644 --- a/packages/@aws-cdk/aws-events/test/schedule.test.ts +++ b/packages/@aws-cdk/aws-events/test/schedule.test.ts @@ -8,7 +8,6 @@ describe('schedule', () => { minute: '0/10', weekDay: 'MON-FRI', }).expressionString); - }); test('cron expressions day and dow are mutex: given month day', () => { @@ -18,7 +17,6 @@ describe('schedule', () => { hour: '8', day: '1', }).expressionString); - }); test('cron expressions day and dow are mutex: given neither', () => { @@ -27,28 +25,24 @@ describe('schedule', () => { minute: '0', hour: '10', }).expressionString); - }); test('rate must be whole number of minutes', () => { expect(() => { events.Schedule.rate(Duration.minutes(0.13456)); }).toThrow(/'0.13456 minutes' cannot be converted into a whole number of seconds/); - }); test('rate must be whole number', () => { expect(() => { events.Schedule.rate(Duration.minutes(1/8)); }).toThrow(/'0.125 minutes' cannot be converted into a whole number of seconds/); - }); test('rate cannot be 0', () => { expect(() => { events.Schedule.rate(Duration.days(0)); }).toThrow(/Duration cannot be 0/); - }); test('rate can be from a token', () => { @@ -56,41 +50,35 @@ describe('schedule', () => { const lazyDuration = Duration.minutes(Lazy.number({ produce: () => 5 })); const rate = events.Schedule.rate(lazyDuration); expect('rate(5 minutes)').toEqual(stack.resolve(rate).expressionString); - }); test('rate can be in minutes', () => { expect('rate(10 minutes)').toEqual( events.Schedule.rate(Duration.minutes(10)) .expressionString); - }); test('rate can be in days', () => { expect('rate(10 days)').toEqual( events.Schedule.rate(Duration.days(10)) .expressionString); - }); test('rate can be in hours', () => { expect('rate(10 hours)').toEqual( events.Schedule.rate(Duration.hours(10)) .expressionString); - }); test('rate can be in seconds', () => { expect('rate(2 minutes)').toEqual( events.Schedule.rate(Duration.seconds(120)) .expressionString); - }); test('rate must not be in seconds when specified as a token', () => { expect(() => { events.Schedule.rate(Duration.seconds(Lazy.number({ produce: () => 5 }))); }).toThrow(/Allowed units for scheduling/); - }); }); diff --git a/packages/@aws-cdk/aws-events/test/util.test.ts b/packages/@aws-cdk/aws-events/test/util.test.ts index eb354ca9c48a0..b885041163316 100644 --- a/packages/@aws-cdk/aws-events/test/util.test.ts +++ b/packages/@aws-cdk/aws-events/test/util.test.ts @@ -23,21 +23,18 @@ describe('util', () => { case: [1], }, }); - }); test('merge into an empty destination', () => { expect(mergeEventPattern(undefined, { foo: ['123'] })).toEqual({ foo: ['123'] }); expect(mergeEventPattern(undefined, { foo: { bar: ['123'] } })).toEqual({ foo: { bar: ['123'] } }); expect(mergeEventPattern({ }, { foo: { bar: ['123'] } })).toEqual({ foo: { bar: ['123'] } }); - }); test('fails if a field is not an array', () => { expect(() => mergeEventPattern(undefined, 123)).toThrow(/Invalid event pattern '123', expecting an object or an array/); expect(() => mergeEventPattern(undefined, 'Hello')).toThrow(/Invalid event pattern '"Hello"', expecting an object or an array/); expect(() => mergeEventPattern(undefined, { foo: '123' })).toThrow(/Invalid event pattern field { foo: "123" }. All fields must be arrays/); - }); test('fails if mismatch between dest and src', () => { @@ -52,7 +49,6 @@ describe('util', () => { }, }, })).toThrow(/Invalid event pattern field array. Type mismatch between existing pattern \[1\] and added pattern \{"value":\["hello"\]\}/); - }); test('deduplicate match values in pattern array', () => { @@ -90,7 +86,6 @@ describe('util', () => { 'detail-type': ['AWS API Call via CloudTrail'], 'time': [{ prefix: '2017-10-02' }, { prefix: '2017-10-03' }], }); - }); }); }); diff --git a/packages/@aws-cdk/aws-rds/package.json b/packages/@aws-cdk/aws-rds/package.json index bd56c7dd94539..0da51c0d59691 100644 --- a/packages/@aws-cdk/aws-rds/package.json +++ b/packages/@aws-cdk/aws-rds/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/aws-events-targets": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-rds/test/cluster-engine.test.ts b/packages/@aws-cdk/aws-rds/test/cluster-engine.test.ts index 826c988688c24..f6e3824092442 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster-engine.test.ts +++ b/packages/@aws-cdk/aws-rds/test/cluster-engine.test.ts @@ -1,4 +1,3 @@ -import '@aws-cdk/assert-internal/jest'; import { AuroraEngineVersion, AuroraMysqlEngineVersion, AuroraPostgresEngineVersion, DatabaseClusterEngine } from '../lib'; describe('cluster engine', () => { @@ -11,8 +10,6 @@ describe('cluster engine', () => { // THEN expect(family).toEqual('aurora5.6'); - - }); test("default parameterGroupFamily for versionless Aurora MySQL cluster engine is 'aurora-mysql5.7'", () => { @@ -24,8 +21,6 @@ describe('cluster engine', () => { // THEN expect(family).toEqual('aurora-mysql5.7'); - - }); test('default parameterGroupFamily for versionless Aurora PostgreSQL is not defined', () => { @@ -37,8 +32,6 @@ describe('cluster engine', () => { // THEN expect(family).toEqual(undefined); - - }); test('cluster parameter group correctly determined for AURORA and given version', () => { @@ -52,8 +45,6 @@ describe('cluster engine', () => { // THEN expect(family).toEqual('aurora5.6'); - - }); test('cluster parameter group correctly determined for AURORA_MYSQL and given version', () => { @@ -67,8 +58,6 @@ describe('cluster engine', () => { // THEN expect(family).toEqual('aurora-mysql5.7'); - - }); test('cluster parameter group correctly determined for AURORA_MYSQL and given version 3', () => { @@ -95,8 +84,6 @@ describe('cluster engine', () => { // THEN expect(family).toEqual('aurora-postgresql11'); - - }); test('parameter group family', () => { @@ -117,8 +104,6 @@ describe('cluster engine', () => { 'aurora-postgresql9.6'); expect(DatabaseClusterEngine.auroraPostgres({ version: AuroraPostgresEngineVersion.of('10.0', '10') }).parameterGroupFamily).toEqual( 'aurora-postgresql10'); - - }); test('supported log types', () => { @@ -126,6 +111,5 @@ describe('cluster engine', () => { expect(DatabaseClusterEngine.aurora({ version: AuroraEngineVersion.VER_1_22_2 }).supportedLogTypes).toEqual(mysqlLogTypes); expect(DatabaseClusterEngine.auroraMysql({ version: AuroraMysqlEngineVersion.VER_2_08_1 }).supportedLogTypes).toEqual(mysqlLogTypes); expect(DatabaseClusterEngine.auroraPostgres({ version: AuroraPostgresEngineVersion.VER_9_6_9 }).supportedLogTypes).toEqual(['postgresql']); - }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-rds/test/cluster.test.ts b/packages/@aws-cdk/aws-rds/test/cluster.test.ts index b3dbdfc81d007..1681f397c5a9f 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-rds/test/cluster.test.ts @@ -1,5 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; -import { ABSENT, ResourcePart, SynthUtils } from '@aws-cdk/assert-internal'; +import { Match, Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import { ManagedPolicy, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; @@ -34,7 +33,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResource('AWS::RDS::DBCluster', { Properties: { Engine: 'aurora', DBSubnetGroupName: { Ref: 'DatabaseSubnets56F17B9A' }, @@ -46,13 +45,13 @@ describe('cluster', () => { }, DeletionPolicy: 'Snapshot', UpdateReplacePolicy: 'Snapshot', - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toCountResources('AWS::RDS::DBInstance', 2); - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).resourceCountIs('AWS::RDS::DBInstance', 2); + Template.fromStack(stack).hasResource('AWS::RDS::DBInstance', { DeletionPolicy: 'Delete', UpdateReplacePolicy: 'Delete', - }, ResourcePart.CompleteDefinition); + }); }); test('validates that the number of instances is not a deploy-time value', () => { @@ -91,7 +90,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { Engine: 'aurora', DBSubnetGroupName: { Ref: 'DatabaseSubnets56F17B9A' }, MasterUsername: 'admin', @@ -146,7 +145,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { Engine: 'aurora', DBSubnetGroupName: { Ref: 'DatabaseSubnets56F17B9A' }, MasterUsername: 'admin', @@ -182,7 +181,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { DBClusterParameterGroupName: { Ref: 'ParamsA8366201' }, }); }); @@ -201,10 +200,10 @@ describe('cluster', () => { removalPolicy: cdk.RemovalPolicy.RETAIN, }); - expect(stack).toHaveResourceLike('AWS::RDS::DBSubnetGroup', { + Template.fromStack(stack).hasResource('AWS::RDS::DBSubnetGroup', { DeletionPolicy: 'Retain', UpdateReplacePolicy: 'Retain', - }, ResourcePart.CompleteDefinition); + }); }); test('creates a secret when master credentials are not specified', () => { @@ -226,7 +225,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { MasterUsername: { 'Fn::Join': [ '', @@ -253,7 +252,7 @@ describe('cluster', () => { }, }); - expect(stack).toHaveResource('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { GenerateSecretString: { ExcludeCharacters: '\"@/\\', GenerateStringKey: 'password', @@ -282,7 +281,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { KmsKeyId: { 'Fn::GetAtt': [ 'Key961B73FD', @@ -316,7 +315,7 @@ describe('cluster', () => { }, }); - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { DBParameterGroupName: { Ref: 'ParameterGroup5E32DECB', }, @@ -343,7 +342,7 @@ describe('cluster', () => { }, }); - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { EnablePerformanceInsights: true, PerformanceInsightsRetentionPeriod: 731, PerformanceInsightsKMSKeyId: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, @@ -367,7 +366,7 @@ describe('cluster', () => { }, }); - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { EnablePerformanceInsights: true, PerformanceInsightsRetentionPeriod: 731, }); @@ -408,7 +407,7 @@ describe('cluster', () => { }, }); - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { AutoMinorVersionUpgrade: false, }); }); @@ -427,7 +426,7 @@ describe('cluster', () => { }, }); - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { AllowMajorVersionUpgrade: true, }); }); @@ -446,7 +445,7 @@ describe('cluster', () => { }, }); - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { DeleteAutomatedBackups: false, }); }); @@ -471,7 +470,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { Engine: 'aurora-mysql', EngineVersion: '5.7.mysql_aurora.2.04.4', }); @@ -497,12 +496,10 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { Engine: 'aurora-postgresql', EngineVersion: '10.7', }); - - }); test('cluster exposes different read and write endpoints', () => { @@ -546,7 +543,7 @@ describe('cluster', () => { cluster.connections.allowToAnyIpv4(ec2.Port.tcp(443)); // THEN - expect(stack).toHaveResource('AWS::EC2::SecurityGroupEgress', { + Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroupEgress', { GroupId: 'sg-123456789', }); }); @@ -559,8 +556,6 @@ describe('cluster', () => { }); expect(cluster.clusterIdentifier).toEqual('identifier'); - - }); test('minimal imported cluster throws on accessing attributes for unprovided parameters', () => { @@ -621,8 +616,6 @@ describe('cluster', () => { account: '12345', region: 'us-test-1', }); - - }); test('cluster with enabled monitoring', () => { @@ -645,14 +638,14 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { MonitoringInterval: 60, MonitoringRoleArn: { 'Fn::GetAtt': ['DatabaseMonitoringRole576991DA', 'Arn'], }, - }, ResourcePart.Properties); + }); - expect(stack).toHaveResource('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [ { @@ -680,8 +673,6 @@ describe('cluster', () => { }, ], }); - - }); test('create a cluster with imported monitoring role', () => { @@ -712,14 +703,12 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { MonitoringInterval: 60, MonitoringRoleArn: { 'Fn::GetAtt': ['MonitoringRole90457BF9', 'Arn'], }, - }, ResourcePart.Properties); - - + }); }); test('addRotationSingleUser()', () => { @@ -737,7 +726,7 @@ describe('cluster', () => { // WHEN cluster.addRotationSingleUser(); - expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::RotationSchedule', { SecretId: { Ref: 'DatabaseSecretAttachmentE5D1B020', }, @@ -768,7 +757,7 @@ describe('cluster', () => { const userSecret = new DatabaseSecret(stack, 'UserSecret', { username: 'user' }); cluster.addRotationMultiUser('user', { secret: userSecret.attach(cluster) }); - expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::RotationSchedule', { SecretId: { Ref: 'UserSecretAttachment16ACBE6D', }, @@ -783,7 +772,7 @@ describe('cluster', () => { }, }); - expect(stack).toHaveResourceLike('AWS::Serverless::Application', { + Template.fromStack(stack).hasResourceProperties('AWS::Serverless::Application', { Parameters: { masterSecretArn: { Ref: 'DatabaseSecretAttachmentE5D1B020', @@ -822,13 +811,13 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::RotationSchedule', { RotationRules: { AutomaticallyAfterDays: 15, }, }); - expect(stack).toHaveResource('AWS::Serverless::Application', { + Template.fromStack(stack).hasResourceProperties('AWS::Serverless::Application', { Parameters: { endpoint: { 'Fn::Join': ['', [ @@ -882,7 +871,7 @@ describe('cluster', () => { // Rotation in isolated subnet with access to Secrets Manager API via endpoint cluster.addRotationSingleUser({ endpoint }); - expect(stack).toHaveResource('AWS::Serverless::Application', { + Template.fromStack(stack).hasResourceProperties('AWS::Serverless::Application', { Parameters: { endpoint: { 'Fn::Join': ['', [ @@ -933,8 +922,6 @@ describe('cluster', () => { // THEN expect(() => cluster.addRotationSingleUser()).toThrow(/without secret/); - - }); test('throws when trying to add single user rotation multiple times', () => { @@ -955,8 +942,6 @@ describe('cluster', () => { // THEN expect(() => cluster.addRotationSingleUser()).toThrow(/A single user rotation was already added to this cluster/); - - }); test('create a cluster with s3 import role', () => { @@ -983,7 +968,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { AssociatedRoles: [{ RoleArn: { 'Fn::GetAtt': [ @@ -994,7 +979,7 @@ describe('cluster', () => { }], }); - expect(stack).toHaveResource('AWS::RDS::DBClusterParameterGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBClusterParameterGroup', { Family: 'aurora5.6', Parameters: { aurora_load_from_s3_role: { @@ -1005,8 +990,6 @@ describe('cluster', () => { }, }, }); - - }); test('create a cluster with s3 import buckets', () => { @@ -1031,7 +1014,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { AssociatedRoles: [{ RoleArn: { 'Fn::GetAtt': [ @@ -1042,7 +1025,7 @@ describe('cluster', () => { }], }); - expect(stack).toHaveResource('AWS::RDS::DBClusterParameterGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBClusterParameterGroup', { Family: 'aurora5.6', Parameters: { aurora_load_from_s3_role: { @@ -1054,7 +1037,7 @@ describe('cluster', () => { }, }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -1091,8 +1074,6 @@ describe('cluster', () => { Version: '2012-10-17', }, }); - - }); test('cluster with s3 import bucket adds supported feature name to IAM role', () => { @@ -1119,7 +1100,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { AssociatedRoles: [{ RoleArn: { 'Fn::GetAtt': [ @@ -1130,8 +1111,6 @@ describe('cluster', () => { FeatureName: 's3Import', }], }); - - }); test('throws when s3 import bucket or s3 export bucket is supplied for a Postgres version that does not support it', () => { @@ -1175,8 +1154,6 @@ describe('cluster', () => { s3ExportBuckets: [bucket], }); }).toThrow(/s3Export is not supported for Postgres version: 10.4. Use a version that supports the s3Export feature./); - - }); test('cluster with s3 export bucket adds supported feature name to IAM role', () => { @@ -1203,7 +1180,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { AssociatedRoles: [{ RoleArn: { 'Fn::GetAtt': [ @@ -1214,8 +1191,6 @@ describe('cluster', () => { FeatureName: 's3Export', }], }); - - }); test('create a cluster with s3 export role', () => { @@ -1242,7 +1217,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { AssociatedRoles: [{ RoleArn: { 'Fn::GetAtt': [ @@ -1253,7 +1228,7 @@ describe('cluster', () => { }], }); - expect(stack).toHaveResource('AWS::RDS::DBClusterParameterGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBClusterParameterGroup', { Family: 'aurora5.6', Parameters: { aurora_select_into_s3_role: { @@ -1264,8 +1239,6 @@ describe('cluster', () => { }, }, }); - - }); testFutureBehavior('create a cluster with s3 export buckets', { [cxapi.S3_GRANT_WRITE_WITHOUT_ACL]: true }, cdk.App, (app) => { @@ -1290,7 +1263,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { AssociatedRoles: [{ RoleArn: { 'Fn::GetAtt': [ @@ -1301,7 +1274,7 @@ describe('cluster', () => { }], }); - expect(stack).toHaveResource('AWS::RDS::DBClusterParameterGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBClusterParameterGroup', { Family: 'aurora5.6', Parameters: { aurora_select_into_s3_role: { @@ -1313,7 +1286,7 @@ describe('cluster', () => { }, }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -1353,8 +1326,6 @@ describe('cluster', () => { Version: '2012-10-17', }, }); - - }); test('create a cluster with s3 import and export buckets', () => { @@ -1381,7 +1352,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { AssociatedRoles: [{ RoleArn: { 'Fn::GetAtt': [ @@ -1400,7 +1371,7 @@ describe('cluster', () => { }], }); - expect(stack).toHaveResource('AWS::RDS::DBClusterParameterGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBClusterParameterGroup', { Family: 'aurora5.6', Parameters: { aurora_load_from_s3_role: { @@ -1417,8 +1388,6 @@ describe('cluster', () => { }, }, }); - - }); test('create a cluster with s3 import and export buckets and custom parameter group', () => { @@ -1453,7 +1422,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { AssociatedRoles: [{ RoleArn: { 'Fn::GetAtt': [ @@ -1472,7 +1441,7 @@ describe('cluster', () => { }], }); - expect(stack).toHaveResource('AWS::RDS::DBClusterParameterGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBClusterParameterGroup', { Family: 'aurora5.6', Parameters: { key: 'value', @@ -1490,8 +1459,6 @@ describe('cluster', () => { }, }, }); - - }); test('PostgreSQL cluster with s3 export buckets does not generate custom parameter group and specifies the correct port', () => { @@ -1518,7 +1485,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { AssociatedRoles: [{ RoleArn: { 'Fn::GetAtt': [ @@ -1531,9 +1498,7 @@ describe('cluster', () => { Port: 5432, }); - expect(stack).not.toHaveResource('AWS::RDS::DBClusterParameterGroup'); - - + Template.fromStack(stack).resourceCountIs('AWS::RDS::DBClusterParameterGroup', 0); }); test('unversioned PostgreSQL cluster can be used with s3 import and s3 export buckets', () => { @@ -1560,7 +1525,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { AssociatedRoles: [ { FeatureName: 's3Import', @@ -1582,8 +1547,6 @@ describe('cluster', () => { }, ], }); - - }); test("Aurora PostgreSQL cluster uses a different default master username than 'admin', which is a reserved word", () => { @@ -1600,13 +1563,11 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { GenerateSecretString: { SecretStringTemplate: '{"username":"postgres"}', }, }); - - }); test('MySQL cluster without S3 exports or imports references the correct default ParameterGroup', () => { @@ -1628,13 +1589,11 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { DBClusterParameterGroupName: 'default.aurora-mysql5.7', }); - expect(stack).not.toHaveResource('AWS::RDS::DBClusterParameterGroup'); - - + Template.fromStack(stack).resourceCountIs('AWS::RDS::DBClusterParameterGroup', 0); }); test('throws when s3ExportRole and s3ExportBuckets properties are both specified', () => { @@ -1661,8 +1620,6 @@ describe('cluster', () => { s3ExportRole: exportRole, s3ExportBuckets: [exportBucket], })).toThrow(); - - }); test('throws when s3ImportRole and s3ImportBuckets properties are both specified', () => { @@ -1689,8 +1646,6 @@ describe('cluster', () => { s3ImportRole: importRole, s3ImportBuckets: [importBucket], })).toThrow(); - - }); test('can set CloudWatch log exports', () => { @@ -1713,11 +1668,9 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { EnableCloudwatchLogsExports: ['error', 'general', 'slowquery', 'audit'], }); - - }); test('can set CloudWatch log retention', () => { @@ -1741,7 +1694,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('Custom::LogRetention', { + Template.fromStack(stack).hasResourceProperties('Custom::LogRetention', { ServiceToken: { 'Fn::GetAtt': [ 'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A', @@ -1751,7 +1704,7 @@ describe('cluster', () => { LogGroupName: { 'Fn::Join': ['', ['/aws/rds/cluster/', { Ref: 'DatabaseB269D8BB' }, '/error']] }, RetentionInDays: 90, }); - expect(stack).toHaveResource('Custom::LogRetention', { + Template.fromStack(stack).hasResourceProperties('Custom::LogRetention', { ServiceToken: { 'Fn::GetAtt': [ 'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A', @@ -1761,8 +1714,6 @@ describe('cluster', () => { LogGroupName: { 'Fn::Join': ['', ['/aws/rds/cluster/', { Ref: 'DatabaseB269D8BB' }, '/general']] }, RetentionInDays: 90, }); - - }); test('throws if given unsupported CloudWatch log exports', () => { @@ -1784,8 +1735,6 @@ describe('cluster', () => { cloudwatchLogsExports: ['error', 'general', 'slowquery', 'audit', 'thislogdoesnotexist', 'neitherdoesthisone'], }); }).toThrow(/Unsupported logs for the current engine type: thislogdoesnotexist,neitherdoesthisone/); - - }); test('can set deletion protection', () => { @@ -1808,16 +1757,15 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { DeletionProtection: true, }); - - }); test('does not throw (but adds a node error) if a (dummy) VPC does not have sufficient subnets', () => { // GIVEN - const stack = testStack(); + const app = new cdk.App(); + const stack = testStack(app, 'TestStack'); const vpc = ec2.Vpc.fromLookup(stack, 'VPC', { isDefault: true }); // WHEN @@ -1837,11 +1785,9 @@ describe('cluster', () => { }); // THEN - const art = SynthUtils.synthesize(stack); + const art = app.synth().getStackArtifact('TestStack'); const meta = art.findMetadataByType('aws:cdk:error'); expect(meta[0].data).toEqual('Cluster requires at least 2 subnets, got 0'); - - }); test('create a cluster from a snapshot', () => { @@ -1859,7 +1805,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResource('AWS::RDS::DBCluster', { Properties: { Engine: 'aurora', EngineVersion: '5.6.mysql_aurora.1.22.2', @@ -1870,9 +1816,9 @@ describe('cluster', () => { }, DeletionPolicy: 'Snapshot', UpdateReplacePolicy: 'Snapshot', - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toCountResources('AWS::RDS::DBInstance', 2); + Template.fromStack(stack).resourceCountIs('AWS::RDS::DBInstance', 2); expect(cluster.instanceIdentifiers).toHaveLength(2); expect(stack.resolve(cluster.instanceIdentifiers[0])).toEqual({ @@ -1915,12 +1861,10 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { DBSubnetGroupName: 'my-subnet-group', }); - expect(stack).toCountResources('AWS::RDS::DBSubnetGroup', 0); - - + Template.fromStack(stack).resourceCountIs('AWS::RDS::DBSubnetGroup', 0); }); test('defaultChild returns the DB Cluster', () => { @@ -1941,8 +1885,6 @@ describe('cluster', () => { // THEN expect(cluster.node.defaultChild instanceof CfnDBCluster).toBeTruthy(); - - }); test('fromGeneratedSecret', () => { @@ -1960,7 +1902,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { MasterUsername: 'admin', // username is a string MasterUserPassword: { 'Fn::Join': [ @@ -1994,7 +1936,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { ReplicaRegions: [ { Region: 'eu-west-1', @@ -2023,7 +1965,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { Name: secretName, }); }); @@ -2044,7 +1986,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { Name: secretName, }); }); @@ -2066,12 +2008,10 @@ describe('cluster', () => { }, }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { Engine: 'aurora', PubliclyAccessible: true, }); - - }); test('can set public accessibility for database cluster with instances in public subnet', () => { @@ -2091,12 +2031,10 @@ describe('cluster', () => { }, }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { Engine: 'aurora', PubliclyAccessible: false, }); - - }); test('database cluster instances in public subnet should by default have publiclyAccessible set to true', () => { @@ -2115,12 +2053,10 @@ describe('cluster', () => { }, }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { Engine: 'aurora', PubliclyAccessible: true, }); - - }); test('changes the case of the cluster identifier if the lowercaseDbIdentifier feature flag is enabled', () => { @@ -2140,7 +2076,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { DBClusterIdentifier: clusterIdentifier.toLowerCase(), }); }); @@ -2160,7 +2096,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { DBClusterIdentifier: clusterIdentifier, }); }); @@ -2179,7 +2115,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { CopyTagsToSnapshot: true, }); }); @@ -2199,7 +2135,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { CopyTagsToSnapshot: false, }); }); @@ -2219,7 +2155,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { CopyTagsToSnapshot: true, }); }); @@ -2239,7 +2175,7 @@ describe('cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { BacktrackWindow: 24 * 60 * 60, }); }); @@ -2247,8 +2183,8 @@ describe('cluster', () => { test.each([ [cdk.RemovalPolicy.RETAIN, 'Retain', 'Retain', 'Retain'], - [cdk.RemovalPolicy.SNAPSHOT, 'Snapshot', 'Delete', ABSENT], - [cdk.RemovalPolicy.DESTROY, 'Delete', 'Delete', ABSENT], + [cdk.RemovalPolicy.SNAPSHOT, 'Snapshot', 'Delete', Match.absent()], + [cdk.RemovalPolicy.DESTROY, 'Delete', 'Delete', Match.absent()], ])('if Cluster RemovalPolicy is \'%s\', the DBCluster has DeletionPolicy \'%s\', the DBInstance has \'%s\' and the DBSubnetGroup has \'%s\'', (clusterRemovalPolicy, clusterValue, instanceValue, subnetValue) => { const stack = new cdk.Stack(); @@ -2264,25 +2200,25 @@ test.each([ }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResource('AWS::RDS::DBCluster', { DeletionPolicy: clusterValue, UpdateReplacePolicy: clusterValue, - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResource('AWS::RDS::DBInstance', { DeletionPolicy: instanceValue, UpdateReplacePolicy: instanceValue, - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResourceLike('AWS::RDS::DBSubnetGroup', { + Template.fromStack(stack).hasResource('AWS::RDS::DBSubnetGroup', { DeletionPolicy: subnetValue, - }, ResourcePart.CompleteDefinition); + }); }); test.each([ [cdk.RemovalPolicy.RETAIN, 'Retain', 'Retain', 'Retain'], - [cdk.RemovalPolicy.SNAPSHOT, 'Snapshot', 'Delete', ABSENT], - [cdk.RemovalPolicy.DESTROY, 'Delete', 'Delete', ABSENT], + [cdk.RemovalPolicy.SNAPSHOT, 'Snapshot', 'Delete', Match.absent()], + [cdk.RemovalPolicy.DESTROY, 'Delete', 'Delete', Match.absent()], ])('if Cluster RemovalPolicy is \'%s\', the DBCluster has DeletionPolicy \'%s\', the DBInstance has \'%s\' and the DBSubnetGroup has \'%s\'', (clusterRemovalPolicy, clusterValue, instanceValue, subnetValue) => { const stack = new cdk.Stack(); @@ -2298,25 +2234,24 @@ test.each([ }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResource('AWS::RDS::DBCluster', { DeletionPolicy: clusterValue, UpdateReplacePolicy: clusterValue, - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResource('AWS::RDS::DBInstance', { DeletionPolicy: instanceValue, UpdateReplacePolicy: instanceValue, - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResourceLike('AWS::RDS::DBSubnetGroup', { + Template.fromStack(stack).hasResource('AWS::RDS::DBSubnetGroup', { DeletionPolicy: subnetValue, UpdateReplacePolicy: subnetValue, - }, ResourcePart.CompleteDefinition); + }); }); - -function testStack(app?: cdk.App) { - const stack = new cdk.Stack(app, undefined, { env: { account: '12345', region: 'us-test-1' } }); +function testStack(app?: cdk.App, stackId?: string) { + const stack = new cdk.Stack(app, stackId, { env: { account: '12345', region: 'us-test-1' } }); stack.node.setContext('availability-zones:12345:us-test-1', ['us-test-1a', 'us-test-1b']); return stack; } diff --git a/packages/@aws-cdk/aws-rds/test/database-secret.test.ts b/packages/@aws-cdk/aws-rds/test/database-secret.test.ts index 9fe7793536a1c..424fe4164fd74 100644 --- a/packages/@aws-cdk/aws-rds/test/database-secret.test.ts +++ b/packages/@aws-cdk/aws-rds/test/database-secret.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import { CfnResource, Stack } from '@aws-cdk/core'; import { DatabaseSecret } from '../lib'; import { DEFAULT_PASSWORD_EXCLUDE_CHARS } from '../lib/private/util'; @@ -14,7 +14,7 @@ describe('database secret', () => { }); // THEN - expect(stack).toHaveResource('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { Description: { 'Fn::Join': [ '', @@ -35,8 +35,6 @@ describe('database secret', () => { }); expect(getSecretLogicalId(dbSecret, stack)).toEqual('SecretA720EF05'); - - }); test('with master secret', () => { @@ -54,7 +52,7 @@ describe('database secret', () => { }); // THEN - expect(stack).toHaveResource('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { GenerateSecretString: { ExcludeCharacters: '"@/\\', GenerateStringKey: 'password', @@ -73,8 +71,6 @@ describe('database secret', () => { }, }, }); - - }); test('replace on password critera change', () => { @@ -106,8 +102,6 @@ describe('database secret', () => { replaceOnPasswordCriteriaChanges: true, }); expect(dbSecretlogicalId).not.toEqual(getSecretLogicalId(otherSecret2, stack)); - - }); }); diff --git a/packages/@aws-cdk/aws-rds/test/database-secretmanager.test.ts b/packages/@aws-cdk/aws-rds/test/database-secretmanager.test.ts index 135704a893f75..8d1da9e04b1b6 100644 --- a/packages/@aws-cdk/aws-rds/test/database-secretmanager.test.ts +++ b/packages/@aws-cdk/aws-rds/test/database-secretmanager.test.ts @@ -1,5 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; -import { ResourcePart } from '@aws-cdk/assert-internal'; +import { Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import * as cdk from '@aws-cdk/core'; @@ -21,7 +20,7 @@ describe('database secret manager', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResource('AWS::RDS::DBCluster', { Properties: { Engine: 'aurora-postgresql', DBClusterParameterGroupName: 'default.aurora-postgresql10', @@ -65,9 +64,7 @@ describe('database secret manager', () => { }, DeletionPolicy: 'Snapshot', UpdateReplacePolicy: 'Snapshot', - }, ResourcePart.CompleteDefinition); - - + }); }); }); diff --git a/packages/@aws-cdk/aws-rds/test/instance-engine.test.ts b/packages/@aws-cdk/aws-rds/test/instance-engine.test.ts index 99ed162c4f5eb..f9a93f3bca867 100644 --- a/packages/@aws-cdk/aws-rds/test/instance-engine.test.ts +++ b/packages/@aws-cdk/aws-rds/test/instance-engine.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as rds from '../lib'; @@ -10,8 +10,6 @@ describe('instance engine', () => { const family = engine.parameterGroupFamily; expect(family).toEqual(undefined); - - }); test('default parameterGroupFamily for versionless MySQL instance engine is not defined', () => { @@ -20,8 +18,6 @@ describe('instance engine', () => { const family = engine.parameterGroupFamily; expect(family).toEqual(undefined); - - }); test('default parameterGroupFamily for versionless PostgreSQL instance engine is not defined', () => { @@ -30,8 +26,6 @@ describe('instance engine', () => { const family = engine.parameterGroupFamily; expect(family).toEqual(undefined); - - }); test("default parameterGroupFamily for versionless Oracle SE instance engine is 'oracle-se-11.2'", () => { @@ -40,8 +34,6 @@ describe('instance engine', () => { const family = engine.parameterGroupFamily; expect(family).toEqual('oracle-se-11.2'); - - }); test("default parameterGroupFamily for versionless Oracle SE 1 instance engine is 'oracle-se1-11.2'", () => { @@ -50,8 +42,6 @@ describe('instance engine', () => { const family = engine.parameterGroupFamily; expect(family).toEqual('oracle-se1-11.2'); - - }); test('default parameterGroupFamily for versionless Oracle SE 2 instance engine is not defined', () => { @@ -60,8 +50,6 @@ describe('instance engine', () => { const family = engine.parameterGroupFamily; expect(family).toEqual(undefined); - - }); test('default parameterGroupFamily for versionless Oracle EE instance engine is not defined', () => { @@ -70,8 +58,6 @@ describe('instance engine', () => { const family = engine.parameterGroupFamily; expect(family).toEqual(undefined); - - }); test('default parameterGroupFamily for versionless SQL Server SE instance engine is not defined', () => { @@ -80,8 +66,6 @@ describe('instance engine', () => { const family = engine.parameterGroupFamily; expect(family).toEqual(undefined); - - }); test('default parameterGroupFamily for versionless SQL Server EX instance engine is not defined', () => { @@ -90,8 +74,6 @@ describe('instance engine', () => { const family = engine.parameterGroupFamily; expect(family).toEqual(undefined); - - }); test('default parameterGroupFamily for versionless SQL Server Web instance engine is not defined', () => { @@ -100,8 +82,6 @@ describe('instance engine', () => { const family = engine.parameterGroupFamily; expect(family).toEqual(undefined); - - }); test('default parameterGroupFamily for versionless SQL Server EE instance engine is not defined', () => { @@ -110,8 +90,6 @@ describe('instance engine', () => { const family = engine.parameterGroupFamily; expect(family).toEqual(undefined); - - }); describe('Oracle engine bindToInstance', () => { @@ -122,8 +100,6 @@ describe('instance engine', () => { const engineConfig = engine.bindToInstance(new cdk.Stack(), {}); expect(engineConfig.features?.s3Import).toEqual('S3_INTEGRATION'); expect(engineConfig.features?.s3Export).toEqual('S3_INTEGRATION'); - - }); test('s3 import/export - creates an option group if needed', () => { @@ -136,15 +112,13 @@ describe('instance engine', () => { }); expect(engineConfig.optionGroup).toBeDefined(); - expect(stack).toHaveResourceLike('AWS::RDS::OptionGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::OptionGroup', { EngineName: 'oracle-se2', OptionConfigurations: [{ OptionName: 'S3_INTEGRATION', OptionVersion: '1.0', }], }); - - }); test('s3 import/export - appends to an existing option group if it exists', () => { @@ -163,7 +137,7 @@ describe('instance engine', () => { }); expect(engineConfig.optionGroup).toEqual(optionGroup); - expect(stack).toHaveResourceLike('AWS::RDS::OptionGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::OptionGroup', { EngineName: 'oracle-se2', OptionConfigurations: [{ OptionName: 'MY_OPTION_CONFIG', @@ -173,8 +147,6 @@ describe('instance engine', () => { OptionVersion: '1.0', }], }); - - }); }); @@ -185,8 +157,6 @@ describe('instance engine', () => { const engineConfig = engine.bindToInstance(new cdk.Stack(), {}); expect(engineConfig.features?.s3Import).toEqual('S3_INTEGRATION'); expect(engineConfig.features?.s3Export).toEqual('S3_INTEGRATION'); - - }); test('s3 import/export - throws if roles are not equal', () => { @@ -200,8 +170,6 @@ describe('instance engine', () => { expect(() => engine.bindToInstance(new cdk.Stack(), { s3ImportRole })).not.toThrow(); expect(() => engine.bindToInstance(new cdk.Stack(), { s3ExportRole })).not.toThrow(); expect(() => engine.bindToInstance(new cdk.Stack(), { s3ImportRole, s3ExportRole: s3ImportRole })).not.toThrow(); - - }); test('s3 import/export - creates an option group if needed', () => { @@ -214,7 +182,7 @@ describe('instance engine', () => { }); expect(engineConfig.optionGroup).toBeDefined(); - expect(stack).toHaveResourceLike('AWS::RDS::OptionGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::OptionGroup', { EngineName: 'sqlserver-se', OptionConfigurations: [{ OptionName: 'SQLSERVER_BACKUP_RESTORE', @@ -224,8 +192,6 @@ describe('instance engine', () => { }], }], }); - - }); test('s3 import/export - appends to an existing option group if it exists', () => { @@ -244,7 +210,7 @@ describe('instance engine', () => { }); expect(engineConfig.optionGroup).toEqual(optionGroup); - expect(stack).toHaveResourceLike('AWS::RDS::OptionGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::OptionGroup', { EngineName: 'sqlserver-se', OptionConfigurations: [{ OptionName: 'MY_OPTION_CONFIG', @@ -257,8 +223,6 @@ describe('instance engine', () => { }], }], }); - - }); }); @@ -269,8 +233,6 @@ describe('instance engine', () => { const engineConfig = engineNewerVersion.bindToInstance(new cdk.Stack(), {}); expect(engineConfig.features?.s3Import).toEqual(undefined); expect(engineConfig.features?.s3Export).toEqual(undefined); - - }); test('returns s3 import/export feature if the version supports it', () => { @@ -279,8 +241,6 @@ describe('instance engine', () => { const engineConfig = engineNewerVersion.bindToInstance(new cdk.Stack(), {}); expect(engineConfig.features?.s3Import).toEqual('s3Import'); expect(engineConfig.features?.s3Export).toEqual('s3Export'); - - }); }); }); diff --git a/packages/@aws-cdk/aws-rds/test/instance.test.ts b/packages/@aws-cdk/aws-rds/test/instance.test.ts index 484ad765cb790..29ced7cbb1940 100644 --- a/packages/@aws-cdk/aws-rds/test/instance.test.ts +++ b/packages/@aws-cdk/aws-rds/test/instance.test.ts @@ -1,5 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; -import { ABSENT, ResourcePart, anything } from '@aws-cdk/assert-internal'; +import { Match, Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as targets from '@aws-cdk/aws-events-targets'; import { ManagedPolicy, Role, ServicePrincipal, AccountPrincipal } from '@aws-cdk/aws-iam'; @@ -49,7 +48,7 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResource('AWS::RDS::DBInstance', { Properties: { DBInstanceClass: 'db.t2.medium', AllocatedStorage: '100', @@ -117,9 +116,9 @@ describe('instance', () => { }, DeletionPolicy: 'Snapshot', UpdateReplacePolicy: 'Snapshot', - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResource('AWS::RDS::DBSubnetGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBSubnetGroup', { DBSubnetGroupDescription: 'Subnet group for Instance database', SubnetIds: [ { @@ -131,11 +130,11 @@ describe('instance', () => { ], }); - expect(stack).toHaveResource('AWS::EC2::SecurityGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroup', { GroupDescription: 'Security group for Instance database', }); - expect(stack).toHaveResource('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [ { @@ -164,7 +163,7 @@ describe('instance', () => { ], }); - expect(stack).toHaveResource('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { Description: { 'Fn::Join': [ '', @@ -184,7 +183,7 @@ describe('instance', () => { }, }); - expect(stack).toHaveResource('AWS::SecretsManager::SecretTargetAttachment', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::SecretTargetAttachment', { SecretId: { Ref: 'InstanceSecret478E0A47', }, @@ -194,9 +193,7 @@ describe('instance', () => { TargetType: 'AWS::RDS::DBInstance', }); - expect(stack).toCountResources('Custom::LogRetention', 4); - - + Template.fromStack(stack).resourceCountIs('Custom::LogRetention', 4); }); test('throws when create database with specific AZ and multiAZ enabled', () => { @@ -239,7 +236,7 @@ describe('instance', () => { parameterGroup, }); - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { DBParameterGroupName: { Ref: 'ParameterGroup5E32DECB', }, @@ -247,8 +244,6 @@ describe('instance', () => { Ref: 'OptionGroupACA43DC1', }, }); - - }); test('can specify subnet type', () => { @@ -263,13 +258,13 @@ describe('instance', () => { }, }); - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { DBSubnetGroupName: { Ref: 'InstanceSubnetGroupF2CBA54F', }, PubliclyAccessible: false, }); - expect(stack).toHaveResource('AWS::RDS::DBSubnetGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBSubnetGroup', { DBSubnetGroupDescription: 'Subnet group for Instance database', SubnetIds: [ { @@ -280,8 +275,6 @@ describe('instance', () => { }, ], }); - - }); describe('DatabaseInstanceFromSnapshot', () => { @@ -293,11 +286,9 @@ describe('instance', () => { vpc, }); - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { DBSnapshotIdentifier: 'my-snapshot', }); - - }); test('can generate a new snapshot password', () => { @@ -310,8 +301,8 @@ describe('instance', () => { }), }); - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { - MasterUsername: ABSENT, + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { + MasterUsername: Match.absent(), MasterUserPassword: { 'Fn::Join': ['', [ '{{resolve:secretsmanager:', @@ -320,7 +311,7 @@ describe('instance', () => { ]], }, }); - expect(stack).toHaveResource('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { Description: { 'Fn::Join': ['', ['Generated by the CDK for stack: ', { Ref: 'AWS::StackName' }]], }, @@ -331,8 +322,6 @@ describe('instance', () => { SecretStringTemplate: '{"username":"admin"}', }, }); - - }); test('fromGeneratedSecret with replica regions', () => { @@ -345,7 +334,7 @@ describe('instance', () => { }), }); - expect(stack).toHaveResource('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { ReplicaRegions: [ { Region: 'eu-west-1', @@ -361,8 +350,6 @@ describe('instance', () => { vpc, credentials: { generatePassword: true }, })).toThrow(/`credentials` `username` must be specified when `generatePassword` is set to true/); - - }); test('can set a new snapshot password from an existing SecretValue', () => { @@ -374,12 +361,10 @@ describe('instance', () => { }); // TODO - Expect this to be broken - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { - MasterUsername: ABSENT, + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { + MasterUsername: Match.absent(), MasterUserPassword: 'mysecretpassword', }); - - }); test('can set a new snapshot password from an existing Secret', () => { @@ -394,14 +379,12 @@ describe('instance', () => { credentials: rds.SnapshotCredentials.fromSecret(secret), }); - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { - MasterUsername: ABSENT, + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { + MasterUsername: Match.absent(), MasterUserPassword: { 'Fn::Join': ['', ['{{resolve:secretsmanager:', { Ref: 'DBSecretD58955BC' }, ':SecretString:password::}}']], }, }); - - }); test('can create a new database instance with fromDatabaseInstanceAttributes using a token for the port', () => { @@ -425,9 +408,9 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveOutput({ - exportName: 'databaseUrl', - outputValue: { + Template.fromStack(stack).hasOutput('portOutput', { + Export: { Name: 'databaseUrl' }, + Value: { Ref: 'DatabasePort', }, }); @@ -449,7 +432,7 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { SourceDBInstanceIdentifier: { 'Fn::Join': ['', [ 'arn:', @@ -466,8 +449,6 @@ describe('instance', () => { Ref: 'ReadReplicaSubnetGroup680C605C', }, }); - - }); test('on event', () => { @@ -485,7 +466,7 @@ describe('instance', () => { instance.onEvent('InstanceEvent', { target: new targets.LambdaFunction(fn) }); // THEN - expect(stack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { EventPattern: { source: [ 'aws.rds', @@ -528,8 +509,6 @@ describe('instance', () => { }, ], }); - - }); test('on event without target', () => { @@ -542,7 +521,7 @@ describe('instance', () => { instance.onEvent('InstanceEvent'); // THEN - expect(stack).toHaveResource('AWS::Events::Rule', { + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { EventPattern: { source: [ 'aws.rds', @@ -574,8 +553,6 @@ describe('instance', () => { ], }, }); - - }); test('can use metricCPUUtilization', () => { @@ -593,8 +570,6 @@ describe('instance', () => { period: cdk.Duration.minutes(5), statistic: 'Average', }); - - }); test('can resolve endpoint port and socket address', () => { @@ -618,8 +593,6 @@ describe('instance', () => { ], ], }); - - }); test('can deactivate backup', () => { @@ -631,11 +604,9 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { BackupRetentionPeriod: 0, }); - - }); test('imported instance with imported security group with allowAllOutbound set to false', () => { @@ -652,11 +623,9 @@ describe('instance', () => { instance.connections.allowToAnyIpv4(ec2.Port.tcp(443)); // THEN - expect(stack).toHaveResource('AWS::EC2::SecurityGroupEgress', { + Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroupEgress', { GroupId: 'sg-123456789', }); - - }); test('create an instance with imported monitoring role', () => { @@ -676,14 +645,12 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { MonitoringInterval: 60, MonitoringRoleArn: { 'Fn::GetAtt': ['MonitoringRole90457BF9', 'Arn'], }, - }, ResourcePart.Properties); - - + }); }); test('create an instance with an existing security group', () => { @@ -700,11 +667,11 @@ describe('instance', () => { instance.connections.allowDefaultPortFromAnyIpv4(); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { VPCSecurityGroups: ['sg-123456789'], }); - expect(stack).toHaveResource('AWS::EC2::SecurityGroupIngress', { + Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroupIngress', { FromPort: { 'Fn::GetAtt': [ 'InstanceC1063A87', @@ -719,8 +686,6 @@ describe('instance', () => { ], }, }); - - }); test('addRotationSingleUser()', () => { @@ -734,7 +699,7 @@ describe('instance', () => { instance.addRotationSingleUser(); // THEN - expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::RotationSchedule', { SecretId: { Ref: 'DatabaseSecretAttachmentE5D1B020', }, @@ -762,7 +727,7 @@ describe('instance', () => { instance.addRotationMultiUser('user', { secret: userSecret.attach(instance) }); // THEN - expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::RotationSchedule', { SecretId: { Ref: 'UserSecretAttachment16ACBE6D', }, @@ -777,7 +742,7 @@ describe('instance', () => { }, }); - expect(stack).toHaveResourceLike('AWS::Serverless::Application', { + Template.fromStack(stack).hasResourceProperties('AWS::Serverless::Application', { Parameters: { masterSecretArn: { Ref: 'DatabaseSecretAttachmentE5D1B020', @@ -812,13 +777,13 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResource('AWS::SecretsManager::RotationSchedule', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::RotationSchedule', { RotationRules: { AutomaticallyAfterDays: 15, }, }); - expect(stack).toHaveResource('AWS::Serverless::Application', { + Template.fromStack(stack).hasResourceProperties('AWS::Serverless::Application', { Parameters: { endpoint: { 'Fn::Join': ['', [ @@ -870,7 +835,7 @@ describe('instance', () => { instance.addRotationSingleUser({ endpoint }); // THEN - expect(stack).toHaveResource('AWS::Serverless::Application', { + Template.fromStack(stack).hasResourceProperties('AWS::Serverless::Application', { Parameters: { endpoint: { 'Fn::Join': ['', [ @@ -910,8 +875,6 @@ describe('instance', () => { // THEN expect(() => instance.addRotationSingleUser()).toThrow(/without secret/); - - }); test('throws when trying to add single user rotation multiple times', () => { @@ -927,8 +890,6 @@ describe('instance', () => { // THEN expect(() => instance.addRotationSingleUser()).toThrow(/A single user rotation was already added to this instance/); - - }); test('throws when timezone is set for non-sqlserver database engine', () => { @@ -953,8 +914,6 @@ describe('instance', () => { vpc, })).toThrow(/timezone property can not be configured for/); }); - - }); test('create an instance from snapshot with maximum allocated storage', () => { @@ -967,12 +926,10 @@ describe('instance', () => { maxAllocatedStorage: 200, }); - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { DBSnapshotIdentifier: 'my-snapshot', MaxAllocatedStorage: 200, }); - - }); test('create a DB instance with maximum allocated storage', () => { @@ -985,12 +942,10 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { BackupRetentionPeriod: 0, MaxAllocatedStorage: 250, }); - - }); test('iam authentication - off by default', () => { @@ -999,11 +954,9 @@ describe('instance', () => { vpc, }); - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { - EnableIAMDatabaseAuthentication: ABSENT, + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { + EnableIAMDatabaseAuthentication: Match.absent(), }); - - }); test('createGrant - creates IAM policy and enables IAM auth', () => { @@ -1016,10 +969,10 @@ describe('instance', () => { }); instance.grantConnect(role); - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { EnableIAMDatabaseAuthentication: true, }); - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [{ Effect: 'Allow', @@ -1031,8 +984,6 @@ describe('instance', () => { Version: '2012-10-17', }, }); - - }); test('createGrant - throws if IAM auth disabled', () => { @@ -1046,8 +997,6 @@ describe('instance', () => { }); expect(() => { instance.grantConnect(role); }).toThrow(/Cannot grant connect when IAM authentication is disabled/); - - }); test('domain - sets domain property', () => { @@ -1061,11 +1010,9 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { Domain: domain, }); - - }); test('domain - uses role if provided', () => { @@ -1081,12 +1028,10 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { Domain: domain, DomainIAMRoleName: stack.resolve(role.roleName), }); - - }); test('domain - creates role if not provided', () => { @@ -1100,12 +1045,12 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { Domain: domain, - DomainIAMRoleName: anything(), + DomainIAMRoleName: Match.anyValue(), }); - expect(stack).toHaveResource('AWS::IAM::Role', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [ { @@ -1133,8 +1078,6 @@ describe('instance', () => { }, ], }); - - }); test('throws when domain is set for mariadb database engine', () => { @@ -1161,8 +1104,6 @@ describe('instance', () => { vpc, })).toThrow(expectedError); }); - - }); describe('performance insights', () => { @@ -1175,13 +1116,11 @@ describe('instance', () => { performanceInsightEncryptionKey: new kms.Key(stack, 'Key'), }); - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { EnablePerformanceInsights: true, PerformanceInsightsRetentionPeriod: 731, PerformanceInsightsKMSKeyId: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, }); - - }); test('setting performance insights fields enables performance insights', () => { @@ -1191,12 +1130,10 @@ describe('instance', () => { performanceInsightRetention: rds.PerformanceInsightRetention.LONG_TERM, }); - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { EnablePerformanceInsights: true, PerformanceInsightsRetentionPeriod: 731, }); - - }); test('throws if performance insights fields are set but performance insights is disabled', () => { @@ -1208,8 +1145,6 @@ describe('instance', () => { performanceInsightRetention: rds.PerformanceInsightRetention.DEFAULT, }); }).toThrow(/`enablePerformanceInsights` disabled, but `performanceInsightRetention` or `performanceInsightEncryptionKey` was set/); - - }); }); @@ -1220,12 +1155,10 @@ describe('instance', () => { subnetGroup: rds.SubnetGroup.fromSubnetGroupName(stack, 'SubnetGroup', 'my-subnet-group'), }); - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { DBSubnetGroupName: 'my-subnet-group', }); - expect(stack).toCountResources('AWS::RDS::DBSubnetGroup', 0); - - + Template.fromStack(stack).resourceCountIs('AWS::RDS::DBSubnetGroup', 0); }); test('defaultChild returns the DB Instance', () => { @@ -1236,8 +1169,6 @@ describe('instance', () => { // THEN expect(instance.node.defaultChild instanceof rds.CfnDBInstance).toBeTruthy(); - - }); test("PostgreSQL database instance uses a different default master username than 'admin', which is a reserved word", () => { @@ -1249,13 +1180,11 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { GenerateSecretString: { SecretStringTemplate: '{"username":"postgres"}', }, }); - - }); describe('S3 Import/Export', () => { @@ -1269,7 +1198,7 @@ describe('instance', () => { s3ExportBuckets: [new s3.Bucket(stack, 'S3Export')], }); - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { AssociatedRoles: [ { FeatureName: 'S3_INTEGRATION', @@ -1280,7 +1209,7 @@ describe('instance', () => { }); // Can read from import bucket, and read/write from export bucket - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [{ Action: [ @@ -1312,8 +1241,6 @@ describe('instance', () => { Version: '2012-10-17', }, }); - - }); test('throws if using s3 import on unsupported engine', () => { @@ -1335,8 +1262,6 @@ describe('instance', () => { s3ImportRole, }); }).toThrow(/Engine 'mysql-8.0.19' does not support S3 import/); - - }); test('throws if using s3 export on unsupported engine', () => { @@ -1358,8 +1283,6 @@ describe('instance', () => { s3ExportRole: s3ExportRole, }); }).toThrow(/Engine 'mysql-8.0.19' does not support S3 export/); - - }); test('throws if provided two different roles for import/export', () => { @@ -1378,8 +1301,6 @@ describe('instance', () => { s3ExportRole, }); }).toThrow(/S3 import and export roles must be the same/); - - }); }); @@ -1392,7 +1313,7 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { MasterUsername: 'postgres', // username is a string MasterUserPassword: { 'Fn::Join': [ @@ -1420,7 +1341,7 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResource('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { ReplicaRegions: [ { Region: 'eu-west-1', @@ -1438,7 +1359,7 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { MasterUsername: 'postgres', // username is a string MasterUserPassword: '{{resolve:ssm-secure:/dbPassword:1}}', // reference to SSM }); @@ -1460,7 +1381,7 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { Name: secretName, }); }); @@ -1477,7 +1398,7 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { Name: secretName, }); }); @@ -1494,11 +1415,9 @@ describe('instance', () => { publiclyAccessible: false, }); - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { PubliclyAccessible: false, }); - - }); test('can set publiclyAccessible to true with private subnets', () => { @@ -1513,7 +1432,7 @@ describe('instance', () => { publiclyAccessible: true, }); - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { PubliclyAccessible: true, }); }); @@ -1537,7 +1456,7 @@ describe('instance', () => { } ); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { DBInstanceIdentifier: instanceIdentifier.toLowerCase(), }); }); @@ -1559,7 +1478,7 @@ describe('instance', () => { } ); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { DBInstanceIdentifier: instanceIdentifier, }); }); @@ -1605,7 +1524,7 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { DBParameterGroupName: { Ref: 'ParameterGroup5E32DECB', }, @@ -1622,7 +1541,7 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { Port: '3306', }); }); @@ -1642,7 +1561,7 @@ describe('instance', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBInstance', { Port: { Ref: 'Port', }, @@ -1652,8 +1571,8 @@ describe('instance', () => { test.each([ [cdk.RemovalPolicy.RETAIN, 'Retain', 'Retain'], - [cdk.RemovalPolicy.SNAPSHOT, 'Snapshot', ABSENT], - [cdk.RemovalPolicy.DESTROY, 'Delete', ABSENT], + [cdk.RemovalPolicy.SNAPSHOT, 'Snapshot', Match.absent()], + [cdk.RemovalPolicy.DESTROY, 'Delete', Match.absent()], ])('if Instance RemovalPolicy is \'%s\', the instance has DeletionPolicy \'%s\' and the DBSubnetGroup has \'%s\'', (instanceRemovalPolicy, instanceValue, subnetValue) => { // GIVEN stack = new cdk.Stack(); @@ -1670,15 +1589,15 @@ test.each([ }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBInstance', { + Template.fromStack(stack).hasResource('AWS::RDS::DBInstance', { DeletionPolicy: instanceValue, UpdateReplacePolicy: instanceValue, - }, ResourcePart.CompleteDefinition); + }); - expect(stack).toHaveResourceLike('AWS::RDS::DBSubnetGroup', { + Template.fromStack(stack).hasResource('AWS::RDS::DBSubnetGroup', { DeletionPolicy: subnetValue, UpdateReplacePolicy: subnetValue, - }, ResourcePart.CompleteDefinition); + }); }); describe('cross-account instance', () => { @@ -1707,7 +1626,7 @@ describe('cross-account instance', () => { value: instance.instanceIdentifier, }); - expect(outputStack).toMatchTemplate({ + Template.fromStack(outputStack).templateMatches({ Outputs: { DatabaseInstanceArn: { Value: { diff --git a/packages/@aws-cdk/aws-rds/test/option-group.test.ts b/packages/@aws-cdk/aws-rds/test/option-group.test.ts index a0ab91ac0dfdf..5e8fb5b9f5261 100644 --- a/packages/@aws-cdk/aws-rds/test/option-group.test.ts +++ b/packages/@aws-cdk/aws-rds/test/option-group.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 cdk from '@aws-cdk/core'; import { DatabaseInstanceEngine, OptionGroup, OracleEngineVersion } from '../lib'; @@ -21,7 +21,7 @@ describe('option group', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::OptionGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::OptionGroup', { EngineName: 'oracle-se2', MajorEngineVersion: '12.1', OptionConfigurations: [ @@ -30,8 +30,6 @@ describe('option group', () => { }, ], }); - - }); test('option group with new security group', () => { @@ -55,7 +53,7 @@ describe('option group', () => { optionGroup.optionConnections.OEM.connections.allowDefaultPortFromAnyIpv4(); // THEN - expect(stack).toHaveResource('AWS::RDS::OptionGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::OptionGroup', { OptionConfigurations: [ { OptionName: 'OEM', @@ -72,7 +70,7 @@ describe('option group', () => { ], }); - expect(stack).toHaveResource('AWS::EC2::SecurityGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroup', { GroupDescription: 'Security group for OEM option', SecurityGroupIngress: [ { @@ -87,8 +85,6 @@ describe('option group', () => { Ref: 'VPCB9E5F0B4', }, }); - - }); test('option group with existing security group', () => { @@ -113,7 +109,7 @@ describe('option group', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::OptionGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::OptionGroup', { OptionConfigurations: [ { OptionName: 'OEM', @@ -129,8 +125,6 @@ describe('option group', () => { }, ], }); - - }); test('throws when using an option with port and no vpc', () => { @@ -149,7 +143,5 @@ describe('option group', () => { }, ], })).toThrow(/`port`.*`vpc`/); - - }); }); diff --git a/packages/@aws-cdk/aws-rds/test/parameter-group.test.ts b/packages/@aws-cdk/aws-rds/test/parameter-group.test.ts index bf8e789aee0ad..594fab914310a 100644 --- a/packages/@aws-cdk/aws-rds/test/parameter-group.test.ts +++ b/packages/@aws-cdk/aws-rds/test/parameter-group.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as cdk from '@aws-cdk/core'; import { DatabaseClusterEngine, ParameterGroup } from '../lib'; @@ -17,10 +17,8 @@ describe('parameter group', () => { }); // THEN - expect(stack).toCountResources('AWS::RDS::DBParameterGroup', 0); - expect(stack).toCountResources('AWS::RDS::DBClusterParameterGroup', 0); - - + Template.fromStack(stack).resourceCountIs('AWS::RDS::DBParameterGroup', 0); + Template.fromStack(stack).resourceCountIs('AWS::RDS::DBClusterParameterGroup', 0); }); test('create a parameter group when bound to an instance', () => { @@ -38,15 +36,13 @@ describe('parameter group', () => { parameterGroup.bindToInstance({}); // THEN - expect(stack).toHaveResource('AWS::RDS::DBParameterGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBParameterGroup', { Description: 'desc', Family: 'aurora5.6', Parameters: { key: 'value', }, }); - - }); test('create a parameter group when bound to a cluster', () => { @@ -64,15 +60,13 @@ describe('parameter group', () => { parameterGroup.bindToCluster({}); // THEN - expect(stack).toHaveResource('AWS::RDS::DBClusterParameterGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBClusterParameterGroup', { Description: 'desc', Family: 'aurora5.6', Parameters: { key: 'value', }, }); - - }); test('creates 2 parameter groups when bound to a cluster and an instance', () => { @@ -91,10 +85,8 @@ describe('parameter group', () => { parameterGroup.bindToInstance({}); // THEN - expect(stack).toCountResources('AWS::RDS::DBParameterGroup', 1); - expect(stack).toCountResources('AWS::RDS::DBClusterParameterGroup', 1); - - + Template.fromStack(stack).resourceCountIs('AWS::RDS::DBParameterGroup', 1); + Template.fromStack(stack).resourceCountIs('AWS::RDS::DBClusterParameterGroup', 1); }); test('Add an additional parameter to an existing parameter group', () => { @@ -114,7 +106,7 @@ describe('parameter group', () => { clusterParameterGroup.addParameter('key2', 'value2'); // THEN - expect(stack).toHaveResource('AWS::RDS::DBClusterParameterGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBClusterParameterGroup', { Description: 'desc', Family: 'aurora5.6', Parameters: { @@ -122,7 +114,5 @@ describe('parameter group', () => { key2: 'value2', }, }); - - }); }); diff --git a/packages/@aws-cdk/aws-rds/test/proxy.test.ts b/packages/@aws-cdk/aws-rds/test/proxy.test.ts index 9cd48e7686dd9..f98e36bdf3647 100644 --- a/packages/@aws-cdk/aws-rds/test/proxy.test.ts +++ b/packages/@aws-cdk/aws-rds/test/proxy.test.ts @@ -1,5 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; -import { ABSENT, ResourcePart } from '@aws-cdk/assert-internal'; +import { Match, Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import { AccountPrincipal, Role } from '@aws-cdk/aws-iam'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; @@ -15,8 +14,6 @@ describe('proxy', () => { beforeEach(() => { stack = new cdk.Stack(); vpc = new ec2.Vpc(stack, 'VPC'); - - }); test('create a DB proxy from an instance', () => { @@ -36,7 +33,7 @@ describe('proxy', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBProxy', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBProxy', { Auth: [ { AuthScheme: 'SECRETS', @@ -66,7 +63,7 @@ describe('proxy', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBProxyTargetGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBProxyTargetGroup', { DBProxyName: { Ref: 'ProxyCB0DFB71', }, @@ -78,8 +75,6 @@ describe('proxy', () => { ], TargetGroupName: 'default', }); - - }); test('create a DB proxy from a cluster', () => { @@ -99,7 +94,7 @@ describe('proxy', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBProxy', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBProxy', { Auth: [ { AuthScheme: 'SECRETS', @@ -127,7 +122,7 @@ describe('proxy', () => { }, ], }); - expect(stack).toHaveResourceLike('AWS::RDS::DBProxyTargetGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBProxyTargetGroup', { DBProxyName: { Ref: 'ProxyCB0DFB71', }, @@ -137,10 +132,10 @@ describe('proxy', () => { Ref: 'DatabaseB269D8BB', }, ], - DBInstanceIdentifiers: ABSENT, + DBInstanceIdentifiers: Match.absent(), TargetGroupName: 'default', }); - expect(stack).toHaveResourceLike('AWS::EC2::SecurityGroupIngress', { + Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroupIngress', { IpProtocol: 'tcp', Description: 'Allow connections to the database Cluster from the Proxy', FromPort: { @@ -156,8 +151,6 @@ describe('proxy', () => { 'Fn::GetAtt': ['DatabaseB269D8BB', 'Endpoint.Port'], }, }); - - }); test('One or more secrets are required.', () => { @@ -175,8 +168,6 @@ describe('proxy', () => { vpc, }); }).toThrow('One or more secrets are required.'); - - }); test('fails when trying to create a proxy for a target without an engine', () => { @@ -191,8 +182,6 @@ describe('proxy', () => { secrets: [new secretsmanager.Secret(stack, 'Secret')], }); }).toThrow(/Could not determine engine for proxy target 'Default\/Cluster'\. Please provide it explicitly when importing the resource/); - - }); test("fails when trying to create a proxy for a target with an engine that doesn't have engineFamily", () => { @@ -213,8 +202,6 @@ describe('proxy', () => { secrets: [new secretsmanager.Secret(stack, 'Secret')], }); }).toThrow(/Engine 'mariadb-10\.0\.24' does not support proxies/); - - }); test('correctly creates a proxy for an imported Cluster if its engine is known', () => { @@ -232,20 +219,18 @@ describe('proxy', () => { secrets: [new secretsmanager.Secret(stack, 'Secret')], }); - expect(stack).toHaveResourceLike('AWS::RDS::DBProxy', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBProxy', { EngineFamily: 'POSTGRESQL', }); - expect(stack).toHaveResourceLike('AWS::RDS::DBProxyTargetGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBProxyTargetGroup', { DBClusterIdentifiers: [ 'my-cluster', ], }); - expect(stack).toHaveResourceLike('AWS::EC2::SecurityGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroup', { GroupDescription: 'SecurityGroup for Database Proxy', VpcId: { Ref: 'VPCB9E5F0B4' }, }); - - }); describe('imported Proxies', () => { @@ -256,8 +241,6 @@ describe('proxy', () => { endpoint: 'my-endpoint', securityGroups: [], }); - - }); test('grant rds-db:connect in grantConnect() with a dbUser explicitly passed', () => { @@ -269,7 +252,7 @@ describe('proxy', () => { importedDbProxy.grantConnect(role, databaseUser); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [{ Effect: 'Allow', @@ -289,8 +272,6 @@ describe('proxy', () => { Version: '2012-10-17', }, }); - - }); test('throws when grantConnect() is used without a dbUser', () => { @@ -303,8 +284,6 @@ describe('proxy', () => { expect(() => { importedDbProxy.grantConnect(role); }).toThrow(/For imported Database Proxies, the dbUser is required in grantConnect/); - - }); }); @@ -328,7 +307,7 @@ describe('proxy', () => { proxy.grantConnect(role); // THEN - expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [{ Effect: 'Allow', @@ -362,8 +341,6 @@ describe('proxy', () => { Version: '2012-10-17', }, }); - - }); test('new Proxy with multiple Secrets cannot use grantConnect() without a dbUser passed', () => { @@ -391,8 +368,6 @@ describe('proxy', () => { expect(() => { proxy.grantConnect(role); }).toThrow(/When the Proxy contains multiple Secrets, you must pass a dbUser explicitly to grantConnect/); - - }); test('DBProxyTargetGroup should have dependency on the proxy targets', () => { @@ -412,7 +387,7 @@ describe('proxy', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBProxyTargetGroup', { + Template.fromStack(stack).hasResource('AWS::RDS::DBProxyTargetGroup', { Properties: { DBProxyName: { Ref: 'proxy3A1DA9C7', @@ -429,8 +404,6 @@ describe('proxy', () => { 'clusterSecurityGroupF441DCEA', 'clusterSubnets81E3593F', ], - }, ResourcePart.CompleteDefinition); - - + }); }); }); diff --git a/packages/@aws-cdk/aws-rds/test/serverless-cluster-from-snapshot.test.ts b/packages/@aws-cdk/aws-rds/test/serverless-cluster-from-snapshot.test.ts index 4953fa6da7a71..489cd91ae861d 100644 --- a/packages/@aws-cdk/aws-rds/test/serverless-cluster-from-snapshot.test.ts +++ b/packages/@aws-cdk/aws-rds/test/serverless-cluster-from-snapshot.test.ts @@ -1,5 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; -import { ABSENT, ResourcePart } from '@aws-cdk/assert-internal'; +import { Match, Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import * as cdk from '@aws-cdk/core'; @@ -18,7 +17,7 @@ describe('serverless cluster from snapshot', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResource('AWS::RDS::DBCluster', { Properties: { Engine: 'aurora-mysql', DBClusterParameterGroupName: 'default.aurora-mysql5.7', @@ -39,7 +38,7 @@ describe('serverless cluster from snapshot', () => { }, DeletionPolicy: 'Snapshot', UpdateReplacePolicy: 'Snapshot', - }, ResourcePart.CompleteDefinition); + }); }); test('can generate a new snapshot password', () => { @@ -57,8 +56,8 @@ describe('serverless cluster from snapshot', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { - MasterUsername: ABSENT, + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { + MasterUsername: Match.absent(), MasterUserPassword: { 'Fn::Join': ['', [ '{{resolve:secretsmanager:', @@ -67,7 +66,7 @@ describe('serverless cluster from snapshot', () => { ]], }, }); - expect(stack).toHaveResource('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { Description: { 'Fn::Join': ['', ['Generated by the CDK for stack: ', { Ref: 'AWS::StackName' }]], }, @@ -95,7 +94,7 @@ describe('serverless cluster from snapshot', () => { }); // THEN - expect(stack).toHaveResource('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { ReplicaRegions: [ { Region: 'eu-west-1', @@ -130,8 +129,8 @@ describe('serverless cluster from snapshot', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { - MasterUsername: ABSENT, + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { + MasterUsername: Match.absent(), MasterUserPassword: 'mysecretpassword', }); }); @@ -153,8 +152,8 @@ describe('serverless cluster from snapshot', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { - MasterUsername: ABSENT, + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { + MasterUsername: Match.absent(), MasterUserPassword: { 'Fn::Join': ['', ['{{resolve:secretsmanager:', { Ref: 'DBSecretD58955BC' }, ':SecretString:password::}}']], }, @@ -162,8 +161,8 @@ describe('serverless cluster from snapshot', () => { }); }); -function testStack(app?: cdk.App, id?: string): cdk.Stack { - const stack = new cdk.Stack(app, id, { env: { account: '12345', region: 'us-test-1' } }); +function testStack(): cdk.Stack { + const stack = new cdk.Stack(undefined, undefined, { env: { account: '12345', region: 'us-test-1' } }); stack.node.setContext('availability-zones:12345:us-test-1', ['us-test-1a', 'us-test-1b']); return stack; } diff --git a/packages/@aws-cdk/aws-rds/test/serverless-cluster.test.ts b/packages/@aws-cdk/aws-rds/test/serverless-cluster.test.ts index dff8d035b78a3..3849a99cdeb02 100644 --- a/packages/@aws-cdk/aws-rds/test/serverless-cluster.test.ts +++ b/packages/@aws-cdk/aws-rds/test/serverless-cluster.test.ts @@ -1,5 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; -import { ResourcePart, SynthUtils } from '@aws-cdk/assert-internal'; +import { Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; @@ -25,7 +24,7 @@ describe('serverless cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResource('AWS::RDS::DBCluster', { Properties: { Engine: 'aurora-postgresql', DBClusterParameterGroupName: 'default.aurora-postgresql10', @@ -47,9 +46,7 @@ describe('serverless cluster', () => { }, DeletionPolicy: 'Snapshot', UpdateReplacePolicy: 'Snapshot', - }, ResourcePart.CompleteDefinition); - - + }); }); test('can create a Serverless Cluster with Aurora Mysql database engine', () => { @@ -64,7 +61,7 @@ describe('serverless cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResource('AWS::RDS::DBCluster', { Properties: { Engine: 'aurora-mysql', DBClusterParameterGroupName: 'default.aurora-mysql5.7', @@ -108,8 +105,7 @@ describe('serverless cluster', () => { }, DeletionPolicy: 'Snapshot', UpdateReplacePolicy: 'Snapshot', - }, ResourcePart.CompleteDefinition); - + }); }); test('can create a Serverless cluster with imported vpc and security group', () => { @@ -129,7 +125,7 @@ describe('serverless cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { Engine: 'aurora-postgresql', DBClusterParameterGroupName: 'default.aurora-postgresql10', EngineMode: 'serverless', @@ -160,8 +156,6 @@ describe('serverless cluster', () => { }, VpcSecurityGroupIds: ['SecurityGroupId12345'], }); - - }); test("sets the retention policy of the SubnetGroup to 'Retain' if the Serverless Cluster is created with 'Retain'", () => { @@ -174,12 +168,10 @@ describe('serverless cluster', () => { removalPolicy: cdk.RemovalPolicy.RETAIN, }); - expect(stack).toHaveResourceLike('AWS::RDS::DBSubnetGroup', { + Template.fromStack(stack).hasResource('AWS::RDS::DBSubnetGroup', { DeletionPolicy: 'Retain', UpdateReplacePolicy: 'Retain', - }, ResourcePart.CompleteDefinition); - - + }); }); test('creates a secret when master credentials are not specified', () => { @@ -198,7 +190,7 @@ describe('serverless cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { MasterUsername: { 'Fn::Join': [ '', @@ -225,7 +217,7 @@ describe('serverless cluster', () => { }, }); - expect(stack).toHaveResource('AWS::SecretsManager::Secret', { + Template.fromStack(stack).hasResourceProperties('AWS::SecretsManager::Secret', { GenerateSecretString: { ExcludeCharacters: '"@/\\', GenerateStringKey: 'password', @@ -233,8 +225,6 @@ describe('serverless cluster', () => { SecretStringTemplate: '{"username":"myuser"}', }, }); - - }); test('create an Serverless cluster with custom KMS key for storage', () => { @@ -250,7 +240,7 @@ describe('serverless cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { KmsKeyId: { 'Fn::GetAtt': [ 'Key961B73FD', @@ -258,8 +248,6 @@ describe('serverless cluster', () => { ], }, }); - - }); test('create a cluster using a specific version of Postgresql', () => { @@ -276,13 +264,11 @@ describe('serverless cluster', () => { }); // THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { Engine: 'aurora-postgresql', EngineMode: 'serverless', EngineVersion: '10.7', }); - - }); test('cluster exposes different read and write endpoints', () => { @@ -302,8 +288,6 @@ describe('serverless cluster', () => { // THEN expect(stack.resolve(cluster.clusterEndpoint)).not .toEqual(stack.resolve(cluster.clusterReadEndpoint)); - - }); test('imported cluster with imported security group honors allowAllOutbound', () => { @@ -324,11 +308,9 @@ describe('serverless cluster', () => { cluster.connections.allowToAnyIpv4(ec2.Port.tcp(443)); // THEN - expect(stack).toHaveResource('AWS::EC2::SecurityGroupEgress', { + Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroupEgress', { GroupId: 'sg-123456789', }); - - }); test('can import a serverless cluster with minimal attributes', () => { @@ -339,8 +321,6 @@ describe('serverless cluster', () => { }); expect(cluster.clusterIdentifier).toEqual('identifier'); - - }); test('minimal imported cluster throws on accessing attributes for missing parameters', () => { @@ -352,8 +332,6 @@ describe('serverless cluster', () => { expect(() => cluster.clusterEndpoint).toThrow(/Cannot access `clusterEndpoint` of an imported cluster/); expect(() => cluster.clusterReadEndpoint).toThrow(/Cannot access `clusterReadEndpoint` of an imported cluster/); - - }); test('imported cluster can access properties if attributes are provided', () => { @@ -371,8 +349,6 @@ describe('serverless cluster', () => { expect(cluster.clusterEndpoint.socketAddress).toEqual('addr:3306'); expect(cluster.clusterReadEndpoint.socketAddress).toEqual('reader-address:3306'); - - }); test('throws when trying to add rotation to a serverless cluster without secret', () => { @@ -392,8 +368,6 @@ describe('serverless cluster', () => { // THEN expect(() => cluster.addRotationSingleUser()).toThrow(/without secret/); - - }); test('throws when trying to add single user rotation multiple times', () => { @@ -411,8 +385,6 @@ describe('serverless cluster', () => { // THEN expect(() => cluster.addRotationSingleUser()).toThrow(/A single user rotation was already added to this cluster/); - - }); test('can set deletion protection', () => { @@ -428,11 +400,9 @@ describe('serverless cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { DeletionProtection: true, }); - - }); test('can set backup retention', () => { @@ -448,16 +418,15 @@ describe('serverless cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { BackupRetentionPeriod: 2, }); - - }); test('does not throw (but adds a node error) if a (dummy) VPC does not have sufficient subnets', () => { // GIVEN - const stack = testStack(); + const app = new cdk.App(); + const stack = testStack(app, 'TestStack'); const vpc = ec2.Vpc.fromLookup(stack, 'VPC', { isDefault: true }); // WHEN @@ -470,11 +439,9 @@ describe('serverless cluster', () => { }); // THEN - const art = SynthUtils.synthesize(stack); + const art = app.synth().getStackArtifact('TestStack'); const meta = art.findMetadataByType('aws:cdk:error'); expect(meta[0].data).toEqual('Cluster requires at least 2 subnets, got 0'); - - }); test('can set scaling configuration', () => { @@ -494,7 +461,7 @@ describe('serverless cluster', () => { }); //THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { ScalingConfiguration: { AutoPause: true, MaxCapacity: 128, @@ -502,8 +469,6 @@ describe('serverless cluster', () => { SecondsUntilAutoPause: 600, }, }); - - }); test('can enable Data API', () => { @@ -519,11 +484,9 @@ describe('serverless cluster', () => { }); //THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { EnableHttpEndpoint: true, }); - - }); test('default scaling options', () => { @@ -539,13 +502,11 @@ describe('serverless cluster', () => { }); //THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { ScalingConfiguration: { AutoPause: true, }, }); - - }); test('auto pause is disabled if a time of zero is specified', () => { @@ -563,13 +524,11 @@ describe('serverless cluster', () => { }); //THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { ScalingConfiguration: { AutoPause: false, }, }); - - }); test('throws when invalid auto pause time is specified', () => { @@ -595,8 +554,6 @@ describe('serverless cluster', () => { autoPause: cdk.Duration.days(2), }, })).toThrow(/auto pause time must be between 5 minutes and 1 day./); - - }); test('throws when invalid backup retention period is specified', () => { @@ -618,8 +575,6 @@ describe('serverless cluster', () => { vpc, backupRetention: cdk.Duration.days(36), })).toThrow(/backup retention period must be between 1 and 35 days. received: 36/); - - }); test('throws error when min capacity is greater than max capacity', () => { @@ -637,8 +592,6 @@ describe('serverless cluster', () => { maxCapacity: AuroraCapacityUnit.ACU_1, }, })).toThrow(/maximum capacity must be greater than or equal to minimum capacity./); - - }); test('check that clusterArn property works', () => { @@ -669,7 +622,6 @@ describe('serverless cluster', () => { ], ], }); - }); test('can grant Data API access', () => { @@ -687,7 +639,7 @@ describe('serverless cluster', () => { cluster.grantDataApiAccess(user); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -721,8 +673,6 @@ describe('serverless cluster', () => { }, ], }); - - }); test('can grant Data API access on imported cluster with given secret', () => { @@ -742,7 +692,7 @@ describe('serverless cluster', () => { cluster.grantDataApiAccess(user); // THEN - expect(stack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -776,8 +726,6 @@ describe('serverless cluster', () => { }, ], }); - - }); test('grant Data API access enables the Data API', () => { @@ -794,11 +742,9 @@ describe('serverless cluster', () => { cluster.grantDataApiAccess(user); //THEN - expect(stack).toHaveResource('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { EnableHttpEndpoint: true, }); - - }); test('grant Data API access throws if the Data API is disabled', () => { @@ -814,8 +760,6 @@ describe('serverless cluster', () => { // WHEN expect(() => cluster.grantDataApiAccess(user)).toThrow(/Cannot grant Data API access when the Data API is disabled/); - - }); test('changes the case of the cluster identifier if the lowercaseDbIdentifier feature flag is enabled', () => { @@ -835,11 +779,9 @@ describe('serverless cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { DBClusterIdentifier: clusterIdentifier.toLowerCase(), }); - - }); test('does not change the case of the cluster identifier if the lowercaseDbIdentifier feature flag is disabled', () => { @@ -857,11 +799,9 @@ describe('serverless cluster', () => { }); // THEN - expect(stack).toHaveResourceLike('AWS::RDS::DBCluster', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBCluster', { DBClusterIdentifier: clusterIdentifier, }); - - }); }); diff --git a/packages/@aws-cdk/aws-rds/test/sql-server/sql-server.instance-engine.test.ts b/packages/@aws-cdk/aws-rds/test/sql-server/sql-server.instance-engine.test.ts index 72615d08f2093..a591dfd4a7bf1 100644 --- a/packages/@aws-cdk/aws-rds/test/sql-server/sql-server.instance-engine.test.ts +++ b/packages/@aws-cdk/aws-rds/test/sql-server/sql-server.instance-engine.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as core from '@aws-cdk/core'; import * as rds from '../../lib'; @@ -12,11 +12,9 @@ describe('sql server instance engine', () => { }), }).bindToInstance({}); - expect(stack).toHaveResourceLike('AWS::RDS::DBParameterGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBParameterGroup', { Family: 'sqlserver-web-11.0', }); - - }); test("has MajorEngineVersion ending in '11.00' for major version 11", () => { @@ -35,11 +33,9 @@ describe('sql server instance engine', () => { ], }); - expect(stack).toHaveResourceLike('AWS::RDS::OptionGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::OptionGroup', { MajorEngineVersion: '11.00', }); - - }); }); }); diff --git a/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts b/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts index 1dc9718258deb..44fd4e24482a8 100644 --- a/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts +++ b/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts @@ -1,4 +1,4 @@ -import '@aws-cdk/assert-internal/jest'; +import { Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import * as rds from '../lib'; @@ -10,7 +10,6 @@ describe('subnet group', () => { beforeEach(() => { stack = new cdk.Stack(); vpc = new ec2.Vpc(stack, 'VPC'); - }); test('creates a subnet group from minimal properties', () => { @@ -19,15 +18,13 @@ describe('subnet group', () => { vpc, }); - expect(stack).toHaveResource('AWS::RDS::DBSubnetGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBSubnetGroup', { DBSubnetGroupDescription: 'MyGroup', SubnetIds: [ { Ref: 'VPCPrivateSubnet1Subnet8BCA10E0' }, { Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A' }, ], }); - - }); test('creates a subnet group from all properties', () => { @@ -38,7 +35,7 @@ describe('subnet group', () => { vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE }, }); - expect(stack).toHaveResource('AWS::RDS::DBSubnetGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBSubnetGroup', { DBSubnetGroupDescription: 'My Shared Group', DBSubnetGroupName: 'sharedgroup', SubnetIds: [ @@ -46,8 +43,6 @@ describe('subnet group', () => { { Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A' }, ], }); - - }); test('correctly creates a subnet group with a deploy-time value for its name', () => { @@ -59,13 +54,11 @@ describe('subnet group', () => { vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE }, }); - expect(stack).toHaveResourceLike('AWS::RDS::DBSubnetGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBSubnetGroup', { DBSubnetGroupName: { Ref: 'Parameter', }, }); - - }); describe('subnet selection', () => { @@ -75,15 +68,13 @@ describe('subnet group', () => { vpc, }); - expect(stack).toHaveResource('AWS::RDS::DBSubnetGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBSubnetGroup', { DBSubnetGroupDescription: 'MyGroup', SubnetIds: [ { Ref: 'VPCPrivateSubnet1Subnet8BCA10E0' }, { Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A' }, ], }); - - }); test('can specify subnet type', () => { @@ -93,14 +84,13 @@ describe('subnet group', () => { vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, }); - expect(stack).toHaveResource('AWS::RDS::DBSubnetGroup', { + Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBSubnetGroup', { DBSubnetGroupDescription: 'MyGroup', SubnetIds: [ { Ref: 'VPCPublicSubnet1SubnetB4246D30' }, { Ref: 'VPCPublicSubnet2Subnet74179F39' }, ], }); - }); }); @@ -108,8 +98,5 @@ describe('subnet group', () => { const subnetGroup = rds.SubnetGroup.fromSubnetGroupName(stack, 'Group', 'my-subnet-group'); expect(subnetGroup.subnetGroupName).toEqual('my-subnet-group'); - - }); - });