From b3ba35e9b8b157303a29350031885eff0c73b05b Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Wed, 29 Sep 2021 11:06:18 -0700 Subject: [PATCH 01/23] feat(lambda): support for ARM architecture --- packages/@aws-cdk/aws-lambda/README.md | 25 +++++++++++ .../@aws-cdk/aws-lambda/lib/architecture.ts | 32 +++++++++++++++ packages/@aws-cdk/aws-lambda/lib/function.ts | 13 ++++++ packages/@aws-cdk/aws-lambda/lib/index.ts | 1 + packages/@aws-cdk/aws-lambda/lib/layers.ts | 8 ++++ .../@aws-cdk/aws-lambda/test/function.test.ts | 15 +++++++ .../@aws-cdk/aws-lambda/test/layers.test.ts | 14 +++++++ .../spec-source/530_Lambda_ARM_patch.json | 41 +++++++++++++++++++ 8 files changed, 149 insertions(+) create mode 100644 packages/@aws-cdk/aws-lambda/lib/architecture.ts create mode 100644 packages/@aws-cdk/cfnspec/spec-source/530_Lambda_ARM_patch.json diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index c6098ff19fe11..63b0a97e5df4a 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -326,6 +326,31 @@ new LayerVersion(this, 'MyLayer', { }); ``` +## Architecture + +Lambda functions, by default, run on compute systems that have the 64 bit x86 architecture. + +The AWS Lambda service also runs compute on the ARM architecture, which can reduce cost +for some workloads. + +A lambda function can be configured to be run on one or both of these platforms - + +```ts +new Function(this, 'MyFunction', { + ... + architectures: [ Architecture.X86_64, Architecture.ARM_64 ], +}); +``` + +Similarly, lambda layer versions can also be tagged with architectures it is compatible with. + +```ts +new LayerVersion(this, 'MyLayer', { + ... + compatibleArchitectures: [ Architecture.X86_64, Architecture.ARM_64 ], +}); +``` + ## Lambda Insights Lambda functions can be configured to use CloudWatch [Lambda Insights](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights.html) diff --git a/packages/@aws-cdk/aws-lambda/lib/architecture.ts b/packages/@aws-cdk/aws-lambda/lib/architecture.ts new file mode 100644 index 0000000000000..40edee1896755 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/lib/architecture.ts @@ -0,0 +1,32 @@ +/** + * Architectures supported by AWS Lambda + */ +export class Architecture { + /** + * 64 bit architecture with x86 instruction set. + */ + public static readonly X86_64 = new Architecture('x86_64'); + + /** + * 64 bit architecture with the ARM instruction set. + */ + public static readonly ARM_64 = new Architecture('arm64'); + + /** + * Used to specify a custom architecture name. + * Use this if the architecture name is not yet supported by the CDK. + * @param name the architecture name as recognized by AWS Lambda. + */ + public static custom(name: string) { + return new Architecture(name); + } + + /** + * The name of the architecture as recognized by the AWS Lambda service APIs. + */ + public readonly name: string; + + private constructor(archName: string) { + this.name = archName; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 9cd67a478f003..e267f17d6f056 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -7,6 +7,7 @@ import * as logs from '@aws-cdk/aws-logs'; import * as sqs from '@aws-cdk/aws-sqs'; import { Annotations, CfnResource, Duration, Fn, Lazy, Names, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; +import { Architecture } from './architecture'; import { Code, CodeConfig } from './code'; import { ICodeSigningConfig } from './code-signing-config'; import { EventInvokeConfigOptions } from './event-invoke-config'; @@ -310,6 +311,12 @@ export interface FunctionOptions extends EventInvokeConfigOptions { * @default - Not Sign the Code */ readonly codeSigningConfig?: ICodeSigningConfig; + + /** + * The system architectures compatible with this lambda function. + * @default [Architecture.X86_64] + */ + readonly architectures?: Architecture[]; } export interface FunctionProps extends FunctionOptions { @@ -680,6 +687,7 @@ export class Function extends FunctionBase { kmsKeyArn: props.environmentEncryption?.keyArn, fileSystemConfigs, codeSigningConfigArn: props.codeSigningConfig?.codeSigningConfigArn, + architectures: props.architectures?.map(a => a.name), }); resource.node.addDependency(this.role); @@ -792,6 +800,11 @@ export class Function extends FunctionBase { const runtimes = layer.compatibleRuntimes.map(runtime => runtime.name).join(', '); throw new Error(`This lambda function uses a runtime that is incompatible with this layer (${this.runtime.name} is not in [${runtimes}])`); } + + // Currently no validations for compatible architectures since Lambda service + // allows layers configured one architecture to be used with a Lambda function + // from another architecture. + this.layers.push(layer); } } diff --git a/packages/@aws-cdk/aws-lambda/lib/index.ts b/packages/@aws-cdk/aws-lambda/lib/index.ts index 41c77bb8d038d..875e05e995631 100644 --- a/packages/@aws-cdk/aws-lambda/lib/index.ts +++ b/packages/@aws-cdk/aws-lambda/lib/index.ts @@ -19,6 +19,7 @@ export * from './scalable-attribute-api'; export * from './code-signing-config'; export * from './lambda-insights'; export * from './log-retention'; +export * from './architecture'; // AWS::Lambda CloudFormation Resources: export * from './lambda.generated'; diff --git a/packages/@aws-cdk/aws-lambda/lib/layers.ts b/packages/@aws-cdk/aws-lambda/lib/layers.ts index babf91079b8b6..7176badb0ee42 100644 --- a/packages/@aws-cdk/aws-lambda/lib/layers.ts +++ b/packages/@aws-cdk/aws-lambda/lib/layers.ts @@ -1,5 +1,6 @@ import { IResource, RemovalPolicy, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; +import { Architecture } from './architecture'; import { Code } from './code'; import { CfnLayerVersion, CfnLayerVersionPermission } from './lambda.generated'; import { Runtime } from './runtime'; @@ -46,6 +47,12 @@ export interface LayerVersionProps extends LayerVersionOptions { */ readonly compatibleRuntimes?: Runtime[]; + /** + * The system architectures compatible with this layer. + * @default [Architecture.X86_64] + */ + readonly compatibleArchitectures?: Architecture[]; + /** * The content of this Layer. * @@ -196,6 +203,7 @@ export class LayerVersion extends LayerVersionBase { const resource: CfnLayerVersion = new CfnLayerVersion(this, 'Resource', { compatibleRuntimes: props.compatibleRuntimes && props.compatibleRuntimes.map(r => r.name), + compatibleArchitectures: props.compatibleArchitectures?.map(a => a.name), content: { s3Bucket: code.s3Location.bucketName, s3Key: code.s3Location.objectKey, diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index b4f79aff6cc9e..a4aa4a31e10cc 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -2174,6 +2174,21 @@ describe('function', () => { })).toThrow(/Layers are not supported for container image functions/); }); + test('specified architecture is recognized', () => { + const stack = new cdk.Stack(); + new lambda.Function(stack, 'MyFunction', { + code: lambda.Code.fromInline('foo'), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + + architectures: [lambda.Architecture.ARM_64], + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Architectures: ['arm64'], + }); + }); + }); function newTestLambda(scope: constructs.Construct) { diff --git a/packages/@aws-cdk/aws-lambda/test/layers.test.ts b/packages/@aws-cdk/aws-lambda/test/layers.test.ts index 3d8cc9d70de6f..1c416236c0980 100644 --- a/packages/@aws-cdk/aws-lambda/test/layers.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/layers.test.ts @@ -103,4 +103,18 @@ describe('layers', () => { DeletionPolicy: 'Retain', }, ResourcePart.CompleteDefinition); }); + + test('specified compatible architectures is recognized', () => { + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Bucket'); + const code = new lambda.S3Code(bucket, 'ObjectKey'); + new lambda.LayerVersion(stack, 'MyLayer', { + code, + compatibleArchitectures: [lambda.Architecture.ARM_64], + }); + + expect(stack).toHaveResource('AWS::Lambda::LayerVersion', { + CompatibleArchitectures: ['arm64'], + }); + }); }); diff --git a/packages/@aws-cdk/cfnspec/spec-source/530_Lambda_ARM_patch.json b/packages/@aws-cdk/cfnspec/spec-source/530_Lambda_ARM_patch.json new file mode 100644 index 0000000000000..13c481eff424d --- /dev/null +++ b/packages/@aws-cdk/cfnspec/spec-source/530_Lambda_ARM_patch.json @@ -0,0 +1,41 @@ +{ + "ResourceType": { + "AWS::Lambda::Function": { + "patch": { + "description": "AWS::Lambda::Function changes for early support of Lambda ARM launch. Remove once CFN spec is updated", + "operations": [ + { + "op": "add", + "path": "/Properties/Architectures", + "value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-architectures", + "UpdateType": "Mutable", + "Required": false, + "Type": "List", + "PrimitiveItemType": "String", + "DuplicatesAllowed": true + } + } + ] + } + }, + "AWS::Lambda::LayerVersion": { + "patch": { + "description": "AWS::Lambda::LayerVersion changes for early support of Lambda ARM launch. Remove once CFN spec is updated", + "operations": [ + { + "op": "add", + "path": "/Properties/CompatibleArchitectures", + "value": { + "PrimitiveItemType": "String", + "Type": "List", + "Required": false, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-layerversion.html#cfn-lambda-layerversion-compatiblearchitectures", + "UpdateType": "Immutable" + } + } + ] + } + } + } +} \ No newline at end of file From c59f4ce2fe026818e08416fda947a48a1060095f Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Wed, 29 Sep 2021 11:09:45 -0700 Subject: [PATCH 02/23] chore(release): 1.125.0 --- CHANGELOG.md | 7 +++++++ version.v1.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a5462acabd4e..e47c967a1926e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.125.0](https://github.com/aws/aws-cdk/compare/v1.124.0...v1.125.0) (2021-09-29) + + +### Features + +* **lambda:** support for ARM architecture ([b3ba35e](https://github.com/aws/aws-cdk/commit/b3ba35e9b8b157303a29350031885eff0c73b05b)) + ## [1.124.0](https://github.com/aws/aws-cdk/compare/v1.123.0...v1.124.0) (2021-09-21) diff --git a/version.v1.json b/version.v1.json index f92dd8626fad0..e3a6d956268a9 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.124.0" + "version": "1.125.0" } \ No newline at end of file From 64c95242c731fd08e2244148525554d7499d0c46 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Wed, 29 Sep 2021 11:49:54 -0700 Subject: [PATCH 03/23] Include Madeline's comment suggestion Co-authored-by: Madeline Kusters <80541297+madeline-k@users.noreply.github.com> --- packages/@aws-cdk/aws-lambda/lib/function.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index e267f17d6f056..72ae40d5babbe 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -802,7 +802,7 @@ export class Function extends FunctionBase { } // Currently no validations for compatible architectures since Lambda service - // allows layers configured one architecture to be used with a Lambda function + // allows layers configured with one architecture to be used with a Lambda function // from another architecture. this.layers.push(layer); From 7fbf092c613f5618575b9b1a10b9e06fa36abad3 Mon Sep 17 00:00:00 2001 From: Romain Marcadier Date: Wed, 29 Sep 2021 22:02:56 +0200 Subject: [PATCH 04/23] chore: allow-list devDeps to keep in transform.sh (#16711) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- buildspec-pr.yaml | 6 ++++++ buildspec.yaml | 6 ++++++ packages/aws-cdk-migration/lib/rewrite.ts | 12 +++++++++++- .../copy-files-removing-deps.ts | 18 +++++++++++++++++- 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/buildspec-pr.yaml b/buildspec-pr.yaml index 3d6ee87c8d1ff..a8a22717ad996 100644 --- a/buildspec-pr.yaml +++ b/buildspec-pr.yaml @@ -2,6 +2,12 @@ version: 0.2 # This buildspec is intended to be used by GitHub PR builds. +env: + variables: + # Globally allow node to use a lot of memory + NODE_OPTIONS: --max-old-space-size=4096 + JSII_ROSETTA_MAX_WORKER_COUNT: 8 + phases: install: commands: diff --git a/buildspec.yaml b/buildspec.yaml index 3f2bb4e7e1102..d410e74e47b74 100644 --- a/buildspec.yaml +++ b/buildspec.yaml @@ -2,6 +2,12 @@ version: 0.2 # This buildspec is intended to be run by CodePipeline builds. +env: + variables: + # Globally allow node to use a lot of memory + NODE_OPTIONS: --max-old-space-size=4096 + JSII_ROSETTA_MAX_WORKER_COUNT: 8 + phases: install: commands: diff --git a/packages/aws-cdk-migration/lib/rewrite.ts b/packages/aws-cdk-migration/lib/rewrite.ts index 3c120cd7ab1b1..2e17dfcb08130 100644 --- a/packages/aws-cdk-migration/lib/rewrite.ts +++ b/packages/aws-cdk-migration/lib/rewrite.ts @@ -123,6 +123,12 @@ export function rewriteImports(sourceText: string, fileName: string = 'index.ts' const EXEMPTIONS = new Set([ '@aws-cdk/cloudformation-diff', + // The dev-tools + '@aws-cdk/cdk-build-tools', + '@aws-cdk/cdk-integ-tools', + '@aws-cdk/cfn2ts', + '@aws-cdk/eslint-plugin', + '@aws-cdk/pkglint', ]); function updatedLocationOf(modulePath: string, options: RewriteOptions, importedElements?: ts.NodeArray): string | undefined { @@ -146,7 +152,11 @@ function updatedLocationOf(modulePath: string, options: RewriteOptions, imported return `aws-cdk-lib/${options.packageUnscopedName}`; } - if (!modulePath.startsWith('@aws-cdk/') || EXEMPTIONS.has(modulePath)) { + if ( + !modulePath.startsWith('@aws-cdk/') + || EXEMPTIONS.has(modulePath) + || Array.from(EXEMPTIONS).some((ex) => modulePath.startsWith(`${ex}/`)) + ) { return undefined; } diff --git a/tools/@aws-cdk/individual-pkg-gen/copy-files-removing-deps.ts b/tools/@aws-cdk/individual-pkg-gen/copy-files-removing-deps.ts index 76b10bfee7749..c9be13d39568f 100644 --- a/tools/@aws-cdk/individual-pkg-gen/copy-files-removing-deps.ts +++ b/tools/@aws-cdk/individual-pkg-gen/copy-files-removing-deps.ts @@ -4,6 +4,18 @@ import * as fs from 'fs-extra'; // eslint-disable-next-line @typescript-eslint/no-require-imports const lerna_project = require('@lerna/project'); +/** + * @aws-cdk/ scoped packages that may be present in devDependencies and need to + * be retained (or else pkglint might declare the package unworthy). + */ +const REQUIRED_TOOLS = new Set([ + '@aws-cdk/cdk-build-tools', + '@aws-cdk/cdk-integ-tools', + '@aws-cdk/cfn2ts', + '@aws-cdk/eslint-plugin', + '@aws-cdk/pkglint', +]); + transformPackages(); function transformPackages(): void { @@ -199,7 +211,7 @@ function transformPackageJsonDependencies(packageJson: any, pkg: any, alphaPacka default: if (alphaPackages[v1DevDependency]) { alphaDevDependencies[alphaPackages[v1DevDependency]] = pkg.version; - } else if (!v1DevDependency.startsWith('@aws-cdk/')) { + } else if (!v1DevDependency.startsWith('@aws-cdk/') || isRequiredTool(v1DevDependency)) { devDependencies[v1DevDependency] = packageJson.devDependencies[v1DevDependency]; } } @@ -260,3 +272,7 @@ function packageIsAlpha(pkg: any): boolean { // and those that are JSII-enabled (so no @aws-cdk/assert) return pkg.name.startsWith('@aws-cdk/') && !!pkg.get('jsii'); } + +function isRequiredTool(name: string) { + return REQUIRED_TOOLS.has(name); +} From 5c028c54aa7635dd55095257ebe81bdf2158ea39 Mon Sep 17 00:00:00 2001 From: Chen Date: Thu, 30 Sep 2021 17:33:56 +0800 Subject: [PATCH 05/23] feat(cloudfront): support Behavior-specific viewer protocol policy for CloudFrontWebDistribution (#16389) This pr fixes issue #7086 by allowing user to set viewer protocol policy in Behavior. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-cloudfront/lib/web-distribution.ts | 8 +- .../test/web-distribution.test.ts | 103 ++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts index 29a63fd681bb3..ab2fbbd44b03c 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts @@ -454,6 +454,12 @@ export interface Behavior { */ readonly functionAssociations?: FunctionAssociation[]; + /** + * The viewer policy for this behavior. + * + * @default - the distribution wide viewer protocol policy will be used + */ + readonly viewerProtocolPolicy?: ViewerProtocolPolicy; } export interface LambdaFunctionAssociation { @@ -992,7 +998,7 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu trustedKeyGroups: input.trustedKeyGroups?.map(key => key.keyGroupId), trustedSigners: input.trustedSigners, targetOriginId: input.targetOriginId, - viewerProtocolPolicy: protoPolicy || ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + viewerProtocolPolicy: input.viewerProtocolPolicy || protoPolicy || ViewerProtocolPolicy.REDIRECT_TO_HTTPS, }; if (!input.isDefaultBehavior) { toReturn = Object.assign(toReturn, { pathPattern: input.pathPattern }); diff --git a/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts index 9e40e24e3fe42..600750bc4deca 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts @@ -605,6 +605,109 @@ added the ellipsis so a user would know there was more to ...`, }); + test('distribution with ViewerProtocolPolicy overridden in Behavior', () => { + const stack = new cdk.Stack(); + const sourceBucket = new s3.Bucket(stack, 'Bucket'); + + new CloudFrontWebDistribution(stack, 'AnAmazingWebsiteProbably', { + viewerProtocolPolicy: ViewerProtocolPolicy.ALLOW_ALL, + originConfigs: [ + { + s3OriginSource: { + s3BucketSource: sourceBucket, + }, + behaviors: [ + { + isDefaultBehavior: true, + }, + { + pathPattern: '/test/*', + viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + }, + ], + }, + ], + }); + + expect(stack).toMatchTemplate({ + 'Resources': { + 'Bucket83908E77': { + 'Type': 'AWS::S3::Bucket', + 'DeletionPolicy': 'Retain', + 'UpdateReplacePolicy': 'Retain', + }, + 'AnAmazingWebsiteProbablyCFDistribution47E3983B': { + 'Type': 'AWS::CloudFront::Distribution', + 'Properties': { + 'DistributionConfig': { + 'CacheBehaviors': [ + { + 'AllowedMethods': [ + 'GET', + 'HEAD', + ], + 'CachedMethods': [ + 'GET', + 'HEAD', + ], + 'Compress': true, + 'ForwardedValues': { + 'Cookies': { + 'Forward': 'none', + }, + 'QueryString': false, + }, + 'PathPattern': '/test/*', + 'TargetOriginId': 'origin1', + 'ViewerProtocolPolicy': 'redirect-to-https', + }, + ], + 'DefaultRootObject': 'index.html', + 'Origins': [ + { + 'ConnectionAttempts': 3, + 'ConnectionTimeout': 10, + 'DomainName': { + 'Fn::GetAtt': [ + 'Bucket83908E77', + 'RegionalDomainName', + ], + }, + 'Id': 'origin1', + 'S3OriginConfig': {}, + }, + ], + 'ViewerCertificate': { + 'CloudFrontDefaultCertificate': true, + }, + 'PriceClass': 'PriceClass_100', + 'DefaultCacheBehavior': { + 'AllowedMethods': [ + 'GET', + 'HEAD', + ], + 'CachedMethods': [ + 'GET', + 'HEAD', + ], + 'TargetOriginId': 'origin1', + 'ViewerProtocolPolicy': 'allow-all', + 'ForwardedValues': { + 'QueryString': false, + 'Cookies': { 'Forward': 'none' }, + }, + 'Compress': true, + }, + 'Enabled': true, + 'IPV6Enabled': true, + 'HttpVersion': 'http2', + }, + }, + }, + }, + }); + }); + test('distribution with disabled compression', () => { const stack = new cdk.Stack(); const sourceBucket = new s3.Bucket(stack, 'Bucket'); From 341303f6028384e01869546b6308c62bceb15f2b Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Thu, 30 Sep 2021 12:19:55 +0100 Subject: [PATCH 06/23] chore: (revert) chore: move modules to assertions (#16671) (#16727) This reverts commit be6aa2e53a17df41abdeb3fa87d86cd740a06e24. This commit, coupled with the newStyleSynthesis flag (enabled by default on the v2 branch) do not play nicely together. This is due to the default resources present in every template (Parameter and Rule for checking bootstrap version). The `templateMatches` usage introduced in the original PR does not filter out these default inclusions, leading to most every of those tests to fail. Rather than block the forward merge on trying to come up with a clever solution, temporarily reverting. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-applicationautoscaling/package.json | 1 - .../test/scalable-target.test.ts | 14 +- .../test/step-scaling-policy.test.ts | 15 +- .../test/target-tracking.test.ts | 8 +- packages/@aws-cdk/aws-chatbot/package.json | 1 - .../test/slack-channel-configuration.test.ts | 27 +-- packages/@aws-cdk/aws-codecommit/package.json | 1 - .../aws-codecommit/test/codecommit.test.ts | 6 +- .../test/notification-rule.test.ts | 6 +- .../@aws-cdk/aws-codepipeline/package.json | 1 - .../aws-codepipeline/test/action.test.ts | 16 +- .../aws-codepipeline/test/artifacts.test.ts | 14 +- .../aws-codepipeline/test/cross-env.test.ts | 28 +-- .../test/general-validation.test.ts | 1 + .../test/notification-rule.test.ts | 10 +- .../aws-codepipeline/test/pipeline.test.ts | 82 ++++----- .../aws-codepipeline/test/stages.test.ts | 16 +- .../aws-codepipeline/test/variables.test.ts | 31 ++-- packages/@aws-cdk/aws-ecr-assets/package.json | 1 - .../aws-ecr-assets/test/image-asset.test.ts | 6 +- .../aws-ecr-assets/test/tarball-asset.test.ts | 6 +- packages/@aws-cdk/aws-ecr/package.json | 1 - .../@aws-cdk/aws-ecr/test/auth-token.test.ts | 10 +- .../@aws-cdk/aws-ecr/test/repository.test.ts | 80 ++++----- packages/@aws-cdk/aws-logs/package.json | 1 - .../aws-logs/test/destination.test.ts | 6 +- .../aws-logs/test/log-retention.test.ts | 19 +- .../@aws-cdk/aws-logs/test/loggroup.test.ts | 32 ++-- .../@aws-cdk/aws-logs/test/logstream.test.ts | 4 +- .../aws-logs/test/metricfilter.test.ts | 4 +- .../aws-logs/test/subscriptionfilter.test.ts | 4 +- packages/@aws-cdk/aws-s3-assets/package.json | 1 - .../@aws-cdk/aws-s3-assets/test/asset.test.ts | 25 +-- packages/@aws-cdk/aws-s3/package.json | 1 - packages/@aws-cdk/aws-s3/test/aspect.test.ts | 12 +- .../aws-s3/test/bucket-policy.test.ts | 8 +- packages/@aws-cdk/aws-s3/test/bucket.test.ts | 163 +++++++++--------- packages/@aws-cdk/aws-s3/test/cors.test.ts | 6 +- packages/@aws-cdk/aws-s3/test/metrics.test.ts | 10 +- .../@aws-cdk/aws-s3/test/notification.test.ts | 13 +- packages/@aws-cdk/aws-s3/test/rules.test.ts | 12 +- packages/@aws-cdk/aws-s3/test/util.test.ts | 1 + packages/@aws-cdk/aws-sqs/package.json | 1 - packages/@aws-cdk/aws-sqs/test/sqs.test.ts | 35 ++-- packages/@aws-cdk/aws-ssm/package.json | 1 - .../test/parameter-store-string.test.ts | 4 +- .../@aws-cdk/aws-ssm/test/parameter.test.ts | 18 +- .../aws-ssm/test/ssm-document.test.ts | 6 +- 48 files changed, 380 insertions(+), 389 deletions(-) diff --git a/packages/@aws-cdk/aws-applicationautoscaling/package.json b/packages/@aws-cdk/aws-applicationautoscaling/package.json index 409aab62f91ce..f1bf7ba4c5d95 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/package.json +++ b/packages/@aws-cdk/aws-applicationautoscaling/package.json @@ -73,7 +73,6 @@ "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-applicationautoscaling/test/scalable-target.test.ts b/packages/@aws-cdk/aws-applicationautoscaling/test/scalable-target.test.ts index 1ef4b7aac38b7..8628a8624468c 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/test/scalable-target.test.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/test/scalable-target.test.ts @@ -1,4 +1,4 @@ -import { Match, Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as cdk from '@aws-cdk/core'; import * as appscaling from '../lib'; @@ -19,7 +19,7 @@ describe('scalable target', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalableTarget', { + expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalableTarget', { ServiceNamespace: 'dynamodb', ScalableDimension: 'test:TestCount', ResourceId: 'test:this/test', @@ -44,7 +44,7 @@ describe('scalable target', () => { }); // THEN: no exception - Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalableTarget', { + expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalableTarget', { ServiceNamespace: 'dynamodb', ScalableDimension: 'test:TestCount', ResourceId: 'test:this/test', @@ -68,7 +68,7 @@ describe('scalable target', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalableTarget', { + expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalableTarget', { ScheduledActions: [ { ScalableTargetAction: { @@ -109,11 +109,11 @@ describe('scalable target', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { - Period: Match.absentProperty(), + expect(stack).not.toHaveResource('AWS::CloudWatch::Alarm', { + Period: 60, }); - Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { + expect(stack).toHaveResource('AWS::CloudWatch::Alarm', { ComparisonOperator: 'LessThanOrEqualToThreshold', EvaluationPeriods: 1, Metrics: [ diff --git a/packages/@aws-cdk/aws-applicationautoscaling/test/step-scaling-policy.test.ts b/packages/@aws-cdk/aws-applicationautoscaling/test/step-scaling-policy.test.ts index 052bf76b8df19..528a84b997306 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/test/step-scaling-policy.test.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/test/step-scaling-policy.test.ts @@ -1,4 +1,5 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; +import { SynthUtils } from '@aws-cdk/assert-internal'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as cdk from '@aws-cdk/core'; import * as fc from 'fast-check'; @@ -131,7 +132,7 @@ describe('step scaling policy', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalingPolicy', { + expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { PolicyType: 'StepScaling', ScalingTargetId: { Ref: 'Target3191CF44', @@ -168,14 +169,14 @@ describe('step scaling policy', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalingPolicy', { + expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalingPolicy', { PolicyType: 'StepScaling', StepScalingPolicyConfiguration: { AdjustmentType: 'ChangeInCapacity', MetricAggregationType: 'Average', }, }); - Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { + expect(stack).toHaveResource('AWS::CloudWatch::Alarm', { ComparisonOperator: 'GreaterThanOrEqualToThreshold', EvaluationPeriods: 1, AlarmActions: [ @@ -208,14 +209,14 @@ describe('step scaling policy', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalingPolicy', { + expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalingPolicy', { PolicyType: 'StepScaling', StepScalingPolicyConfiguration: { AdjustmentType: 'ChangeInCapacity', MetricAggregationType: 'Maximum', }, }); - Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { + expect(stack).toHaveResource('AWS::CloudWatch::Alarm', { ComparisonOperator: 'GreaterThanOrEqualToThreshold', EvaluationPeriods: 10, ExtendedStatistic: 'p99', @@ -240,7 +241,7 @@ function setupStepScaling(intervals: appscaling.ScalingInterval[]) { scalingSteps: intervals, }); - return new ScalingStackTemplate(Template.fromStack(stack).toJSON()); + return new ScalingStackTemplate(SynthUtils.synthesize(stack).template); } class ScalingStackTemplate { diff --git a/packages/@aws-cdk/aws-applicationautoscaling/test/target-tracking.test.ts b/packages/@aws-cdk/aws-applicationautoscaling/test/target-tracking.test.ts index 876a5c649812f..2ec9f8dd07a26 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/test/target-tracking.test.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/test/target-tracking.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as cdk from '@aws-cdk/core'; import * as appscaling from '../lib'; @@ -17,7 +17,7 @@ describe('target tracking', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalingPolicy', { + expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { PolicyType: 'TargetTrackingScaling', TargetTrackingScalingPolicyConfiguration: { PredefinedMetricSpecification: { PredefinedMetricType: 'EC2SpotFleetRequestAverageCPUUtilization' }, @@ -41,7 +41,7 @@ describe('target tracking', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalingPolicy', { + expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { PolicyType: 'TargetTrackingScaling', TargetTrackingScalingPolicyConfiguration: { PredefinedMetricSpecification: { PredefinedMetricType: 'LambdaProvisionedConcurrencyUtilization' }, @@ -65,7 +65,7 @@ describe('target tracking', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ApplicationAutoScaling::ScalingPolicy', { + expect(stack).toHaveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { PolicyType: 'TargetTrackingScaling', TargetTrackingScalingPolicyConfiguration: { CustomizedMetricSpecification: { diff --git a/packages/@aws-cdk/aws-chatbot/package.json b/packages/@aws-cdk/aws-chatbot/package.json index f544a4c148ace..353033b0e9224 100644 --- a/packages/@aws-cdk/aws-chatbot/package.json +++ b/packages/@aws-cdk/aws-chatbot/package.json @@ -75,7 +75,6 @@ "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-chatbot/test/slack-channel-configuration.test.ts b/packages/@aws-cdk/aws-chatbot/test/slack-channel-configuration.test.ts index 195bbdcd51794..63bf61b46d21c 100644 --- a/packages/@aws-cdk/aws-chatbot/test/slack-channel-configuration.test.ts +++ b/packages/@aws-cdk/aws-chatbot/test/slack-channel-configuration.test.ts @@ -1,4 +1,5 @@ -import { Match, Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; +import { ABSENT } from '@aws-cdk/assert-internal'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; @@ -20,7 +21,7 @@ describe('SlackChannelConfiguration', () => { slackChannelConfigurationName: 'Test', }); - Template.fromStack(stack).hasResourceProperties('AWS::Chatbot::SlackChannelConfiguration', { + expect(stack).toHaveResourceLike('AWS::Chatbot::SlackChannelConfiguration', { ConfigurationName: 'Test', IamRoleArn: { 'Fn::GetAtt': [ @@ -32,7 +33,7 @@ describe('SlackChannelConfiguration', () => { SlackWorkspaceId: 'ABC123', }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResourceLike('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [ { @@ -56,7 +57,7 @@ describe('SlackChannelConfiguration', () => { loggingLevel: chatbot.LoggingLevel.ERROR, }); - Template.fromStack(stack).hasResourceProperties('AWS::Chatbot::SlackChannelConfiguration', { + expect(stack).toHaveResourceLike('AWS::Chatbot::SlackChannelConfiguration', { ConfigurationName: 'Test', IamRoleArn: { 'Fn::GetAtt': [ @@ -80,7 +81,7 @@ describe('SlackChannelConfiguration', () => { notificationTopics: [topic], }); - Template.fromStack(stack).hasResourceProperties('AWS::Chatbot::SlackChannelConfiguration', { + expect(stack).toHaveResourceLike('AWS::Chatbot::SlackChannelConfiguration', { ConfigurationName: 'Test', IamRoleArn: { 'Fn::GetAtt': [ @@ -108,7 +109,7 @@ describe('SlackChannelConfiguration', () => { role: role, }); - Template.fromStack(stack).resourceCountIs('AWS::IAM::Role', 0); + expect(stack).toCountResources('AWS::IAM::Role', 0); }); test('created with new role and extra iam policies', () => { @@ -126,7 +127,7 @@ describe('SlackChannelConfiguration', () => { resources: ['arn:aws:s3:::abc/xyz/123.txt'], })); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -148,7 +149,7 @@ describe('SlackChannelConfiguration', () => { logRetention: logs.RetentionDays.ONE_MONTH, }); - Template.fromStack(stack).hasResourceProperties('Custom::LogRetention', { + expect(stack).toHaveResourceLike('Custom::LogRetention', { LogGroupName: '/aws/chatbot/ConfigurationName', RetentionInDays: 30, LogGroupRegion: 'us-east-1', @@ -178,7 +179,7 @@ describe('SlackChannelConfiguration', () => { }, metricName: 'MetricName', })); - Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { + expect(stack).toHaveResourceLike('AWS::CloudWatch::Alarm', { Namespace: 'AWS/Chatbot', MetricName: 'MetricName', Dimensions: [ @@ -207,10 +208,10 @@ describe('SlackChannelConfiguration', () => { region: 'us-east-1', metricName: 'MetricName', })); - Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { + expect(stack).toHaveResourceLike('AWS::CloudWatch::Alarm', { Namespace: 'AWS/Chatbot', MetricName: 'MetricName', - Dimensions: Match.absentProperty(), + Dimensions: ABSENT, ComparisonOperator: 'GreaterThanThreshold', EvaluationPeriods: 1, Threshold: 0, @@ -228,8 +229,8 @@ describe('SlackChannelConfiguration', () => { resources: ['arn:aws:s3:::abc/xyz/123.txt'], })); - Template.fromStack(stack).resourceCountIs('AWS::IAM::Role', 0); - Template.fromStack(stack).resourceCountIs('AWS::IAM::Policy', 0); + expect(stack).toCountResources('AWS::IAM::Role', 0); + expect(stack).toCountResources('AWS::IAM::Policy', 0); }); test('should throw error if ARN invalid', () => { diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index b221e74bcddfa..d5041d7e5711b 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -78,7 +78,6 @@ "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/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-codecommit/test/codecommit.test.ts b/packages/@aws-cdk/aws-codecommit/test/codecommit.test.ts index f59b18badf73d..927c245b3bdd9 100644 --- a/packages/@aws-cdk/aws-codecommit/test/codecommit.test.ts +++ b/packages/@aws-cdk/aws-codecommit/test/codecommit.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; import { Repository, RepositoryProps } from '../lib'; @@ -16,7 +16,7 @@ describe('codecommit', () => { new Repository(stack, 'MyRepository', props).notify(snsArn); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyRepository4C4BD5FC: { Type: 'AWS::CodeCommit::Repository', @@ -172,7 +172,7 @@ describe('codecommit', () => { repository.grantPullPush(role); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-codecommit/test/notification-rule.test.ts b/packages/@aws-cdk/aws-codecommit/test/notification-rule.test.ts index e7a90d6e8761e..32540aefc1111 100644 --- a/packages/@aws-cdk/aws-codecommit/test/notification-rule.test.ts +++ b/packages/@aws-cdk/aws-codecommit/test/notification-rule.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; import * as codecommit from '../lib'; @@ -16,7 +16,7 @@ describe('notification rule', () => { repository.notifiyOnPullRequestMerged('NotifyOnPullRequestMerged', target); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResource('AWS::CodeStarNotifications::NotificationRule', { Name: 'MyCodecommitRepositoryNotifyOnPullRequestCreatedBB14EA32', DetailType: 'FULL', EventTypeIds: [ @@ -38,7 +38,7 @@ describe('notification rule', () => { ], }); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResource('AWS::CodeStarNotifications::NotificationRule', { Name: 'MyCodecommitRepositoryNotifyOnPullRequestMerged34A7EDF1', DetailType: 'FULL', EventTypeIds: [ diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index f2008aed1e67d..5f242010eae8b 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -78,7 +78,6 @@ "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-codepipeline/test/action.test.ts b/packages/@aws-cdk/aws-codepipeline/test/action.test.ts index 0167d309b6fec..69a5839385fcd 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/action.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/action.test.ts @@ -1,4 +1,4 @@ -import { Match, Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as codepipeline from '../lib'; @@ -121,7 +121,7 @@ describe('action', () => { }); expect(() => { - Template.fromStack(stack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { }); }).toThrow(/Build\/Fake cannot have more than 3 input artifacts/); @@ -166,7 +166,7 @@ describe('action', () => { }); expect(() => { - Template.fromStack(stack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { }); }).toThrow(/Source\/Fake cannot have more than 4 output artifacts/); @@ -199,25 +199,25 @@ describe('action', () => { ], }); - Template.fromStack(stack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { 'Stages': [ { 'Name': 'Source', 'Actions': [ - Match.objectLike({ + { 'Name': 'CodeCommit', 'OutputArtifacts': [ { 'Name': 'Artifact_Source_CodeCommit', }, ], - }), + }, ], }, { 'Name': 'Build', 'Actions': [ - Match.objectLike({ + { 'Name': 'CodeBuild', 'InputArtifacts': [ { @@ -229,7 +229,7 @@ describe('action', () => { 'Name': 'Artifact_Build_CodeBuild', }, ], - }), + }, ], }, ], diff --git a/packages/@aws-cdk/aws-codepipeline/test/artifacts.test.ts b/packages/@aws-cdk/aws-codepipeline/test/artifacts.test.ts index 4f1fad8805ed3..cf4f733d811be 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/artifacts.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/artifacts.test.ts @@ -1,4 +1,4 @@ -import { Match, Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as codepipeline from '../lib'; import { FakeBuildAction } from './fake-build-action'; @@ -165,7 +165,7 @@ describe('artifacts', () => { ], }); - Template.fromStack(stack).resourceCountIs('AWS::CodePipeline::Pipeline', 1); + expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline'); }); @@ -250,28 +250,28 @@ describe('artifacts', () => { ], }); - Template.fromStack(stack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { 'Stages': [ { 'Name': 'Source.@', 'Actions': [ - Match.objectLike({ + { 'Name': 'source1', 'OutputArtifacts': [ { 'Name': 'Artifact_Source_source1' }, ], - }), + }, ], }, { 'Name': 'Build', 'Actions': [ - Match.objectLike({ + { 'Name': 'build1', 'InputArtifacts': [ { 'Name': 'Artifact_Source_source1' }, ], - }), + }, ], }, ], diff --git a/packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts b/packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts index 4895cf5de921a..f22c80e82cba1 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts @@ -1,4 +1,4 @@ -import { Match, Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import { Stack, App } from '@aws-cdk/core'; @@ -49,8 +49,8 @@ describe.each(['legacy', 'modern'])('with %s synthesis', (synthesisStyle: string test('creates a bucket but no keys', () => { // THEN - Template.fromStack(stack).resourceCountIs('AWS::KMS::Key', 0); - Template.fromStack(stack).resourceCountIs('AWS::S3::Bucket', 1); + expect(stack).not.toHaveResource('AWS::KMS::Key'); + expect(stack).toHaveResource('AWS::S3::Bucket'); }); describe('prevents adding a cross-account action', () => { @@ -118,8 +118,8 @@ describe.each(['legacy', 'modern'])('with %s synthesis', (synthesisStyle: string const supportStack = asm.getStackByName(`${stack.stackName}-support-eu-west-1`); // THEN - Template.fromJSON(supportStack.template).resourceCountIs('AWS::KMS::Key', 0); - Template.fromJSON(supportStack.template).resourceCountIs('AWS::S3::Bucket', 1); + expect(supportStack).not.toHaveResource('AWS::KMS::Key'); + expect(supportStack).toHaveResource('AWS::S3::Bucket'); }); test('when twiddling another stack', () => { @@ -133,8 +133,8 @@ describe.each(['legacy', 'modern'])('with %s synthesis', (synthesisStyle: string })); // THEN - Template.fromStack(stack2).resourceCountIs('AWS::KMS::Key', 0); - Template.fromStack(stack2).resourceCountIs('AWS::S3::Bucket', 1); + expect(stack2).not.toHaveResource('AWS::KMS::Key'); + expect(stack2).toHaveResource('AWS::S3::Bucket'); }); }); }); @@ -183,17 +183,17 @@ describe('cross-environment CodePipeline', function () { const asm = app.synth(); const supportStack = asm.getStackByName(`${pipelineStack.stackName}-support-456`); - Template.fromJSON(supportStack.template).hasResourceProperties('AWS::IAM::Role', { + expect(supportStack).toHaveResourceLike('AWS::IAM::Role', { RoleName: 'pipelinestack-support-456dbuildactionrole91c6f1a469fd11d52dfe', }); - Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { Stages: [ - Match.objectLike({ Name: 'Source' }), - Match.objectLike({ + { Name: 'Source' }, + { Name: 'Build', Actions: [ - Match.objectLike({ + { Name: 'Build', RoleArn: { 'Fn::Join': ['', [ @@ -202,9 +202,9 @@ describe('cross-environment CodePipeline', function () { ':iam::456:role/pipelinestack-support-456dbuildactionrole91c6f1a469fd11d52dfe', ]], }, - }), + }, ], - }), + }, ], }); }); diff --git a/packages/@aws-cdk/aws-codepipeline/test/general-validation.test.ts b/packages/@aws-cdk/aws-codepipeline/test/general-validation.test.ts index 8ca77965e1a72..d8186532d7cea 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/general-validation.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/general-validation.test.ts @@ -1,3 +1,4 @@ +import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import { IStage } from '../lib/action'; import { Artifact } from '../lib/artifact'; diff --git a/packages/@aws-cdk/aws-codepipeline/test/notification-rule.test.ts b/packages/@aws-cdk/aws-codepipeline/test/notification-rule.test.ts index ac178204dd263..46d32794dc3e1 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/notification-rule.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/notification-rule.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as codepipeline from '../lib'; import { FakeBuildAction } from './fake-build-action'; @@ -35,7 +35,7 @@ describe('pipeline with codestar notification integration', () => { pipeline.notifyOnExecutionStateChange('NotifyOnExecutionStateChange', target); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { Name: 'PipelineNotifyOnExecutionStateChange9FE60973', DetailType: 'FULL', EventTypeIds: [ @@ -77,7 +77,7 @@ describe('pipeline with codestar notification integration', () => { pipeline.notifyOnAnyStageStateChange('NotifyOnAnyStageStateChange', target); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { Name: 'PipelineNotifyOnAnyStageStateChange05355CCD', DetailType: 'FULL', EventTypeIds: [ @@ -118,7 +118,7 @@ describe('pipeline with codestar notification integration', () => { pipeline.notifyOnAnyActionStateChange('NotifyOnAnyActionStateChange', target); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { Name: 'PipelineNotifyOnAnyActionStateChange64D5B2AA', DetailType: 'FULL', EventTypeIds: [ @@ -158,7 +158,7 @@ describe('pipeline with codestar notification integration', () => { pipeline.notifyOnAnyManualApprovalStateChange('NotifyOnAnyManualApprovalStateChange', target); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { Name: 'PipelineNotifyOnAnyManualApprovalStateChangeE60778F7', DetailType: 'FULL', EventTypeIds: [ diff --git a/packages/@aws-cdk/aws-codepipeline/test/pipeline.test.ts b/packages/@aws-cdk/aws-codepipeline/test/pipeline.test.ts index e7062c3130c0e..d25f89ad915a0 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/pipeline.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/pipeline.test.ts @@ -1,4 +1,5 @@ -import { Match, Template } from '@aws-cdk/assertions'; +import { expect as ourExpect, ResourcePart, arrayWith, objectLike, haveResourceLike } from '@aws-cdk/assert-internal'; +import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; @@ -17,36 +18,18 @@ describe('', () => { const role = new iam.Role(stack, 'Role', { assumedBy: new iam.ServicePrincipal('codepipeline.amazonaws.com'), }); - const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { + new codepipeline.Pipeline(stack, 'Pipeline', { role, }); - // Adding 2 stages with actions so pipeline validation will pass - const sourceArtifact = new codepipeline.Artifact(); - pipeline.addStage({ - stageName: 'Source', - actions: [new FakeSourceAction({ - actionName: 'FakeSource', - output: sourceArtifact, - })], - }); - - pipeline.addStage({ - stageName: 'Build', - actions: [new FakeBuildAction({ - actionName: 'FakeBuild', - input: sourceArtifact, - })], - }); - - Template.fromStack(stack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + ourExpect(stack, true).to(haveResourceLike('AWS::CodePipeline::Pipeline', { 'RoleArn': { 'Fn::GetAtt': [ 'Role1ABCC5F0', 'Arn', ], }, - }); + })); }); @@ -126,9 +109,9 @@ describe('', () => { ], }); - Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { 'ArtifactStores': [ - Match.objectLike({ + { 'Region': replicationRegion, 'ArtifactStore': { 'Type': 'S3', @@ -148,16 +131,19 @@ describe('', () => { }, }, }, - }), - Match.objectLike({ + }, + { 'Region': pipelineRegion, - }), + }, ], }); - Template.fromStack(replicationStack).hasResourceProperties('AWS::KMS::Key', { + expect(replicationStack).toHaveResourceLike('AWS::KMS::Key', { 'KeyPolicy': { - 'Statement': Match.arrayWith([ + 'Statement': [ + { + // owning account management permissions - we don't care about them in this test + }, { // KMS verifies whether the principal given in its key policy exists when creating that key. // Since the replication bucket must be deployed before the pipeline, @@ -182,7 +168,7 @@ describe('', () => { }, 'Resource': '*', }, - ]), + ], }, }); @@ -218,9 +204,9 @@ describe('', () => { ], }); - Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { 'ArtifactStores': [ - Match.objectLike({ + { 'Region': replicationRegion, 'ArtifactStore': { 'Type': 'S3', @@ -240,17 +226,17 @@ describe('', () => { }, }, }, - }), - Match.objectLike({ + }, + { 'Region': pipelineRegion, - }), + }, ], }); - Template.fromStack(pipeline.crossRegionSupport[replicationRegion].stack).hasResource('AWS::KMS::Alias', { + expect(pipeline.crossRegionSupport[replicationRegion].stack).toHaveResourceLike('AWS::KMS::Alias', { 'DeletionPolicy': 'Delete', 'UpdateReplacePolicy': 'Delete', - }); + }, ResourcePart.CompleteDefinition); }); @@ -290,9 +276,9 @@ describe('', () => { ], }); - Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { 'ArtifactStores': [ - Match.objectLike({ + { 'Region': replicationRegion, 'ArtifactStore': { 'Type': 'S3', @@ -302,10 +288,10 @@ describe('', () => { 'Id': 'arn:aws:kms:us-west-1:123456789012:key/1234-5678-9012', }, }, - }), - Match.objectLike({ + }, + { 'Region': pipelineRegion, - }), + }, ], }); @@ -434,7 +420,7 @@ describe('', () => { ], }); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResourceLike('AWS::KMS::Key', { 'EnableKeyRotation': true, }); }); @@ -469,14 +455,14 @@ describe('test with shared setup', () => { pipeline.stage('Build').addAction(new FakeBuildAction({ actionName: 'debug.com', input: sourceArtifact })); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::CodePipeline::Pipeline', { - Stages: Match.arrayWith([{ + expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ Name: 'Build', Actions: [ - Match.objectLike({ Name: 'Gcc' }), - Match.objectLike({ Name: 'debug.com' }), + objectLike({ Name: 'Gcc' }), + objectLike({ Name: 'debug.com' }), ], - }]), + }), }); }); }); diff --git a/packages/@aws-cdk/aws-codepipeline/test/stages.test.ts b/packages/@aws-cdk/aws-codepipeline/test/stages.test.ts index d93d185bc7fa3..d7931a23a531e 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/stages.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/stages.test.ts @@ -1,4 +1,4 @@ -import { Match, Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as codepipeline from '../lib'; import { Stage } from '../lib/private/stage'; @@ -33,10 +33,10 @@ describe('stages', () => { })); // -- - Template.fromStack(stack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { 'Stages': [ - Match.objectLike({ 'Name': 'FirstStage' }), - Match.objectLike({ 'Name': 'SecondStage' }), + { 'Name': 'FirstStage' }, + { 'Name': 'SecondStage' }, ], }); @@ -72,11 +72,11 @@ describe('stages', () => { })); // -- - Template.fromStack(stack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { 'Stages': [ - Match.objectLike({ 'Name': 'FirstStage' }), - Match.objectLike({ 'Name': 'SecondStage' }), - Match.objectLike({ 'Name': 'ThirdStage' }), + { 'Name': 'FirstStage' }, + { 'Name': 'SecondStage' }, + { 'Name': 'ThirdStage' }, ], }); diff --git a/packages/@aws-cdk/aws-codepipeline/test/variables.test.ts b/packages/@aws-cdk/aws-codepipeline/test/variables.test.ts index e7510de0a407c..537b108df805a 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/variables.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/variables.test.ts @@ -1,4 +1,5 @@ -import { Match, Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; +import { arrayWith, objectLike } from '@aws-cdk/assert-internal'; import * as cdk from '@aws-cdk/core'; import * as codepipeline from '../lib'; import { FakeBuildAction } from './fake-build-action'; @@ -37,18 +38,18 @@ describe('variables', () => { })); // -- - Template.fromStack(stack).hasResourceProperties('AWS::CodePipeline::Pipeline', { - 'Stages': Match.arrayWith([ + expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + 'Stages': [ { 'Name': 'Source', 'Actions': [ - Match.objectLike({ + { 'Name': 'Source', 'Namespace': 'MyNamespace', - }), + }, ], }, - ]), + ], }); @@ -79,20 +80,20 @@ describe('variables', () => { ], }); - Template.fromStack(stack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { 'Stages': [ - Match.objectLike({ + { 'Name': 'Source', - }), + }, { 'Name': 'Build', 'Actions': [ - Match.objectLike({ + { 'Name': 'Build', 'Configuration': { 'CustomConfigKey': '#{SourceVariables.FirstVariable}', }, - }), + }, ], }, ], @@ -143,12 +144,12 @@ describe('variables', () => { ], }); - Template.fromStack(stack).hasResourceProperties('AWS::CodePipeline::Pipeline', { - 'Stages': Match.arrayWith([ + expect(stack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + 'Stages': arrayWith( { 'Name': 'Build', 'Actions': [ - Match.objectLike({ + objectLike({ 'Name': 'Build', 'Configuration': { 'CustomConfigKey': '#{codepipeline.PipelineExecutionId}', @@ -156,7 +157,7 @@ describe('variables', () => { }), ], }, - ]), + ), }); diff --git a/packages/@aws-cdk/aws-ecr-assets/package.json b/packages/@aws-cdk/aws-ecr-assets/package.json index 64520c57ebcda..b6b407041eda6 100644 --- a/packages/@aws-cdk/aws-ecr-assets/package.json +++ b/packages/@aws-cdk/aws-ecr-assets/package.json @@ -66,7 +66,6 @@ "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/cloud-assembly-schema": "0.0.0", diff --git a/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts b/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts index d7f316580889f..667a2ba9d0145 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts +++ b/packages/@aws-cdk/aws-ecr-assets/test/image-asset.test.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; -import { Template } from '@aws-cdk/assertions'; +import { expect as ourExpect, haveResource } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { App, DefaultStackSynthesizer, IgnoreMode, Lazy, LegacyStackSynthesizer, Stack, Stage } from '@aws-cdk/core'; @@ -100,7 +100,7 @@ describe('image asset', () => { asset.repository.grantPull(user); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + ourExpect(stack).to(haveResource('AWS::IAM::Policy', { PolicyDocument: { 'Statement': [ { @@ -145,7 +145,7 @@ describe('image asset', () => { 'Ref': 'MyUserDC45028B', }, ], - }); + })); }); diff --git a/packages/@aws-cdk/aws-ecr-assets/test/tarball-asset.test.ts b/packages/@aws-cdk/aws-ecr-assets/test/tarball-asset.test.ts index b911d594eb5e2..77e22f20dca9d 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/tarball-asset.test.ts +++ b/packages/@aws-cdk/aws-ecr-assets/test/tarball-asset.test.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; -import { Template } from '@aws-cdk/assertions'; +import { expect as ourExpect, haveResource } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { App, Stack } from '@aws-cdk/core'; @@ -64,7 +64,7 @@ describe('image asset', () => { asset.repository.grantPull(user); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + ourExpect(stack).to(haveResource('AWS::IAM::Policy', { PolicyDocument: { 'Statement': [ { @@ -112,7 +112,7 @@ describe('image asset', () => { 'Ref': 'MyUserDC45028B', }, ], - }); + })); }); testFutureBehavior('docker directory is staged if asset staging is enabled', flags, App, (app) => { diff --git a/packages/@aws-cdk/aws-ecr/package.json b/packages/@aws-cdk/aws-ecr/package.json index 8a2346bdf580c..1884a03470d31 100644 --- a/packages/@aws-cdk/aws-ecr/package.json +++ b/packages/@aws-cdk/aws-ecr/package.json @@ -77,7 +77,6 @@ "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-ecr/test/auth-token.test.ts b/packages/@aws-cdk/aws-ecr/test/auth-token.test.ts index 421ba7c2f6645..f9be93b1e15d0 100644 --- a/packages/@aws-cdk/aws-ecr/test/auth-token.test.ts +++ b/packages/@aws-cdk/aws-ecr/test/auth-token.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import { expect as expectCDK, haveResourceLike } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; import { AuthorizationToken, PublicGalleryAuthorizationToken } from '../lib'; @@ -13,7 +13,7 @@ describe('auth-token', () => { AuthorizationToken.grantRead(user); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expectCDK(stack).to(haveResourceLike('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -23,7 +23,7 @@ describe('auth-token', () => { }, ], }, - }); + })); }); test('PublicGalleryAuthorizationToken.grantRead()', () => { @@ -35,7 +35,7 @@ describe('auth-token', () => { PublicGalleryAuthorizationToken.grantRead(user); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expectCDK(stack).to(haveResourceLike('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -48,6 +48,6 @@ describe('auth-token', () => { }, ], }, - }); + })); }); }); diff --git a/packages/@aws-cdk/aws-ecr/test/repository.test.ts b/packages/@aws-cdk/aws-ecr/test/repository.test.ts index c112b2da3eb6c..37cc58a8485eb 100644 --- a/packages/@aws-cdk/aws-ecr/test/repository.test.ts +++ b/packages/@aws-cdk/aws-ecr/test/repository.test.ts @@ -1,5 +1,5 @@ import { EOL } from 'os'; -import { Template } from '@aws-cdk/assertions'; +import { expect as expectCDK, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as ecr from '../lib'; @@ -15,7 +15,7 @@ describe('repository', () => { new ecr.Repository(stack, 'Repo'); // THEN - Template.fromStack(stack).templateMatches({ + expectCDK(stack).toMatch({ Resources: { Repo02AC86CF: { Type: 'AWS::ECR::Repository', @@ -34,11 +34,11 @@ describe('repository', () => { new ecr.Repository(stack, 'Repo', { imageScanOnPush: true }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECR::Repository', { + expectCDK(stack).to(haveResource('AWS::ECR::Repository', { ImageScanningConfiguration: { ScanOnPush: true, }, - }); + })); }); test('tag-based lifecycle policy', () => { @@ -50,12 +50,12 @@ describe('repository', () => { repo.addLifecycleRule({ tagPrefixList: ['abc'], maxImageCount: 1 }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECR::Repository', { + expectCDK(stack).to(haveResource('AWS::ECR::Repository', { LifecyclePolicy: { // eslint-disable-next-line max-len LifecyclePolicyText: '{"rules":[{"rulePriority":1,"selection":{"tagStatus":"tagged","tagPrefixList":["abc"],"countType":"imageCountMoreThan","countNumber":1},"action":{"type":"expire"}}]}', }, - }); + })); }); test('image tag mutability can be set', () => { @@ -64,9 +64,9 @@ describe('repository', () => { new ecr.Repository(stack, 'Repo', { imageTagMutability: ecr.TagMutability.IMMUTABLE }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECR::Repository', { + expectCDK(stack).to(haveResource('AWS::ECR::Repository', { ImageTagMutability: 'IMMUTABLE', - }); + })); }); test('add day-based lifecycle policy', () => { @@ -80,12 +80,12 @@ describe('repository', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECR::Repository', { + expectCDK(stack).to(haveResource('AWS::ECR::Repository', { LifecyclePolicy: { // eslint-disable-next-line max-len LifecyclePolicyText: '{"rules":[{"rulePriority":1,"selection":{"tagStatus":"any","countType":"sinceImagePushed","countNumber":5,"countUnit":"days"},"action":{"type":"expire"}}]}', }, - }); + })); }); test('add count-based lifecycle policy', () => { @@ -99,12 +99,12 @@ describe('repository', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECR::Repository', { + expectCDK(stack).to(haveResource('AWS::ECR::Repository', { LifecyclePolicy: { // eslint-disable-next-line max-len LifecyclePolicyText: '{"rules":[{"rulePriority":1,"selection":{"tagStatus":"any","countType":"imageCountMoreThan","countNumber":5},"action":{"type":"expire"}}]}', }, - }); + })); }); test('mixing numbered and unnumbered rules', () => { @@ -117,12 +117,12 @@ describe('repository', () => { repo.addLifecycleRule({ rulePriority: 10, tagStatus: ecr.TagStatus.TAGGED, tagPrefixList: ['b'], maxImageCount: 5 }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECR::Repository', { + expectCDK(stack).to(haveResource('AWS::ECR::Repository', { LifecyclePolicy: { // eslint-disable-next-line max-len LifecyclePolicyText: '{"rules":[{"rulePriority":10,"selection":{"tagStatus":"tagged","tagPrefixList":["b"],"countType":"imageCountMoreThan","countNumber":5},"action":{"type":"expire"}},{"rulePriority":11,"selection":{"tagStatus":"tagged","tagPrefixList":["a"],"countType":"imageCountMoreThan","countNumber":5},"action":{"type":"expire"}}]}', }, - }); + })); }); test('tagstatus Any is automatically sorted to the back', () => { @@ -135,12 +135,12 @@ describe('repository', () => { repo.addLifecycleRule({ tagStatus: ecr.TagStatus.TAGGED, tagPrefixList: ['important'], maxImageCount: 999 }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECR::Repository', { + expectCDK(stack).to(haveResource('AWS::ECR::Repository', { LifecyclePolicy: { // eslint-disable-next-line max-len LifecyclePolicyText: '{"rules":[{"rulePriority":1,"selection":{"tagStatus":"tagged","tagPrefixList":["important"],"countType":"imageCountMoreThan","countNumber":999},"action":{"type":"expire"}},{"rulePriority":2,"selection":{"tagStatus":"any","countType":"imageCountMoreThan","countNumber":5},"action":{"type":"expire"}}]}', }, - }); + })); }); test('lifecycle rules can be added upon initialization', () => { @@ -155,12 +155,12 @@ describe('repository', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECR::Repository', { + expectCDK(stack).to(haveResource('AWS::ECR::Repository', { 'LifecyclePolicy': { // eslint-disable-next-line max-len 'LifecyclePolicyText': '{"rules":[{"rulePriority":1,"selection":{"tagStatus":"any","countType":"imageCountMoreThan","countNumber":3},"action":{"type":"expire"}}]}', }, - }); + })); }); test('calculate repository URI', () => { @@ -173,7 +173,7 @@ describe('repository', () => { // THEN const arnSplit = { 'Fn::Split': [':', { 'Fn::GetAtt': ['Repo02AC86CF', 'Arn'] }] }; - expect(stack.resolve(uri)).toEqual({ + expectCDK(stack.resolve(uri)).toMatch({ 'Fn::Join': ['', [ { 'Fn::Select': [4, arnSplit] }, '.dkr.ecr.', @@ -219,8 +219,8 @@ describe('repository', () => { }); // THEN - expect(stack.resolve(repo.repositoryArn)).toEqual({ 'Fn::GetAtt': ['Boom', 'Arn'] }); - expect(stack.resolve(repo.repositoryName)).toEqual({ 'Fn::GetAtt': ['Boom', 'Name'] }); + expectCDK(stack.resolve(repo.repositoryArn)).toMatch({ 'Fn::GetAtt': ['Boom', 'Arn'] }); + expectCDK(stack.resolve(repo.repositoryName)).toMatch({ 'Fn::GetAtt': ['Boom', 'Name'] }); }); test('import only with a repository name (arn is deduced)', () => { @@ -231,7 +231,7 @@ describe('repository', () => { const repo = ecr.Repository.fromRepositoryName(stack, 'just-name', 'my-repo'); // THEN - expect(stack.resolve(repo.repositoryArn)).toEqual({ + expectCDK(stack.resolve(repo.repositoryArn)).toMatch({ 'Fn::Join': ['', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -257,8 +257,8 @@ describe('repository', () => { }); // THEN - expect(stack.resolve(repo.repositoryName)).toEqual({ 'Fn::GetAtt': ['Boom', 'Name'] }); - expect(stack.resolve(repo.repositoryArn)).toEqual({ + expectCDK(stack.resolve(repo.repositoryName)).toMatch({ 'Fn::GetAtt': ['Boom', 'Name'] }); + expectCDK(stack.resolve(repo.repositoryArn)).toMatch({ 'Fn::Join': ['', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -284,7 +284,7 @@ describe('repository', () => { })); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECR::Repository', { + expectCDK(stack).to(haveResource('AWS::ECR::Repository', { RepositoryPolicyText: { Statement: [ { @@ -295,7 +295,7 @@ describe('repository', () => { ], Version: '2012-10-17', }, - }); + })); }); test('fails if repository policy has no actions', () => { @@ -341,7 +341,7 @@ describe('repository', () => { }, }); - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expectCDK(stack).to(haveResourceLike('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'aws.ecr', @@ -360,7 +360,7 @@ describe('repository', () => { }, }, 'State': 'ENABLED', - }); + })); }); test('onImageScanCompleted without imageTags creates the correct event', () => { @@ -373,7 +373,7 @@ describe('repository', () => { }, }); - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expectCDK(stack).to(haveResourceLike('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'aws.ecr', @@ -390,7 +390,7 @@ describe('repository', () => { }, }, 'State': 'ENABLED', - }); + })); }); test('onImageScanCompleted with one imageTag creates the correct event', () => { @@ -404,7 +404,7 @@ describe('repository', () => { }, }); - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expectCDK(stack).to(haveResourceLike('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'aws.ecr', @@ -424,7 +424,7 @@ describe('repository', () => { }, }, 'State': 'ENABLED', - }); + })); }); test('onImageScanCompleted with multiple imageTags creates the correct event', () => { @@ -438,7 +438,7 @@ describe('repository', () => { }, }); - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expectCDK(stack).to(haveResourceLike('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'aws.ecr', @@ -460,7 +460,7 @@ describe('repository', () => { }, }, 'State': 'ENABLED', - }); + })); }); test('removal policy is "Retain" by default', () => { @@ -471,10 +471,10 @@ describe('repository', () => { new ecr.Repository(stack, 'Repo'); // THEN - Template.fromStack(stack).hasResource('AWS::ECR::Repository', { + expectCDK(stack).to(haveResource('AWS::ECR::Repository', { 'Type': 'AWS::ECR::Repository', 'DeletionPolicy': 'Retain', - }); + }, ResourcePart.CompleteDefinition)); }); test('"Delete" removal policy can be set explicitly', () => { @@ -487,10 +487,10 @@ describe('repository', () => { }); // THEN - Template.fromStack(stack).hasResource('AWS::ECR::Repository', { + expectCDK(stack).to(haveResource('AWS::ECR::Repository', { 'Type': 'AWS::ECR::Repository', 'DeletionPolicy': 'Delete', - }); + }, ResourcePart.CompleteDefinition)); }); test('grant adds appropriate resource-*', () => { @@ -502,7 +502,7 @@ describe('repository', () => { repo.grantPull(new iam.AnyPrincipal()); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECR::Repository', { + expectCDK(stack).to(haveResource('AWS::ECR::Repository', { 'RepositoryPolicyText': { 'Statement': [ { @@ -517,7 +517,7 @@ describe('repository', () => { ], 'Version': '2012-10-17', }, - }); + })); }); }); diff --git a/packages/@aws-cdk/aws-logs/package.json b/packages/@aws-cdk/aws-logs/package.json index d89b03cfd4409..9e13692d15a14 100644 --- a/packages/@aws-cdk/aws-logs/package.json +++ b/packages/@aws-cdk/aws-logs/package.json @@ -73,7 +73,6 @@ "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-logs/test/destination.test.ts b/packages/@aws-cdk/aws-logs/test/destination.test.ts index d1a7031af8aaa..05f482441d265 100644 --- a/packages/@aws-cdk/aws-logs/test/destination.test.ts +++ b/packages/@aws-cdk/aws-logs/test/destination.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import { CrossAccountDestination } from '../lib'; @@ -19,7 +19,7 @@ describe('destination', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Logs::Destination', { + expect(stack).toHaveResource('AWS::Logs::Destination', { DestinationName: 'MyDestination', RoleArn: { 'Fn::GetAtt': ['Role1ABCC5F0', 'Arn'] }, TargetArn: 'arn:bogus', @@ -47,7 +47,7 @@ describe('destination', () => { })); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Logs::Destination', (props: any) => { + expect(stack).toHaveResource('AWS::Logs::Destination', (props: any) => { const pol = JSON.parse(props.DestinationPolicy); return pol.Statement[0].Action === 'logs:TalkToMe'; diff --git a/packages/@aws-cdk/aws-logs/test/log-retention.test.ts b/packages/@aws-cdk/aws-logs/test/log-retention.test.ts index c826baf3aabfa..8f8ed85bb4d47 100644 --- a/packages/@aws-cdk/aws-logs/test/log-retention.test.ts +++ b/packages/@aws-cdk/aws-logs/test/log-retention.test.ts @@ -1,4 +1,5 @@ -import { Match, Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; +import { ABSENT } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import { LogRetention, RetentionDays } from '../lib'; @@ -17,7 +18,7 @@ describe('log retention', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -39,12 +40,12 @@ describe('log retention', () => { ], }); - Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { + expect(stack).toHaveResource('AWS::Lambda::Function', { Handler: 'index.handler', Runtime: 'nodejs14.x', }); - Template.fromStack(stack).hasResourceProperties('Custom::LogRetention', { + expect(stack).toHaveResource('Custom::LogRetention', { 'ServiceToken': { 'Fn::GetAtt': [ 'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A', @@ -71,7 +72,7 @@ describe('log retention', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -91,7 +92,7 @@ describe('log retention', () => { ], }); - Template.fromStack(stack).resourceCountIs('AWS::IAM::Role', 0); + expect(stack).toCountResources('AWS::IAM::Role', 0); }); @@ -104,8 +105,8 @@ describe('log retention', () => { retention: RetentionDays.INFINITE, }); - Template.fromStack(stack).hasResourceProperties('Custom::LogRetention', { - RetentionInDays: Match.absentProperty(), + expect(stack).toHaveResource('Custom::LogRetention', { + RetentionInDays: ABSENT, }); @@ -119,7 +120,7 @@ describe('log retention', () => { retention: RetentionDays.INFINITE, }); - Template.fromStack(stack).hasResourceProperties('Custom::LogRetention', { + expect(stack).toHaveResource('Custom::LogRetention', { LogGroupRegion: 'us-east-1', }); diff --git a/packages/@aws-cdk/aws-logs/test/loggroup.test.ts b/packages/@aws-cdk/aws-logs/test/loggroup.test.ts index 3237a564dd617..72b3c390a2f96 100644 --- a/packages/@aws-cdk/aws-logs/test/loggroup.test.ts +++ b/packages/@aws-cdk/aws-logs/test/loggroup.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import { CfnParameter, RemovalPolicy, Stack } from '@aws-cdk/core'; @@ -16,7 +16,7 @@ describe('log group', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { + expect(stack).toHaveResource('AWS::Logs::LogGroup', { KmsKeyId: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, }); @@ -34,7 +34,7 @@ describe('log group', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { + expect(stack).toHaveResource('AWS::Logs::LogGroup', { RetentionInDays: 7, }); @@ -49,7 +49,7 @@ describe('log group', () => { new LogGroup(stack, 'LogGroup'); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { + expect(stack).toHaveResource('AWS::Logs::LogGroup', { RetentionInDays: 731, }); @@ -66,7 +66,7 @@ describe('log group', () => { }); // THEN - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { LogGroupF5B46931: { Type: 'AWS::Logs::LogGroup', @@ -92,7 +92,7 @@ describe('log group', () => { }); // THEN - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { LogGroupF5B46931: { Type: 'AWS::Logs::LogGroup', @@ -116,7 +116,7 @@ describe('log group', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { + expect(stack).toHaveResource('AWS::Logs::LogGroup', { RetentionInDays: { Ref: 'RetentionInDays', }, @@ -136,7 +136,7 @@ describe('log group', () => { }); // THEN - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { LogGroupF5B46931: { Type: 'AWS::Logs::LogGroup', @@ -160,7 +160,7 @@ describe('log group', () => { // THEN expect(imported.logGroupName).toEqual('my-log-group'); expect(imported.logGroupArn).toEqual('arn:aws:logs:us-east-1:123456789012:log-group:my-log-group:*'); - Template.fromStack(stack2).hasResourceProperties('AWS::Logs::LogStream', { + expect(stack2).toHaveResource('AWS::Logs::LogStream', { LogGroupName: 'my-log-group', }); @@ -178,7 +178,7 @@ describe('log group', () => { expect(imported.logGroupName).toEqual('my-log-group'); expect(imported.logGroupArn).toMatch(/^arn:.+:logs:.+:.+:log-group:my-log-group:\*$/); - Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogStream', { + expect(stack).toHaveResource('AWS::Logs::LogStream', { LogGroupName: 'my-log-group', }); @@ -199,7 +199,7 @@ describe('log group', () => { imported.grantWrite(user); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Version: '2012-10-17', Statement: [ @@ -241,7 +241,7 @@ describe('log group', () => { imported.grantWrite(user); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Version: '2012-10-17', Statement: [ @@ -267,7 +267,7 @@ describe('log group', () => { const metric = lg.extractMetric('$.myField', 'MyService', 'Field'); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Logs::MetricFilter', { + expect(stack).toHaveResource('AWS::Logs::MetricFilter', { FilterPattern: '{ $.myField = "*" }', LogGroupName: { Ref: 'LogGroupF5B46931' }, MetricTransformations: [ @@ -293,7 +293,7 @@ describe('log group', () => { const metric = lg.extractMetric('$.myField', 'MyNamespace/MyService', 'Field'); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Logs::MetricFilter', { + expect(stack).toHaveResource('AWS::Logs::MetricFilter', { FilterPattern: '{ $.myField = "*" }', MetricTransformations: [ { @@ -319,7 +319,7 @@ describe('log group', () => { lg.grantWrite(user); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -346,7 +346,7 @@ describe('log group', () => { // THEN expect(logGroup.logGroupPhysicalName()).toEqual('my-log-group'); - Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { + expect(stack).toHaveResource('AWS::Logs::LogGroup', { LogGroupName: 'my-log-group', }); diff --git a/packages/@aws-cdk/aws-logs/test/logstream.test.ts b/packages/@aws-cdk/aws-logs/test/logstream.test.ts index 6a23dc2274257..c55501adc6b1b 100644 --- a/packages/@aws-cdk/aws-logs/test/logstream.test.ts +++ b/packages/@aws-cdk/aws-logs/test/logstream.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { Stack } from '@aws-cdk/core'; import { LogGroup, LogStream } from '../lib'; @@ -15,7 +15,7 @@ describe('log stream', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogStream', { + expect(stack).toHaveResource('AWS::Logs::LogStream', { }); diff --git a/packages/@aws-cdk/aws-logs/test/metricfilter.test.ts b/packages/@aws-cdk/aws-logs/test/metricfilter.test.ts index b2ba39bc2fbb8..15814140a9488 100644 --- a/packages/@aws-cdk/aws-logs/test/metricfilter.test.ts +++ b/packages/@aws-cdk/aws-logs/test/metricfilter.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { Metric } from '@aws-cdk/aws-cloudwatch'; import { Stack } from '@aws-cdk/core'; import { FilterPattern, LogGroup, MetricFilter } from '../lib'; @@ -19,7 +19,7 @@ describe('metric filter', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Logs::MetricFilter', { + expect(stack).toHaveResource('AWS::Logs::MetricFilter', { MetricTransformations: [{ MetricNamespace: 'AWS/Test', MetricName: 'Latency', diff --git a/packages/@aws-cdk/aws-logs/test/subscriptionfilter.test.ts b/packages/@aws-cdk/aws-logs/test/subscriptionfilter.test.ts index d4f8b26d6bc1d..595e4933745ca 100644 --- a/packages/@aws-cdk/aws-logs/test/subscriptionfilter.test.ts +++ b/packages/@aws-cdk/aws-logs/test/subscriptionfilter.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { FilterPattern, ILogGroup, ILogSubscriptionDestination, LogGroup, SubscriptionFilter } from '../lib'; @@ -17,7 +17,7 @@ describe('subscription filter', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Logs::SubscriptionFilter', { + expect(stack).toHaveResource('AWS::Logs::SubscriptionFilter', { DestinationArn: 'arn:bogus', FilterPattern: 'some pattern', LogGroupName: { Ref: 'LogGroupF5B46931' }, diff --git a/packages/@aws-cdk/aws-s3-assets/package.json b/packages/@aws-cdk/aws-s3-assets/package.json index ad88fc4e71baf..8ab66aee891bb 100644 --- a/packages/@aws-cdk/aws-s3-assets/package.json +++ b/packages/@aws-cdk/aws-s3-assets/package.json @@ -71,7 +71,6 @@ "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/cloud-assembly-schema": "0.0.0", diff --git a/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts b/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts index 995e96a99570c..ccc93159714ff 100644 --- a/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts +++ b/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts @@ -1,11 +1,12 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import { Match, Template } from '@aws-cdk/assertions'; +import { ResourcePart, SynthUtils } from '@aws-cdk/assert-internal'; +import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; import { Asset } from '../lib/asset'; const SAMPLE_ASSET_DIR = path.join(__dirname, 'sample-asset-directory'); @@ -78,7 +79,7 @@ test('"file" assets', () => { expect(entry).toBeTruthy(); // synthesize first so "prepare" is called - const template = Template.fromStack(stack).toJSON(); + const template = SynthUtils.synthesize(stack).template; expect(stack.resolve(entry!.data)).toEqual({ path: 'asset.78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197.txt', @@ -107,7 +108,7 @@ test('"readers" or "grantRead" can be used to grant read permissions on the asse asset.grantRead(group); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Version: '2012-10-17', Statement: [ @@ -199,12 +200,12 @@ test('addResourceMetadata can be used to add CFN metadata to resources', () => { asset.addResourceMetadata(resource, 'PropName'); // THEN - Template.fromStack(stack).hasResource('My::Resource::Type', { + expect(stack).toHaveResource('My::Resource::Type', { Metadata: { 'aws:asset:path': 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', 'aws:asset:property': 'PropName', }, - }); + }, ResourcePart.CompleteDefinition); }); test('asset metadata is only emitted if ASSET_RESOURCE_METADATA_ENABLED_CONTEXT is defined', () => { @@ -218,12 +219,12 @@ test('asset metadata is only emitted if ASSET_RESOURCE_METADATA_ENABLED_CONTEXT asset.addResourceMetadata(resource, 'PropName'); // THEN - Template.fromStack(stack).hasResource('My::Resource::Type', Match.not({ + expect(stack).not.toHaveResource('My::Resource::Type', { Metadata: { 'aws:asset:path': SAMPLE_ASSET_DIR, 'aws:asset:property': 'PropName', }, - })); + }, ResourcePart.CompleteDefinition); }); test('nested assemblies share assets: legacy synth edition', () => { @@ -347,7 +348,7 @@ describe('staging', () => { // WHEN asset.addResourceMetadata(resource, 'PropName'); - const template = Template.fromStack(stack).toJSON(); + const template = SynthUtils.synthesize(stack).template; expect(template.Resources.MyResource.Metadata).toEqual({ 'aws:asset:path': 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', 'aws:asset:property': 'PropName', @@ -373,7 +374,7 @@ describe('staging', () => { // WHEN asset.addResourceMetadata(resource, 'PropName'); - const template = Template.fromStack(stack).toJSON(); + const template = SynthUtils.synthesize(stack).template; expect(template.Resources.MyResource.Metadata).toEqual({ 'aws:asset:path': SAMPLE_ASSET_DIR, 'aws:asset:property': 'PropName', diff --git a/packages/@aws-cdk/aws-s3/package.json b/packages/@aws-cdk/aws-s3/package.json index 855e5ea00f59e..752032c268f34 100644 --- a/packages/@aws-cdk/aws-s3/package.json +++ b/packages/@aws-cdk/aws-s3/package.json @@ -73,7 +73,6 @@ "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-s3/test/aspect.test.ts b/packages/@aws-cdk/aws-s3/test/aspect.test.ts index c5631c8f20ab0..023ef826c18f8 100644 --- a/packages/@aws-cdk/aws-s3/test/aspect.test.ts +++ b/packages/@aws-cdk/aws-s3/test/aspect.test.ts @@ -1,3 +1,5 @@ +import '@aws-cdk/assert-internal/jest'; +import { SynthUtils } from '@aws-cdk/assert-internal'; import * as cdk from '@aws-cdk/core'; import { IConstruct } from 'constructs'; import * as s3 from '../lib'; @@ -5,15 +7,14 @@ import * as s3 from '../lib'; describe('aspect', () => { test('bucket must have versioning: failure', () => { // GIVEN - const app = new cdk.App(); - const stack = new cdk.Stack(app); + const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket'); // WHEN cdk.Aspects.of(stack).add(new BucketVersioningChecker()); // THEN - const assembly = app.synth().getStackArtifact(stack.artifactId); + const assembly = SynthUtils.synthesize(stack); const errorMessage = assembly.messages.find(m => m.entry.data === 'Bucket versioning is not enabled'); expect(errorMessage).toBeDefined(); @@ -22,8 +23,7 @@ describe('aspect', () => { test('bucket must have versioning: success', () => { // GIVEN - const app = new cdk.App(); - const stack = new cdk.Stack(app); + const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { versioned: true, }); @@ -32,7 +32,7 @@ describe('aspect', () => { cdk.Aspects.of(stack).add(new BucketVersioningChecker()); // THEN - const assembly = app.synth().getStackArtifact(stack.artifactId); + const assembly = SynthUtils.synthesize(stack); expect(assembly.messages.length).toEqual(0); diff --git a/packages/@aws-cdk/aws-s3/test/bucket-policy.test.ts b/packages/@aws-cdk/aws-s3/test/bucket-policy.test.ts index cabced21c7bb2..13b4b798bbd09 100644 --- a/packages/@aws-cdk/aws-s3/test/bucket-policy.test.ts +++ b/packages/@aws-cdk/aws-s3/test/bucket-policy.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { AnyPrincipal, PolicyStatement } from '@aws-cdk/aws-iam'; import { RemovalPolicy, Stack, App } from '@aws-cdk/core'; import * as s3 from '../lib'; @@ -20,7 +20,7 @@ describe('bucket policy', () => { principals: [new AnyPrincipal()], })); - Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', { + expect(stack).toHaveResource('AWS::S3::BucketPolicy', { Bucket: { 'Ref': 'MyBucketF68F3FF0', }, @@ -54,7 +54,7 @@ describe('bucket policy', () => { principals: [new AnyPrincipal()], })); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -99,7 +99,7 @@ describe('bucket policy', () => { })); myBucket.policy?.applyRemovalPolicy(RemovalPolicy.RETAIN); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', diff --git a/packages/@aws-cdk/aws-s3/test/bucket.test.ts b/packages/@aws-cdk/aws-s3/test/bucket.test.ts index 567e8aea25de3..3ef166722c1b6 100644 --- a/packages/@aws-cdk/aws-s3/test/bucket.test.ts +++ b/packages/@aws-cdk/aws-s3/test/bucket.test.ts @@ -1,5 +1,6 @@ +import '@aws-cdk/assert-internal/jest'; import { EOL } from 'os'; -import { Match, Template } from '@aws-cdk/assertions'; +import { ResourcePart, SynthUtils, arrayWith, objectLike } from '@aws-cdk/assert-internal'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as cdk from '@aws-cdk/core'; @@ -18,7 +19,7 @@ describe('bucket', () => { new s3.Bucket(stack, 'MyBucket'); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -38,7 +39,7 @@ describe('bucket', () => { }); expect(() => { - Template.fromStack(stack).toJSON(); + SynthUtils.synthesize(stack); }).toThrow(/bucketName: 5 should be a string/); @@ -50,7 +51,7 @@ describe('bucket', () => { encryption: s3.BucketEncryption.UNENCRYPTED, }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -69,7 +70,7 @@ describe('bucket', () => { encryption: s3.BucketEncryption.KMS_MANAGED, }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -256,9 +257,9 @@ describe('bucket', () => { new s3.Bucket(stack, 'MyBucket', { encryptionKey, encryption: s3.BucketEncryption.KMS }); - Template.fromStack(stack).resourceCountIs('AWS::KMS::Key', 1); + expect(stack).toHaveResource('AWS::KMS::Key'); - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { 'BucketEncryption': { 'ServerSideEncryptionConfiguration': [ { @@ -282,7 +283,7 @@ describe('bucket', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { enforceSSL: true }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -343,7 +344,7 @@ describe('bucket', () => { new s3.Bucket(stack, 'MyBucket', { bucketKeyEnabled: true, encryption: s3.BucketEncryption.KMS }); - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { 'BucketEncryption': { 'ServerSideEncryptionConfiguration': [ { @@ -383,7 +384,7 @@ describe('bucket', () => { versioned: true, }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -406,7 +407,7 @@ describe('bucket', () => { blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -432,7 +433,7 @@ describe('bucket', () => { blockPublicAccess: s3.BlockPublicAccess.BLOCK_ACLS, }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -456,7 +457,7 @@ describe('bucket', () => { blockPublicAccess: new s3.BlockPublicAccess({ restrictPublicBuckets: true }), }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -479,7 +480,7 @@ describe('bucket', () => { accessControl: s3.BucketAccessControl.LOG_DELIVERY_WRITE, }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -506,7 +507,7 @@ describe('bucket', () => { principals: [new iam.AnyPrincipal()], })); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -631,7 +632,7 @@ describe('bucket', () => { encryption: s3.BucketEncryption.UNENCRYPTED, }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyBucketF68F3FF0: { Type: 'AWS::S3::Bucket', @@ -676,7 +677,7 @@ describe('bucket', () => { expect(bucket.bucketArn).toEqual(bucketArn); expect(stack.resolve(bucket.bucketName)).toEqual('my-bucket'); - expect(Template.fromStack(stack).toJSON()).toEqual({}); + expect(SynthUtils.synthesize(stack).template).toEqual({}); }); @@ -690,7 +691,7 @@ describe('bucket', () => { })); // at this point we technically didn't create any resources in the consuming stack. - Template.fromStack(stack).templateMatches({}); + expect(stack).toMatchTemplate({}); }); @@ -707,7 +708,7 @@ describe('bucket', () => { actions: ['s3:*'], })); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyUserDC45028B': { 'Type': 'AWS::IAM::User', @@ -761,7 +762,7 @@ describe('bucket', () => { const reader = new iam.User(stack, 'Reader'); const bucket = new s3.Bucket(stack, 'MyBucket'); bucket.grantRead(reader); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'ReaderF7BF189D': { 'Type': 'AWS::IAM::User', @@ -829,7 +830,7 @@ describe('bucket', () => { const user = new iam.User(stack, 'MyUser'); bucket.grantReadWrite(user); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -903,7 +904,7 @@ describe('bucket', () => { bucket.grantRead(new iam.OrganizationPrincipal('o-1234')); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', { + expect(stack).toHaveResource('AWS::S3::BucketPolicy', { PolicyDocument: { 'Version': '2012-10-17', 'Statement': [ @@ -921,9 +922,9 @@ describe('bucket', () => { }, }); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResourceLike('AWS::KMS::Key', { 'KeyPolicy': { - 'Statement': Match.arrayWith([ + 'Statement': arrayWith( { 'Action': ['kms:Decrypt', 'kms:DescribeKey'], 'Effect': 'Allow', @@ -931,7 +932,7 @@ describe('bucket', () => { 'Principal': { AWS: '*' }, 'Condition': { 'StringEquals': { 'aws:PrincipalOrgID': 'o-1234' } }, }, - ]), + ), 'Version': '2012-10-17', }, @@ -946,7 +947,7 @@ describe('bucket', () => { const user = new iam.User(stack, 'MyUser'); bucket.grantReadWrite(user); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketKeyC17130CF': { 'Type': 'AWS::KMS::Key', @@ -1120,10 +1121,10 @@ describe('bucket', () => { bucket.grantReadWrite(user); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ - Match.objectLike({ + { 'Action': [ 's3:GetObject*', 's3:GetBucket*', @@ -1141,7 +1142,7 @@ describe('bucket', () => { ]], }, ], - }), + }, ], }, }); @@ -1157,7 +1158,7 @@ describe('bucket', () => { const user = new iam.User(stack, 'MyUser'); bucket.grantWrite(user); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -1226,10 +1227,10 @@ describe('bucket', () => { bucket.grantWrite(user); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ - Match.objectLike({ + { 'Action': [ 's3:DeleteObject*', 's3:PutObject', @@ -1244,7 +1245,7 @@ describe('bucket', () => { ]], }, ], - }), + }, ], }, }); @@ -1261,10 +1262,10 @@ describe('bucket', () => { bucket.grantPut(user); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ - Match.objectLike({ + { 'Action': [ 's3:PutObject', 's3:Abort*', @@ -1275,7 +1276,7 @@ describe('bucket', () => { '/*', ]], }, - }), + }, ], }, }); @@ -1295,7 +1296,7 @@ describe('bucket', () => { bucket.grantWrite(writer); bucket.grantDelete(deleter); - const resources = Template.fromStack(stack).toJSON().Resources; + const resources = SynthUtils.synthesize(stack).template.Resources; const actions = (id: string) => resources[id].Properties.PolicyDocument.Statement[0].Action; expect(actions('WriterDefaultPolicyDC585BCE')).toEqual(['s3:DeleteObject*', 's3:PutObject', 's3:Abort*']); @@ -1319,7 +1320,7 @@ describe('bucket', () => { bucket.grantDelete(deleter); // then - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -1358,7 +1359,7 @@ describe('bucket', () => { const user = new iam.User(stackB, 'UserWhoNeedsAccess'); bucketFromStackA.grantRead(user); - Template.fromStack(stackA).templateMatches({ + expect(stackA).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -1381,7 +1382,7 @@ describe('bucket', () => { }, }); - Template.fromStack(stackB).templateMatches({ + expect(stackB).toMatchTemplate({ 'Resources': { 'UserWhoNeedsAccessF8959C3D': { 'Type': 'AWS::IAM::User', @@ -1449,10 +1450,10 @@ describe('bucket', () => { bucketFromStackA.grantRead(roleFromStackB); // then - Template.fromStack(stackA).hasResourceProperties('AWS::S3::BucketPolicy', { + expect(stackA).toHaveResourceLike('AWS::S3::BucketPolicy', { 'PolicyDocument': { 'Statement': [ - Match.objectLike({ + { 'Action': [ 's3:GetObject*', 's3:GetBucket*', @@ -1473,12 +1474,12 @@ describe('bucket', () => { ], }, }, - }), + }, ], }, }); - Template.fromStack(stackB).hasResourceProperties('AWS::IAM::Policy', { + expect(stackB).toHaveResourceLike('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -1542,10 +1543,13 @@ describe('bucket', () => { bucketFromStackA.grantRead(roleFromStackB); // then - Template.fromStack(stackA).hasResourceProperties('AWS::KMS::Key', { + expect(stackA).toHaveResourceLike('AWS::KMS::Key', { 'KeyPolicy': { - 'Statement': Match.arrayWith([ - Match.objectLike({ + 'Statement': [ + { + // grant to the root of the owning account + }, + { 'Action': [ 'kms:Decrypt', 'kms:DescribeKey', @@ -1565,14 +1569,17 @@ describe('bucket', () => { ], }, }, - }), - ]), + }, + ], }, }); - Template.fromStack(stackB).hasResourceProperties('AWS::IAM::Policy', { + expect(stackB).toHaveResourceLike('AWS::IAM::Policy', { 'PolicyDocument': { - 'Statement': Match.arrayWith([ + 'Statement': [ + { + // Bucket grant + }, { 'Action': [ 'kms:Decrypt', @@ -1581,7 +1588,7 @@ describe('bucket', () => { 'Effect': 'Allow', 'Resource': '*', }, - ]), + ], }, }); @@ -1602,7 +1609,7 @@ describe('bucket', () => { new cdk.CfnOutput(stack, 'YourFileURL', { value: bucket.urlForObject('/your/file.txt') }); // "/" is optional new cdk.CfnOutput(stack, 'RegionBucketURL', { value: bucketWithRegion.urlForObject() }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -1704,7 +1711,7 @@ describe('bucket', () => { new cdk.CfnOutput(stack, 'MyFileS3URL', { value: bucket.s3UrlForObject('my/file.txt') }); new cdk.CfnOutput(stack, 'YourFileS3URL', { value: bucket.s3UrlForObject('/your/file.txt') }); // "/" is optional - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -1770,7 +1777,7 @@ describe('bucket', () => { bucket.grantPublicAccess(); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', { + expect(stack).toHaveResource('AWS::S3::BucketPolicy', { 'PolicyDocument': { 'Statement': [ { @@ -1795,7 +1802,7 @@ describe('bucket', () => { bucket.grantPublicAccess('only/access/these/*'); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', { + expect(stack).toHaveResource('AWS::S3::BucketPolicy', { 'PolicyDocument': { 'Statement': [ { @@ -1820,7 +1827,7 @@ describe('bucket', () => { bucket.grantPublicAccess('*', 's3:GetObject', 's3:PutObject'); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', { + expect(stack).toHaveResource('AWS::S3::BucketPolicy', { 'PolicyDocument': { 'Statement': [ { @@ -1846,7 +1853,7 @@ describe('bucket', () => { result.resourceStatement!.addCondition('IpAddress', { 'aws:SourceIp': '54.240.143.0/24' }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', { + expect(stack).toHaveResource('AWS::S3::BucketPolicy', { 'PolicyDocument': { 'Statement': [ { @@ -1885,7 +1892,7 @@ describe('bucket', () => { new s3.Bucket(stack, 'Website', { websiteIndexDocument: 'index2.html', }); - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { WebsiteConfiguration: { IndexDocument: 'index2.html', }, @@ -1907,7 +1914,7 @@ describe('bucket', () => { websiteIndexDocument: 'index2.html', websiteErrorDocument: 'error.html', }); - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { WebsiteConfiguration: { IndexDocument: 'index2.html', ErrorDocument: 'error.html', @@ -1983,7 +1990,7 @@ describe('bucket', () => { protocol: s3.RedirectProtocol.HTTPS, }, }); - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { WebsiteConfiguration: { RedirectAllRequestsTo: { HostName: 'www.example.com', @@ -2032,7 +2039,7 @@ describe('bucket', () => { }, }], }); - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { WebsiteConfiguration: { RoutingRules: [{ RedirectRule: { @@ -2166,7 +2173,7 @@ describe('bucket', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { LoggingConfiguration: { DestinationBucketName: { Ref: 'AccessLogs8B620ECA', @@ -2189,7 +2196,7 @@ describe('bucket', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { LoggingConfiguration: { DestinationBucketName: { Ref: 'AccessLogs8B620ECA', @@ -2210,7 +2217,7 @@ describe('bucket', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { LoggingConfiguration: { LogFilePrefix: 'hello', }, @@ -2252,7 +2259,7 @@ describe('bucket', () => { ], }); - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResourceLike('AWS::S3::Bucket', { InventoryConfigurations: [ { Enabled: true, @@ -2267,10 +2274,10 @@ describe('bucket', () => { ], }); - Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', { + expect(stack).toHaveResourceLike('AWS::S3::BucketPolicy', { Bucket: { Ref: 'InventoryBucketA869B8CB' }, PolicyDocument: { - Statement: Match.arrayWith([Match.objectLike({ + Statement: arrayWith(objectLike({ Action: 's3:PutObject', Principal: { Service: 's3.amazonaws.com' }, Resource: [ @@ -2281,7 +2288,7 @@ describe('bucket', () => { 'Fn::Join': ['', [{ 'Fn::GetAtt': ['InventoryBucketA869B8CB', 'Arn'] }, '/*']], }, ], - })]), + })), }, }); @@ -2293,7 +2300,7 @@ describe('bucket', () => { new s3.Bucket(stack, 'MyBucket', { objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_PREFERRED, }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -2319,7 +2326,7 @@ describe('bucket', () => { new s3.Bucket(stack, 'MyBucket', { objectOwnership: s3.ObjectOwnership.OBJECT_WRITER, }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -2345,7 +2352,7 @@ describe('bucket', () => { new s3.Bucket(stack, 'MyBucket', { objectOwnership: undefined, }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -2365,12 +2372,12 @@ describe('bucket', () => { autoDeleteObjects: true, }); - Template.fromStack(stack).hasResource('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { UpdateReplacePolicy: 'Delete', DeletionPolicy: 'Delete', - }); + }, ResourcePart.CompleteDefinition); - Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', { + expect(stack).toHaveResource('AWS::S3::BucketPolicy', { Bucket: { Ref: 'MyBucketF68F3FF0', }, @@ -2419,7 +2426,7 @@ describe('bucket', () => { }, }); - Template.fromStack(stack).hasResource('Custom::S3AutoDeleteObjects', { + expect(stack).toHaveResource('Custom::S3AutoDeleteObjects', { 'Properties': { 'ServiceToken': { 'Fn::GetAtt': [ @@ -2434,7 +2441,7 @@ describe('bucket', () => { 'DependsOn': [ 'MyBucketPolicyE7FBAC7B', ], - }); + }, ResourcePart.CompleteDefinition); }); @@ -2452,7 +2459,7 @@ describe('bucket', () => { autoDeleteObjects: true, }); - Template.fromStack(stack).resourceCountIs('AWS::Lambda::Function', 1); + expect(stack).toCountResources('AWS::Lambda::Function', 1); }); diff --git a/packages/@aws-cdk/aws-s3/test/cors.test.ts b/packages/@aws-cdk/aws-s3/test/cors.test.ts index f3ef167ea79ea..feddf0b159669 100644 --- a/packages/@aws-cdk/aws-s3/test/cors.test.ts +++ b/packages/@aws-cdk/aws-s3/test/cors.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { Stack } from '@aws-cdk/core'; import { Bucket, HttpMethods } from '../lib'; @@ -15,7 +15,7 @@ describe('cors', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { CorsConfiguration: { CorsRules: [{ AllowedMethods: ['GET', 'HEAD'], @@ -73,7 +73,7 @@ describe('cors', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { CorsConfiguration: { CorsRules: [ { diff --git a/packages/@aws-cdk/aws-s3/test/metrics.test.ts b/packages/@aws-cdk/aws-s3/test/metrics.test.ts index 8a1e497e98b82..9072c33a5fcb6 100644 --- a/packages/@aws-cdk/aws-s3/test/metrics.test.ts +++ b/packages/@aws-cdk/aws-s3/test/metrics.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { Stack } from '@aws-cdk/core'; import { Bucket } from '../lib'; @@ -14,7 +14,7 @@ describe('metrics', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { MetricsConfigurations: [{ Id: 'test', }], @@ -36,7 +36,7 @@ describe('metrics', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { MetricsConfigurations: [{ Id: 'test', Prefix: 'prefix', @@ -59,7 +59,7 @@ describe('metrics', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { MetricsConfigurations: [{ Id: 'test', TagFilters: [ @@ -92,7 +92,7 @@ describe('metrics', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { MetricsConfigurations: [{ Id: 'test', TagFilters: [ diff --git a/packages/@aws-cdk/aws-s3/test/notification.test.ts b/packages/@aws-cdk/aws-s3/test/notification.test.ts index 62dba52814049..2f3178fb42af0 100644 --- a/packages/@aws-cdk/aws-s3/test/notification.test.ts +++ b/packages/@aws-cdk/aws-s3/test/notification.test.ts @@ -1,4 +1,5 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; +import { ResourcePart } from '@aws-cdk/assert-internal'; import * as cdk from '@aws-cdk/core'; import * as s3 from '../lib'; @@ -15,8 +16,8 @@ describe('notification', () => { }), }); - Template.fromStack(stack).resourceCountIs('AWS::S3::Bucket', 1); - Template.fromStack(stack).hasResourceProperties('Custom::S3BucketNotifications', { + expect(stack).toHaveResource('AWS::S3::Bucket'); + expect(stack).toHaveResource('Custom::S3BucketNotifications', { NotificationConfiguration: { TopicConfigurations: [ { @@ -44,7 +45,7 @@ describe('notification', () => { }), }, { prefix: 'images/', suffix: '.png' }); - Template.fromStack(stack).hasResourceProperties('Custom::S3BucketNotifications', { + expect(stack).toHaveResource('Custom::S3BucketNotifications', { NotificationConfiguration: { TopicConfigurations: [ { @@ -86,7 +87,7 @@ describe('notification', () => { }), }); - Template.fromStack(stack).hasResource('AWS::Lambda::Function', { + expect(stack).toHaveResourceLike('AWS::Lambda::Function', { Type: 'AWS::Lambda::Function', Properties: { Role: { @@ -98,7 +99,7 @@ describe('notification', () => { }, DependsOn: ['BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleDefaultPolicy2CF63D36', 'BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC'], - }); + }, ResourcePart.CompleteDefinition ); }); diff --git a/packages/@aws-cdk/aws-s3/test/rules.test.ts b/packages/@aws-cdk/aws-s3/test/rules.test.ts index 3591d41d98446..818ae7d830439 100644 --- a/packages/@aws-cdk/aws-s3/test/rules.test.ts +++ b/packages/@aws-cdk/aws-s3/test/rules.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { Duration, Stack } from '@aws-cdk/core'; import { Bucket, StorageClass } from '../lib'; @@ -15,7 +15,7 @@ describe('rules', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { LifecycleConfiguration: { Rules: [{ ExpirationInDays: 30, @@ -38,7 +38,7 @@ describe('rules', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { LifecycleConfiguration: { Rules: [{ ExpirationInDays: 30, @@ -62,7 +62,7 @@ describe('rules', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { LifecycleConfiguration: { Rules: [{ ExpirationDate: '2018-01-01T00:00:00', @@ -89,7 +89,7 @@ describe('rules', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { LifecycleConfiguration: { Rules: [{ Transitions: [{ @@ -140,7 +140,7 @@ describe('rules', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { LifecycleConfiguration: { Rules: [{ ExpiredObjectDeleteMarker: true, diff --git a/packages/@aws-cdk/aws-s3/test/util.test.ts b/packages/@aws-cdk/aws-s3/test/util.test.ts index 0a89105246403..e688932a0f7eb 100644 --- a/packages/@aws-cdk/aws-s3/test/util.test.ts +++ b/packages/@aws-cdk/aws-s3/test/util.test.ts @@ -1,3 +1,4 @@ +import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import { parseBucketArn, parseBucketName } from '../lib/util'; diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index 82c8a3509dfdd..d3ab48584fd61 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -73,7 +73,6 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert-internal": "0.0.0", - "@aws-cdk/assertions": "0.0.0", "@aws-cdk/aws-s3": "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-sqs/test/sqs.test.ts b/packages/@aws-cdk/aws-sqs/test/sqs.test.ts index ef626c8a5565c..3f86e6d68b6da 100644 --- a/packages/@aws-cdk/aws-sqs/test/sqs.test.ts +++ b/packages/@aws-cdk/aws-sqs/test/sqs.test.ts @@ -1,4 +1,5 @@ -import { Template } from '@aws-cdk/assertions'; +import { ResourcePart } from '@aws-cdk/assert-internal'; +import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import { CfnParameter, Duration, Stack, App, Token } from '@aws-cdk/core'; @@ -12,7 +13,7 @@ test('default properties', () => { expect(q.fifo).toEqual(false); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'Queue4A7E3555': { 'Type': 'AWS::SQS::Queue', @@ -22,9 +23,9 @@ test('default properties', () => { }, }); - Template.fromStack(stack).hasResource('AWS::SQS::Queue', { + expect(stack).toHaveResource('AWS::SQS::Queue', { DeletionPolicy: 'Delete', - }); + }, ResourcePart.CompleteDefinition); }); test('with a dead letter queue', () => { @@ -32,7 +33,7 @@ test('with a dead letter queue', () => { const dlq = new sqs.Queue(stack, 'DLQ'); new sqs.Queue(stack, 'Queue', { deadLetterQueue: { queue: dlq, maxReceiveCount: 3 } }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'DLQ581697C4': { 'Type': 'AWS::SQS::Queue', @@ -87,7 +88,7 @@ test('message retention period can be provided as a parameter', () => { }); // THEN - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Parameters': { 'myretentionperiod': { 'Type': 'Number', @@ -118,7 +119,7 @@ test('addToPolicy will automatically create a policy for this queue', () => { principals: [new iam.ArnPrincipal('arn')], })); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyQueueE6CA6235': { 'Type': 'AWS::SQS::Queue', @@ -319,7 +320,7 @@ describe('grants', () => { queue.grantPurge(user); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -346,7 +347,7 @@ describe('queue encryption', () => { const queue = new sqs.Queue(stack, 'Queue', { encryptionMasterKey: key }); expect(queue.encryptionMasterKey).toEqual(key); - Template.fromStack(stack).hasResourceProperties('AWS::SQS::Queue', { + expect(stack).toHaveResource('AWS::SQS::Queue', { 'KmsMasterKeyId': { 'Fn::GetAtt': ['CustomKey1E6D0D07', 'Arn'] }, }); }); @@ -356,8 +357,8 @@ describe('queue encryption', () => { new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.KMS }); - Template.fromStack(stack).resourceCountIs('AWS::KMS::Key', 1); - Template.fromStack(stack).hasResourceProperties('AWS::SQS::Queue', { + expect(stack).toHaveResource('AWS::KMS::Key'); + expect(stack).toHaveResource('AWS::SQS::Queue', { 'KmsMasterKeyId': { 'Fn::GetAtt': [ 'QueueKey39FCBAE6', @@ -371,7 +372,7 @@ describe('queue encryption', () => { const stack = new Stack(); new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.KMS_MANAGED }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'Queue4A7E3555': { 'Type': 'AWS::SQS::Queue', @@ -399,7 +400,7 @@ describe('queue encryption', () => { queue.grantSendMessages(role); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -436,7 +437,7 @@ test('test ".fifo" suffixed queues register as fifo', () => { expect(queue.fifo).toEqual(true); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'Queue4A7E3555': { 'Type': 'AWS::SQS::Queue', @@ -459,7 +460,7 @@ test('test a fifo queue is observed when the "fifo" property is specified', () = expect(queue.fifo).toEqual(true); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'Queue4A7E3555': { 'Type': 'AWS::SQS::Queue', @@ -482,7 +483,7 @@ test('test a fifo queue is observed when high throughput properties are specifie }); expect(queue.fifo).toEqual(true); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'Queue4A7E3555': { 'Type': 'AWS::SQS::Queue', @@ -580,7 +581,7 @@ function testGrant(action: (q: sqs.Queue, principal: iam.IPrincipal) => void, .. action(queue, principal); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { diff --git a/packages/@aws-cdk/aws-ssm/package.json b/packages/@aws-cdk/aws-ssm/package.json index 9d7c71967e062..c30ae7e97c8f1 100644 --- a/packages/@aws-cdk/aws-ssm/package.json +++ b/packages/@aws-cdk/aws-ssm/package.json @@ -73,7 +73,6 @@ "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-ssm/test/parameter-store-string.test.ts b/packages/@aws-cdk/aws-ssm/test/parameter-store-string.test.ts index d9da8a0697535..a8ce58f22fded 100644 --- a/packages/@aws-cdk/aws-ssm/test/parameter-store-string.test.ts +++ b/packages/@aws-cdk/aws-ssm/test/parameter-store-string.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as ssm from '../lib'; @@ -26,7 +26,7 @@ test('can reference SSMPS string - latest version', () => { }); // THEN - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Parameters: { RefParameter: { Type: 'AWS::SSM::Parameter::Value', diff --git a/packages/@aws-cdk/aws-ssm/test/parameter.test.ts b/packages/@aws-cdk/aws-ssm/test/parameter.test.ts index 8b0b1d19b1097..2e40e4626cbd3 100644 --- a/packages/@aws-cdk/aws-ssm/test/parameter.test.ts +++ b/packages/@aws-cdk/aws-ssm/test/parameter.test.ts @@ -1,6 +1,6 @@ /* eslint-disable max-len */ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as cdk from '@aws-cdk/core'; @@ -19,7 +19,7 @@ test('creating a String SSM Parameter', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::SSM::Parameter', { + expect(stack).toHaveResource('AWS::SSM::Parameter', { AllowedPattern: '.*', Description: 'The value Foo', Name: 'FooParameter', @@ -42,7 +42,7 @@ test('expect String SSM Parameter to have tier properly set', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::SSM::Parameter', { + expect(stack).toHaveResource('AWS::SSM::Parameter', { Tier: 'Advanced', }); }); @@ -82,7 +82,7 @@ test('creating a StringList SSM Parameter', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::SSM::Parameter', { + expect(stack).toHaveResource('AWS::SSM::Parameter', { AllowedPattern: '(Foo|Bar)', Description: 'The values Foo and Bar', Name: 'FooParameter', @@ -284,7 +284,7 @@ test('StringParameter.fromStringParameterName', () => { expect(stack.resolve(param.parameterName)).toEqual('MyParamName'); expect(stack.resolve(param.parameterType)).toEqual('String'); expect(stack.resolve(param.stringValue)).toEqual({ Ref: 'MyParamNameParameter' }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Parameters: { MyParamNameParameter: { Type: 'AWS::SSM::Parameter::Value', @@ -431,7 +431,7 @@ test('StringParameter.fromSecureStringParameterAttributes with encryption key cr param.grantRead(role); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -491,7 +491,7 @@ test('StringParameter.fromSecureStringParameterAttributes with encryption key cr param.grantWrite(role); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -589,7 +589,7 @@ describe('valueForStringParameter', () => { const value = ssm.StringParameter.valueForStringParameter(stack, 'my-param-name'); // THEN - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Parameters: { SsmParameterValuemyparamnameC96584B6F00A464EAD1953AFF4B05118Parameter: { Type: 'AWS::SSM::Parameter::Value', @@ -611,7 +611,7 @@ describe('valueForStringParameter', () => { ssm.StringParameter.valueForStringParameter(stack, 'my-param-name'); // THEN - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Parameters: { SsmParameterValuemyparamnameC96584B6F00A464EAD1953AFF4B05118Parameter: { Type: 'AWS::SSM::Parameter::Value', diff --git a/packages/@aws-cdk/aws-ssm/test/ssm-document.test.ts b/packages/@aws-cdk/aws-ssm/test/ssm-document.test.ts index ba57c2ed0e4a3..36f6f54c566e6 100644 --- a/packages/@aws-cdk/aws-ssm/test/ssm-document.test.ts +++ b/packages/@aws-cdk/aws-ssm/test/ssm-document.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import { expect, haveResource } from '@aws-cdk/assert-internal'; import * as cdk from '@aws-cdk/core'; import * as ssm from '../lib'; @@ -16,11 +16,11 @@ test('association name is rendered properly in L1 construct', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::SSM::Association', { + expect(stack).to(haveResource('AWS::SSM::Association', { Name: 'document', Parameters: { a: ['a'], B: [], }, - }); + })); }); From 94659d7d3c9489b9a2b626bb1c10d342ad6d3b67 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Thu, 30 Sep 2021 13:32:16 +0100 Subject: [PATCH 07/23] chore: (backport) enable publishing of independent alpha modules (#16693) This is the (hopefully!) final commit to enable us to independently publish the experimental and dev-preview (collectively "alpha") modules as independent packages. After this change, the next automated v2 release will publish both aws-cdk-lib, as well as one @aws-cdk/aws-foobar-alpha module for each service that is either in experimental or dev-preview. NOTE: This is a backport of #16674, minus the changes to `version.v2.json`. fixes #16432 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- lerna.json | 3 ++- package.json | 3 ++- scripts/bump.js | 3 +-- tools/@aws-cdk/individual-pkg-gen/README.md | 2 +- .../individual-pkg-gen/bin/individual-pkg-gen | 2 +- ...y-files-removing-deps.ts => transform-packages.ts} | 8 ++++++++ tools/@aws-cdk/pkglint/lib/rules.ts | 11 +++++++---- 7 files changed, 22 insertions(+), 10 deletions(-) rename tools/@aws-cdk/individual-pkg-gen/{copy-files-removing-deps.ts => transform-packages.ts} (96%) diff --git a/lerna.json b/lerna.json index 719bfb368e4a6..81738d74fb433 100644 --- a/lerna.json +++ b/lerna.json @@ -10,7 +10,8 @@ "packages/@aws-cdk/*/lambda-packages/*", "tools/*", "tools/@aws-cdk/*", - "scripts/script-tests" + "scripts/script-tests", + "packages/individual-packages/*" ], "rejectCycles": "true", "version": "0.0.0" diff --git a/package.json b/package.json index 1f4f938a8eb1c..038642572d7b1 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,8 @@ "packages/@aws-cdk/*/lambda-packages/*", "tools/*", "tools/@aws-cdk/*", - "scripts/@aws-cdk/script-tests" + "scripts/@aws-cdk/script-tests", + "packages/individual-packages/*" ], "nohoist": [ "**/jszip", diff --git a/scripts/bump.js b/scripts/bump.js index 7461970c2d3c9..14bb70fb0a5c8 100755 --- a/scripts/bump.js +++ b/scripts/bump.js @@ -65,8 +65,7 @@ async function main() { // good thing we're getting rid of it... opts.verbose = !!process.env.VERBOSE; if (majorVersion > 1) { - // NOTE - Once we start publishing alpha modules independently, this needs to be flipped to 'separate' - opts.experimentalChangesTreatment = 'strip'; + opts.experimentalChangesTreatment = 'separate'; } // Rename some options to match cdk-release inputs (replaces bumpFiles, packageFiles, and infile) opts.versionFile = ver.versionFile; diff --git a/tools/@aws-cdk/individual-pkg-gen/README.md b/tools/@aws-cdk/individual-pkg-gen/README.md index a86d15f679815..bee4467df6795 100644 --- a/tools/@aws-cdk/individual-pkg-gen/README.md +++ b/tools/@aws-cdk/individual-pkg-gen/README.md @@ -10,7 +10,7 @@ We do the translation in 2 phases: 1. Copy all files from the V1 versions of the modules, and remove all dependencies from the packages besides other experimental ones. Save the original dependencies in a `_package.json` files of the copied modules. - This is done in the [`copy-files-removing-deps.ts` file](copy-files-removing-deps.ts). + This is done in the [`transform-packages.ts` file](transform-packages.ts). 2. Run `lerna bootstrap`. 3. In phase 2, bring back the dependencies by renaming the `_package.json` files to `package.json`. This is done in the [`restore-package-jsons.ts` file](restore-package-jsons.ts). diff --git a/tools/@aws-cdk/individual-pkg-gen/bin/individual-pkg-gen b/tools/@aws-cdk/individual-pkg-gen/bin/individual-pkg-gen index 3a335a40c39f7..cb693f991e2e8 100755 --- a/tools/@aws-cdk/individual-pkg-gen/bin/individual-pkg-gen +++ b/tools/@aws-cdk/individual-pkg-gen/bin/individual-pkg-gen @@ -3,6 +3,6 @@ set -euo pipefail scriptdir=$(cd $(dirname $0) && pwd) -node $scriptdir/../copy-files-removing-deps.js +node $scriptdir/../transform-packages.js yarn lerna bootstrap node $scriptdir/../restore-package-jsons.js diff --git a/tools/@aws-cdk/individual-pkg-gen/copy-files-removing-deps.ts b/tools/@aws-cdk/individual-pkg-gen/transform-packages.ts similarity index 96% rename from tools/@aws-cdk/individual-pkg-gen/copy-files-removing-deps.ts rename to tools/@aws-cdk/individual-pkg-gen/transform-packages.ts index c9be13d39568f..9a7f9bbcf5e36 100644 --- a/tools/@aws-cdk/individual-pkg-gen/copy-files-removing-deps.ts +++ b/tools/@aws-cdk/individual-pkg-gen/transform-packages.ts @@ -130,6 +130,14 @@ function transformPackageJson(pkg: any, source: string, destination: string, alp packageJson.name += '-alpha'; packageJson.repository.directory = `packages/individual-packages/${pkgUnscopedName}`; + // All individual packages are private by default on v2. + // This needs to be removed for the alpha modules to be published. + // However, we should only do this for packages we intend to publish (those with a `publishConfig`) + if (packageJson.publishConfig) { + packageJson.private = undefined; + packageJson.publishConfig.tag = 'latest'; + } + // disable awslint (some rules are hard-coded to @aws-cdk/core) packageJson.awslint = { exclude: ['*:*'], diff --git a/tools/@aws-cdk/pkglint/lib/rules.ts b/tools/@aws-cdk/pkglint/lib/rules.ts index 8f308273a84e5..140674492f0cc 100644 --- a/tools/@aws-cdk/pkglint/lib/rules.ts +++ b/tools/@aws-cdk/pkglint/lib/rules.ts @@ -59,9 +59,11 @@ export class PublishConfigTagIsRequired extends ValidationRule { // While v2 is still under development, we publish all v2 packages with the 'next' // distribution tag, while still tagging all v1 packages as 'latest'. - // The one exception is 'aws-cdk-lib', since it's a new package for v2. + // There are two sets of exceptions: + // 'aws-cdk-lib' (new v2 package) and all of the '*-alpha' modules, since they are also new packages for v2. const newV2Packages = ['aws-cdk-lib']; - const defaultPublishTag = (cdkMajorVersion() === 2 && !newV2Packages.includes(pkg.json.name)) ? 'next' : 'latest'; + const isNewPackageForV2 = newV2Packages.includes(pkg.json.name) || pkg.packageName.endsWith('-alpha'); + const defaultPublishTag = (cdkMajorVersion() === 2 && !isNewPackageForV2) ? 'next' : 'latest'; if (pkg.json.publishConfig?.tag !== defaultPublishTag) { pkg.report({ @@ -1570,6 +1572,7 @@ export class JestSetup extends ValidationRule { export class UbergenPackageVisibility extends ValidationRule { public readonly name = 'ubergen/package-visibility'; + // The ONLY (non-alpha) packages that should be published for v2. // These include dependencies of the CDK CLI (aws-cdk). private readonly publicPackages = [ '@aws-cdk/cfnspec', @@ -1586,7 +1589,7 @@ export class UbergenPackageVisibility extends ValidationRule { public validate(pkg: PackageJson): void { if (cdkMajorVersion() === 2) { - // Only packages in the publicPackages list should be "public". Everything else should be private. + // Only alpha packages and packages in the publicPackages list should be "public". Everything else should be private. if (this.publicPackages.includes(pkg.json.name) && pkg.json.private === true) { pkg.report({ ruleName: this.name, @@ -1595,7 +1598,7 @@ export class UbergenPackageVisibility extends ValidationRule { delete pkg.json.private; }, }); - } else if (!this.publicPackages.includes(pkg.json.name) && pkg.json.private !== true) { + } else if (!this.publicPackages.includes(pkg.json.name) && pkg.json.private !== true && !pkg.packageName.endsWith('-alpha')) { pkg.report({ ruleName: this.name, message: 'Package must not be public', From 3a724a2f2bc31ac50fc2b58bf07b047562cdb41e Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 30 Sep 2021 15:59:20 +0200 Subject: [PATCH 08/23] chore(init-templates): fix dependencies in v2 TypeScript templates (#16577) We should not be using `dependencies` in libraries, but `devDependencies` instead. Fixes #16572. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../init-templates/v2/lib/typescript/package.template.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/aws-cdk/lib/init-templates/v2/lib/typescript/package.template.json b/packages/aws-cdk/lib/init-templates/v2/lib/typescript/package.template.json index 9d140a074a2ba..b388f5270b769 100644 --- a/packages/aws-cdk/lib/init-templates/v2/lib/typescript/package.template.json +++ b/packages/aws-cdk/lib/init-templates/v2/lib/typescript/package.template.json @@ -11,6 +11,8 @@ "devDependencies": { "@types/jest": "^26.0.10", "@types/node": "10.17.27", + "aws-cdk-lib": "%cdk-version%", + "constructs": "%constructs-version%", "jest": "^26.4.2", "ts-jest": "^26.2.0", "typescript": "~3.9.7" @@ -18,9 +20,5 @@ "peerDependencies": { "aws-cdk-lib": "%cdk-version%", "constructs": "%constructs-version%" - }, - "dependencies": { - "aws-cdk-lib": "%cdk-version%", - "constructs": "%constructs-version%" } } From 7f7be089fa84afd0ab009a7feca2df4315749bc3 Mon Sep 17 00:00:00 2001 From: Taehyun Kim Date: Thu, 30 Sep 2021 23:52:26 +0900 Subject: [PATCH 09/23] feat(eks): `connectAutoScalingGroupCapacity` on imported clusters (#14650) This PR will enable `connectAutoScalingGroupCapacity` to imported eks cluster. I'm using this in our eks cluster, and it works fine. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-eks/README.md | 12 + packages/@aws-cdk/aws-eks/lib/cluster.ts | 273 ++++++++++-------- packages/@aws-cdk/aws-eks/lib/user-data.ts | 21 +- .../@aws-cdk/aws-eks/test/cluster.test.ts | 30 ++ .../@aws-cdk/aws-eks/test/user-data.test.ts | 58 ++++ 5 files changed, 270 insertions(+), 124 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index 1437446e6640a..3b04f5d9e7f61 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -372,6 +372,18 @@ const asg = new ec2.AutoScalingGroup(...); cluster.connectAutoScalingGroupCapacity(asg); ``` +To connect a self-managed node group to an imported cluster, use the `cluster.connectAutoScalingGroupCapacity()` method: + +```ts +const importedCluster = eks.Cluster.fromClusterAttributes(stack, 'ImportedCluster', { + clusterName: cluster.clusterName, + clusterSecurityGroupId: cluster.clusterSecurityGroupId, +}); + +const asg = new ec2.AutoScalingGroup(...); +importedCluster.connectAutoScalingGroupCapacity(asg); +``` + In both cases, the [cluster security group](https://docs.aws.amazon.com/eks/latest/userguide/sec-group-reqs.html#cluster-sg) will be automatically attached to the auto-scaling group, allowing for traffic to flow freely between managed and self-managed nodes. diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 731df91ea606d..2db0438537f97 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -181,6 +181,27 @@ export interface ICluster extends IResource, ec2.IConnectable { */ addCdk8sChart(id: string, chart: Construct): KubernetesManifest; + /** + * Connect capacity in the form of an existing AutoScalingGroup to the EKS cluster. + * + * The AutoScalingGroup must be running an EKS-optimized AMI containing the + * /etc/eks/bootstrap.sh script. This method will configure Security Groups, + * add the right policies to the instance role, apply the right tags, and add + * the required user data to the instance's launch configuration. + * + * Spot instances will be labeled `lifecycle=Ec2Spot` and tainted with `PreferNoSchedule`. + * If kubectl is enabled, the + * [spot interrupt handler](https://github.com/awslabs/ec2-spot-labs/tree/master/ec2-spot-eks-solution/spot-termination-handler) + * daemon will be installed on all spot instances to handle + * [EC2 Spot Instance Termination Notices](https://aws.amazon.com/blogs/aws/new-ec2-spot-instance-termination-notices/). + * + * Prefer to use `addAutoScalingGroupCapacity` if possible. + * + * @see https://docs.aws.amazon.com/eks/latest/userguide/launch-workers.html + * @param autoScalingGroup [disable-awslint:ref-via-interface] + * @param options options for adding auto scaling groups, like customizing the bootstrap script + */ + connectAutoScalingGroupCapacity(autoScalingGroup: autoscaling.AutoScalingGroup, options: AutoScalingGroupOptions): void; } /** @@ -721,6 +742,16 @@ abstract class ClusterBase extends Resource implements ICluster { public abstract readonly kubectlMemory?: Size; public abstract readonly prune: boolean; public abstract readonly openIdConnectProvider: iam.IOpenIdConnectProvider; + public abstract readonly awsAuth: AwsAuth; + + private _spotInterruptHandler?: HelmChart; + + /** + * Manages the aws-auth config map. + * + * @internal + */ + protected _awsAuth?: AwsAuth; /** * Defines a Kubernetes resource in this cluster. @@ -771,6 +802,124 @@ abstract class ClusterBase extends Resource implements ICluster { cluster: this, }); } + + /** + * Installs the AWS spot instance interrupt handler on the cluster if it's not + * already added. + */ + private addSpotInterruptHandler() { + if (!this._spotInterruptHandler) { + this._spotInterruptHandler = this.addHelmChart('spot-interrupt-handler', { + chart: 'aws-node-termination-handler', + version: '0.13.2', + repository: 'https://aws.github.io/eks-charts', + namespace: 'kube-system', + values: { + nodeSelector: { + lifecycle: LifecycleLabel.SPOT, + }, + }, + }); + } + + return this._spotInterruptHandler; + } + + /** + * Connect capacity in the form of an existing AutoScalingGroup to the EKS cluster. + * + * The AutoScalingGroup must be running an EKS-optimized AMI containing the + * /etc/eks/bootstrap.sh script. This method will configure Security Groups, + * add the right policies to the instance role, apply the right tags, and add + * the required user data to the instance's launch configuration. + * + * Spot instances will be labeled `lifecycle=Ec2Spot` and tainted with `PreferNoSchedule`. + * If kubectl is enabled, the + * [spot interrupt handler](https://github.com/awslabs/ec2-spot-labs/tree/master/ec2-spot-eks-solution/spot-termination-handler) + * daemon will be installed on all spot instances to handle + * [EC2 Spot Instance Termination Notices](https://aws.amazon.com/blogs/aws/new-ec2-spot-instance-termination-notices/). + * + * Prefer to use `addAutoScalingGroupCapacity` if possible. + * + * @see https://docs.aws.amazon.com/eks/latest/userguide/launch-workers.html + * @param autoScalingGroup [disable-awslint:ref-via-interface] + * @param options options for adding auto scaling groups, like customizing the bootstrap script + */ + public connectAutoScalingGroupCapacity(autoScalingGroup: autoscaling.AutoScalingGroup, options: AutoScalingGroupOptions) { + // self rules + autoScalingGroup.connections.allowInternally(ec2.Port.allTraffic()); + + // Cluster to:nodes rules + autoScalingGroup.connections.allowFrom(this, ec2.Port.tcp(443)); + autoScalingGroup.connections.allowFrom(this, ec2.Port.tcpRange(1025, 65535)); + + // Allow HTTPS from Nodes to Cluster + autoScalingGroup.connections.allowTo(this, ec2.Port.tcp(443)); + + // Allow all node outbound traffic + autoScalingGroup.connections.allowToAnyIpv4(ec2.Port.allTcp()); + autoScalingGroup.connections.allowToAnyIpv4(ec2.Port.allUdp()); + autoScalingGroup.connections.allowToAnyIpv4(ec2.Port.allIcmp()); + + // allow traffic to/from managed node groups (eks attaches this security group to the managed nodes) + autoScalingGroup.addSecurityGroup(this.clusterSecurityGroup); + + const bootstrapEnabled = options.bootstrapEnabled ?? true; + if (options.bootstrapOptions && !bootstrapEnabled) { + throw new Error('Cannot specify "bootstrapOptions" if "bootstrapEnabled" is false'); + } + + if (bootstrapEnabled) { + const userData = options.machineImageType === MachineImageType.BOTTLEROCKET ? + renderBottlerocketUserData(this) : + renderAmazonLinuxUserData(this, autoScalingGroup, options.bootstrapOptions); + autoScalingGroup.addUserData(...userData); + } + + autoScalingGroup.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSWorkerNodePolicy')); + autoScalingGroup.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEKS_CNI_Policy')); + autoScalingGroup.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryReadOnly')); + + // EKS Required Tags + // https://docs.aws.amazon.com/eks/latest/userguide/worker.html + Tags.of(autoScalingGroup).add(`kubernetes.io/cluster/${this.clusterName}`, 'owned', { + applyToLaunchedInstances: true, + // exclude security groups to avoid multiple "owned" security groups. + // (the cluster security group already has this tag) + excludeResourceTypes: ['AWS::EC2::SecurityGroup'], + }); + + // do not attempt to map the role if `kubectl` is not enabled for this + // cluster or if `mapRole` is set to false. By default this should happen. + let mapRole = options.mapRole ?? true; + if (mapRole && !(this instanceof Cluster)) { + // do the mapping... + Annotations.of(autoScalingGroup).addWarning('Auto-mapping aws-auth role for imported cluster is not supported, please map role manually'); + mapRole = false; + } + if (mapRole) { + // see https://docs.aws.amazon.com/en_us/eks/latest/userguide/add-user-role.html + this.awsAuth.addRoleMapping(autoScalingGroup.role, { + username: 'system:node:{{EC2PrivateDNSName}}', + groups: [ + 'system:bootstrappers', + 'system:nodes', + ], + }); + } else { + // since we are not mapping the instance role to RBAC, synthesize an + // output so it can be pasted into `aws-auth-cm.yaml` + new CfnOutput(autoScalingGroup, 'InstanceRoleARN', { + value: autoScalingGroup.role.roleArn, + }); + } + + const addSpotInterruptHandler = options.spotInterruptHandler ?? true; + // if this is an ASG with spot instances, install the spot interrupt handler (only if kubectl is enabled). + if (autoScalingGroup.spotPrice && addSpotInterruptHandler) { + this.addSpotInterruptHandler(); + } + } } /** @@ -959,13 +1108,6 @@ export class Cluster extends ClusterBase { */ private readonly _clusterResource: ClusterResource; - /** - * Manages the aws-auth config map. - */ - private _awsAuth?: AwsAuth; - - private _spotInterruptHandler?: HelmChart; - private _neuronDevicePlugin?: KubernetesManifest; private readonly endpointAccess: EndpointAccess; @@ -1278,97 +1420,6 @@ export class Cluster extends ClusterBase { }); } - /** - * Connect capacity in the form of an existing AutoScalingGroup to the EKS cluster. - * - * The AutoScalingGroup must be running an EKS-optimized AMI containing the - * /etc/eks/bootstrap.sh script. This method will configure Security Groups, - * add the right policies to the instance role, apply the right tags, and add - * the required user data to the instance's launch configuration. - * - * Spot instances will be labeled `lifecycle=Ec2Spot` and tainted with `PreferNoSchedule`. - * If kubectl is enabled, the - * [spot interrupt handler](https://github.com/awslabs/ec2-spot-labs/tree/master/ec2-spot-eks-solution/spot-termination-handler) - * daemon will be installed on all spot instances to handle - * [EC2 Spot Instance Termination Notices](https://aws.amazon.com/blogs/aws/new-ec2-spot-instance-termination-notices/). - * - * Prefer to use `addAutoScalingGroupCapacity` if possible. - * - * @see https://docs.aws.amazon.com/eks/latest/userguide/launch-workers.html - * @param autoScalingGroup [disable-awslint:ref-via-interface] - * @param options options for adding auto scaling groups, like customizing the bootstrap script - */ - public connectAutoScalingGroupCapacity(autoScalingGroup: autoscaling.AutoScalingGroup, options: AutoScalingGroupOptions) { - // self rules - autoScalingGroup.connections.allowInternally(ec2.Port.allTraffic()); - - // Cluster to:nodes rules - autoScalingGroup.connections.allowFrom(this, ec2.Port.tcp(443)); - autoScalingGroup.connections.allowFrom(this, ec2.Port.tcpRange(1025, 65535)); - - // Allow HTTPS from Nodes to Cluster - autoScalingGroup.connections.allowTo(this, ec2.Port.tcp(443)); - - // Allow all node outbound traffic - autoScalingGroup.connections.allowToAnyIpv4(ec2.Port.allTcp()); - autoScalingGroup.connections.allowToAnyIpv4(ec2.Port.allUdp()); - autoScalingGroup.connections.allowToAnyIpv4(ec2.Port.allIcmp()); - - // allow traffic to/from managed node groups (eks attaches this security group to the managed nodes) - autoScalingGroup.addSecurityGroup(this.clusterSecurityGroup); - - const bootstrapEnabled = options.bootstrapEnabled ?? true; - if (options.bootstrapOptions && !bootstrapEnabled) { - throw new Error('Cannot specify "bootstrapOptions" if "bootstrapEnabled" is false'); - } - - if (bootstrapEnabled) { - const userData = options.machineImageType === MachineImageType.BOTTLEROCKET ? - renderBottlerocketUserData(this) : - renderAmazonLinuxUserData(this, autoScalingGroup, options.bootstrapOptions); - autoScalingGroup.addUserData(...userData); - } - - autoScalingGroup.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSWorkerNodePolicy')); - autoScalingGroup.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEKS_CNI_Policy')); - autoScalingGroup.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryReadOnly')); - - // EKS Required Tags - // https://docs.aws.amazon.com/eks/latest/userguide/worker.html - Tags.of(autoScalingGroup).add(`kubernetes.io/cluster/${this.clusterName}`, 'owned', { - applyToLaunchedInstances: true, - // exclude security groups to avoid multiple "owned" security groups. - // (the cluster security group already has this tag) - excludeResourceTypes: ['AWS::EC2::SecurityGroup'], - }); - - // do not attempt to map the role if `kubectl` is not enabled for this - // cluster or if `mapRole` is set to false. By default this should happen. - const mapRole = options.mapRole ?? true; - if (mapRole) { - // see https://docs.aws.amazon.com/en_us/eks/latest/userguide/add-user-role.html - this.awsAuth.addRoleMapping(autoScalingGroup.role, { - username: 'system:node:{{EC2PrivateDNSName}}', - groups: [ - 'system:bootstrappers', - 'system:nodes', - ], - }); - } else { - // since we are not mapping the instance role to RBAC, synthesize an - // output so it can be pasted into `aws-auth-cm.yaml` - new CfnOutput(autoScalingGroup, 'InstanceRoleARN', { - value: autoScalingGroup.role.roleArn, - }); - } - - const addSpotInterruptHandler = options.spotInterruptHandler ?? true; - // if this is an ASG with spot instances, install the spot interrupt handler (only if kubectl is enabled). - if (autoScalingGroup.spotPrice && addSpotInterruptHandler) { - this.addSpotInterruptHandler(); - } - } - /** * Lazily creates the AwsAuth resource, which manages AWS authentication mapping. */ @@ -1509,28 +1560,6 @@ export class Cluster extends ClusterBase { return privateSubnets; } - /** - * Installs the AWS spot instance interrupt handler on the cluster if it's not - * already added. - */ - private addSpotInterruptHandler() { - if (!this._spotInterruptHandler) { - this._spotInterruptHandler = this.addHelmChart('spot-interrupt-handler', { - chart: 'aws-node-termination-handler', - version: '0.13.2', - repository: 'https://aws.github.io/eks-charts', - namespace: 'kube-system', - values: { - nodeSelector: { - lifecycle: LifecycleLabel.SPOT, - }, - }, - }); - } - - return this._spotInterruptHandler; - } - /** * Installs the Neuron device plugin on the cluster if it's not * already added. @@ -1868,6 +1897,10 @@ class ImportedCluster extends ClusterBase { } return this.props.openIdConnectProvider; } + + public get awsAuth(): AwsAuth { + throw new Error('"awsAuth" is not supported on imported clusters'); + } } /** diff --git a/packages/@aws-cdk/aws-eks/lib/user-data.ts b/packages/@aws-cdk/aws-eks/lib/user-data.ts index 8add0f7cb5bbc..f14c254b1f24f 100644 --- a/packages/@aws-cdk/aws-eks/lib/user-data.ts +++ b/packages/@aws-cdk/aws-eks/lib/user-data.ts @@ -1,9 +1,9 @@ import * as autoscaling from '@aws-cdk/aws-autoscaling'; import { Stack } from '@aws-cdk/core'; -import { BootstrapOptions, ICluster, Cluster } from './cluster'; +import { BootstrapOptions, ICluster } from './cluster'; // eslint-disable-next-line max-len -export function renderAmazonLinuxUserData(cluster: Cluster, autoScalingGroup: autoscaling.AutoScalingGroup, options: BootstrapOptions = {}): string[] { +export function renderAmazonLinuxUserData(cluster: ICluster, autoScalingGroup: autoscaling.AutoScalingGroup, options: BootstrapOptions = {}): string[] { const stack = Stack.of(autoScalingGroup); @@ -13,8 +13,21 @@ export function renderAmazonLinuxUserData(cluster: Cluster, autoScalingGroup: au const extraArgs = new Array(); - extraArgs.push(`--apiserver-endpoint '${cluster.clusterEndpoint}'`); - extraArgs.push(`--b64-cluster-ca '${cluster.clusterCertificateAuthorityData}'`); + try { + const clusterEndpoint = cluster.clusterEndpoint; + const clusterCertificateAuthorityData = + cluster.clusterCertificateAuthorityData; + extraArgs.push(`--apiserver-endpoint '${clusterEndpoint}'`); + extraArgs.push(`--b64-cluster-ca '${clusterCertificateAuthorityData}'`); + } catch (e) { + /** + * Errors are ignored here. + * apiserver-endpoint and b64-cluster-ca arguments are added in #12659 to make nodes join the cluster faster. + * As these are not necessary arguments, we don't need to pass these arguments when they don't exist. + * + * @see https://github.com/aws/aws-cdk/pull/12659 + */ + } extraArgs.push(`--use-max-pods ${options.useMaxPods ?? true}`); diff --git a/packages/@aws-cdk/aws-eks/test/cluster.test.ts b/packages/@aws-cdk/aws-eks/test/cluster.test.ts index fa52d069e8d15..14e2bfad0746b 100644 --- a/packages/@aws-cdk/aws-eks/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-eks/test/cluster.test.ts @@ -218,8 +218,38 @@ describe('cluster', () => { expect(template.Resources.ClusterselfmanagedInstanceSecurityGroup64468C3A.Properties.Tags).toEqual([ { Key: 'Name', Value: 'Stack/Cluster/self-managed' }, ]); + }); + + test('connect autoscaling group with imported cluster', () => { + + // GIVEN + const { stack, vpc } = testFixture(); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + defaultCapacity: 0, + version: CLUSTER_VERSION, + prune: false, + }); + const importedCluster = eks.Cluster.fromClusterAttributes(stack, 'ImportedCluster', { + clusterName: cluster.clusterName, + clusterSecurityGroupId: cluster.clusterSecurityGroupId, + }); + const selfManaged = new asg.AutoScalingGroup(stack, 'self-managed', { + instanceType: new ec2.InstanceType('t2.medium'), + vpc: vpc, + machineImage: new ec2.AmazonLinuxImage(), + }); + + // WHEN + importedCluster.connectAutoScalingGroupCapacity(selfManaged, {}); + + const template = SynthUtils.toCloudFormation(stack); + expect(template.Resources.selfmanagedLaunchConfigD41289EB.Properties.SecurityGroups).toEqual([ + { 'Fn::GetAtt': ['selfmanagedInstanceSecurityGroupEA6D80C9', 'GroupId'] }, + { 'Fn::GetAtt': ['Cluster9EE0221C', 'ClusterSecurityGroupId'] }, + ]); }); test('cluster security group is attached when connecting self-managed nodes', () => { diff --git a/packages/@aws-cdk/aws-eks/test/user-data.test.ts b/packages/@aws-cdk/aws-eks/test/user-data.test.ts index a7ed42a5f3759..2e808ed723bca 100644 --- a/packages/@aws-cdk/aws-eks/test/user-data.test.ts +++ b/packages/@aws-cdk/aws-eks/test/user-data.test.ts @@ -35,8 +35,66 @@ describe('user data', () => { }, '/opt/aws/bin/cfn-signal --exit-code $? --stack my-stack --resource ASG46ED3070 --region us-west-33', ]); + }); + + test('imported cluster without clusterEndpoint', () => { + // GIVEN + const { asg, stack, cluster } = newFixtures(); + + const importedCluster = Cluster.fromClusterAttributes(stack, 'ImportedCluster', { + clusterName: cluster.clusterName, + openIdConnectProvider: cluster.openIdConnectProvider, + clusterCertificateAuthorityData: cluster.clusterCertificateAuthorityData, + }); + + // WHEN + const userData = stack.resolve(renderAmazonLinuxUserData(importedCluster, asg)); + + // THEN + expect(userData).toEqual([ + 'set -o xtrace', + { + 'Fn::Join': [ + '', + [ + '/etc/eks/bootstrap.sh ', + { Ref: 'clusterC5B25D0D' }, + ' --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true', + ], + ], + }, + '/opt/aws/bin/cfn-signal --exit-code $? --stack my-stack --resource ASG46ED3070 --region us-west-33', + ]); + }); + + test('imported cluster without clusterCertificateAuthorityData', () => { + // GIVEN + const { asg, stack, cluster } = newFixtures(); + const importedCluster = Cluster.fromClusterAttributes(stack, 'ImportedCluster', { + clusterName: cluster.clusterName, + openIdConnectProvider: cluster.openIdConnectProvider, + clusterEndpoint: cluster.clusterEndpoint, + }); + // WHEN + const userData = stack.resolve(renderAmazonLinuxUserData(importedCluster, asg)); + + // THEN + expect(userData).toEqual([ + 'set -o xtrace', + { + 'Fn::Join': [ + '', + [ + '/etc/eks/bootstrap.sh ', + { Ref: 'clusterC5B25D0D' }, + ' --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true', + ], + ], + }, + '/opt/aws/bin/cfn-signal --exit-code $? --stack my-stack --resource ASG46ED3070 --region us-west-33', + ]); }); test('--use-max-pods=true', () => { From 5d06641cc82d05917a89da21cd79392ec9092c51 Mon Sep 17 00:00:00 2001 From: Romain Marcadier Date: Thu, 30 Sep 2021 19:28:21 +0200 Subject: [PATCH 10/23] fix: set ROSETTA_MAX_WORKER_COUNT in pack.sh (#16738) Remove global environment variables that are set in buildspec.yml files as these interfere with more granular settings set in pack.sh, and instead move all settings there. The max heap size (8G) configured in pack.sh was overridden by the one set in buildspec.yml because the last time the option is passed wins, and pack.sh _prepends_ to `NODE_OPTIONS`. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- buildspec-pr.yaml | 6 ------ buildspec.yaml | 6 ------ pack.sh | 1 + 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/buildspec-pr.yaml b/buildspec-pr.yaml index a8a22717ad996..3d6ee87c8d1ff 100644 --- a/buildspec-pr.yaml +++ b/buildspec-pr.yaml @@ -2,12 +2,6 @@ version: 0.2 # This buildspec is intended to be used by GitHub PR builds. -env: - variables: - # Globally allow node to use a lot of memory - NODE_OPTIONS: --max-old-space-size=4096 - JSII_ROSETTA_MAX_WORKER_COUNT: 8 - phases: install: commands: diff --git a/buildspec.yaml b/buildspec.yaml index d410e74e47b74..3f2bb4e7e1102 100644 --- a/buildspec.yaml +++ b/buildspec.yaml @@ -2,12 +2,6 @@ version: 0.2 # This buildspec is intended to be run by CodePipeline builds. -env: - variables: - # Globally allow node to use a lot of memory - NODE_OPTIONS: --max-old-space-size=4096 - JSII_ROSETTA_MAX_WORKER_COUNT: 8 - phases: install: commands: diff --git a/pack.sh b/pack.sh index d270fb28271c5..99ae852188ebe 100755 --- a/pack.sh +++ b/pack.sh @@ -5,6 +5,7 @@ set -eu export PATH=$PWD/node_modules/.bin:$PATH export NODE_OPTIONS="--max-old-space-size=8192 ${NODE_OPTIONS:-}" +export JSII_ROSETTA_MAX_WORKER_COUNT="${JSII_ROSETTA_MAX_WORKER_COUNT:-8}" root=$PWD # Get version and changelog file name (these require that .versionrc.json would have been generated) From 5e8e7abc16ba392f325ee4bbae05ebfee20f043f Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Thu, 30 Sep 2021 19:41:45 +0100 Subject: [PATCH 11/23] chore: Revert "chore: move modules from assert to assertions (#16628)" (#16740) This reverts commit 5e452f8ee7625d2fc28d6cf695ad94c9220c1434. Similar to #16727, reverting the move to assertions until the correct API to use for the combination of `matchTemplate` and `newStyleSynthesis` can be defined. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/assets/package.json | 2 +- packages/@aws-cdk/assets/test/compat.test.ts | 1 + packages/@aws-cdk/assets/test/staging.test.ts | 1 + packages/@aws-cdk/aws-acmpca/package.json | 2 +- .../@aws-cdk/aws-acmpca/test/acmpca.test.ts | 1 + .../aws-codeguruprofiler/package.json | 2 +- .../test/codeguruprofiler.test.ts | 6 + .../test/profiling-group.test.ts | 20 +-- .../aws-codestarnotifications/package.json | 2 +- .../test/notification-rule.test.ts | 22 ++-- packages/@aws-cdk/aws-events/package.json | 2 +- .../@aws-cdk/aws-events/test/archive.test.ts | 10 +- .../aws-events/test/event-bus.test.ts | 28 ++--- .../@aws-cdk/aws-events/test/input.test.ts | 62 +++++----- .../@aws-cdk/aws-events/test/rule.test.ts | 116 +++++++++--------- packages/@aws-cdk/aws-iam/package.json | 2 +- .../test/auto-cross-stack-refs.test.ts | 9 +- .../aws-iam/test/cross-account.test.ts | 22 ++-- .../aws-iam/test/escape-hatch.test.ts | 8 +- packages/@aws-cdk/aws-iam/test/grant.test.ts | 7 +- packages/@aws-cdk/aws-iam/test/group.test.ts | 8 +- .../aws-iam/test/immutable-role.test.ts | 20 ++- .../@aws-cdk/aws-iam/test/lazy-role.test.ts | 6 +- .../aws-iam/test/managed-policy.test.ts | 24 ++-- .../aws-iam/test/oidc-provider.test.ts | 8 +- .../aws-iam/test/permissions-boundary.test.ts | 31 ++--- .../aws-iam/test/policy-document.test.ts | 1 + .../aws-iam/test/policy-statement.test.ts | 1 + packages/@aws-cdk/aws-iam/test/policy.test.ts | 27 ++-- .../@aws-cdk/aws-iam/test/principals.test.ts | 8 +- .../aws-iam/test/role.from-role-arn.test.ts | 16 +-- packages/@aws-cdk/aws-iam/test/role.test.ts | 36 +++--- .../aws-iam/test/saml-provider.test.ts | 6 +- packages/@aws-cdk/aws-iam/test/user.test.ts | 18 +-- packages/@aws-cdk/aws-kms/package.json | 2 +- packages/@aws-cdk/aws-kms/test/alias.test.ts | 28 +++-- .../aws-kms/test/key.from-lookup.test.ts | 1 + packages/@aws-cdk/aws-kms/test/key.test.ts | 112 ++++++++--------- .../test/via-service-principal.test.ts | 1 + packages/@aws-cdk/aws-signer/package.json | 2 +- .../aws-signer/test/signing-profile.test.ts | 10 +- 41 files changed, 357 insertions(+), 334 deletions(-) create mode 100644 packages/@aws-cdk/aws-codeguruprofiler/test/codeguruprofiler.test.ts diff --git a/packages/@aws-cdk/assets/package.json b/packages/@aws-cdk/assets/package.json index c7a5c2a0b5642..7b77655ea3a16 100644 --- a/packages/@aws-cdk/assets/package.json +++ b/packages/@aws-cdk/assets/package.json @@ -69,7 +69,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assertions": "0.0.0", + "@aws-cdk/assert-internal": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/cdk-integ-tools": "0.0.0", "@aws-cdk/pkglint": "0.0.0", diff --git a/packages/@aws-cdk/assets/test/compat.test.ts b/packages/@aws-cdk/assets/test/compat.test.ts index dfb3c3afd2daa..25afe6dd9411b 100644 --- a/packages/@aws-cdk/assets/test/compat.test.ts +++ b/packages/@aws-cdk/assets/test/compat.test.ts @@ -1,4 +1,5 @@ import { SymlinkFollowMode } from '@aws-cdk/core'; +import '@aws-cdk/assert-internal/jest'; import { FollowMode } from '../lib'; import { toSymlinkFollow } from '../lib/compat'; diff --git a/packages/@aws-cdk/assets/test/staging.test.ts b/packages/@aws-cdk/assets/test/staging.test.ts index 77fe04e3016fa..4c95f236f2d81 100644 --- a/packages/@aws-cdk/assets/test/staging.test.ts +++ b/packages/@aws-cdk/assets/test/staging.test.ts @@ -2,6 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { App, Stack } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; +import '@aws-cdk/assert-internal/jest'; import { Staging } from '../lib'; describe('staging', () => { diff --git a/packages/@aws-cdk/aws-acmpca/package.json b/packages/@aws-cdk/aws-acmpca/package.json index 9733ca6925726..a3478244753f9 100644 --- a/packages/@aws-cdk/aws-acmpca/package.json +++ b/packages/@aws-cdk/aws-acmpca/package.json @@ -74,7 +74,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assertions": "0.0.0", + "@aws-cdk/assert-internal": "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-acmpca/test/acmpca.test.ts b/packages/@aws-cdk/aws-acmpca/test/acmpca.test.ts index b7175f106094e..c4505ad966984 100644 --- a/packages/@aws-cdk/aws-acmpca/test/acmpca.test.ts +++ b/packages/@aws-cdk/aws-acmpca/test/acmpca.test.ts @@ -1,3 +1,4 @@ +import '@aws-cdk/assert-internal/jest'; import {} from '../lib'; test('No tests are specified for this package', () => { diff --git a/packages/@aws-cdk/aws-codeguruprofiler/package.json b/packages/@aws-cdk/aws-codeguruprofiler/package.json index c3efb8fd2b665..247e4696080fe 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/package.json +++ b/packages/@aws-cdk/aws-codeguruprofiler/package.json @@ -74,7 +74,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assertions": "0.0.0", + "@aws-cdk/assert-internal": "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-codeguruprofiler/test/codeguruprofiler.test.ts b/packages/@aws-cdk/aws-codeguruprofiler/test/codeguruprofiler.test.ts new file mode 100644 index 0000000000000..c4505ad966984 --- /dev/null +++ b/packages/@aws-cdk/aws-codeguruprofiler/test/codeguruprofiler.test.ts @@ -0,0 +1,6 @@ +import '@aws-cdk/assert-internal/jest'; +import {} from '../lib'; + +test('No tests are specified for this package', () => { + expect(true).toBe(true); +}); diff --git a/packages/@aws-cdk/aws-codeguruprofiler/test/profiling-group.test.ts b/packages/@aws-cdk/aws-codeguruprofiler/test/profiling-group.test.ts index 1a780200bcc3a..00212902bbec6 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/test/profiling-group.test.ts +++ b/packages/@aws-cdk/aws-codeguruprofiler/test/profiling-group.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import { expect, haveResourceLike } from '@aws-cdk/assert-internal'; import { AccountRootPrincipal, Role } from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; import { ProfilingGroup, ComputePlatform } from '../lib'; @@ -17,7 +17,7 @@ describe('profiling group', () => { const profilingGroup = ProfilingGroup.fromProfilingGroupArn(stack, 'MyProfilingGroup', 'arn:aws:codeguru-profiler:us-east-1:1234567890:profilingGroup/MyAwesomeProfilingGroup'); profilingGroup.grantRead(readAppRole); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatch({ 'Resources': { 'ReadAppRole52FE6317': { 'Type': 'AWS::IAM::Role', @@ -89,7 +89,7 @@ describe('profiling group', () => { const profilingGroup = ProfilingGroup.fromProfilingGroupName(stack, 'MyProfilingGroup', 'MyAwesomeProfilingGroup'); profilingGroup.grantPublish(publishAppRole); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatch({ 'Resources': { 'PublishAppRole9FEBD682': { 'Type': 'AWS::IAM::Role', @@ -176,7 +176,7 @@ describe('profiling group', () => { profilingGroupName: 'MyAwesomeProfilingGroup', }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatch({ 'Resources': { 'MyProfilingGroup829F0507': { 'Type': 'AWS::CodeGuruProfiler::ProfilingGroup', @@ -195,9 +195,9 @@ describe('profiling group', () => { computePlatform: ComputePlatform.AWS_LAMBDA, }); - Template.fromStack(stack).hasResourceProperties('AWS::CodeGuruProfiler::ProfilingGroup', { + expect(stack).to(haveResourceLike('AWS::CodeGuruProfiler::ProfilingGroup', { 'ComputePlatform': 'AWSLambda', - }); + })); }); test('default profiling group without name', () => { @@ -205,7 +205,7 @@ describe('profiling group', () => { new ProfilingGroup(stack, 'MyProfilingGroup', { }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatch({ 'Resources': { 'MyProfilingGroup829F0507': { 'Type': 'AWS::CodeGuruProfiler::ProfilingGroup', @@ -222,7 +222,7 @@ describe('profiling group', () => { new ProfilingGroup(stack, 'MyProfilingGroupWithAReallyLongProfilingGroupNameThatExceedsTheLimitOfProfilingGroupNameSize_InOrderToDoSoTheNameMustBeGreaterThanTwoHundredAndFiftyFiveCharacters_InSuchCasesWePickUpTheFirstOneTwentyCharactersFromTheBeginningAndTheEndAndConcatenateThemToGetTheIdentifier', { }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatch({ 'Resources': { 'MyProfilingGroupWithAReallyLongProfilingGroupNameThatExceedsTheLimitOfProfilingGroupNameSizeInOrderToDoSoTheNameMustBeGreaterThanTwoHundredAndFiftyFiveCharactersInSuchCasesWePickUpTheFirstOneTwentyCharactersFromTheBeginningAndTheEndAndConca4B39908C': { 'Type': 'AWS::CodeGuruProfiler::ProfilingGroup', @@ -245,7 +245,7 @@ describe('profiling group', () => { profilingGroup.grantPublish(publishAppRole); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatch({ 'Resources': { 'MyProfilingGroup829F0507': { 'Type': 'AWS::CodeGuruProfiler::ProfilingGroup', @@ -329,7 +329,7 @@ describe('profiling group', () => { profilingGroup.grantRead(readAppRole); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatch({ 'Resources': { 'MyProfilingGroup829F0507': { 'Type': 'AWS::CodeGuruProfiler::ProfilingGroup', diff --git a/packages/@aws-cdk/aws-codestarnotifications/package.json b/packages/@aws-cdk/aws-codestarnotifications/package.json index b24cace565aab..ec43414e43e45 100644 --- a/packages/@aws-cdk/aws-codestarnotifications/package.json +++ b/packages/@aws-cdk/aws-codestarnotifications/package.json @@ -74,7 +74,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assertions": "0.0.0", + "@aws-cdk/assert-internal": "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-codestarnotifications/test/notification-rule.test.ts b/packages/@aws-cdk/aws-codestarnotifications/test/notification-rule.test.ts index 6670135d5a416..332e808ca3176 100644 --- a/packages/@aws-cdk/aws-codestarnotifications/test/notification-rule.test.ts +++ b/packages/@aws-cdk/aws-codestarnotifications/test/notification-rule.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as notifications from '../lib'; import { @@ -24,7 +24,7 @@ describe('NotificationRule', () => { events: ['codebuild-project-build-state-succeeded'], }); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { Resource: project.projectArn, EventTypeIds: ['codebuild-project-build-state-succeeded'], }); @@ -41,7 +41,7 @@ describe('NotificationRule', () => { ], }); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { Resource: repository.repositoryArn, EventTypeIds: [ 'codecommit-repository-pull-request-created', @@ -65,7 +65,7 @@ describe('NotificationRule', () => { targets: [slack], }); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { Name: 'MyNotificationRule', DetailType: 'FULL', EventTypeIds: [ @@ -90,7 +90,7 @@ describe('NotificationRule', () => { events: ['codebuild-project-build-state-succeeded'], }); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { Name: 'MyNotificationRuleGeneratedFromId', Resource: project.projectArn, EventTypeIds: ['codebuild-project-build-state-succeeded'], @@ -105,7 +105,7 @@ describe('NotificationRule', () => { events: ['codebuild-project-build-state-succeeded'], }); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { Name: 'ificationRuleGeneratedFromIdIsToooooooooooooooooooooooooooooLong', Resource: project.projectArn, EventTypeIds: ['codebuild-project-build-state-succeeded'], @@ -121,7 +121,7 @@ describe('NotificationRule', () => { events: ['codebuild-project-build-state-succeeded'], }); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { Name: 'MyNotificationRule', Resource: project.projectArn, EventTypeIds: ['codebuild-project-build-state-succeeded'], @@ -138,7 +138,7 @@ describe('NotificationRule', () => { enabled: false, }); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { Name: 'MyNotificationRule', Resource: project.projectArn, EventTypeIds: ['codebuild-project-build-state-succeeded'], @@ -155,7 +155,7 @@ describe('NotificationRule', () => { enabled: true, }); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { Name: 'MyNotificationRule', Resource: project.projectArn, EventTypeIds: ['codebuild-project-build-state-succeeded'], @@ -177,7 +177,7 @@ describe('NotificationRule', () => { expect(rule.addTarget(topic)).toEqual(true); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { Resource: project.projectArn, EventTypeIds: ['codebuild-project-build-state-succeeded'], Targets: [ @@ -206,7 +206,7 @@ describe('NotificationRule', () => { ], }); - Template.fromStack(stack).hasResourceProperties('AWS::CodeStarNotifications::NotificationRule', { + expect(stack).toHaveResourceLike('AWS::CodeStarNotifications::NotificationRule', { Resource: pipeline.pipelineArn, EventTypeIds: [ 'codepipeline-pipeline-pipeline-execution-succeeded', diff --git a/packages/@aws-cdk/aws-events/package.json b/packages/@aws-cdk/aws-events/package.json index b7603d1e7b1f7..e2cefb728f130 100644 --- a/packages/@aws-cdk/aws-events/package.json +++ b/packages/@aws-cdk/aws-events/package.json @@ -74,7 +74,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assertions": "0.0.0", + "@aws-cdk/assert-internal": "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 ebb1936703ac4..0c37a0ec4ae39 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 { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { Duration, Stack } from '@aws-cdk/core'; import { EventBus } from '../lib'; import { Archive } from '../lib/archive'; @@ -20,11 +20,11 @@ describe('archive', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { + expect(stack).toHaveResource('AWS::Events::EventBus', { Name: 'Bus', }); - Template.fromStack(stack).hasResourceProperties('AWS::Events::Archive', { + expect(stack).toHaveResource('AWS::Events::Archive', { EventPattern: { account: [{ Ref: 'AWS::AccountId', @@ -58,11 +58,11 @@ describe('archive', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { + expect(stack).toHaveResource('AWS::Events::EventBus', { Name: 'Bus', }); - Template.fromStack(stack).hasResourceProperties('AWS::Events::Archive', { + expect(stack).toHaveResource('AWS::Events::Archive', { EventPattern: { 'account': [{ Ref: 'AWS::AccountId', 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 47c011c48dbb7..b4384255ea7b4 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 { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; import { Aws, CfnResource, Stack, Arn } from '@aws-cdk/core'; import { EventBus } from '../lib'; @@ -12,7 +12,7 @@ describe('event bus', () => { new EventBus(stack, 'Bus'); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { + expect(stack).toHaveResource('AWS::Events::EventBus', { Name: 'Bus', }); @@ -29,7 +29,7 @@ describe('event bus', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { + expect(stack).toHaveResource('AWS::Events::EventBus', { Name: 'myEventBus', }); @@ -46,7 +46,7 @@ describe('event bus', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { + expect(stack).toHaveResource('AWS::Events::EventBus', { Name: 'aws.partner/PartnerName/acct1/repo1', EventSourceName: 'aws.partner/PartnerName/acct1/repo1', }); @@ -70,7 +70,7 @@ describe('event bus', () => { }, }); - Template.fromStack(stack).hasResourceProperties('Test::Resource', { + expect(stack).toHaveResource('Test::Resource', { EventBusArn1: { 'Fn::GetAtt': ['BusEA82B648', 'Arn'] }, EventBusArn2: { 'Fn::GetAtt': ['BusEA82B648', 'Arn'] }, }); @@ -142,7 +142,7 @@ describe('event bus', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('Test::Resource', { + expect(stack).toHaveResource('Test::Resource', { EventBusName: { Ref: 'BusEA82B648' }, }); @@ -165,7 +165,7 @@ describe('event bus', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('Test::Resource', { + expect(stack).toHaveResource('Test::Resource', { EventBusArn: { 'Fn::GetAtt': ['BusEA82B648', 'Arn'] }, }); @@ -298,7 +298,7 @@ describe('event bus', () => { EventBus.grantPutEvents(role); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -330,7 +330,7 @@ describe('event bus', () => { EventBus.grantAllPutEvents(role); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -363,7 +363,7 @@ describe('event bus', () => { eventBus.grantPutEventsTo(role); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -403,11 +403,11 @@ describe('event bus', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { + expect(stack).toHaveResource('AWS::Events::EventBus', { Name: 'Bus', }); - Template.fromStack(stack).hasResourceProperties('AWS::Events::Archive', { + expect(stack).toHaveResource('AWS::Events::Archive', { SourceArn: { 'Fn::GetAtt': [ 'BusEA82B648', @@ -456,11 +456,11 @@ describe('event bus', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { + expect(stack).toHaveResource('AWS::Events::EventBus', { Name: 'Bus', }); - Template.fromStack(stack).hasResourceProperties('AWS::Events::Archive', { + expect(stack).toHaveResource('AWS::Events::Archive', { SourceArn: { 'Fn::GetAtt': [ 'BusEA82B648', diff --git a/packages/@aws-cdk/aws-events/test/input.test.ts b/packages/@aws-cdk/aws-events/test/input.test.ts index d88bf9239c132..76ec2cd2d2bd9 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 { Match, Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { User } from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import { EventField, IRuleTarget, RuleTargetInput, Schedule } from '../lib'; @@ -17,11 +17,11 @@ describe('input', () => { rule.addTarget(new SomeTarget(RuleTargetInput.fromObject({ SomeObject: 'withAValue' }))); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResourceLike('AWS::Events::Rule', { Targets: [ - Match.objectLike({ + { Input: '{"SomeObject":"withAValue"}', - }), + }, ], }); @@ -41,9 +41,9 @@ describe('input', () => { }))); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResourceLike('AWS::Events::Rule', { Targets: [ - Match.objectLike({ + { InputTransformer: { InputPathsMap: { f1: '$', @@ -59,7 +59,7 @@ describe('input', () => { ], }, }, - }), + }, ], }); @@ -80,9 +80,9 @@ describe('input', () => { }))); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResourceLike('AWS::Events::Rule', { Targets: [ - Match.objectLike({ + { InputTransformer: { InputPathsMap: { f1: '$', @@ -98,7 +98,7 @@ describe('input', () => { ], }, }, - }), + }, ], }); @@ -119,9 +119,9 @@ describe('input', () => { }))); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResourceLike('AWS::Events::Rule', { Targets: [ - Match.objectLike({ + { InputTransformer: { InputPathsMap: { f1: '$', @@ -137,7 +137,7 @@ describe('input', () => { ], }, }, - }), + }, ], }); @@ -158,9 +158,9 @@ describe('input', () => { }))); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResourceLike('AWS::Events::Rule', { Targets: [ - Match.objectLike({ + { InputTransformer: { InputPathsMap: { f1: '$', @@ -176,7 +176,7 @@ describe('input', () => { ], }, }, - }), + }, ], }); @@ -197,9 +197,9 @@ describe('input', () => { }))); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResourceLike('AWS::Events::Rule', { Targets: [ - Match.objectLike({ + { InputTransformer: { InputPathsMap: { f1: '$', @@ -215,7 +215,7 @@ describe('input', () => { ], }, }, - }), + }, ], }); @@ -234,9 +234,9 @@ describe('input', () => { rule.addTarget(new SomeTarget(RuleTargetInput.fromObject({ userArn: user.userArn }))); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResourceLike('AWS::Events::Rule', { Targets: [ - Match.objectLike({ + { Input: { 'Fn::Join': [ '', @@ -252,7 +252,7 @@ describe('input', () => { ], ], }, - }), + }, ], }); @@ -271,11 +271,11 @@ describe('input', () => { rule.addTarget(new SomeTarget(RuleTargetInput.fromMultilineText('I have\nmultiple lines'))); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResourceLike('AWS::Events::Rule', { Targets: [ - Match.objectLike({ + { Input: '"I have"\n"multiple lines"', - }), + }, ], }); @@ -293,11 +293,11 @@ describe('input', () => { rule.addTarget(new SomeTarget(RuleTargetInput.fromMultilineText('this is not\\na real newline'))), // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResourceLike('AWS::Events::Rule', { Targets: [ - Match.objectLike({ + { Input: '"this is not\\\\na real newline"', - }), + }, ], }); @@ -317,11 +317,11 @@ describe('input', () => { rule.addTarget(new SomeTarget(RuleTargetInput.fromText(`hello ${world}`))); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResourceLike('AWS::Events::Rule', { Targets: [ - Match.objectLike({ + { 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 3475ca43ca32d..21782c089b40c 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 { Match, Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; 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)), }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyRuleA44AB831': { 'Type': 'AWS::Events::Rule', @@ -42,7 +42,7 @@ describe('rule', () => { }, }); - Template.fromStack(stack).hasResourceProperties('Test::Resource', { + expect(stack).toHaveResource('Test::Resource', { RuleName: { Ref: 'MyRuleA44AB831' }, }); @@ -60,7 +60,7 @@ describe('rule', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResourceLike('AWS::Events::Rule', { 'Name': 'rateInMinutes', 'ScheduleExpression': 'rate(5 minutes)', }); @@ -93,7 +93,7 @@ describe('rule', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResource('AWS::Events::Rule', { Name: 'PhysicalName', }); @@ -119,7 +119,7 @@ describe('rule', () => { }, }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyRuleA44AB831': { 'Type': 'AWS::Events::Rule', @@ -173,7 +173,7 @@ describe('rule', () => { }, }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyRuleA44AB831': { 'Type': 'AWS::Events::Rule', @@ -217,7 +217,7 @@ describe('rule', () => { detailType: ['EC2 Instance State-change Notification', 'AWS API Call via CloudTrail'], }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyRuleA44AB831': { 'Type': 'AWS::Events::Rule', @@ -261,7 +261,7 @@ describe('rule', () => { rule.addTarget(t2); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'EventRule5A491D2C': { 'Type': 'AWS::Events::Rule', @@ -337,7 +337,7 @@ describe('rule', () => { }), }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'EventRule5A491D2C': { 'Type': 'AWS::Events::Rule', @@ -404,7 +404,7 @@ describe('rule', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResourceLike('AWS::Events::Rule', { 'Targets': [ { 'Arn': 'ARN2', @@ -442,24 +442,24 @@ describe('rule', () => { }); // THEN - Template.fromStack(ruleStack).hasResourceProperties('AWS::Events::Rule', { + expect(ruleStack).toHaveResourceLike('AWS::Events::Rule', { Targets: [ - Match.objectLike({ + { Arn: { 'Fn::Join': ['', [ 'arn:', { 'Ref': 'AWS::Partition' }, ':events:us-east-1:5678:event-bus/default', ]] }, - }), + }, ], }); - Template.fromStack(targetStack).hasResourceProperties('AWS::Events::Rule', { + expect(targetStack).toHaveResourceLike('AWS::Events::Rule', { 'Targets': [ - Match.objectLike({ + { 'Arn': 'ARN2', 'Id': 'Target0', 'RoleArn': { 'Fn::GetAtt': ['SomeRole6DDC54DD', 'Arn'] }, - }), + }, ], }); @@ -517,7 +517,7 @@ describe('rule', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResource('AWS::Events::Rule', { 'State': 'DISABLED', }); @@ -535,22 +535,22 @@ describe('rule', () => { rule.addTarget(new SomeTarget()); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResource('AWS::Events::Rule', { Targets: [ - Match.objectLike({ + { 'Arn': 'ARN1', 'Id': 'Target0', 'KinesisParameters': { 'PartitionKeyPath': 'partitionKeyPath', }, - }), - Match.objectLike({ + }, + { 'Arn': 'ARN1', 'Id': 'Target1', 'KinesisParameters': { 'PartitionKeyPath': 'partitionKeyPath', }, - }), + }, ], }); @@ -572,15 +572,15 @@ describe('rule', () => { targets: [t1], }); - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResource('AWS::Events::Rule', { Targets: [ - Match.objectLike({ + { 'Arn': 'ARN1', 'Id': 'Target0', 'SqsParameters': { 'MessageGroupId': 'messageGroupId', }, - }), + }, ], }); @@ -600,7 +600,7 @@ describe('rule', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + expect(stack).toHaveResource('AWS::Events::Rule', { EventBusName: { Ref: 'EventBus7B8748AA', }, @@ -639,15 +639,15 @@ describe('rule', () => { rule.addTarget(new SomeTarget('T', resource)); - Template.fromStack(sourceStack).hasResourceProperties('AWS::Events::Rule', { + expect(sourceStack).toHaveResource('AWS::Events::Rule', { Targets: [ - Match.objectLike({ + { 'Arn': 'ARN1', 'Id': 'T', 'KinesisParameters': { 'PartitionKeyPath': 'partitionKeyPath', }, - }), + }, ], }); @@ -726,7 +726,7 @@ describe('rule', () => { rule.addTarget(new SomeTarget('T', resource)); - Template.fromStack(sourceStack).hasResourceProperties('AWS::Events::Rule', { + expect(sourceStack).toHaveResourceLike('AWS::Events::Rule', { 'State': 'ENABLED', 'Targets': [ { @@ -745,7 +745,7 @@ describe('rule', () => { ], }); - Template.fromStack(targetStack).hasResourceProperties('AWS::Events::Rule', { + expect(targetStack).toHaveResource('AWS::Events::Rule', { Targets: [ { 'Arn': 'ARN1', @@ -779,10 +779,10 @@ describe('rule', () => { rule.addTarget(new SomeTarget('T', resource)); - Template.fromStack(sourceStack).hasResourceProperties('AWS::Events::Rule', { + expect(sourceStack).toHaveResourceLike('AWS::Events::Rule', { 'State': 'ENABLED', 'Targets': [ - Match.objectLike({ + { 'Id': 'T', 'Arn': { 'Fn::Join': [ @@ -794,19 +794,19 @@ describe('rule', () => { ], ], }, - }), + }, ], }); - Template.fromStack(targetStack).hasResourceProperties('AWS::Events::Rule', { + expect(targetStack).toHaveResource('AWS::Events::Rule', { Targets: [ - Match.objectLike({ + { 'Arn': 'ARN1', 'Id': 'T', 'KinesisParameters': { 'PartitionKeyPath': 'partitionKeyPath', }, - }), + }, ], }); @@ -834,10 +834,10 @@ describe('rule', () => { // same target should be skipped rule.addTarget(new SomeTarget('T1', resource)); - Template.fromStack(sourceStack).hasResourceProperties('AWS::Events::Rule', { + expect(sourceStack).toHaveResourceLike('AWS::Events::Rule', { 'State': 'ENABLED', 'Targets': [ - Match.objectLike({ + { 'Id': 'T', 'Arn': { 'Fn::Join': [ @@ -849,14 +849,14 @@ describe('rule', () => { ], ], }, - }), + }, ], }); - Template.fromStack(sourceStack).hasResourceProperties('AWS::Events::Rule', Match.not({ + expect(sourceStack).not.toHaveResourceLike('AWS::Events::Rule', { 'State': 'ENABLED', 'Targets': [ - Match.objectLike({ + { 'Id': 'T1', 'Arn': { 'Fn::Join': [ @@ -868,9 +868,9 @@ describe('rule', () => { ], ], }, - }), + }, ], - })); + }); }); @@ -944,7 +944,7 @@ describe('rule', () => { rule.addTarget(new SomeTarget('T1', resource1)); rule.addTarget(new SomeTarget('T2', resource2)); - Template.fromStack(sourceStack).hasResourceProperties('AWS::Events::Rule', { + expect(sourceStack).toHaveResourceLike('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'some-event', @@ -952,7 +952,7 @@ describe('rule', () => { }, 'State': 'ENABLED', 'Targets': [ - Match.objectLike({ + { 'Id': 'T1', 'Arn': { 'Fn::Join': [ @@ -964,11 +964,11 @@ describe('rule', () => { ], ], }, - }), + }, ], }); - Template.fromStack(targetStack).hasResourceProperties('AWS::Events::Rule', { + expect(targetStack).toHaveResourceLike('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'some-event', @@ -976,13 +976,13 @@ describe('rule', () => { }, 'State': 'ENABLED', 'Targets': [ - Match.objectLike({ + { 'Id': 'T1', 'Arn': 'ARN1', - }), + }, ], }); - Template.fromStack(targetStack).hasResourceProperties('AWS::Events::Rule', { + expect(targetStack).toHaveResourceLike('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'some-event', @@ -990,15 +990,15 @@ describe('rule', () => { }, 'State': 'ENABLED', 'Targets': [ - Match.objectLike({ + { 'Id': 'T2', 'Arn': 'ARN1', - }), + }, ], }); const eventBusPolicyStack = app.node.findChild(`EventBusPolicy-${sourceAccount}-us-west-2-${targetAccount}`) as cdk.Stack; - Template.fromStack(eventBusPolicyStack).hasResourceProperties('AWS::Events::EventBusPolicy', { + expect(eventBusPolicyStack).toHaveResourceLike('AWS::Events::EventBusPolicy', { 'Action': 'events:PutEvents', 'StatementId': `Allow-account-${sourceAccount}`, 'Principal': sourceAccount, @@ -1034,7 +1034,7 @@ describe('rule', () => { source: ['some-event'], }); - Template.fromStack(targetStack).hasResourceProperties('AWS::Events::Rule', { + expect(targetStack).toHaveResourceLike('AWS::Events::Rule', { 'EventPattern': { 'source': [ 'some-event', @@ -1042,10 +1042,10 @@ describe('rule', () => { }, 'State': 'ENABLED', 'Targets': [ - Match.objectLike({ + { 'Id': 'T', 'Arn': 'ARN1', - }), + }, ], }); diff --git a/packages/@aws-cdk/aws-iam/package.json b/packages/@aws-cdk/aws-iam/package.json index db9860df74397..1449128836c09 100644 --- a/packages/@aws-cdk/aws-iam/package.json +++ b/packages/@aws-cdk/aws-iam/package.json @@ -72,7 +72,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assertions": "0.0.0", + "@aws-cdk/assert-internal": "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-iam/test/auto-cross-stack-refs.test.ts b/packages/@aws-cdk/aws-iam/test/auto-cross-stack-refs.test.ts index b5dcbc68268b3..1f8384c600fb9 100644 --- a/packages/@aws-cdk/aws-iam/test/auto-cross-stack-refs.test.ts +++ b/packages/@aws-cdk/aws-iam/test/auto-cross-stack-refs.test.ts @@ -1,4 +1,5 @@ -import { Template } from '@aws-cdk/assertions'; +import { SynthUtils } from '@aws-cdk/assert-internal'; +import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as iam from '../lib'; @@ -24,7 +25,7 @@ describe('automatic cross-stack references', () => { // // THEN - Template.fromStack(stackWithUser).templateMatches({ + expect(stackWithUser).toMatchTemplate({ Resources: { User00B015A1: { Type: 'AWS::IAM::User', @@ -34,7 +35,7 @@ describe('automatic cross-stack references', () => { }, }, }); - Template.fromStack(stackWithGroup).templateMatches({ + expect(stackWithGroup).toMatchTemplate({ Outputs: { ExportsOutputRefGroupC77FDACD8CF7DD5B: { Value: { Ref: 'GroupC77FDACD' }, @@ -58,6 +59,6 @@ describe('automatic cross-stack references', () => { group.addUser(user); // THEN - expect(() => Template.fromStack(stack1)).toThrow(/Cannot reference across apps/); + expect(() => SynthUtils.synthesize(stack1)).toThrow(/Cannot reference across apps/); }); }); diff --git a/packages/@aws-cdk/aws-iam/test/cross-account.test.ts b/packages/@aws-cdk/aws-iam/test/cross-account.test.ts index de44f48659623..21ca3ce48c945 100644 --- a/packages/@aws-cdk/aws-iam/test/cross-account.test.ts +++ b/packages/@aws-cdk/aws-iam/test/cross-account.test.ts @@ -1,4 +1,4 @@ -import { Match, Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; import * as iam from '../lib'; @@ -191,7 +191,7 @@ function doGrant(resource: FakeResource, principal: iam.IPrincipal) { } function assertTrustCreated(stack: cdk.Stack, principal: any) { - Template.fromStack(stack).hasResourceProperties('Test::Fake::Resource', { + expect(stack).toHaveResource('Test::Fake::Resource', { ResourcePolicy: { Statement: [ { @@ -207,19 +207,17 @@ function assertTrustCreated(stack: cdk.Stack, principal: any) { } function noTrustCreated(stack: cdk.Stack) { - Template.fromStack(stack).hasResource('Test::Fake::Resource', Match.not({ - Properties: { - ResourcePolicy: { - Statement: [ - {}, - ], - }, + expect(stack).not.toHaveResourceLike('Test::Fake::Resource', { + ResourcePolicy: { + Statement: [ + {}, + ], }, - })); + }); } function assertPolicyCreated(stack: cdk.Stack) { - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -234,5 +232,5 @@ function assertPolicyCreated(stack: cdk.Stack) { } function noPolicyCreated(stack: cdk.Stack) { - Template.fromStack(stack).resourceCountIs('AWS::IAM::Policy', 0); + expect(stack).not.toHaveResource('AWS::IAM::Policy'); } diff --git a/packages/@aws-cdk/aws-iam/test/escape-hatch.test.ts b/packages/@aws-cdk/aws-iam/test/escape-hatch.test.ts index f22639c83c447..b9467638c7307 100644 --- a/packages/@aws-cdk/aws-iam/test/escape-hatch.test.ts +++ b/packages/@aws-cdk/aws-iam/test/escape-hatch.test.ts @@ -1,7 +1,7 @@ // tests for the L1 escape hatches (overrides). those are in the IAM module // because we want to verify them end-to-end, as a complement to the unit // tests in the @aws-cdk/core module -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { Stack } from '@aws-cdk/core'; import * as iam from '../lib'; @@ -17,7 +17,7 @@ describe('IAM escape hatches', () => { const cfn = user.node.findChild('Resource') as iam.CfnUser; cfn.addPropertyOverride('UserName', 'OverriddenUserName'); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'user2C2B57AE': { 'Type': 'AWS::IAM::User', @@ -39,7 +39,7 @@ describe('IAM escape hatches', () => { cfn.addPropertyOverride('Hello.World', 'Boom'); // THEN - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'user2C2B57AE': { 'Type': 'AWS::IAM::User', @@ -69,7 +69,7 @@ describe('IAM escape hatches', () => { cfn.addOverride('UpdatePolicy.UseOnlineResharding.Type', 'None'); // THEN - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ 'Resources': { 'user2C2B57AE': { 'Type': 'AWS::IAM::User', diff --git a/packages/@aws-cdk/aws-iam/test/grant.test.ts b/packages/@aws-cdk/aws-iam/test/grant.test.ts index 1a4ca30016d6c..04b2466ea21da 100644 --- a/packages/@aws-cdk/aws-iam/test/grant.test.ts +++ b/packages/@aws-cdk/aws-iam/test/grant.test.ts @@ -1,4 +1,5 @@ -import { Template } from '@aws-cdk/assertions'; +import { ResourcePart } from '@aws-cdk/assert-internal'; +import '@aws-cdk/assert-internal/jest'; import { CfnResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import * as iam from '../lib'; @@ -108,9 +109,9 @@ function applyGrantWithDependencyTo(principal: iam.IPrincipal) { } function expectDependencyOn(id: string) { - Template.fromStack(stack).hasResource('CDK::Test::SomeResource', (props: any) => { + expect(stack).toHaveResource('CDK::Test::SomeResource', (props: any) => { return (props?.DependsOn ?? []).includes(id); - }); + }, ResourcePart.CompleteDefinition); } class FakeResourceWithPolicy extends Resource implements iam.IResourceWithPolicy { diff --git a/packages/@aws-cdk/aws-iam/test/group.test.ts b/packages/@aws-cdk/aws-iam/test/group.test.ts index b98d3e618da1d..d8b7c6fb47913 100644 --- a/packages/@aws-cdk/aws-iam/test/group.test.ts +++ b/packages/@aws-cdk/aws-iam/test/group.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { App, Stack } from '@aws-cdk/core'; import { Group, ManagedPolicy, User } from '../lib'; @@ -8,7 +8,7 @@ describe('IAM groups', () => { const stack = new Stack(app, 'MyStack'); new Group(stack, 'MyGroup'); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyGroupCBA54B1B: { Type: 'AWS::IAM::Group' } }, }); }); @@ -22,7 +22,7 @@ describe('IAM groups', () => { user1.addToGroup(group); group.addUser(user2); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyGroupCBA54B1B: { Type: 'AWS::IAM::Group' }, @@ -50,7 +50,7 @@ describe('IAM groups', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Group', { + expect(stack).toHaveResource('AWS::IAM::Group', { ManagedPolicyArns: [ { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/asdf']] }, ], diff --git a/packages/@aws-cdk/aws-iam/test/immutable-role.test.ts b/packages/@aws-cdk/aws-iam/test/immutable-role.test.ts index c889043f08214..3ba5c870f8659 100644 --- a/packages/@aws-cdk/aws-iam/test/immutable-role.test.ts +++ b/packages/@aws-cdk/aws-iam/test/immutable-role.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { Stack } from '@aws-cdk/core'; import * as iam from '../lib'; @@ -33,7 +33,7 @@ describe('ImmutableRole', () => { immutableRole.attachInlinePolicy(policy); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -58,7 +58,7 @@ describe('ImmutableRole', () => { immutableRole.addManagedPolicy({ managedPolicyArn: 'Arn2' }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResourceLike('AWS::IAM::Role', { 'ManagedPolicyArns': [ 'Arn1', ], @@ -76,7 +76,7 @@ describe('ImmutableRole', () => { actions: ['s3:*'], })); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { 'PolicyDocument': { 'Version': '2012-10-17', 'Statement': [ @@ -98,7 +98,17 @@ describe('ImmutableRole', () => { resourceArns: ['*'], }); - Template.fromStack(stack).resourceCountIs('AWS::IAM::Policy', 0); + expect(stack).not.toHaveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': [ + { + 'Resource': '*', + 'Action': 's3:*', + 'Effect': 'Allow', + }, + ], + }, + }); }); // this pattern is used here: diff --git a/packages/@aws-cdk/aws-iam/test/lazy-role.test.ts b/packages/@aws-cdk/aws-iam/test/lazy-role.test.ts index a21baf9fb5963..34d8919ccd14c 100644 --- a/packages/@aws-cdk/aws-iam/test/lazy-role.test.ts +++ b/packages/@aws-cdk/aws-iam/test/lazy-role.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as iam from '../lib'; @@ -13,7 +13,7 @@ describe('IAM lazy role', () => { }); // THEN - Template.fromStack(stack).resourceCountIs('AWS::IAM::Role', 0); + expect(stack).not.toHaveResource('AWS::IAM::Role'); }); test('creates the resource when a property is read', () => { @@ -27,7 +27,7 @@ describe('IAM lazy role', () => { // THEN expect(roleArn).not.toBeNull(); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [{ diff --git a/packages/@aws-cdk/aws-iam/test/managed-policy.test.ts b/packages/@aws-cdk/aws-iam/test/managed-policy.test.ts index 6e7c90a306741..3561ed4f79f19 100644 --- a/packages/@aws-cdk/aws-iam/test/managed-policy.test.ts +++ b/packages/@aws-cdk/aws-iam/test/managed-policy.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import { Group, ManagedPolicy, PolicyDocument, PolicyStatement, Role, ServicePrincipal, User } from '../lib'; @@ -49,7 +49,7 @@ describe('managed policy', () => { const group = new Group(stack, 'MyGroup'); group.addManagedPolicy(policy); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyManagedPolicy9F3720AE: { Type: 'AWS::IAM::ManagedPolicy', @@ -89,7 +89,7 @@ describe('managed policy', () => { }), }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyManagedPolicy9F3720AE: { Type: 'AWS::IAM::ManagedPolicy', @@ -120,7 +120,7 @@ describe('managed policy', () => { statements: [new PolicyStatement({ resources: ['arn'], actions: ['sns:Subscribe'] })], }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyManagedPolicy9F3720AE: { Type: 'AWS::IAM::ManagedPolicy', @@ -148,7 +148,7 @@ describe('managed policy', () => { const group = new Group(stack, 'MyGroup'); group.addManagedPolicy(policy); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyManagedPolicy9F3720AE: { Type: 'AWS::IAM::ManagedPolicy', @@ -192,7 +192,7 @@ describe('managed policy', () => { statements: [new PolicyStatement({ resources: ['*'], actions: ['dynamodb:PutItem'] })], }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { User1E278A736: { Type: 'AWS::IAM::User' }, Group1BEBD4686: { Type: 'AWS::IAM::Group' }, @@ -248,7 +248,7 @@ describe('managed policy', () => { p.attachToRole(role); p.attachToRole(role); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyManagedPolicy9F3720AE: { Type: 'AWS::IAM::ManagedPolicy', @@ -295,7 +295,7 @@ describe('managed policy', () => { p.attachToRole(new Role(stack, 'Role1', { assumedBy: new ServicePrincipal('test.service') })); p.addStatements(new PolicyStatement({ resources: ['*'], actions: ['dynamodb:GetItem'] })); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyManagedPolicy9F3720AE: { Type: 'AWS::IAM::ManagedPolicy', @@ -346,7 +346,7 @@ describe('managed policy', () => { policy.addStatements(new PolicyStatement({ resources: ['*'], actions: ['*'] })); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyManagedPolicy9F3720AE: { Type: 'AWS::IAM::ManagedPolicy', @@ -390,7 +390,7 @@ describe('managed policy', () => { group.addManagedPolicy(policy); role.addManagedPolicy(policy); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyUserDC45028B: { Type: 'AWS::IAM::User', @@ -466,7 +466,7 @@ describe('managed policy', () => { group.addManagedPolicy(policy); role.addManagedPolicy(policy); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyUserDC45028B: { Type: 'AWS::IAM::User', @@ -594,7 +594,7 @@ describe('managed policy', () => { value: mp.managedPolicyArn, }); - Template.fromStack(stack2).templateMatches({ + expect(stack2).toMatchTemplate({ Outputs: { Output: { Value: { diff --git a/packages/@aws-cdk/aws-iam/test/oidc-provider.test.ts b/packages/@aws-cdk/aws-iam/test/oidc-provider.test.ts index eda196a97aa5f..805e7bbb66ac0 100644 --- a/packages/@aws-cdk/aws-iam/test/oidc-provider.test.ts +++ b/packages/@aws-cdk/aws-iam/test/oidc-provider.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { App, Stack, Token } from '@aws-cdk/core'; import * as sinon from 'sinon'; import * as iam from '../lib'; @@ -20,7 +20,7 @@ describe('OpenIdConnectProvider resource', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('Custom::AWSCDKOpenIdConnectProvider', { + expect(stack).toHaveResource('Custom::AWSCDKOpenIdConnectProvider', { Url: 'https://openid-endpoint', }); }); @@ -61,7 +61,7 @@ describe('OpenIdConnectProvider resource', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('Custom::AWSCDKOpenIdConnectProvider', { + expect(stack).toHaveResource('Custom::AWSCDKOpenIdConnectProvider', { Url: 'https://my-url', ClientIDList: ['client1', 'client2'], ThumbprintList: ['thumb1'], @@ -103,7 +103,7 @@ describe('custom resource provider infrastructure', () => { new iam.OpenIdConnectProvider(stack, 'Provider1', { url: 'provider1' }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { Policies: [ { PolicyName: 'Inline', diff --git a/packages/@aws-cdk/aws-iam/test/permissions-boundary.test.ts b/packages/@aws-cdk/aws-iam/test/permissions-boundary.test.ts index a916cd53896eb..ab304f3481a15 100644 --- a/packages/@aws-cdk/aws-iam/test/permissions-boundary.test.ts +++ b/packages/@aws-cdk/aws-iam/test/permissions-boundary.test.ts @@ -1,5 +1,6 @@ import * as path from 'path'; -import { Match, Template } from '@aws-cdk/assertions'; +import { ABSENT } from '@aws-cdk/assert-internal'; +import '@aws-cdk/assert-internal/jest'; import { App, CfnResource, CustomResourceProvider, CustomResourceProviderRuntime, Stack } from '@aws-cdk/core'; import * as iam from '../lib'; @@ -20,7 +21,7 @@ test('apply imported boundary to a role', () => { iam.PermissionsBoundary.of(role).apply(iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { PermissionsBoundary: { 'Fn::Join': ['', [ 'arn:', @@ -39,7 +40,7 @@ test('apply imported boundary to a user', () => { iam.PermissionsBoundary.of(user).apply(iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::User', { + expect(stack).toHaveResource('AWS::IAM::User', { PermissionsBoundary: { 'Fn::Join': ['', [ 'arn:', @@ -67,7 +68,7 @@ test('apply newly created boundary to a role', () => { })); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { PermissionsBoundary: { Ref: 'Policy23B91518' }, }); }); @@ -90,7 +91,7 @@ test('apply boundary to role created by a custom resource', () => { })); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { PermissionsBoundary: { Ref: 'Policy23B91518' }, }); }); @@ -112,7 +113,7 @@ test('apply boundary to users created via CfnResource', () => { })); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::User', { + expect(stack).toHaveResource('AWS::IAM::User', { PermissionsBoundary: { Ref: 'Policy23B91518' }, }); }); @@ -134,7 +135,7 @@ test('apply boundary to roles created via CfnResource', () => { })); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { PermissionsBoundary: { Ref: 'Policy23B91518' }, }); }); @@ -148,12 +149,8 @@ test('unapply inherited boundary from a user: order 1', () => { iam.PermissionsBoundary.of(user).clear(); // THEN - // Template.fromStack(stack).hasResource('AWS::IAM::User', { - // PermissionsBoundary: Match.absentProperty(), - // }); - // Correct after when this issue is fixed - https://github.com/aws/aws-cdk/issues/16626 - Template.fromStack(stack).hasResource('AWS::IAM::User', { - Properties: Match.absentProperty(), + expect(stack).toHaveResource('AWS::IAM::User', { + PermissionsBoundary: ABSENT, }); }); @@ -166,11 +163,7 @@ test('unapply inherited boundary from a user: order 2', () => { iam.PermissionsBoundary.of(stack).apply(iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')); // THEN - // Template.fromStack(stack).hasResource('AWS::IAM::User', { - // PermissionsBoundary: Match.absentProperty(), - // }); - // Correct after when this issue is fixed - https://github.com/aws/aws-cdk/issues/16626 - Template.fromStack(stack).hasResource('AWS::IAM::User', { - Properties: Match.absentProperty(), + expect(stack).toHaveResource('AWS::IAM::User', { + PermissionsBoundary: ABSENT, }); }); diff --git a/packages/@aws-cdk/aws-iam/test/policy-document.test.ts b/packages/@aws-cdk/aws-iam/test/policy-document.test.ts index 0cc4bf18a6b20..c548b1aeb63fd 100644 --- a/packages/@aws-cdk/aws-iam/test/policy-document.test.ts +++ b/packages/@aws-cdk/aws-iam/test/policy-document.test.ts @@ -1,3 +1,4 @@ +import '@aws-cdk/assert-internal/jest'; import { Lazy, Stack, Token } from '@aws-cdk/core'; import { AccountPrincipal, Anyone, AnyPrincipal, ArnPrincipal, CanonicalUserPrincipal, CompositePrincipal, diff --git a/packages/@aws-cdk/aws-iam/test/policy-statement.test.ts b/packages/@aws-cdk/aws-iam/test/policy-statement.test.ts index cd627f3b87e00..929343ac240c7 100644 --- a/packages/@aws-cdk/aws-iam/test/policy-statement.test.ts +++ b/packages/@aws-cdk/aws-iam/test/policy-statement.test.ts @@ -1,3 +1,4 @@ +import '@aws-cdk/assert-internal/jest'; import { Stack } from '@aws-cdk/core'; import { AnyPrincipal, Group, PolicyDocument, PolicyStatement } from '../lib'; diff --git a/packages/@aws-cdk/aws-iam/test/policy.test.ts b/packages/@aws-cdk/aws-iam/test/policy.test.ts index ea40450756935..cb6c2fc88cf52 100644 --- a/packages/@aws-cdk/aws-iam/test/policy.test.ts +++ b/packages/@aws-cdk/aws-iam/test/policy.test.ts @@ -1,4 +1,5 @@ -import { Template } from '@aws-cdk/assertions'; +import { ResourcePart } from '@aws-cdk/assert-internal'; +import '@aws-cdk/assert-internal/jest'; import { App, CfnResource, Stack } from '@aws-cdk/core'; import { AnyPrincipal, CfnPolicy, Group, Policy, PolicyDocument, PolicyStatement, Role, ServicePrincipal, User } from '../lib'; @@ -27,7 +28,7 @@ describe('IAM policy', () => { const group = new Group(stack, 'MyGroup'); group.attachInlinePolicy(policy); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyPolicy39D66CF6: @@ -68,7 +69,7 @@ describe('IAM policy', () => { const group = new Group(stack, 'MyGroup'); group.attachInlinePolicy(policy); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyPolicy39D66CF6: { Type: 'AWS::IAM::Policy', @@ -95,7 +96,7 @@ describe('IAM policy', () => { const user = new User(stack, 'MyUser'); user.attachInlinePolicy(policy); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyPolicy39D66CF6: @@ -134,7 +135,7 @@ describe('IAM policy', () => { statements: [new PolicyStatement({ resources: ['*'], actions: ['dynamodb:PutItem'] })], }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { User1E278A736: { Type: 'AWS::IAM::User' }, @@ -185,7 +186,7 @@ describe('IAM policy', () => { p.attachToUser(user); p.attachToUser(user); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyPolicy39D66CF6: @@ -218,7 +219,7 @@ describe('IAM policy', () => { p.attachToRole(new Role(stack, 'Role1', { assumedBy: new ServicePrincipal('test.service') })); p.addStatements(new PolicyStatement({ resources: ['*'], actions: ['dynamodb:GetItem'] })); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyTestPolicy316BDB50: @@ -274,7 +275,7 @@ describe('IAM policy', () => { policy.addStatements(new PolicyStatement({ resources: ['*'], actions: ['*'] })); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyPolicy39D66CF6: @@ -348,7 +349,7 @@ describe('IAM policy', () => { test("generated policy name is the same as the logical id if it's shorter than 128 characters", () => { createPolicyWithLogicalId(stack, 'Foo'); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { 'PolicyName': 'Foo', }); }); @@ -359,7 +360,7 @@ describe('IAM policy', () => { createPolicyWithLogicalId(stack, logicalIdOver128); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { 'PolicyName': logicalId128, }); @@ -384,7 +385,7 @@ describe('IAM policy', () => { res.node.addDependency(pol); // THEN - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { Resource: { Type: 'Some::Resource', @@ -410,10 +411,10 @@ describe('IAM policy', () => { res.node.addDependency(pol); // THEN - Template.fromStack(stack).hasResource('Some::Resource', { + expect(stack).toHaveResource('Some::Resource', { Type: 'Some::Resource', DependsOn: ['Pol0FE9AD5D'], - }); + }, ResourcePart.CompleteDefinition); }); test('empty policy is OK if force=false', () => { diff --git a/packages/@aws-cdk/aws-iam/test/principals.test.ts b/packages/@aws-cdk/aws-iam/test/principals.test.ts index d916f24812512..1914f174adfd4 100644 --- a/packages/@aws-cdk/aws-iam/test/principals.test.ts +++ b/packages/@aws-cdk/aws-iam/test/principals.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { App, CfnOutput, Stack } from '@aws-cdk/core'; import * as iam from '../lib'; @@ -20,7 +20,7 @@ test('use of cross-stack role reference does not lead to URLSuffix being exporte // THEN app.synth(); - Template.fromStack(first).templateMatches({ + expect(first).toMatchTemplate({ Resources: { Role1ABCC5F0: { Type: 'AWS::IAM::Role', @@ -144,7 +144,7 @@ test('SAML principal', () => { // THEN expect(stack.resolve(principal.federated)).toStrictEqual({ Ref: 'MyProvider730BA1C8' }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [ { @@ -184,7 +184,7 @@ test('PrincipalWithConditions.addCondition should work', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [ { diff --git a/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts b/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts index d03dd908370da..68e36b388fa0f 100644 --- a/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts +++ b/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { App, Aws, CfnElement, Lazy, Stack } from '@aws-cdk/core'; import { AnyPrincipal, ArnPrincipal, IRole, Policy, PolicyStatement, Role } from '../lib'; @@ -196,7 +196,7 @@ describe('IAM Role.fromRoleArn', () => { }); test("does NOT generate a default Policy resource pointing at the imported role's physical name", () => { - Template.fromStack(roleStack).resourceCountIs('AWS::IAM::Policy', 0); + expect(roleStack).not.toHaveResourceLike('AWS::IAM::Policy'); }); }); @@ -283,7 +283,7 @@ describe('IAM Role.fromRoleArn', () => { }); test("does NOT generate a default Policy resource pointing at the imported role's physical name", () => { - Template.fromStack(roleStack).resourceCountIs('AWS::IAM::Policy', 0); + expect(roleStack).not.toHaveResourceLike('AWS::IAM::Policy'); }); }); @@ -322,7 +322,7 @@ describe('IAM Role.fromRoleArn', () => { }); test("does NOT generate a default Policy resource pointing at the imported role's physical name", () => { - Template.fromStack(roleStack).resourceCountIs('AWS::IAM::Policy', 0); + expect(roleStack).not.toHaveResourceLike('AWS::IAM::Policy'); }); }); }); @@ -357,7 +357,7 @@ describe('IAM Role.fromRoleArn', () => { assumedBy: new ArnPrincipal(importedRole.roleName), }); - Template.fromStack(roleStack).hasResourceProperties('AWS::IAM::Role', { + expect(roleStack).toHaveResourceLike('AWS::IAM::Role', { 'AssumeRolePolicyDocument': { 'Statement': [ { @@ -515,7 +515,7 @@ describe('IAM Role.fromRoleArn', () => { roles: [importedRole], }); - Template.fromStack(roleStack).hasResourceProperties('AWS::IAM::Policy', { + expect(roleStack).toHaveResourceLike('AWS::IAM::Policy', { 'Roles': [ 'codebuild-role', ], @@ -535,7 +535,7 @@ describe('IAM Role.fromRoleArn', () => { roles: [importedRole], }); - Template.fromStack(roleStack).hasResourceProperties('AWS::IAM::Policy', { + expect(roleStack).toHaveResourceLike('AWS::IAM::Policy', { 'Roles': [ 'codebuild-role', ], @@ -611,5 +611,5 @@ function _assertStackContainsPolicyResource(stack: Stack, roleNames: any[], name expected.PolicyName = nameOfPolicy; } - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', expected); + expect(stack).toHaveResourceLike('AWS::IAM::Policy', expected); } diff --git a/packages/@aws-cdk/aws-iam/test/role.test.ts b/packages/@aws-cdk/aws-iam/test/role.test.ts index 20a9a8cd296ee..1e64f8a9a9369 100644 --- a/packages/@aws-cdk/aws-iam/test/role.test.ts +++ b/packages/@aws-cdk/aws-iam/test/role.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { Duration, Stack, App } from '@aws-cdk/core'; import { AnyPrincipal, ArnPrincipal, CompositePrincipal, FederatedPrincipal, ManagedPolicy, PolicyStatement, Role, ServicePrincipal, User, Policy, PolicyDocument } from '../lib'; @@ -10,7 +10,7 @@ describe('IAM role', () => { assumedBy: new ServicePrincipal('sns.amazonaws.com'), }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyRoleF48FFE04: @@ -44,7 +44,7 @@ describe('IAM role', () => { role.grantPassRole(user); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -69,7 +69,7 @@ describe('IAM role', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [ { @@ -97,7 +97,7 @@ describe('IAM role', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [ { @@ -125,7 +125,7 @@ describe('IAM role', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [ { @@ -146,13 +146,13 @@ describe('IAM role', () => { // by default we don't expect a role policy const before = new Stack(); new Role(before, 'MyRole', { assumedBy: new ServicePrincipal('sns.amazonaws.com') }); - Template.fromStack(before).resourceCountIs('AWS::IAM::Policy', 0); + expect(before).not.toHaveResource('AWS::IAM::Policy'); // add a policy to the role const after = new Stack(); const afterRole = new Role(after, 'MyRole', { assumedBy: new ServicePrincipal('sns.amazonaws.com') }); afterRole.addToPolicy(new PolicyStatement({ resources: ['myresource'], actions: ['service:myaction'] })); - Template.fromStack(after).hasResourceProperties('AWS::IAM::Policy', { + expect(after).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -182,7 +182,7 @@ describe('IAM role', () => { }); role.addManagedPolicy({ managedPolicyArn: 'managed3' }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyRoleF48FFE04: @@ -217,7 +217,7 @@ describe('IAM role', () => { new Role(stack, 'MyRole', { assumedBy: cognitoPrincipal }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [ @@ -239,7 +239,7 @@ describe('IAM role', () => { test('is not specified by default', () => { const stack = new Stack(); new Role(stack, 'MyRole', { assumedBy: new ServicePrincipal('sns.amazonaws.com') }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyRoleF48FFE04: { Type: 'AWS::IAM::Role', @@ -267,7 +267,7 @@ describe('IAM role', () => { new Role(stack, 'MyRole', { maxSessionDuration: Duration.seconds(3700), assumedBy: new ServicePrincipal('sns.amazonaws.com') }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { MaxSessionDuration: 3700, }); }); @@ -300,7 +300,7 @@ describe('IAM role', () => { ), }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [ { @@ -328,7 +328,7 @@ describe('IAM role', () => { permissionsBoundary, }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { PermissionsBoundary: { 'Fn::Join': [ '', @@ -356,7 +356,7 @@ describe('IAM role', () => { assumedBy: new AnyPrincipal(), }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [ { @@ -378,7 +378,7 @@ describe('IAM role', () => { description: 'This is a role description.', }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyRoleF48FFE04: @@ -411,7 +411,7 @@ describe('IAM role', () => { description: '', }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyRoleF48FFE04: @@ -548,7 +548,7 @@ test('managed policy ARNs are deduplicated', () => { }); role.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('SuperDeveloper')); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + expect(stack).toHaveResource('AWS::IAM::Role', { ManagedPolicyArns: [ { 'Fn::Join': [ diff --git a/packages/@aws-cdk/aws-iam/test/saml-provider.test.ts b/packages/@aws-cdk/aws-iam/test/saml-provider.test.ts index edcbde4685296..83ade3741fb3a 100644 --- a/packages/@aws-cdk/aws-iam/test/saml-provider.test.ts +++ b/packages/@aws-cdk/aws-iam/test/saml-provider.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { Stack } from '@aws-cdk/core'; import { SamlMetadataDocument, SamlProvider } from '../lib'; @@ -12,7 +12,7 @@ test('SAML provider', () => { metadataDocument: SamlMetadataDocument.fromXml('document'), }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::SAMLProvider', { + expect(stack).toHaveResource('AWS::IAM::SAMLProvider', { SamlMetadataDocument: 'document', }); }); @@ -23,7 +23,7 @@ test('SAML provider name', () => { name: 'provider-name', }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::SAMLProvider', { + expect(stack).toHaveResource('AWS::IAM::SAMLProvider', { SamlMetadataDocument: 'document', Name: 'provider-name', }); diff --git a/packages/@aws-cdk/aws-iam/test/user.test.ts b/packages/@aws-cdk/aws-iam/test/user.test.ts index 6480adc8cdf5e..5cc42ae015619 100644 --- a/packages/@aws-cdk/aws-iam/test/user.test.ts +++ b/packages/@aws-cdk/aws-iam/test/user.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { App, SecretValue, Stack } from '@aws-cdk/core'; import { Group, ManagedPolicy, Policy, PolicyStatement, User } from '../lib'; @@ -7,7 +7,7 @@ describe('IAM user', () => { const app = new App(); const stack = new Stack(app, 'MyStack'); new User(stack, 'MyUser'); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyUserDC45028B: { Type: 'AWS::IAM::User' } }, }); }); @@ -19,7 +19,7 @@ describe('IAM user', () => { password: SecretValue.plainText('1234'), }); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyUserDC45028B: @@ -48,7 +48,7 @@ describe('IAM user', () => { }); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::User', { + expect(stack).toHaveResource('AWS::IAM::User', { ManagedPolicyArns: [ { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/asdf']] }, ], @@ -65,7 +65,7 @@ describe('IAM user', () => { permissionsBoundary, }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::User', { + expect(stack).toHaveResource('AWS::IAM::User', { PermissionsBoundary: { 'Fn::Join': [ '', @@ -132,7 +132,7 @@ describe('IAM user', () => { })); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { Users: ['john'], PolicyDocument: { Statement: [ @@ -163,7 +163,7 @@ describe('IAM user', () => { })); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { Users: ['john'], PolicyDocument: { Statement: [ @@ -190,7 +190,7 @@ describe('IAM user', () => { otherGroup.addUser(user); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::IAM::UserToGroupAddition', { + expect(stack).toHaveResource('AWS::IAM::UserToGroupAddition', { GroupName: { Ref: 'GroupC77FDACD', }, @@ -199,7 +199,7 @@ describe('IAM user', () => { ], }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::UserToGroupAddition', { + expect(stack).toHaveResource('AWS::IAM::UserToGroupAddition', { GroupName: { Ref: 'OtherGroup85E5C653', }, diff --git a/packages/@aws-cdk/aws-kms/package.json b/packages/@aws-cdk/aws-kms/package.json index 4239273b4fb73..74fd0a5f586aa 100644 --- a/packages/@aws-cdk/aws-kms/package.json +++ b/packages/@aws-cdk/aws-kms/package.json @@ -72,7 +72,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assertions": "0.0.0", + "@aws-cdk/assert-internal": "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-kms/test/alias.test.ts b/packages/@aws-cdk/aws-kms/test/alias.test.ts index 01e0b6f353b62..094d2a3e7dbed 100644 --- a/packages/@aws-cdk/aws-kms/test/alias.test.ts +++ b/packages/@aws-cdk/aws-kms/test/alias.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import { ArnPrincipal, PolicyStatement } from '@aws-cdk/aws-iam'; import { App, CfnOutput, Stack } from '@aws-cdk/core'; import { Alias } from '../lib/alias'; @@ -15,7 +15,7 @@ test('default alias', () => { new Alias(stack, 'Alias', { targetKey: key, aliasName: 'alias/foo' }); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Alias', { + expect(stack).toHaveResource('AWS::KMS::Alias', { AliasName: 'alias/foo', TargetKeyId: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, }); @@ -35,7 +35,7 @@ test('add "alias/" prefix if not given.', () => { targetKey: key, }); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Alias', { + expect(stack).toHaveResource('AWS::KMS::Alias', { AliasName: 'alias/foo', TargetKeyId: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, }); @@ -51,7 +51,7 @@ test('can create alias directly while creating the key', () => { alias: 'foo', }); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Alias', { + expect(stack).toHaveResource('AWS::KMS::Alias', { AliasName: 'alias/foo', TargetKeyId: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] }, }); @@ -140,11 +140,13 @@ test('can be used wherever a key is expected', () => { new MyConstruct(stack, 'MyConstruct', myAlias); /* eslint-enable @aws-cdk/no-core-construct */ - Template.fromStack(stack).hasOutput('OutId', { - Value: 'alias/myAlias', + expect(stack).toHaveOutput({ + outputName: 'OutId', + outputValue: 'alias/myAlias', }); - Template.fromStack(stack).hasOutput('OutArn', { - Value: { + expect(stack).toHaveOutput({ + outputName: 'OutArn', + outputValue: { 'Fn::Join': ['', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -179,11 +181,13 @@ test('imported alias by name - can be used where a key is expected', () => { new MyConstruct(stack, 'MyConstruct', myAlias); /* eslint-enable @aws-cdk/no-core-construct */ - Template.fromStack(stack).hasOutput('OutId', { - Value: 'alias/myAlias', + expect(stack).toHaveOutput({ + outputName: 'OutId', + outputValue: 'alias/myAlias', }); - Template.fromStack(stack).hasOutput('OutArn', { - Value: { + expect(stack).toHaveOutput({ + outputName: 'OutArn', + outputValue: { 'Fn::Join': ['', [ 'arn:', { Ref: 'AWS::Partition' }, diff --git a/packages/@aws-cdk/aws-kms/test/key.from-lookup.test.ts b/packages/@aws-cdk/aws-kms/test/key.from-lookup.test.ts index 72598e01708f1..f7af3c8c0dd14 100644 --- a/packages/@aws-cdk/aws-kms/test/key.from-lookup.test.ts +++ b/packages/@aws-cdk/aws-kms/test/key.from-lookup.test.ts @@ -3,6 +3,7 @@ import { ContextProvider, GetContextValueOptions, GetContextValueResult, Lazy, S import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import { Key } from '../lib'; +import '@aws-cdk/assert-internal/jest'; test('requires concrete values', () => { expect(() => { diff --git a/packages/@aws-cdk/aws-kms/test/key.test.ts b/packages/@aws-cdk/aws-kms/test/key.test.ts index 9eb6d3cf26260..fae1564223d45 100644 --- a/packages/@aws-cdk/aws-kms/test/key.test.ts +++ b/packages/@aws-cdk/aws-kms/test/key.test.ts @@ -1,4 +1,5 @@ -import { Match, Template } from '@aws-cdk/assertions'; +import { arrayWith, countResources, expect as expectCdk, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert-internal'; +import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; @@ -46,7 +47,7 @@ testFutureBehavior('default key', flags, cdk.App, (app) => { const stack = new cdk.Stack(app); new kms.Key(stack, 'MyKey'); - Template.fromStack(stack).hasResource('AWS::KMS::Key', { + expect(stack).toHaveResource('AWS::KMS::Key', { Properties: { KeyPolicy: { Statement: [ @@ -64,14 +65,14 @@ testFutureBehavior('default key', flags, cdk.App, (app) => { }, DeletionPolicy: 'Retain', UpdateReplacePolicy: 'Retain', - }); + }, ResourcePart.CompleteDefinition); }); testFutureBehavior('default with no retention', flags, cdk.App, (app) => { const stack = new cdk.Stack(app); new kms.Key(stack, 'MyKey', { removalPolicy: cdk.RemovalPolicy.DESTROY }); - Template.fromStack(stack).hasResource('AWS::KMS::Key', { DeletionPolicy: 'Delete', UpdateReplacePolicy: 'Delete' }); + expect(stack).toHaveResource('AWS::KMS::Key', { DeletionPolicy: 'Delete', UpdateReplacePolicy: 'Delete' }, ResourcePart.CompleteDefinition); }); describe('key policies', () => { @@ -84,7 +85,7 @@ describe('key policies', () => { new kms.Key(stack, 'MyKey', { policy }); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResource('AWS::KMS::Key', { KeyPolicy: { Statement: [ { @@ -109,7 +110,7 @@ describe('key policies', () => { const key = new kms.Key(stack, 'MyKey'); key.addToResourcePolicy(statement); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResource('AWS::KMS::Key', { KeyPolicy: { Statement: [ { @@ -145,7 +146,7 @@ describe('key policies', () => { // THEN // Key policy should be unmodified by the grant. - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResource('AWS::KMS::Key', { KeyPolicy: { Statement: [ { @@ -159,7 +160,7 @@ describe('key policies', () => { }, }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -184,7 +185,7 @@ describe('key policies', () => { // THEN // Key policy should be unmodified by the grant. - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResource('AWS::KMS::Key', { KeyPolicy: { Statement: [ { @@ -198,7 +199,7 @@ describe('key policies', () => { }, }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -225,7 +226,7 @@ describe('key policies', () => { key.grantEncrypt(principal); - Template.fromStack(principalStack).hasResourceProperties('AWS::IAM::Policy', { + expect(principalStack).toHaveResourceLike('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -257,9 +258,9 @@ describe('key policies', () => { key.grantEncrypt(principal); - Template.fromStack(keyStack).hasResourceProperties('AWS::KMS::Key', { + expect(keyStack).toHaveResourceLike('AWS::KMS::Key', { KeyPolicy: { - Statement: Match.arrayWith([ + Statement: arrayWith( { Action: [ 'kms:Encrypt', @@ -270,11 +271,11 @@ describe('key policies', () => { Principal: { AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::', { Ref: 'AWS::AccountId' }, ':role/MyRolePhysicalName']] } }, Resource: '*', }, - ]), + ), Version: '2012-10-17', }, }); - Template.fromStack(principalStack).hasResourceProperties('AWS::IAM::Policy', { + expect(principalStack).toHaveResourceLike('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -304,12 +305,12 @@ describe('key policies', () => { key.grantEncrypt(principal); - Template.fromStack(keyStack).hasResourceProperties('AWS::KMS::Key', { + expect(keyStack).toHaveResourceLike('AWS::KMS::Key', { KeyPolicy: { Statement: [ - Match.objectLike({ + { // Default policy, unmodified - }), + }, { Action: [ 'kms:Encrypt', @@ -324,7 +325,7 @@ describe('key policies', () => { Version: '2012-10-17', }, }); - Template.fromStack(principalStack).hasResourceProperties('AWS::IAM::Policy', { + expect(principalStack).toHaveResourceLike('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -347,7 +348,7 @@ describe('key policies', () => { const adminRole = iam.Role.fromRoleArn(stack, 'Admin', 'arn:aws:iam::123456789012:role/TrustedAdmin'); new kms.Key(stack, 'MyKey', { admins: [adminRole] }); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResource('AWS::KMS::Key', { KeyPolicy: { Statement: [ { @@ -379,7 +380,7 @@ describe('key policies', () => { }); new kms.Key(stack, 'MyKey', { admins: [adminRole] }); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResource('AWS::KMS::Key', { KeyPolicy: { // Unmodified - default key policy Statement: [ @@ -395,7 +396,7 @@ describe('key policies', () => { Version: '2012-10-17', }, }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -422,7 +423,7 @@ testFutureBehavior('key with some options', flags, cdk.App, (app) => { cdk.Tags.of(key).add('tag2', 'value2'); cdk.Tags.of(key).add('tag3', ''); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResourceLike('AWS::KMS::Key', { Enabled: false, EnableKeyRotation: true, PendingWindowInDays: 7, @@ -465,8 +466,8 @@ testFutureBehavior('addAlias creates an alias', flags, cdk.App, (app) => { const alias = key.addAlias('alias/xoo'); expect(alias.aliasName).toBeDefined(); - Template.fromStack(stack).resourceCountIs('AWS::KMS::Alias', 1); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Alias', { + expect(stack).toCountResources('AWS::KMS::Alias', 1); + expect(stack).toHaveResource('AWS::KMS::Alias', { AliasName: 'alias/xoo', TargetKeyId: { 'Fn::GetAtt': [ @@ -489,8 +490,8 @@ testFutureBehavior('can run multiple addAlias', flags, cdk.App, (app) => { expect(alias1.aliasName).toBeDefined(); expect(alias2.aliasName).toBeDefined(); - Template.fromStack(stack).resourceCountIs('AWS::KMS::Alias', 2); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Alias', { + expect(stack).toCountResources('AWS::KMS::Alias', 2); + expect(stack).toHaveResource('AWS::KMS::Alias', { AliasName: 'alias/alias1', TargetKeyId: { 'Fn::GetAtt': [ @@ -499,7 +500,7 @@ testFutureBehavior('can run multiple addAlias', flags, cdk.App, (app) => { ], }, }); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Alias', { + expect(stack).toHaveResource('AWS::KMS::Alias', { AliasName: 'alias/alias2', TargetKeyId: { 'Fn::GetAtt': [ @@ -518,8 +519,9 @@ testFutureBehavior('keyId resolves to a Ref', flags, cdk.App, (app) => { value: key.keyId, }); - Template.fromStack(stack).hasOutput('Out', { - Value: { Ref: 'MyKey6AB29FA6' }, + expect(stack).toHaveOutput({ + outputName: 'Out', + outputValue: { Ref: 'MyKey6AB29FA6' }, }); }); @@ -566,7 +568,7 @@ describe('imported keys', () => { expect(myKeyImported.keyId).toEqual('12345678-1234-1234-1234-123456789012'); - Template.fromStack(stack2).templateMatches({ + expect(stack2).toMatchTemplate({ Resources: { MyKeyImportedAliasB1C5269F: { Type: 'AWS::KMS::Alias', @@ -624,7 +626,7 @@ describe('fromCfnKey()', () => { }); test('preserves the KMS Key resource', () => { - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expectCdk(stack).to(haveResource('AWS::KMS::Key', { KeyPolicy: { Statement: [ { @@ -646,9 +648,9 @@ describe('fromCfnKey()', () => { ], Version: '2012-10-17', }, - }); + })); - Template.fromStack(stack).resourceCountIs('AWS::KMS::Key', 1); + expectCdk(stack).to(countResources('AWS::KMS::Key', 1)); }); describe("calling 'addToResourcePolicy()' on the returned Key", () => { @@ -667,7 +669,7 @@ describe('fromCfnKey()', () => { }); test('preserves the mutating call in the resulting template', () => { - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expectCdk(stack).to(haveResource('AWS::KMS::Key', { KeyPolicy: { Statement: [ { @@ -695,7 +697,7 @@ describe('fromCfnKey()', () => { ], Version: '2012-10-17', }, - }); + })); }); }); @@ -713,7 +715,7 @@ describe('fromCfnKey()', () => { }); test('creates the correct IAM Policy', () => { - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expectCdk(stack).to(haveResourceLike('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -725,11 +727,11 @@ describe('fromCfnKey()', () => { }, ], }, - }); + })); }); test('correctly mutates the Policy of the underlying CfnKey', () => { - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expectCdk(stack).to(haveResourceLike('AWS::KMS::Key', { KeyPolicy: { Statement: [ { @@ -761,7 +763,7 @@ describe('fromCfnKey()', () => { ], Version: '2012-10-17', }, - }); + })); }); }); }); @@ -925,7 +927,7 @@ describe('when the defaultKeyPolicies feature flag is disabled', () => { const stack = new cdk.Stack(app); new kms.Key(stack, 'MyKey'); - Template.fromStack(stack).hasResource('AWS::KMS::Key', { + expect(stack).toHaveResource('AWS::KMS::Key', { Properties: { KeyPolicy: { Statement: [ @@ -943,7 +945,7 @@ describe('when the defaultKeyPolicies feature flag is disabled', () => { }, DeletionPolicy: 'Retain', UpdateReplacePolicy: 'Retain', - }); + }, ResourcePart.CompleteDefinition); }); testLegacyBehavior('policy if specified appends to the default key policy', cdk.App, (app) => { @@ -953,7 +955,7 @@ describe('when the defaultKeyPolicies feature flag is disabled', () => { p.addArnPrincipal('arn:aws:iam::111122223333:root'); key.addToResourcePolicy(p); - Template.fromStack(stack).templateMatches({ + expect(stack).toMatchTemplate({ Resources: { MyKey6AB29FA6: { Type: 'AWS::KMS::Key', @@ -990,7 +992,7 @@ describe('when the defaultKeyPolicies feature flag is disabled', () => { testLegacyBehavior('trustAccountIdentities changes key policy to allow IAM control', cdk.App, (app) => { const stack = new cdk.Stack(app); new kms.Key(stack, 'MyKey', { trustAccountIdentities: true }); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResourceLike('AWS::KMS::Key', { KeyPolicy: { Statement: [ { @@ -1011,7 +1013,7 @@ describe('when the defaultKeyPolicies feature flag is disabled', () => { const adminRole = iam.Role.fromRoleArn(stack, 'Admin', 'arn:aws:iam::123456789012:role/TrustedAdmin'); new kms.Key(stack, 'MyKey', { admins: [adminRole] }); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResource('AWS::KMS::Key', { KeyPolicy: { Statement: [ { @@ -1043,7 +1045,7 @@ describe('when the defaultKeyPolicies feature flag is disabled', () => { }); new kms.Key(stack, 'MyKey', { admins: [adminRole] }); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResource('AWS::KMS::Key', { KeyPolicy: { Statement: [ { @@ -1066,7 +1068,7 @@ describe('when the defaultKeyPolicies feature flag is disabled', () => { Version: '2012-10-17', }, }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -1091,7 +1093,7 @@ describe('when the defaultKeyPolicies feature flag is disabled', () => { key.grantDecrypt(user); // THEN - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResource('AWS::KMS::Key', { KeyPolicy: { Statement: [ // This one is there by default @@ -1113,7 +1115,7 @@ describe('when the defaultKeyPolicies feature flag is disabled', () => { }, }); - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + expect(stack).toHaveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -1140,9 +1142,9 @@ describe('when the defaultKeyPolicies feature flag is disabled', () => { key.grantEncrypt(principal); - Template.fromStack(keyStack).hasResourceProperties('AWS::KMS::Key', { + expect(keyStack).toHaveResourceLike('AWS::KMS::Key', { KeyPolicy: { - Statement: Match.arrayWith([{ + Statement: arrayWith({ Action: [ 'kms:Encrypt', 'kms:ReEncrypt*', @@ -1153,7 +1155,7 @@ describe('when the defaultKeyPolicies feature flag is disabled', () => { AWS: { 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':iam::', { Ref: 'AWS::AccountId' }, ':root']] }, }, Resource: '*', - }]), + }), }, }); }); @@ -1165,7 +1167,7 @@ describe('key specs and key usages', () => { const stack = new cdk.Stack(app); new kms.Key(stack, 'Key', { keySpec: kms.KeySpec.ECC_SECG_P256K1, keyUsage: kms.KeyUsage.SIGN_VERIFY }); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResourceLike('AWS::KMS::Key', { KeySpec: 'ECC_SECG_P256K1', KeyUsage: 'SIGN_VERIFY', }); @@ -1175,7 +1177,7 @@ describe('key specs and key usages', () => { const stack = new cdk.Stack(app); new kms.Key(stack, 'Key', { keyUsage: kms.KeyUsage.ENCRYPT_DECRYPT }); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResourceLike('AWS::KMS::Key', { KeyUsage: 'ENCRYPT_DECRYPT', }); }); @@ -1184,7 +1186,7 @@ describe('key specs and key usages', () => { const stack = new cdk.Stack(app); new kms.Key(stack, 'Key', { keySpec: kms.KeySpec.RSA_4096 }); - Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + expect(stack).toHaveResourceLike('AWS::KMS::Key', { KeySpec: 'RSA_4096', }); }); diff --git a/packages/@aws-cdk/aws-kms/test/via-service-principal.test.ts b/packages/@aws-cdk/aws-kms/test/via-service-principal.test.ts index b00640f675378..1e5eeb95f28ff 100644 --- a/packages/@aws-cdk/aws-kms/test/via-service-principal.test.ts +++ b/packages/@aws-cdk/aws-kms/test/via-service-principal.test.ts @@ -1,3 +1,4 @@ +import '@aws-cdk/assert-internal/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '../lib'; diff --git a/packages/@aws-cdk/aws-signer/package.json b/packages/@aws-cdk/aws-signer/package.json index bdda4be1cabf9..84489e43c845b 100644 --- a/packages/@aws-cdk/aws-signer/package.json +++ b/packages/@aws-cdk/aws-signer/package.json @@ -74,7 +74,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assertions": "0.0.0", + "@aws-cdk/assert-internal": "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-signer/test/signing-profile.test.ts b/packages/@aws-cdk/aws-signer/test/signing-profile.test.ts index 44db183b565a5..49ada5da2f596 100644 --- a/packages/@aws-cdk/aws-signer/test/signing-profile.test.ts +++ b/packages/@aws-cdk/aws-signer/test/signing-profile.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import '@aws-cdk/assert-internal/jest'; import * as cdk from '@aws-cdk/core'; import * as signer from '../lib'; @@ -14,7 +14,7 @@ describe('signing profile', () => { const platform = signer.Platform.AWS_LAMBDA_SHA384_ECDSA; new signer.SigningProfile( stack, 'SigningProfile', { platform } ); - Template.fromStack(stack).hasResourceProperties('AWS::Signer::SigningProfile', { + expect(stack).toHaveResource('AWS::Signer::SigningProfile', { PlatformId: platform.platformId, SignatureValidityPeriod: { Type: 'MONTHS', @@ -30,7 +30,7 @@ describe('signing profile', () => { signatureValidity: cdk.Duration.days( 7 ), } ); - Template.fromStack(stack).hasResourceProperties('AWS::Signer::SigningProfile', { + expect(stack).toHaveResource('AWS::Signer::SigningProfile', { PlatformId: platform.platformId, SignatureValidityPeriod: { Type: 'DAYS', @@ -47,7 +47,7 @@ describe('signing profile', () => { cdk.Tags.of(signing).add('tag2', 'value2'); cdk.Tags.of(signing).add('tag3', ''); - Template.fromStack(stack).hasResourceProperties('AWS::Signer::SigningProfile', { + expect(stack).toHaveResource('AWS::Signer::SigningProfile', { PlatformId: platform.platformId, SignatureValidityPeriod: { Type: 'MONTHS', @@ -109,7 +109,7 @@ describe('signing profile', () => { ], ], }); - Template.fromStack(stack).templateMatches({}); + expect(stack).toMatchTemplate({}); }); } ); }); From 810d2d9c0eb8a69e065d25f8a295de79e8465ec4 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Thu, 30 Sep 2021 16:29:40 -0700 Subject: [PATCH 12/23] chore(cli): add more capabilities to the hotswap CFN evaluate sub-system (#16696) The current functionality we use for evaluating CloudFormation in the hotswap part of the CLI is very limited: only allows substituting the values of parameters used for Assets. That's not good enough when doing substitutions for StepFunctions State Machines from [this PR](https://github.com/aws/aws-cdk/pull/16489), for example. Enhance the capabilities of the CFN eval sub-system by introducing a new class, `CloudFormationExecutableTemplate`, that allows resolving references to resources inside the template. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/aws-cdk/lib/api/deploy-stack.ts | 18 +- .../aws-cdk/lib/api/hotswap-deployments.ts | 84 +++--- packages/aws-cdk/lib/api/hotswap/common.ts | 21 +- .../evaluate-cloudformation-template.ts | 259 +++++++++++++++++ .../lib/api/hotswap/lambda-functions.ts | 79 +++--- .../api/util/cloudformation/evaluate-cfn.ts | 89 ------ .../test/api/hotswap-deployments.test.ts | 260 +++++++++++++++++- 7 files changed, 617 insertions(+), 193 deletions(-) create mode 100644 packages/aws-cdk/lib/api/hotswap/evaluate-cloudformation-template.ts delete mode 100644 packages/aws-cdk/lib/api/util/cloudformation/evaluate-cfn.ts diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index b0770ebdf8ea7..a889e0c802fc4 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -10,6 +10,7 @@ import { publishAssets } from '../util/asset-publishing'; import { contentHash } from '../util/content-hash'; import { ISDK, SdkProvider } from './aws-auth'; import { tryHotswapDeployment } from './hotswap-deployments'; +import { CfnEvaluationException } from './hotswap/evaluate-cloudformation-template'; import { ToolkitInfo } from './toolkit-info'; import { changeSetHasNoChanges, CloudFormationStack, TemplateParameters, waitForChangeSet, @@ -252,12 +253,19 @@ export async function deployStack(options: DeployStackOptions): Promise { - const currentTemplate = await cloudFormationStack.template(); - const stackChanges = cfn_diff.diffTemplate(currentTemplate, stackArtifact.template); - // resolve the environment, so we can substitute things like AWS::Region in CFN expressions const resolvedEnv = await sdkProvider.resolveEnvironment(stackArtifact.environment); - const hotswappableChanges = findAllHotswappableChanges(stackChanges, { - ...assetParams, - 'AWS::Region': resolvedEnv.region, - 'AWS::AccountId': resolvedEnv.account, + // create a new SDK using the CLI credentials, because the default one will not work for new-style synthesis - + // it assumes the bootstrap deploy Role, which doesn't have permissions to update Lambda functions + const sdk = await sdkProvider.forEnvironment(resolvedEnv, Mode.ForWriting); + // The current resources of the Stack. + // We need them to figure out the physical name of a resource in case it wasn't specified by the user. + // We fetch it lazily, to save a service call, in case all hotswapped resources have their physical names set. + const listStackResources = new LazyListStackResources(sdk, stackArtifact.stackName); + const evaluateCfnTemplate = new EvaluateCloudFormationTemplate({ + stackArtifact, + parameters: assetParams, + account: resolvedEnv.account, + region: resolvedEnv.region, + // ToDo make this better: + partition: 'aws', + // ToDo make this better: + urlSuffix: 'amazonaws.com', + listStackResources, }); + + const currentTemplate = await cloudFormationStack.template(); + const stackChanges = cfn_diff.diffTemplate(currentTemplate, stackArtifact.template); + const hotswappableChanges = await findAllHotswappableChanges(stackChanges, evaluateCfnTemplate); if (!hotswappableChanges) { // this means there were changes to the template that cannot be short-circuited return undefined; } - // create a new SDK using the CLI credentials, because the default one will not work for new-style synthesis - - // it assumes the bootstrap deploy Role, which doesn't have permissions to update Lambda functions - const sdk = await sdkProvider.forEnvironment(resolvedEnv, Mode.ForWriting); // apply the short-circuitable changes - await applyAllHotswappableChanges(sdk, stackArtifact, hotswappableChanges); + await applyAllHotswappableChanges(sdk, hotswappableChanges); return { noOp: hotswappableChanges.length === 0, stackArn: cloudFormationStack.stackId, outputs: cloudFormationStack.outputs, stackArtifact }; } -function findAllHotswappableChanges( - stackChanges: cfn_diff.TemplateDiff, assetParamsWithEnv: { [key: string]: string }, -): HotswapOperation[] | undefined { - const hotswappableResources = new Array(); - let foundNonHotswappableChange = false; - stackChanges.resources.forEachDifference((logicalId: string, change: cfn_diff.ResourceDifference) => { - const lambdaFunctionShortCircuitChange = isHotswappableLambdaFunctionChange(logicalId, change, assetParamsWithEnv); - if (lambdaFunctionShortCircuitChange === ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT) { - foundNonHotswappableChange = true; - } else if (lambdaFunctionShortCircuitChange === ChangeHotswapImpact.IRRELEVANT) { - // empty 'if' just for flow-aware typing to kick in... - } else { - hotswappableResources.push(lambdaFunctionShortCircuitChange); +async function findAllHotswappableChanges( + stackChanges: cfn_diff.TemplateDiff, evaluateCfnTemplate: EvaluateCloudFormationTemplate, +): Promise { + const promises = new Array>(); + stackChanges.resources.forEachDifference(async (logicalId: string, change: cfn_diff.ResourceDifference) => { + promises.push(isHotswappableLambdaFunctionChange(logicalId, change, evaluateCfnTemplate)); + }); + return Promise.all(promises).then(hotswapDetectionResults => { + const hotswappableResources = new Array(); + let foundNonHotswappableChange = false; + for (const lambdaFunctionShortCircuitChange of hotswapDetectionResults) { + if (lambdaFunctionShortCircuitChange === ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT) { + foundNonHotswappableChange = true; + } else if (lambdaFunctionShortCircuitChange === ChangeHotswapImpact.IRRELEVANT) { + // empty 'if' just for flow-aware typing to kick in... + } else { + hotswappableResources.push(lambdaFunctionShortCircuitChange); + } } + return foundNonHotswappableChange ? undefined : hotswappableResources; }); - return foundNonHotswappableChange ? undefined : hotswappableResources; } async function applyAllHotswappableChanges( - sdk: ISDK, stackArtifact: cxapi.CloudFormationStackArtifact, hotswappableChanges: HotswapOperation[], + sdk: ISDK, hotswappableChanges: HotswapOperation[], ): Promise { - // The current resources of the Stack. - // We need them to figure out the physical name of a function in case it wasn't specified by the user. - // We fetch it lazily, to save a service call, in case all updated Lambdas have their names set. - const listStackResources = new LazyListStackResources(sdk, stackArtifact.stackName); - - return Promise.all(hotswappableChanges.map(hotswapOperation => hotswapOperation.apply(sdk, listStackResources))); + return Promise.all(hotswappableChanges.map(hotswapOperation => { + return hotswapOperation.apply(sdk); + })); } class LazyListStackResources implements ListStackResources { @@ -79,12 +93,12 @@ class LazyListStackResources implements ListStackResources { async listStackResources(): Promise { if (this.stackResources === undefined) { - this.stackResources = await this.getStackResource(); + this.stackResources = await this.getStackResources(); } return this.stackResources; } - private async getStackResource(): Promise { + private async getStackResources(): Promise { const ret = new Array(); let nextToken: string | undefined; do { diff --git a/packages/aws-cdk/lib/api/hotswap/common.ts b/packages/aws-cdk/lib/api/hotswap/common.ts index d509c32b4c781..c11b29d1d7daa 100644 --- a/packages/aws-cdk/lib/api/hotswap/common.ts +++ b/packages/aws-cdk/lib/api/hotswap/common.ts @@ -1,7 +1,6 @@ import * as cfn_diff from '@aws-cdk/cloudformation-diff'; import { CloudFormation } from 'aws-sdk'; import { ISDK } from '../aws-auth'; -import { evaluateCfn } from '../util/cloudformation/evaluate-cfn'; export interface ListStackResources { listStackResources(): Promise; @@ -11,7 +10,7 @@ export interface ListStackResources { * An interface that represents a change that can be deployed in a short-circuit manner. */ export interface HotswapOperation { - apply(sdk: ISDK, stackResources: ListStackResources): Promise; + apply(sdk: ISDK): Promise; } /** @@ -34,24 +33,6 @@ export enum ChangeHotswapImpact { export type ChangeHotswapResult = HotswapOperation | ChangeHotswapImpact; -/** - * For old-style synthesis which uses CFN Parameters, - * the Code properties can have the values of complex CFN expressions. - * For new-style synthesis of env-agnostic stacks, - * the Fn::Sub expression is used for the Asset bucket. - * Evaluate the CFN expressions to concrete string values which we need for the - * updateFunctionCode() service call. - */ -export function stringifyPotentialCfnExpression(value: any, assetParamsWithEnv: { [key: string]: string }): string { - // if we already have a string, nothing to do - if (value == null || typeof value === 'string') { - return value; - } - - // otherwise, we assume this is a CloudFormation expression that we need to evaluate - return evaluateCfn(value, assetParamsWithEnv); -} - export function assetMetadataChanged(change: cfn_diff.ResourceDifference): boolean { return !!change.newValue?.Metadata['aws:asset:path']; } diff --git a/packages/aws-cdk/lib/api/hotswap/evaluate-cloudformation-template.ts b/packages/aws-cdk/lib/api/hotswap/evaluate-cloudformation-template.ts new file mode 100644 index 0000000000000..dc1541ed74771 --- /dev/null +++ b/packages/aws-cdk/lib/api/hotswap/evaluate-cloudformation-template.ts @@ -0,0 +1,259 @@ +import * as cxapi from '@aws-cdk/cx-api'; +import * as AWS from 'aws-sdk'; +import { ListStackResources } from './common'; + +export class CfnEvaluationException extends Error {} + +export interface EvaluateCloudFormationTemplateProps { + readonly stackArtifact: cxapi.CloudFormationStackArtifact; + readonly parameters: { [parameterName: string]: string }; + readonly account: string; + readonly region: string; + readonly partition: string; + readonly urlSuffix: string; + + readonly listStackResources: ListStackResources; +} + +export class EvaluateCloudFormationTemplate { + private readonly stackResources: ListStackResources; + private readonly context: { [k: string]: string }; + private readonly account: string; + private readonly region: string; + private readonly partition: string; + + constructor(props: EvaluateCloudFormationTemplateProps) { + this.stackResources = props.listStackResources; + this.context = { + 'AWS::AccountId': props.account, + 'AWS::Region': props.region, + 'AWS::Partition': props.partition, + 'AWS::URLSuffix': props.urlSuffix, + ...props.parameters, + }; + this.account = props.account; + this.region = props.region; + this.partition = props.partition; + } + + public async findPhysicalNameFor(logicalId: string): Promise { + const stackResources = await this.stackResources.listStackResources(); + return stackResources.find(sr => sr.LogicalResourceId === logicalId)?.PhysicalResourceId; + } + + public async evaluateCfnExpression(cfnExpression: any): Promise { + const self = this; + class CfnIntrinsics { + public evaluateIntrinsic(intrinsic: Intrinsic): any { + const intrinsicFunc = (this as any)[intrinsic.name]; + if (!intrinsicFunc) { + throw new CfnEvaluationException(`CloudFormation function ${intrinsic.name} is not supported`); + } + + const argsAsArray = Array.isArray(intrinsic.args) ? intrinsic.args : [intrinsic.args]; + + return intrinsicFunc.apply(this, argsAsArray); + } + + async 'Fn::Join'(separator: string, args: any[]): Promise { + const evaluatedArgs = await self.evaluateCfnExpression(args); + return evaluatedArgs.join(separator); + } + + async 'Fn::Split'(separator: string, args: any): Promise { + const evaluatedArgs = await self.evaluateCfnExpression(args); + return evaluatedArgs.split(separator); + } + + async 'Fn::Select'(index: number, args: any[]): Promise { + const evaluatedArgs = await self.evaluateCfnExpression(args); + return evaluatedArgs[index]; + } + + async 'Ref'(logicalId: string): Promise { + const refTarget = await self.findRefTarget(logicalId); + if (refTarget) { + return refTarget; + } else { + throw new CfnEvaluationException(`Parameter or resource '${logicalId}' could not be found for evaluation`); + } + } + + async 'Fn::GetAtt'(logicalId: string, attributeName: string): Promise { + // ToDo handle the 'logicalId.attributeName' form of Fn::GetAtt + const attrValue = await self.findGetAttTarget(logicalId, attributeName); + if (attrValue) { + return attrValue; + } else { + throw new CfnEvaluationException(`Attribute '${attributeName}' of resource '${logicalId}' could not be found for evaluation`); + } + } + + async 'Fn::Sub'(template: string, explicitPlaceholders?: { [variable: string]: string }): Promise { + const placeholders = explicitPlaceholders + ? await self.evaluateCfnExpression(explicitPlaceholders) + : {}; + + return asyncGlobalReplace(template, /\${([^}]*)}/g, key => { + if (key in placeholders) { + return placeholders[key]; + } else { + const splitKey = key.split('.'); + return splitKey.length === 1 + ? this.Ref(key) + : this['Fn::GetAtt'](splitKey[0], splitKey.slice(1).join('.')); + } + }); + } + } + + if (cfnExpression == null) { + return cfnExpression; + } + + if (Array.isArray(cfnExpression)) { + return Promise.all(cfnExpression.map(expr => this.evaluateCfnExpression(expr))); + } + + if (typeof cfnExpression === 'object') { + const intrinsic = this.parseIntrinsic(cfnExpression); + if (intrinsic) { + return new CfnIntrinsics().evaluateIntrinsic(intrinsic); + } else { + const ret: { [key: string]: any } = {}; + for (const [key, val] of Object.entries(cfnExpression)) { + ret[key] = await this.evaluateCfnExpression(val); + } + return ret; + } + } + + return cfnExpression; + } + + private parseIntrinsic(x: any): Intrinsic | undefined { + const keys = Object.keys(x); + if (keys.length === 1 && (keys[0].startsWith('Fn::') || keys[0] === 'Ref')) { + return { + name: keys[0], + args: x[keys[0]], + }; + } + return undefined; + } + + private async findRefTarget(logicalId: string): Promise { + // first, check to see if the Ref is a Parameter who's value we have + const parameterTarget = this.context[logicalId]; + if (parameterTarget) { + return parameterTarget; + } + // if it's not a Parameter, we need to search in the current Stack resources + return this.findGetAttTarget(logicalId); + } + + private async findGetAttTarget(logicalId: string, attribute?: string): Promise { + const stackResources = await this.stackResources.listStackResources(); + const foundResource = stackResources.find(sr => sr.LogicalResourceId === logicalId); + if (!foundResource) { + return undefined; + } + // now, we need to format the appropriate identifier depending on the resource type, + // and the requested attribute name + return this.formatResourceAttribute(foundResource, attribute); + } + + private formatResourceAttribute(resource: AWS.CloudFormation.StackResourceSummary, attribute: string | undefined): string | undefined { + const physicalId = resource.PhysicalResourceId; + + // no attribute means Ref expression, for which we use the physical ID directly + if (!attribute) { + return physicalId; + } + + const resourceTypeFormats = RESOURCE_TYPE_ATTRIBUTES_FORMATS[resource.ResourceType]; + if (!resourceTypeFormats) { + throw new CfnEvaluationException(`We don't support attributes of the '${resource.ResourceType}' resource. This is a CDK limitation. ` + + 'Please report it at https://github.com/aws/aws-cdk/issues/new/choose'); + } + const attributeFmtFunc = resourceTypeFormats[attribute]; + if (!attributeFmtFunc) { + throw new CfnEvaluationException(`We don't support the '${attribute}' attribute of the '${resource.ResourceType}' resource. This is a CDK limitation. ` + + 'Please report it at https://github.com/aws/aws-cdk/issues/new/choose'); + } + const service = this.getServiceOfResource(resource); + const resourceTypeArnPart = this.getResourceTypeArnPartOfResource(resource); + return attributeFmtFunc({ + partition: this.partition, + service, + region: this.region, + account: this.account, + resourceType: resourceTypeArnPart, + resourceName: physicalId!, + }); + } + + private getServiceOfResource(resource: AWS.CloudFormation.StackResourceSummary): string { + return resource.ResourceType.split('::')[1].toLowerCase(); + } + + private getResourceTypeArnPartOfResource(resource: AWS.CloudFormation.StackResourceSummary): string { + return resource.ResourceType.split('::')[2].toLowerCase(); + } +} + +interface ArnParts { + readonly partition: string; + readonly service: string; + readonly region: string; + readonly account: string; + readonly resourceType: string; + readonly resourceName: string; +} + +const RESOURCE_TYPE_ATTRIBUTES_FORMATS: { [type: string]: { [attribute: string]: (parts: ArnParts) => string } } = { + 'AWS::IAM::Role': { Arn: iamArnFmt }, + 'AWS::IAM::User': { Arn: iamArnFmt }, + 'AWS::IAM::Group': { Arn: iamArnFmt }, + 'AWS::S3::Bucket': { Arn: s3ArnFmt }, + 'AWS::Lambda::Function': { Arn: stdColonResourceArnFmt }, +}; + +function iamArnFmt(parts: ArnParts): string { + // we skip region for IAM resources + return `arn:${parts.partition}:${parts.service}::${parts.account}:${parts.resourceType}/${parts.resourceName}`; +} + +function s3ArnFmt(parts: ArnParts): string { + // we skip account, region and resourceType for S3 resources + return `arn:${parts.partition}:${parts.service}:::${parts.resourceName}`; +} + +function stdColonResourceArnFmt(parts: ArnParts): string { + // this is a standard format for ARNs like: arn:aws:service:region:account:resourceType:resourceName + return `arn:${parts.partition}:${parts.service}:${parts.region}:${parts.account}:${parts.resourceType}:${parts.resourceName}`; +} + +interface Intrinsic { + readonly name: string; + readonly args: any; +} + +async function asyncGlobalReplace(str: string, regex: RegExp, cb: (x: string) => Promise): Promise { + if (!regex.global) { throw new Error('Regex must be created with /g flag'); } + + const ret = new Array(); + let start = 0; + while (true) { + const match = regex.exec(str); + if (!match) { break; } + + ret.push(str.substring(start, match.index)); + ret.push(await cb(match[1])); + + start = regex.lastIndex; + } + ret.push(str.substr(start)); + + return ret.join(''); +} diff --git a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts index 73b4c529188c5..c110a66b6c9db 100644 --- a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts +++ b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts @@ -1,6 +1,7 @@ import * as cfn_diff from '@aws-cdk/cloudformation-diff'; import { ISDK } from '../aws-auth'; -import { assetMetadataChanged, ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, ListStackResources, stringifyPotentialCfnExpression } from './common'; +import { assetMetadataChanged, ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation } from './common'; +import { CfnEvaluationException, EvaluateCloudFormationTemplate } from './evaluate-cloudformation-template'; /** * Returns `false` if the change cannot be short-circuited, @@ -8,10 +9,10 @@ import { assetMetadataChanged, ChangeHotswapImpact, ChangeHotswapResult, Hotswap * (like a change to CDKMetadata), * or a LambdaFunctionResource if the change can be short-circuited. */ -export function isHotswappableLambdaFunctionChange( - logicalId: string, change: cfn_diff.ResourceDifference, assetParamsWithEnv: { [key: string]: string }, -): ChangeHotswapResult { - const lambdaCodeChange = isLambdaFunctionCodeOnlyChange(change, assetParamsWithEnv); +export async function isHotswappableLambdaFunctionChange( + logicalId: string, change: cfn_diff.ResourceDifference, evaluateCfnTemplate: EvaluateCloudFormationTemplate, +): Promise { + const lambdaCodeChange = await isLambdaFunctionCodeOnlyChange(change, evaluateCfnTemplate); if (typeof lambdaCodeChange === 'string') { return lambdaCodeChange; } else { @@ -23,23 +24,13 @@ export function isHotswappableLambdaFunctionChange( return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; } - let functionPhysicalName: string | undefined; - try { - functionPhysicalName = stringifyPotentialCfnExpression(change.newValue?.Properties?.FunctionName, assetParamsWithEnv); - } catch (e) { - // It's possible we can't evaluate the function's name - - // for example, it can use a Ref to a different resource, - // which we wouldn't have in `assetParamsWithEnv`. - // That's fine though - ignore any errors, - // and treat this case the same way as if the name wasn't provided at all, - // which means it will be looked up using the listStackResources() call - // by the later phase (which actually does the Lambda function update) - functionPhysicalName = undefined; + const functionName = await establishFunctionPhysicalName(logicalId, change, evaluateCfnTemplate); + if (!functionName) { + return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; } return new LambdaFunctionHotswapOperation({ - logicalId, - physicalName: functionPhysicalName, + physicalName: functionName, code: lambdaCodeChange, }); } @@ -54,9 +45,9 @@ export function isHotswappableLambdaFunctionChange( * or a LambdaFunctionCode if the change is to a AWS::Lambda::Function, * and only affects its Code property. */ -function isLambdaFunctionCodeOnlyChange( - change: cfn_diff.ResourceDifference, assetParamsWithEnv: { [key: string]: string }, -): LambdaFunctionCode | ChangeHotswapImpact { +async function isLambdaFunctionCodeOnlyChange( + change: cfn_diff.ResourceDifference, evaluateCfnTemplate: EvaluateCloudFormationTemplate, +): Promise { if (!change.newValue) { return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; } @@ -99,11 +90,11 @@ function isLambdaFunctionCodeOnlyChange( switch (newPropName) { case 'S3Bucket': foundCodeDifference = true; - s3Bucket = stringifyPotentialCfnExpression(updatedProp.newValue[newPropName], assetParamsWithEnv); + s3Bucket = await evaluateCfnTemplate.evaluateCfnExpression(updatedProp.newValue[newPropName]); break; case 'S3Key': foundCodeDifference = true; - s3Key = stringifyPotentialCfnExpression(updatedProp.newValue[newPropName], assetParamsWithEnv); + s3Key = await evaluateCfnTemplate.evaluateCfnExpression(updatedProp.newValue[newPropName]); break; default: return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; @@ -125,8 +116,7 @@ interface LambdaFunctionCode { } interface LambdaFunctionResource { - readonly logicalId: string; - readonly physicalName?: string; + readonly physicalName: string; readonly code: LambdaFunctionCode; } @@ -134,26 +124,29 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { constructor(private readonly lambdaFunctionResource: LambdaFunctionResource) { } - public async apply(sdk: ISDK, stackResources: ListStackResources): Promise { - let functionPhysicalName: string; - if (this.lambdaFunctionResource.physicalName) { - functionPhysicalName = this.lambdaFunctionResource.physicalName; - } else { - const stackResourceList = await stackResources.listStackResources(); - const foundFunctionName = stackResourceList - .find(resSummary => resSummary.LogicalResourceId === this.lambdaFunctionResource.logicalId) - ?.PhysicalResourceId; - if (!foundFunctionName) { - // if we couldn't find the function in the current stack, we can't update it - return; - } - functionPhysicalName = foundFunctionName; - } - + public async apply(sdk: ISDK): Promise { return sdk.lambda().updateFunctionCode({ - FunctionName: functionPhysicalName, + FunctionName: this.lambdaFunctionResource.physicalName, S3Bucket: this.lambdaFunctionResource.code.s3Bucket, S3Key: this.lambdaFunctionResource.code.s3Key, }).promise(); } } + +async function establishFunctionPhysicalName( + logicalId: string, change: cfn_diff.ResourceDifference, evaluateCfnTemplate: EvaluateCloudFormationTemplate, +): Promise { + const functionNameInCfnTemplate = change.newValue?.Properties?.FunctionName; + if (functionNameInCfnTemplate != null) { + try { + return await evaluateCfnTemplate.evaluateCfnExpression(functionNameInCfnTemplate); + } catch (e) { + // If we can't evaluate the function's name CloudFormation expression, + // just look it up in the currently deployed Stack + if (!(e instanceof CfnEvaluationException)) { + throw e; + } + } + } + return evaluateCfnTemplate.findPhysicalNameFor(logicalId); +} diff --git a/packages/aws-cdk/lib/api/util/cloudformation/evaluate-cfn.ts b/packages/aws-cdk/lib/api/util/cloudformation/evaluate-cfn.ts deleted file mode 100644 index bdc395df83814..0000000000000 --- a/packages/aws-cdk/lib/api/util/cloudformation/evaluate-cfn.ts +++ /dev/null @@ -1,89 +0,0 @@ -export function evaluateCfn(object: any, context: { [key: string]: string }): any { - const intrinsicFns: any = { - 'Fn::Join'(separator: string, args: string[]): string { - return evaluate(args).map(evaluate).join(separator); - }, - - 'Fn::Split'(separator: string, args: string): string { - return evaluate(args).split(separator); - }, - - 'Fn::Select'(index: number, args: string[]): string { - return evaluate(args).map(evaluate)[index]; - }, - - 'Ref'(logicalId: string): string { - if (logicalId in context) { - return context[logicalId]; - } else { - throw new Error(`Reference target '${logicalId}' was not found`); - } - }, - - 'Fn::Sub'(template: string, explicitPlaceholders?: { [variable: string]: string }): string { - const placeholders = explicitPlaceholders - ? { ...context, ...evaluate(explicitPlaceholders) } - : context; - - return template.replace(/\${([^}]*)}/g, (_: string, key: string) => { - if (key in placeholders) { - return placeholders[key]; - } else { - throw new Error(`Fn::Sub target '${key}' was not found`); - } - }); - }, - }; - - return evaluate(object); - - function evaluate(obj: any): any { - if (Array.isArray(obj)) { - return obj.map(evaluate); - } - - if (typeof obj === 'object') { - const intrinsic = parseIntrinsic(obj); - if (intrinsic) { - return evaluateIntrinsic(intrinsic); - } - - const ret: { [key: string]: any } = {}; - for (const key of Object.keys(obj)) { - ret[key] = evaluate(obj[key]); - } - return ret; - } - - return obj; - } - - function evaluateIntrinsic(intrinsic: Intrinsic) { - if (!(intrinsic.name in intrinsicFns)) { - throw new Error(`Intrinsic ${intrinsic.name} not supported here`); - } - - const argsAsArray = Array.isArray(intrinsic.args) ? intrinsic.args : [intrinsic.args]; - - return intrinsicFns[intrinsic.name].apply(intrinsicFns, argsAsArray); - } -} - -interface Intrinsic { - readonly name: string; - readonly args: any; -} - -function parseIntrinsic(x: any): Intrinsic | undefined { - if (typeof x !== 'object' || x === null) { - return undefined; - } - const keys = Object.keys(x); - if (keys.length === 1 && (keys[0].startsWith('Fn::') || keys[0] === 'Ref')) { - return { - name: keys[0], - args: x[keys[0]], - }; - } - return undefined; -} diff --git a/packages/aws-cdk/test/api/hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap-deployments.test.ts index a241e7c707d5c..5bb6ff8b7b466 100644 --- a/packages/aws-cdk/test/api/hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap-deployments.test.ts @@ -1,5 +1,5 @@ import * as cxapi from '@aws-cdk/cx-api'; -import { Lambda } from 'aws-sdk'; +import { CloudFormation, Lambda } from 'aws-sdk'; import { tryHotswapDeployment } from '../../lib/api/hotswap-deployments'; import { testStack, TestStackArtifact } from '../util'; import { MockSdkProvider } from '../util/mock-sdk'; @@ -11,6 +11,7 @@ const STACK_ID = 'stackId'; let mockSdkProvider: MockSdkProvider; let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => Lambda.Types.FunctionConfiguration; let currentCfnStack: FakeCloudformationStack; +const currentCfnStackResources: CloudFormation.StackResourceSummary[] = []; beforeEach(() => { jest.resetAllMocks(); @@ -19,6 +20,18 @@ beforeEach(() => { mockSdkProvider.stubLambda({ updateFunctionCode: mockUpdateLambdaCode, }); + // clear the array + currentCfnStackResources.splice(0); + mockSdkProvider.stubCloudFormation({ + listStackResources: ({ StackName: stackName }) => { + if (stackName !== STACK_NAME) { + throw new Error(`Expected Stack name in listStackResources() call to be: '${STACK_NAME}', but received: ${stackName}'`); + } + return { + StackResourceSummaries: currentCfnStackResources, + }; + }, + }); currentCfnStack = new FakeCloudformationStack({ stackName: STACK_NAME, stackId: STACK_ID, @@ -105,9 +118,254 @@ test('calls the updateLambdaCode() API when it receives only a code difference i }); }); +test("correctly evaluates the function's name when it references a different resource from the template", async () => { + // GIVEN + currentCfnStack.setTemplate({ + Resources: { + Bucket: { + Type: 'AWS::S3::Bucket', + }, + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + FunctionName: { + 'Fn::Join': ['-', [ + 'lambda', + { Ref: 'Bucket' }, + 'function', + ]], + }, + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + currentCfnStackResources.push(stackSummaryOf('Bucket', 'AWS::S3::Bucket', 'mybucket')); + const cdkStackArtifact = cdkStackArtifactOf({ + template: { + Resources: { + Bucket: { + Type: 'AWS::S3::Bucket', + }, + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + FunctionName: { + 'Fn::Join': ['-', [ + 'lambda', + { Ref: 'Bucket' }, + 'function', + ]], + }, + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await tryHotswapDeployment(mockSdkProvider, {}, currentCfnStack, cdkStackArtifact); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(mockUpdateLambdaCode).toHaveBeenCalledWith({ + FunctionName: 'lambda-mybucket-function', + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }); +}); + +test("correctly falls back to taking the function's name from the current stack if it can't evaluate it in the template", async () => { + // GIVEN + currentCfnStack.setTemplate({ + Parameters: { + Param1: { Type: 'String' }, + AssetBucketParam: { Type: 'String' }, + }, + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { Ref: 'AssetBucketParam' }, + S3Key: 'current-key', + }, + FunctionName: { Ref: 'Param1' }, + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + currentCfnStackResources.push(stackSummaryOf('Func', 'AWS::Lambda::Function', 'my-function')); + const cdkStackArtifact = cdkStackArtifactOf({ + template: { + Parameters: { + Param1: { Type: 'String' }, + AssetBucketParam: { Type: 'String' }, + }, + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { Ref: 'AssetBucketParam' }, + S3Key: 'new-key', + }, + FunctionName: { Ref: 'Param1' }, + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // WHEN + const deployStackResult = await tryHotswapDeployment(mockSdkProvider, { + AssetBucketParam: 'asset-bucket', + }, currentCfnStack, cdkStackArtifact); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(mockUpdateLambdaCode).toHaveBeenCalledWith({ + FunctionName: 'my-function', + S3Bucket: 'asset-bucket', + S3Key: 'new-key', + }); +}); + +test("will not perform a hotswap deployment if it cannot find a Ref target (outside the function's name)", async () => { + // GIVEN + currentCfnStack.setTemplate({ + Parameters: { + Param1: { Type: 'String' }, + }, + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { 'Fn::Sub': '${Param1}' }, + S3Key: 'current-key', + }, + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + currentCfnStackResources.push(stackSummaryOf('Func', 'AWS::Lambda::Function', 'my-func')); + const cdkStackArtifact = cdkStackArtifactOf({ + template: { + Parameters: { + Param1: { Type: 'String' }, + }, + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { 'Fn::Sub': '${Param1}' }, + S3Key: 'new-key', + }, + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // THEN + await expect(() => + tryHotswapDeployment(mockSdkProvider, {}, currentCfnStack, cdkStackArtifact), + ).rejects.toThrow(/Parameter or resource 'Param1' could not be found for evaluation/); +}); + +test("will not perform a hotswap deployment if it doesn't know how to handle a specific attribute (outside the function's name)", async () => { + // GIVEN + currentCfnStack.setTemplate({ + Resources: { + Bucket: { + Type: 'AWS::S3::Bucket', + }, + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { 'Fn::GetAtt': ['Bucket', 'UnknownAttribute'] }, + S3Key: 'current-key', + }, + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + currentCfnStackResources.push( + stackSummaryOf('Func', 'AWS::Lambda::Function', 'my-func'), + stackSummaryOf('Bucket', 'AWS::S3::Bucket', 'my-bucket'), + ); + const cdkStackArtifact = cdkStackArtifactOf({ + template: { + Resources: { + Bucket: { + Type: 'AWS::S3::Bucket', + }, + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { 'Fn::GetAtt': ['Bucket', 'UnknownAttribute'] }, + S3Key: 'new-key', + }, + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // THEN + await expect(() => + tryHotswapDeployment(mockSdkProvider, {}, currentCfnStack, cdkStackArtifact), + ).rejects.toThrow("We don't support the 'UnknownAttribute' attribute of the 'AWS::S3::Bucket' resource. This is a CDK limitation. Please report it at https://github.com/aws/aws-cdk/issues/new/choose"); +}); + function cdkStackArtifactOf(testStackArtifact: Partial = {}): cxapi.CloudFormationStackArtifact { return testStack({ stackName: STACK_NAME, ...testStackArtifact, }); } + +function stackSummaryOf(logicalId: string, resourceType: string, physicalResourceId: string): CloudFormation.StackResourceSummary { + return { + LogicalResourceId: logicalId, + PhysicalResourceId: physicalResourceId, + ResourceType: resourceType, + ResourceStatus: 'CREATE_COMPLETE', + LastUpdatedTimestamp: new Date(), + }; +} From b6da2b41fe7efc1d53fb5f6ce9b40d96cdfd955e Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Fri, 1 Oct 2021 12:49:35 +0100 Subject: [PATCH 13/23] chore: increase vm.max_map_count to (hopefully) mitigate OOM errors (#16752) The vm.max_map_count on our CodeBuild instances is fairly low (65530) compared to our max threads (1125977). Based on a NodeJS issue troubleshooting thread (https://github.com/nodejs/help/issues/2809), trying to see if increasing this value stabilizes our builds. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- buildspec-pr.yaml | 4 ++++ buildspec.yaml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/buildspec-pr.yaml b/buildspec-pr.yaml index 3d6ee87c8d1ff..647b78849b3e8 100644 --- a/buildspec-pr.yaml +++ b/buildspec-pr.yaml @@ -12,6 +12,10 @@ phases: # Install yarn if it wasn't already present in the image - yarn --version || npm -g install yarn + + # Packing the mono-libraries (monocdk & aws-cdk-lib) can cause + # memory errors. Increasing this value allows our build to more consistently succeed + - (command -v sysctl || yum install -y procps-ng) && /sbin/sysctl -w vm.max_map_count=2251954 build: commands: - /bin/bash ./build.sh --extract diff --git a/buildspec.yaml b/buildspec.yaml index 3f2bb4e7e1102..0fc990a17c805 100644 --- a/buildspec.yaml +++ b/buildspec.yaml @@ -12,6 +12,10 @@ phases: # Install yarn if it wasn't already present in the image - yarn --version || npm -g install yarn + + # Packing the mono-libraries (monocdk & aws-cdk-lib) can cause + # memory errors. Increasing this value allows our build to more consistently succeed + - /sbin/sysctl -w vm.max_map_count=2251954 build: commands: - 'if ${BUMP_CANDIDATE:-false}; then /bin/bash ./scripts/bump-candidate.sh; fi' From ceab036fa9dfcd13c58c7d818339cd05ed515bec Mon Sep 17 00:00:00 2001 From: Romain Marcadier Date: Fri, 1 Oct 2021 14:54:12 +0200 Subject: [PATCH 14/23] fix: CDK does not honor NO_PROXY settings (#16751) CDK was extracting the value of `HTTPS?_PROXY` and passing this to `proxy-agent` explicitly, which resulted in not honoring the `NO_PROXY` setting. This removes that behavior and lets `proxy-agent` delegate to `proxy-from-env`, which will leverage values in `HTTPS?_PROXY` and NO_PROXY correctly. Fixes #7121 --- .../lib/cluster-resource-handler/common.ts | 26 ++++----------- .../aws-cdk/lib/api/aws-auth/sdk-provider.ts | 33 ++++++------------- 2 files changed, 17 insertions(+), 42 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts index 1adb2eb328564..c3f34ae4bbc6f 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts @@ -39,15 +39,13 @@ export abstract class ResourceHandler { RoleSessionName: `AWSCDK.EKSCluster.${this.requestType}.${this.requestId}`, }); - const proxyAddress = this.httpProxyFromEnvironment(); - if (proxyAddress) { - this.log(`Using proxy server: ${proxyAddress}`); - // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies - const ProxyAgent: any = require('proxy-agent'); - aws.config.update({ - httpOptions: { agent: new ProxyAgent(proxyAddress) }, - }); - } + // Configure the proxy agent. By default, this will use HTTPS?_PROXY and + // NO_PROXY environment variables to determine which proxy to use for each + // request. + // + // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies + const ProxyAgent: any = require('proxy-agent'); + aws.config.update({ httpOptions: { agent: new ProxyAgent() } }); } public onEvent() { @@ -75,16 +73,6 @@ export abstract class ResourceHandler { console.log(JSON.stringify(x, undefined, 2)); } - private httpProxyFromEnvironment(): string | undefined { - if (process.env.http_proxy) { - return process.env.http_proxy; - } - if (process.env.HTTP_PROXY) { - return process.env.HTTP_PROXY; - } - return undefined; - } - protected abstract async onCreate(): Promise; protected abstract async onDelete(): Promise; protected abstract async onUpdate(): Promise<(OnEventResponse & EksUpdateId) | void>; diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts index d06fba8a59529..4621d171bc357 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts @@ -374,48 +374,35 @@ function parseHttpOptions(options: SdkHttpOptions) { } config.customUserAgent = userAgent; - const proxyAddress = options.proxyAddress || httpsProxyFromEnvironment(); const caBundlePath = options.caBundlePath || caBundlePathFromEnvironment(); - if (proxyAddress && caBundlePath) { - throw new Error(`At the moment, cannot specify Proxy (${proxyAddress}) and CA Bundle (${caBundlePath}) at the same time. See https://github.com/aws/aws-cdk/issues/5804`); + if (options.proxyAddress && caBundlePath) { + throw new Error(`At the moment, cannot specify Proxy (${options.proxyAddress}) and CA Bundle (${caBundlePath}) at the same time. See https://github.com/aws/aws-cdk/issues/5804`); // Maybe it's possible after all, but I've been staring at // https://github.com/TooTallNate/node-proxy-agent/blob/master/index.js#L79 // a while now trying to figure out what to pass in so that the underlying Agent // object will get the 'ca' argument. It's not trivial and I don't want to risk it. } - if (proxyAddress) { // Ignore empty string on purpose - // https://aws.amazon.com/blogs/developer/using-the-aws-sdk-for-javascript-from-behind-a-proxy/ - debug('Using proxy server: %s', proxyAddress); - // eslint-disable-next-line @typescript-eslint/no-require-imports - const ProxyAgent: any = require('proxy-agent'); - config.httpOptions.agent = new ProxyAgent(proxyAddress); - } if (caBundlePath) { debug('Using CA bundle path: %s', caBundlePath); config.httpOptions.agent = new https.Agent({ ca: readIfPossible(caBundlePath), keepAlive: true, }); + } else { + // Configure the proxy agent. By default, this will use HTTPS?_PROXY and + // NO_PROXY environment variables to determine which proxy to use for each + // request. + // + // eslint-disable-next-line @typescript-eslint/no-require-imports + const ProxyAgent: any = require('proxy-agent'); + config.httpOptions.agent = new ProxyAgent(); } return config; } -/** - * Find and return the configured HTTPS proxy address - */ -function httpsProxyFromEnvironment(): string | undefined { - if (process.env.https_proxy) { - return process.env.https_proxy; - } - if (process.env.HTTPS_PROXY) { - return process.env.HTTPS_PROXY; - } - return undefined; -} - /** * Find and return a CA certificate bundle path to be passed into the SDK. */ From dbc15411741aa860de0896c6c0109868d4d7e07e Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Fri, 1 Oct 2021 16:16:17 +0100 Subject: [PATCH 15/23] chore: reset jsii-rosetta worker count to default (#16755) This value was reduced as part of troubleshooting of various Node Worker memory issues. These issues are theorized to have been mitigated by #16752. Our pack time is currently over 2 hours, compared to 20-30 minutes prior to the set of changes. By removing this worker count override, we should be able to get back to normal pack times and speed up the pipeline. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- pack.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/pack.sh b/pack.sh index 99ae852188ebe..d270fb28271c5 100755 --- a/pack.sh +++ b/pack.sh @@ -5,7 +5,6 @@ set -eu export PATH=$PWD/node_modules/.bin:$PATH export NODE_OPTIONS="--max-old-space-size=8192 ${NODE_OPTIONS:-}" -export JSII_ROSETTA_MAX_WORKER_COUNT="${JSII_ROSETTA_MAX_WORKER_COUNT:-8}" root=$PWD # Get version and changelog file name (these require that .versionrc.json would have been generated) From 1875287cb68d7d63a79a2b9381885a25f6d23a4f Mon Sep 17 00:00:00 2001 From: Ryan Parker Date: Fri, 1 Oct 2021 12:18:47 -0700 Subject: [PATCH 16/23] docs(GitHub issue templates): Upgrade to GitHub Issues v2 (#16592) ## Summary This PR updates this repo's GitHub issue templates to v2. ([see prototype](https://github.com/ryparker/proto-github-issues-v2/issues/new/choose)) **Reviewers**: Please make sure that all the fields i've marked with `required: true` are necessary. A user will not be able to create an issue without these required fields being completed. [GitHub issues v2 docs](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms) CleanShot 2021-09-21 at 18 37 06@2x ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .github/ISSUE_TEMPLATE.md | 34 ------- .github/ISSUE_TEMPLATE/bug.md | 54 ----------- .github/ISSUE_TEMPLATE/bug.yml | 108 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/doc.md | 29 ------ .github/ISSUE_TEMPLATE/doc.yml | 32 ++++++ .github/ISSUE_TEMPLATE/feature-request.md | 46 --------- .github/ISSUE_TEMPLATE/feature-request.yml | 56 +++++++++++ .github/ISSUE_TEMPLATE/general-issue.yml | 87 +++++++++++++++++ .github/ISSUE_TEMPLATE/general-issues.md | 35 ------- .github/ISSUE_TEMPLATE/tracking.md | 68 ------------- .github/ISSUE_TEMPLATE/tracking.yml | 76 +++++++++++++++ 11 files changed, 359 insertions(+), 266 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md delete mode 100644 .github/ISSUE_TEMPLATE/bug.md create mode 100644 .github/ISSUE_TEMPLATE/bug.yml delete mode 100644 .github/ISSUE_TEMPLATE/doc.md create mode 100644 .github/ISSUE_TEMPLATE/doc.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature-request.md create mode 100644 .github/ISSUE_TEMPLATE/feature-request.yml create mode 100644 .github/ISSUE_TEMPLATE/general-issue.yml delete mode 100644 .github/ISSUE_TEMPLATE/general-issues.md delete mode 100644 .github/ISSUE_TEMPLATE/tracking.md create mode 100644 .github/ISSUE_TEMPLATE/tracking.yml diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 5371d422793e3..0000000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: "\U00002753 General Issue" -about: Create a new issue -labels: needs-triage ---- - - - -## :question: General Issue - - - -### The Question - - -### Environment - - - **CDK CLI Version:** - - **Module Version:** - - **Node.js Version:** - - **OS:** - - **Language:** - - -### Other information - diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md deleted file mode 100644 index 6835abe99e034..0000000000000 --- a/.github/ISSUE_TEMPLATE/bug.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -name: "\U0001F41B Bug Report" -about: Report a bug -title: "(module name): short issue description" -labels: bug, needs-triage ---- - - - - - - -### Reproduction Steps - - - -### What did you expect to happen? - - - -### What actually happened? - - - - -### Environment - - - **CDK CLI Version :** - - **Framework Version:** - - **Node.js Version:** - - **OS :** - - **Language (Version):** - -### Other - - - - - - ---- - -This is :bug: Bug Report diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000000000..f77fa5beb193e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,108 @@ +name: Bug Report +description: Report a bug +title: "(module name): short issue description" +labels: [bug, needs-triage] +body: + - type: textarea + id: problem + attributes: + label: What is the problem? + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Reproduction Steps + description: | + Minimal amount of code that causes the bug (if possible) or a reference. + + The code sample should be an SSCCE. See http://sscce.org/ for details. + In short, provide a code sample that we can copy/paste, run and reproduce. + validations: + required: true + + - type: textarea + id: expected + attributes: + label: What did you expect to happen? + description: | + What were you trying to achieve by performing the steps above? + validations: + required: true + + - type: textarea + id: actual + attributes: + label: What actually happened? + description: | + What is the unexpected behavior you were seeing? If you got an error, paste it here. + validations: + required: true + + - type: input + id: cdk-version + attributes: + label: CDK CLI Version + description: Output of `cdk version` + validations: + required: true + + - type: input + id: framework-version + attributes: + label: Framework Version + validations: + required: false + + - type: input + id: node-version + attributes: + label: Node.js Version + validations: + required: true + + - type: input + id: operating-system + attributes: + label: OS + validations: + required: true + + - type: dropdown + id: language + attributes: + label: Language + multiple: true + options: + - Typescript + - Python + - .NET + - Java + - Go + validations: + required: true + + - type: input + id: language-version + attributes: + label: Language Version + description: E.g. TypeScript (3.8.3) | Java (8) | Python (3.7.3) + validations: + required: false + + - type: textarea + id: other + attributes: + label: Other information + description: | + e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. associated pull-request, stackoverflow, slack, etc + validations: + required: false + + - type: markdown + attributes: + value: | + --- + + This is :bug: Bug Report diff --git a/.github/ISSUE_TEMPLATE/doc.md b/.github/ISSUE_TEMPLATE/doc.md deleted file mode 100644 index 3c8a1dc691d0e..0000000000000 --- a/.github/ISSUE_TEMPLATE/doc.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: "📕 Documentation Issue" -about: Issue in the reference documentation or developer guide -title: "(module name): short issue description" -labels: feature-request, documentation, needs-triage ---- - - - - - - - - - - - - - ---- - -This is a 📕 documentation issue diff --git a/.github/ISSUE_TEMPLATE/doc.yml b/.github/ISSUE_TEMPLATE/doc.yml new file mode 100644 index 0000000000000..974a752cac810 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/doc.yml @@ -0,0 +1,32 @@ +name: Documentation Issue +description: Issue in the reference documentation or developer guide +title: "(module name): short issue description" +labels: [feature-request, documentation, needs-triage] +body: + - type: markdown + attributes: + value: | + Developer guide? Raise issue/pr here: https://github.com/awsdocs/aws-cdk-guide + + Want to help? Submit a pull request here: https://github.com/aws/aws-cdk + + - type: input + id: doc-link + attributes: + label: link to reference doc page + validations: + required: false + + - type: textarea + id: issue + attributes: + label: Describe your issue? + validations: + required: true + + - type: markdown + attributes: + value: | + --- + + This is a 📕 documentation issue diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md deleted file mode 100644 index 163f2f54d0b88..0000000000000 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -name: "\U0001F680 Feature Request" -about: Request a new feature -title: "(module name): short issue description" -labels: feature-request, needs-triage ---- - - - - - - - -### Use Case - - - - - - - -### Proposed Solution - - - - - - - -### Other - - - - - - - -* [ ] :wave: I may be able to implement this feature request -* [ ] :warning: This feature might incur a breaking change - ---- - -This is a :rocket: Feature Request diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000000000..a16053f420a82 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,56 @@ +name: Feature Request +description: Request a new feature +title: "(module name): short issue description" +labels: [feature-request, needs-triage] +body: + - type: textarea + id: description + attributes: + label: Description + description: Short description of the feature you are proposing. + validations: + required: true + + - type: textarea + id: use-case + attributes: + label: Use Case + description: | + Why do you need this feature? + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Proposed Solution + description: | + Please include prototype/workaround/sketch/reference implementation. + validations: + required: true + + - type: textarea + id: other + attributes: + label: Other information + description: | + e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. associated pull-request, stackoverflow, slack, etc + validations: + required: false + + - type: checkboxes + id: acknowledgments + attributes: + label: Acknowledge + options: + - label: I may be able to implement this feature request + required: false + - label: This feature might incur a breaking change + required: false + + - type: markdown + attributes: + value: | + --- + + This is a :rocket: Feature Request diff --git a/.github/ISSUE_TEMPLATE/general-issue.yml b/.github/ISSUE_TEMPLATE/general-issue.yml new file mode 100644 index 0000000000000..61119a33a761c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/general-issue.yml @@ -0,0 +1,87 @@ +name: General Issue +description: Create a new issue +title: "(module name): short issue description" +labels: [needs-triage, guidance] +body: + - type: markdown + attributes: + value: | + If there is an issue regarding developer guide, please create an issue [here](https://github.com/awsdocs/aws-cdk-guide/issues). + + - type: input + id: issue + attributes: + label: General Issue + description: | + For support questions, please first reference our [documentation](https://docs.aws.amazon.com/cdk/api/latest), then use [Stackoverflow](https://stackoverflow.com/questions/tagged/aws-cdk). This repository's issues are intended for feature requests and bug reports. + validations: + required: true + + - type: textarea + id: question + attributes: + label: The Question + description: | + Ask your question here. Include any details relevant. Make sure you are not falling prey to the [X/Y problem](http://xyproblem.info)! + validations: + required: true + + - type: input + id: cdk-version + attributes: + label: CDK CLI Version + description: Output of `cdk version` + validations: + required: true + + - type: input + id: framework-version + attributes: + label: Framework Version + validations: + required: false + + - type: input + id: node-version + attributes: + label: Node.js Version + validations: + required: false + + - type: input + id: operating-system + attributes: + label: OS + validations: + required: false + + - type: dropdown + id: language + attributes: + label: Language + multiple: true + options: + - Typescript + - Python + - .NET + - Java + - Go + validations: + required: true + + - type: input + id: language-version + attributes: + label: Language Version + description: E.g. TypeScript (3.8.3) | Java (8) | Python (3.7.3) + validations: + required: false + + - type: textarea + id: other + attributes: + label: Other information + description: | + e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. associated pull-request, stackoverflow, slack, etc + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/general-issues.md b/.github/ISSUE_TEMPLATE/general-issues.md deleted file mode 100644 index 2b478904a6fca..0000000000000 --- a/.github/ISSUE_TEMPLATE/general-issues.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: "\U00002753 General Issue" -about: Create a new issue -title: "(module name): short issue description" -labels: needs-triage, guidance ---- - - - -## :question: General Issue - - - -### The Question - - -### Environment - - - **CDK CLI Version:** - - **Module Version:** - - **Node.js Version:** - - **OS:** - - **Language (Version):** - - -### Other information - diff --git a/.github/ISSUE_TEMPLATE/tracking.md b/.github/ISSUE_TEMPLATE/tracking.md deleted file mode 100644 index b3655dfaa6dca..0000000000000 --- a/.github/ISSUE_TEMPLATE/tracking.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -name: "📊 Tracking Issue" -title: "📊Tracking: [service]" -about: Add a module tracking issue (internal use only) -labels: management/tracking ---- - -Add your +1 👍 to help us prioritize high-level constructs for this service ---- - -### Overview: - - - - - - - -[AWS Docs](url) - -### Maturity: CloudFormation Resources Only - - -See the [AWS Construct Library Module Lifecycle doc](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0107-construct-library-module-lifecycle.md) for more information about maturity levels. - - -### Implementation: - -See the [CDK API Reference](url) for more implementation details. - - - - - - -### Issue list: - - - - - - - - ---- -This is a 📊Tracking Issue diff --git a/.github/ISSUE_TEMPLATE/tracking.yml b/.github/ISSUE_TEMPLATE/tracking.yml new file mode 100644 index 0000000000000..0ecfc903b002c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/tracking.yml @@ -0,0 +1,76 @@ +name: Tracking Issue +description: Add a module tracking issue (internal use only) +title: "Tracking: [service]" +labels: [management/tracking] +body: + - type: markdown + attributes: + value: | + Add your +1 👍 to help us prioritize high-level constructs for this service + + - type: textarea + id: overview + attributes: + label: Overview + description: | + Summary of the service (leverage the service’s product page for the text) and a link to the relevant AWS Docs. This should be the same text that we put at the top of the package’s README.md. + validations: + required: true + + - type: input + id: cdk-api-docs-link + attributes: + label: Link to the service’s CDK Construct Library API reference page. + validations: + required: true + + - type: dropdown + id: maturity + attributes: + label: "Maturity: CloudFormation Resources Only" + description: | + See the [AWS Construct Library Module Lifecycle doc](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0107-construct-library-module-lifecycle.md) for more information about maturity levels. + options: + - CloudFormation Resources Only + - Experimental + - Developer Preview + - Stable + validations: + required: true + + - type: textarea + id: implementation + attributes: + label: Implementation + description: | + Checklist of use cases, constructs, features (such as grant methods) that will ship in this package. This is not required until the issue is added to the public roadmap. + validations: + required: true + + - type: textarea + id: issue-list + attributes: + label: Issue list + description: | + Checklist of links to feature requests, bugs, and PRs that are in scope for GA release of this module (not required until the issues is added to the public roadmap). + value: | + - [ ] + - [ ] + validations: + required: true + + - type: markdown + attributes: + value: | + Labels to add: + - package/[name] (create new labels if they don’t already exist) + - needs-design (if cfn-only) + - management/roadmap (when added to the roadmap) + - in-progress (when added to “working on it” column of the roadmap) + + - type: markdown + attributes: + value: | + --- + + This is a 📊 Tracking Issue From eda7e84400d766b8045972c496851e975544c38f Mon Sep 17 00:00:00 2001 From: Ryan Parker Date: Fri, 1 Oct 2021 16:49:48 -0700 Subject: [PATCH 17/23] fix(revert): "fix: CDK does not honor NO_PROXY settings (#16751)" (#16761) ## Summary This [commit](https://github.com/aws/aws-cdk/commit/ceab036fa9dfcd13c58c7d818339cd05ed515bec) broke EKS deployments. CloudFormation throws "Internal failure." when attempting to create an EKS cluster. Full details : https://github.com/aws/aws-cdk/pull/16751/files#r720549975 This reverts commit ceab036fa9dfcd13c58c7d818339cd05ed515bec. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/cluster-resource-handler/common.ts | 26 +++++++++++---- .../aws-cdk/lib/api/aws-auth/sdk-provider.ts | 33 +++++++++++++------ 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts index c3f34ae4bbc6f..1adb2eb328564 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts @@ -39,13 +39,15 @@ export abstract class ResourceHandler { RoleSessionName: `AWSCDK.EKSCluster.${this.requestType}.${this.requestId}`, }); - // Configure the proxy agent. By default, this will use HTTPS?_PROXY and - // NO_PROXY environment variables to determine which proxy to use for each - // request. - // - // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies - const ProxyAgent: any = require('proxy-agent'); - aws.config.update({ httpOptions: { agent: new ProxyAgent() } }); + const proxyAddress = this.httpProxyFromEnvironment(); + if (proxyAddress) { + this.log(`Using proxy server: ${proxyAddress}`); + // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies + const ProxyAgent: any = require('proxy-agent'); + aws.config.update({ + httpOptions: { agent: new ProxyAgent(proxyAddress) }, + }); + } } public onEvent() { @@ -73,6 +75,16 @@ export abstract class ResourceHandler { console.log(JSON.stringify(x, undefined, 2)); } + private httpProxyFromEnvironment(): string | undefined { + if (process.env.http_proxy) { + return process.env.http_proxy; + } + if (process.env.HTTP_PROXY) { + return process.env.HTTP_PROXY; + } + return undefined; + } + protected abstract async onCreate(): Promise; protected abstract async onDelete(): Promise; protected abstract async onUpdate(): Promise<(OnEventResponse & EksUpdateId) | void>; diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts index 4621d171bc357..d06fba8a59529 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts @@ -374,35 +374,48 @@ function parseHttpOptions(options: SdkHttpOptions) { } config.customUserAgent = userAgent; + const proxyAddress = options.proxyAddress || httpsProxyFromEnvironment(); const caBundlePath = options.caBundlePath || caBundlePathFromEnvironment(); - if (options.proxyAddress && caBundlePath) { - throw new Error(`At the moment, cannot specify Proxy (${options.proxyAddress}) and CA Bundle (${caBundlePath}) at the same time. See https://github.com/aws/aws-cdk/issues/5804`); + if (proxyAddress && caBundlePath) { + throw new Error(`At the moment, cannot specify Proxy (${proxyAddress}) and CA Bundle (${caBundlePath}) at the same time. See https://github.com/aws/aws-cdk/issues/5804`); // Maybe it's possible after all, but I've been staring at // https://github.com/TooTallNate/node-proxy-agent/blob/master/index.js#L79 // a while now trying to figure out what to pass in so that the underlying Agent // object will get the 'ca' argument. It's not trivial and I don't want to risk it. } + if (proxyAddress) { // Ignore empty string on purpose + // https://aws.amazon.com/blogs/developer/using-the-aws-sdk-for-javascript-from-behind-a-proxy/ + debug('Using proxy server: %s', proxyAddress); + // eslint-disable-next-line @typescript-eslint/no-require-imports + const ProxyAgent: any = require('proxy-agent'); + config.httpOptions.agent = new ProxyAgent(proxyAddress); + } if (caBundlePath) { debug('Using CA bundle path: %s', caBundlePath); config.httpOptions.agent = new https.Agent({ ca: readIfPossible(caBundlePath), keepAlive: true, }); - } else { - // Configure the proxy agent. By default, this will use HTTPS?_PROXY and - // NO_PROXY environment variables to determine which proxy to use for each - // request. - // - // eslint-disable-next-line @typescript-eslint/no-require-imports - const ProxyAgent: any = require('proxy-agent'); - config.httpOptions.agent = new ProxyAgent(); } return config; } +/** + * Find and return the configured HTTPS proxy address + */ +function httpsProxyFromEnvironment(): string | undefined { + if (process.env.https_proxy) { + return process.env.https_proxy; + } + if (process.env.HTTPS_PROXY) { + return process.env.HTTPS_PROXY; + } + return undefined; +} + /** * Find and return a CA certificate bundle path to be passed into the SDK. */ From b515846d8d8417572e0e3a9add90d3efcfeb53b3 Mon Sep 17 00:00:00 2001 From: Peter Woodworth <44349620+peterwoodworth@users.noreply.github.com> Date: Fri, 1 Oct 2021 17:59:18 -0700 Subject: [PATCH 18/23] chore: set response-requested length to 2 and closing-soon to 5 (#16763) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .github/workflows/close-stale-issues.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/close-stale-issues.yml b/.github/workflows/close-stale-issues.yml index 0798bc9e5d355..487a095e0c372 100644 --- a/.github/workflows/close-stale-issues.yml +++ b/.github/workflows/close-stale-issues.yml @@ -35,8 +35,8 @@ jobs: closed-for-staleness-label: closed-for-staleness # Issue timing - days-before-stale: 7 - days-before-close: 4 + days-before-stale: 2 + days-before-close: 5 days-before-ancient: 365 # If you don't want to mark a issue as being ancient based on a From c89fb0452382d6cd052ca5bc393705c5f9295db0 Mon Sep 17 00:00:00 2001 From: Peter Woodworth <44349620+peterwoodworth@users.noreply.github.com> Date: Mon, 4 Oct 2021 01:40:52 -0700 Subject: [PATCH 19/23] chore(s3-deployments): update python version on BucketDeployment handler (#16771) fixes :#16669 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-ecs/test/ec2/integ.environment-file.expected.json | 2 +- packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts | 2 +- .../test/integ.bucket-deployment-cloudfront.expected.json | 2 +- .../test/integ.bucket-deployment.expected.json | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.environment-file.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.environment-file.expected.json index b26608d3626bf..eee0801d80061 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.environment-file.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.environment-file.expected.json @@ -1267,7 +1267,7 @@ "Ref": "EnvFileDeploymentAwsCliLayerA8FC897D" } ], - "Runtime": "python3.6", + "Runtime": "python3.7", "Timeout": 900 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts index 439af53a90dd9..8e25826647a64 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts +++ b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts @@ -278,7 +278,7 @@ export class BucketDeployment extends CoreConstruct { uuid: this.renderSingletonUuid(props.memoryLimit, props.vpc), code: lambda.Code.fromAsset(path.join(__dirname, 'lambda')), layers: [new AwsCliLayer(this, 'AwsCliLayer')], - runtime: lambda.Runtime.PYTHON_3_6, + runtime: lambda.Runtime.PYTHON_3_7, environment: props.useEfs ? { MOUNT_PATH: mountPath, } : undefined, diff --git a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json index bd5c7309210e3..ee73484606056 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json @@ -343,7 +343,7 @@ "Ref": "DeployWithInvalidationAwsCliLayerDEDD5787" } ], - "Runtime": "python3.6", + "Runtime": "python3.7", "Timeout": 900 }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json index 8b247a49a4428..28a59330cb59b 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json @@ -352,7 +352,7 @@ "Ref": "DeployMeAwsCliLayer5F9219E9" } ], - "Runtime": "python3.6", + "Runtime": "python3.7", "Timeout": 900 }, "DependsOn": [ @@ -1495,7 +1495,7 @@ "Ref": "DeployMeWithEfsStorageAwsCliLayer1619A3EE" } ], - "Runtime": "python3.6", + "Runtime": "python3.7", "Timeout": 900, "VpcConfig": { "SecurityGroupIds": [ From 63614b9018596705d0167bd895d65067bfd0dfc1 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 4 Oct 2021 12:17:53 +0200 Subject: [PATCH 20/23] docs(core): fix reference to nonexistant enum value (#16716) Fixes #16605. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/core/lib/cfn-resource.ts | 8 ++++++++ packages/@aws-cdk/core/lib/resource.ts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/core/lib/cfn-resource.ts b/packages/@aws-cdk/core/lib/cfn-resource.ts index d6be50b9149fb..f1b4d76a10563 100644 --- a/packages/@aws-cdk/core/lib/cfn-resource.ts +++ b/packages/@aws-cdk/core/lib/cfn-resource.ts @@ -101,6 +101,14 @@ export class CfnResource extends CfnRefElement { /** * Sets the deletion policy of the resource based on the removal policy specified. + * + * The Removal Policy controls what happens to this resource when it stops + * being managed by CloudFormation, either because you've removed it from the + * CDK application or because you've made a change that requires the resource + * to be replaced. + * + * The resource can be deleted (`RemovalPolicy.DESTROY`), or left in your AWS + * account for data recovery and cleanup later (`RemovalPolicy.RETAIN`). */ public applyRemovalPolicy(policy: RemovalPolicy | undefined, options: RemovalPolicyOptions = {}) { policy = policy || options.default || RemovalPolicy.RETAIN; diff --git a/packages/@aws-cdk/core/lib/resource.ts b/packages/@aws-cdk/core/lib/resource.ts index 65fcacd55bf25..c61e9ba69955e 100644 --- a/packages/@aws-cdk/core/lib/resource.ts +++ b/packages/@aws-cdk/core/lib/resource.ts @@ -203,7 +203,7 @@ export abstract class Resource extends CoreConstruct implements IResource { * CDK application or because you've made a change that requires the resource * to be replaced. * - * The resource can be deleted (`RemovalPolicy.DELETE`), or left in your AWS + * The resource can be deleted (`RemovalPolicy.DESTROY`), or left in your AWS * account for data recovery and cleanup later (`RemovalPolicy.RETAIN`). */ public applyRemovalPolicy(policy: RemovalPolicy) { From 0bc58cd5f6ae61b909a2c2101f5d62d954977618 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 4 Oct 2021 13:06:56 +0200 Subject: [PATCH 21/23] docs(events): add a note about not using `EventPattern` with `CfnRule` (#16715) Fixes #16563. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-events/lib/event-pattern.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-events/lib/event-pattern.ts b/packages/@aws-cdk/aws-events/lib/event-pattern.ts index bdd586f7e0e78..523223d3903d2 100644 --- a/packages/@aws-cdk/aws-events/lib/event-pattern.ts +++ b/packages/@aws-cdk/aws-events/lib/event-pattern.ts @@ -2,10 +2,14 @@ * Events in Amazon CloudWatch Events are represented as JSON objects. For more * information about JSON objects, see RFC 7159. * + * **Important**: this class can only be used with a `Rule` class. In particular, + * do not use it with `CfnRule` class: your pattern will not be rendered + * correctly. In a `CfnRule` class, write the pattern as you normally would when + * directly writing CloudFormation. + * * Rules use event patterns to select events and route them to targets. A * pattern either matches an event or it doesn't. Event patterns are represented - * as JSON objects with a structure that is similar to that of events, for - * example: + * as JSON objects with a structure that is similar to that of events. * * It is important to remember the following about event pattern matching: * From cfcaf452da163efa33df752b0ff026b3ea608dfc Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Mon, 4 Oct 2021 04:57:36 -0700 Subject: [PATCH 22/23] fix(config): add SourceAccount condition to Lambda permission (#16617) According to [AWS Config best practices](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config_develop-rules_nodejs.html#restricted-lambda-policy), we should add a `SourceAccount` condition to the Lambda Permission we create in `CustomRule`. Note that we cannot add the `SourceArn` condition, because that would cause a cyclic dependency between the `LambdaPermission` resource, and the `Rule` resource (as the `Rule` can only be created _after_ the `LambdaPermission` has been created - this is validated by the AWS Config service - and so needs a `DependOn` for the Lambda Permission). ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-config/lib/rule.ts | 1 + .../@aws-cdk/aws-config/test/integ.rule.lit.expected.json | 7 +++++-- .../aws-config/test/integ.scoped-rule.expected.json | 7 +++++-- packages/@aws-cdk/aws-config/test/rule.test.ts | 3 +++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts index 05ecebe7d93e5..e1ac4d107ec9e 100644 --- a/packages/@aws-cdk/aws-config/lib/rule.ts +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -355,6 +355,7 @@ export class CustomRule extends RuleNew { props.lambdaFunction.addPermission('Permission', { principal: new iam.ServicePrincipal('config.amazonaws.com'), + sourceAccount: this.env.account, }); if (props.lambdaFunction.role) { diff --git a/packages/@aws-cdk/aws-config/test/integ.rule.lit.expected.json b/packages/@aws-cdk/aws-config/test/integ.rule.lit.expected.json index 234f54351bcd1..172382853b95f 100644 --- a/packages/@aws-cdk/aws-config/test/integ.rule.lit.expected.json +++ b/packages/@aws-cdk/aws-config/test/integ.rule.lit.expected.json @@ -72,7 +72,10 @@ "Arn" ] }, - "Principal": "config.amazonaws.com" + "Principal": "config.amazonaws.com", + "SourceAccount": { + "Ref": "AWS::AccountId" + } } }, "Custom8166710A": { @@ -221,4 +224,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-config/test/integ.scoped-rule.expected.json b/packages/@aws-cdk/aws-config/test/integ.scoped-rule.expected.json index 99d314d0c45af..fced1ede4a8f5 100644 --- a/packages/@aws-cdk/aws-config/test/integ.scoped-rule.expected.json +++ b/packages/@aws-cdk/aws-config/test/integ.scoped-rule.expected.json @@ -72,7 +72,10 @@ "Arn" ] }, - "Principal": "config.amazonaws.com" + "Principal": "config.amazonaws.com", + "SourceAccount": { + "Ref": "AWS::AccountId" + } } }, "Custom8166710A": { @@ -106,4 +109,4 @@ ] } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-config/test/rule.test.ts b/packages/@aws-cdk/aws-config/test/rule.test.ts index 77599b8d95308..259727982a330 100644 --- a/packages/@aws-cdk/aws-config/test/rule.test.ts +++ b/packages/@aws-cdk/aws-config/test/rule.test.ts @@ -101,6 +101,9 @@ describe('rule', () => { expect(stack).toHaveResource('AWS::Lambda::Permission', { Principal: 'config.amazonaws.com', + SourceAccount: { + Ref: 'AWS::AccountId', + }, }); expect(stack).toHaveResource('AWS::IAM::Role', { From 0c8ecb8cfc2cec9fd8c9f238c049b604a0f149fe Mon Sep 17 00:00:00 2001 From: Harrison Cannon <57939433+HariboDev@users.noreply.github.com> Date: Mon, 4 Oct 2021 13:46:08 +0100 Subject: [PATCH 23/23] fix(cli): progress bar overshoots count by 1 for stack updates (#16168) Currently, the `resourcesTotal` output is one short as it doesn't account for the `UPDATE_COMPLETE` event emitted when updating a stack. This PR increases the `resourcesTotal` variable depending on whether the stack is being updated or created. Noticed this bug when using the CDK on private projects. This has had a minor fix previously to address the `CREATE_COMPLETE` event emitted when creating a stack, however this did not address the `UPDATE_COMPLETE` event emitted when updating a stack. This caused updated events to produce the following output: ![image](https://user-images.githubusercontent.com/57939433/130373537-5dfacd3c-df7d-4272-abac-a4cf7c04cc47.png) To address this issue, I: - Added `+1` to the `resourcesTotal` prop in `packages/aws-cdk/lib/api/deploy-stack.ts` for the `StackActivityMonitor` class depending on whether the stack being deployed already exists using the `cloudFormationStack.exists` boolean. I also addressed a spacing issue between the pipe (`|`) and the timestamp, as seen in the image above. Collaborators: - @JWK95: Provided code review & valid suggestions ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- CONTRIBUTING.md | 4 ++-- packages/aws-cdk/lib/api/deploy-stack.ts | 4 +++- .../cloudformation/stack-activity-monitor.ts | 2 +- .../test/api/stack-activity-monitor.test.ts | 20 +++++++++---------- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 988848e8f8f0b..cc425a73c7d0f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,9 +16,9 @@ let us know if it's not up-to-date (even better, submit a PR with your correcti - [Getting Started](#getting-started) - [Pull Requests](#pull-requests) - [Step 1: Find something to work on](#step-1-find-something-to-work-on) - - [Step 2: Design (optional)](#step-2-design-optional) + - [Step 2: Design (optional)](#step-2-design) - [Step 3: Work your Magic](#step-3-work-your-magic) - - [Step 4: Pull Request](#step-5-pull-request) + - [Step 4: Pull Request](#step-4-pull-request) - [Step 5: Merge](#step-5-merge) - [Breaking Changes](#breaking-changes) - [Documentation](#documentation) diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index a889e0c802fc4..4888d639ff394 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -340,8 +340,10 @@ async function prepareAndExecuteChangeSet( await cfn.executeChangeSet({ StackName: deployName, ChangeSetName: changeSetName, ...disableRollback }).promise(); // eslint-disable-next-line max-len + const changeSetLength: number = (changeSetDescription.Changes ?? []).length; const monitor = options.quiet ? undefined : StackActivityMonitor.withDefaultPrinter(cfn, deployName, stackArtifact, { - resourcesTotal: (changeSetDescription.Changes ?? []).length, + // +1 for the extra event emitted from updates. + resourcesTotal: cloudFormationStack.exists ? changeSetLength + 1 : changeSetLength, progress: options.progress, changeSetCreationTime: changeSetDescription.CreationTime, }).start(); diff --git a/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts b/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts index 98277d3339fa1..73075a41fee27 100644 --- a/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts +++ b/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts @@ -524,7 +524,7 @@ export class HistoryActivityPrinter extends ActivityPrinterBase { this.stream.write( util.format( ' %s%s | %s | %s | %s %s%s%s\n', - (progress !== false ? ` ${this.progress()} |` : ''), + (progress !== false ? ` ${this.progress()} | ` : ''), new Date(e.Timestamp).toLocaleTimeString(), color(padRight(STATUS_WIDTH, (e.ResourceStatus || '').substr(0, STATUS_WIDTH))), // pad left and trim padRight(this.props.resourceTypeColumnWidth, e.ResourceType || ''), diff --git a/packages/aws-cdk/test/api/stack-activity-monitor.test.ts b/packages/aws-cdk/test/api/stack-activity-monitor.test.ts index 7a1037581c94e..c76ddda1ceb00 100644 --- a/packages/aws-cdk/test/api/stack-activity-monitor.test.ts +++ b/packages/aws-cdk/test/api/stack-activity-monitor.test.ts @@ -32,7 +32,7 @@ test('prints 0/4 progress report, when addActivity is called with an "IN_PROGRES }); }); - expect(output[0].trim()).toStrictEqual(`0/4 |${HUMAN_TIME} | ${reset('IN_PROGRESS ')} | AWS::CloudFormation::Stack | ${reset(bold('stack1'))}`); + expect(output[0].trim()).toStrictEqual(`0/4 | ${HUMAN_TIME} | ${reset('IN_PROGRESS ')} | AWS::CloudFormation::Stack | ${reset(bold('stack1'))}`); }); test('prints 1/4 progress report, when addActivity is called with an "UPDATE_COMPLETE" ResourceStatus', () => { @@ -56,7 +56,7 @@ test('prints 1/4 progress report, when addActivity is called with an "UPDATE_COM }); }); - expect(output[0].trim()).toStrictEqual(`1/4 |${HUMAN_TIME} | ${green('UPDATE_COMPLETE ')} | AWS::CloudFormation::Stack | ${green(bold('stack1'))}`); + expect(output[0].trim()).toStrictEqual(`1/4 | ${HUMAN_TIME} | ${green('UPDATE_COMPLETE ')} | AWS::CloudFormation::Stack | ${green(bold('stack1'))}`); }); test('prints 1/4 progress report, when addActivity is called with an "UPDATE_COMPLETE_CLEAN_IN_PROGRESS" ResourceStatus', () => { @@ -80,7 +80,7 @@ test('prints 1/4 progress report, when addActivity is called with an "UPDATE_COM }); }); - expect(output[0].trim()).toStrictEqual(`1/4 |${HUMAN_TIME} | ${green('UPDATE_COMPLETE_CLEA')} | AWS::CloudFormation::Stack | ${green(bold('stack1'))}`); + expect(output[0].trim()).toStrictEqual(`1/4 | ${HUMAN_TIME} | ${green('UPDATE_COMPLETE_CLEA')} | AWS::CloudFormation::Stack | ${green(bold('stack1'))}`); }); @@ -105,7 +105,7 @@ test('prints 1/4 progress report, when addActivity is called with an "ROLLBACK_C }); }); - expect(output[0].trim()).toStrictEqual(`1/4 |${HUMAN_TIME} | ${yellow('ROLLBACK_COMPLETE_CL')} | AWS::CloudFormation::Stack | ${yellow(bold('stack1'))}`); + expect(output[0].trim()).toStrictEqual(`1/4 | ${HUMAN_TIME} | ${yellow('ROLLBACK_COMPLETE_CL')} | AWS::CloudFormation::Stack | ${yellow(bold('stack1'))}`); }); test('prints 0/4 progress report, when addActivity is called with an "UPDATE_FAILED" ResourceStatus', () => { @@ -129,7 +129,7 @@ test('prints 0/4 progress report, when addActivity is called with an "UPDATE_FAI }); }); - expect(output[0].trim()).toStrictEqual(`0/4 |${HUMAN_TIME} | ${red('UPDATE_FAILED ')} | AWS::CloudFormation::Stack | ${red(bold('stack1'))}`); + expect(output[0].trim()).toStrictEqual(`0/4 | ${HUMAN_TIME} | ${red('UPDATE_FAILED ')} | AWS::CloudFormation::Stack | ${red(bold('stack1'))}`); }); @@ -178,9 +178,9 @@ test('does not print "Failed Resources:" list, when all deployments are successf }); expect(output.length).toStrictEqual(3); - expect(output[0].trim()).toStrictEqual(`0/2 |${HUMAN_TIME} | ${reset('IN_PROGRESS ')} | AWS::CloudFormation::Stack | ${reset(bold('stack1'))}`); - expect(output[1].trim()).toStrictEqual(`1/2 |${HUMAN_TIME} | ${green('UPDATE_COMPLETE ')} | AWS::CloudFormation::Stack | ${green(bold('stack1'))}`); - expect(output[2].trim()).toStrictEqual(`2/2 |${HUMAN_TIME} | ${green('UPDATE_COMPLETE ')} | AWS::CloudFormation::Stack | ${green(bold('stack2'))}`); + expect(output[0].trim()).toStrictEqual(`0/2 | ${HUMAN_TIME} | ${reset('IN_PROGRESS ')} | AWS::CloudFormation::Stack | ${reset(bold('stack1'))}`); + expect(output[1].trim()).toStrictEqual(`1/2 | ${HUMAN_TIME} | ${green('UPDATE_COMPLETE ')} | AWS::CloudFormation::Stack | ${green(bold('stack1'))}`); + expect(output[2].trim()).toStrictEqual(`2/2 | ${HUMAN_TIME} | ${green('UPDATE_COMPLETE ')} | AWS::CloudFormation::Stack | ${green(bold('stack2'))}`); }); test('prints "Failed Resources:" list, when at least one deployment fails', () => { @@ -217,8 +217,8 @@ test('prints "Failed Resources:" list, when at least one deployment fails', () = }); expect(output.length).toStrictEqual(4); - expect(output[0].trim()).toStrictEqual(`0/2 |${HUMAN_TIME} | ${reset('IN_PROGRESS ')} | AWS::CloudFormation::Stack | ${reset(bold('stack1'))}`); - expect(output[1].trim()).toStrictEqual(`0/2 |${HUMAN_TIME} | ${red('UPDATE_FAILED ')} | AWS::CloudFormation::Stack | ${red(bold('stack1'))}`); + expect(output[0].trim()).toStrictEqual(`0/2 | ${HUMAN_TIME} | ${reset('IN_PROGRESS ')} | AWS::CloudFormation::Stack | ${reset(bold('stack1'))}`); + expect(output[1].trim()).toStrictEqual(`0/2 | ${HUMAN_TIME} | ${red('UPDATE_FAILED ')} | AWS::CloudFormation::Stack | ${red(bold('stack1'))}`); expect(output[2].trim()).toStrictEqual('Failed resources:'); expect(output[3].trim()).toStrictEqual(`${HUMAN_TIME} | ${red('UPDATE_FAILED ')} | AWS::CloudFormation::Stack | ${red(bold('stack1'))}`); });