From 7a010993f10e5a8e4035e28a1f977e877ab4ca49 Mon Sep 17 00:00:00 2001 From: AWS CDK Team Date: Fri, 27 May 2022 04:56:56 +0000 Subject: [PATCH 01/25] chore(release): 1.158.0 --- CHANGELOG.md | 22 ++++++++++++++++++++++ version.v1.json | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e09741c20e89e..d1e96f8f71317 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ 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.158.0](https://github.com/aws/aws-cdk/compare/v1.157.0...v1.158.0) (2022-05-27) + + +### Features + +* **apprunner:** VpcConnector construct ([#20471](https://github.com/aws/aws-cdk/issues/20471)) ([5052191](https://github.com/aws/aws-cdk/commit/50521911f22f433323d700db77530e883762138a)) +* **aws-ecr-assets:** support the --platform option when building docker images ([#20439](https://github.com/aws/aws-cdk/issues/20439)) ([adc0368](https://github.com/aws/aws-cdk/commit/adc0368dc1f137aeaa4bd92de77028269e3a48f4)), closes [#12472](https://github.com/aws/aws-cdk/issues/12472) [#16770](https://github.com/aws/aws-cdk/issues/16770) [#16858](https://github.com/aws/aws-cdk/issues/16858) +* **lambda:** validate function description length ([#20476](https://github.com/aws/aws-cdk/issues/20476)) ([de027e2](https://github.com/aws/aws-cdk/commit/de027e28ce5c95e70fed8874e6531eabba24521c)), closes [#20475](https://github.com/aws/aws-cdk/issues/20475) +* **s3:** adds objectSizeGreaterThan property for s3 lifecycle rule ([#20425](https://github.com/aws/aws-cdk/issues/20425)) ([23690e4](https://github.com/aws/aws-cdk/commit/23690e40b1604839f99da8b8f96168dda8679c47)), closes [#20372](https://github.com/aws/aws-cdk/issues/20372) +* **servicecatalog:** ProductStackHistory can retain old ProductStack iterations ([#20244](https://github.com/aws/aws-cdk/issues/20244)) ([1037b8c](https://github.com/aws/aws-cdk/commit/1037b8c7f58ccd162491b49d75954c38d685d67f)) + + +### Bug Fixes + +* **core:** NestedStack defaultChild is undefined ([#20450](https://github.com/aws/aws-cdk/issues/20450)) ([0a49927](https://github.com/aws/aws-cdk/commit/0a49927e9e5bc250f339f664fa843fae2fab92ec)), closes [#11221](https://github.com/aws/aws-cdk/issues/11221) +* **iam:** Role policies cannot grow beyond 10k ([#20400](https://github.com/aws/aws-cdk/issues/20400)) ([75bfce7](https://github.com/aws/aws-cdk/commit/75bfce70dbc57fe688c96b3c5cbb67fc4e6fcc56)), closes [#19276](https://github.com/aws/aws-cdk/issues/19276) [#19939](https://github.com/aws/aws-cdk/issues/19939) [#19835](https://github.com/aws/aws-cdk/issues/19835) +* **integ-runner:** always resynth on deploy ([#20508](https://github.com/aws/aws-cdk/issues/20508)) ([7138057](https://github.com/aws/aws-cdk/commit/71380571b878a50fe4b754c7dac78da075a98242)) +* **integ-tests:** DeployAssert should be private ([#20466](https://github.com/aws/aws-cdk/issues/20466)) ([0f52813](https://github.com/aws/aws-cdk/commit/0f52813bcf6a48c352f697004a899461dd06935d)) +* **lambda:** Fix typo in public subnet warning ([#20470](https://github.com/aws/aws-cdk/issues/20470)) ([85f4e29](https://github.com/aws/aws-cdk/commit/85f4e29e0551d71dd5f2f588584785cbc1ae7b72)) +* **pipelines:** too many CodeBuild steps inflate policy size ([#20396](https://github.com/aws/aws-cdk/issues/20396)) ([f334060](https://github.com/aws/aws-cdk/commit/f334060fca02e928bc4f5fdcfd45244060731d78)), closes [#20189](https://github.com/aws/aws-cdk/issues/20189) [#19276](https://github.com/aws/aws-cdk/issues/19276) [#19939](https://github.com/aws/aws-cdk/issues/19939) [#19835](https://github.com/aws/aws-cdk/issues/19835) +* **s3-deployment:** default role does not get `PutAcl` permissions on… ([#20492](https://github.com/aws/aws-cdk/issues/20492)) ([3e6ec5c](https://github.com/aws/aws-cdk/commit/3e6ec5c48cff41cec2b32566990046fd704f4ec1)) + ## [1.157.0](https://github.com/aws/aws-cdk/compare/v1.156.1...v1.157.0) (2022-05-20) diff --git a/version.v1.json b/version.v1.json index e9b6f5ffd7864..aeca1be32b0b5 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.157.0" + "version": "1.158.0" } \ No newline at end of file From 953afa90e3e331909772e8146760e1de1cf59a59 Mon Sep 17 00:00:00 2001 From: AWS CDK Automation <43080478+aws-cdk-automation@users.noreply.github.com> Date: Fri, 27 May 2022 02:47:09 -0700 Subject: [PATCH 02/25] docs(cfnspec): update CloudFormation documentation (#20519) --- .../spec-source/cfn-docs/cfn-docs.json | 101 +++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json b/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json index 1cee622dab798..c99e5a9c1f3a7 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json +++ b/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json @@ -9109,6 +9109,7 @@ "SmsAuthenticationMessage": "A string representing the SMS authentication message.", "SmsConfiguration": "The SMS configuration with the settings that your Amazon Cognito user pool must use to send an SMS message from your AWS account through Amazon Simple Notification Service. To send SMS messages with Amazon SNS in the AWS Region that you want, the Amazon Cognito user pool uses an AWS Identity and Access Management (IAM) role in your AWS account .", "SmsVerificationMessage": "A string representing the SMS verification message.", + "UserAttributeUpdateSettings": "The settings for updates to user attributes. These settings include the property `AttributesRequireVerificationBeforeUpdate` ,\na user-pool setting that tells Amazon Cognito how to handle changes to the value of your users' email address and phone number attributes. For\nmore information, see [Verifying updates to to email addresses and phone numbers](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-email-phone-verification.html#user-pool-settings-verifications-verify-attribute-updates) .", "UserPoolAddOns": "Enables advanced security risk detection. Set the key `AdvancedSecurityMode` to the value \"AUDIT\".", "UserPoolName": "A string used to name the user pool.", "UserPoolTags": "The tag keys and values to assign to the user pool. A tag is a label that you can use to categorize and manage user pools in different ways, such as by purpose, owner, environment, or other criteria.", @@ -9261,6 +9262,13 @@ "MinLength": "The minimum length." } }, + "AWS::Cognito::UserPool.UserAttributeUpdateSettings": { + "attributes": {}, + "description": "The settings for updates to user attributes. These settings include the property `AttributesRequireVerificationBeforeUpdate` ,\na user-pool setting that tells Amazon Cognito how to handle changes to the value of your users' email address and phone number attributes. For\nmore information, see [Verifying updates to to email addresses and phone numbers](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-email-phone-verification.html#user-pool-settings-verifications-verify-attribute-updates) .", + "properties": { + "AttributesRequireVerificationBeforeUpdate": "Requires that your user verifies their email address, phone number, or both before Amazon Cognito updates the value of that attribute. When you update a user attribute that has this option activated, Amazon Cognito sends a verification message to the new phone number or email address. Amazon Cognito doesn\u2019t change the value of the attribute until your user responds to the verification message and confirms the new value.\n\nYou can verify an updated email address or phone number with a [VerifyUserAttribute](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_VerifyUserAttribute.html) API request. You can also call the [UpdateUserAttributes](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_UpdateUserAttributes.html) or [AdminUpdateUserAttributes](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminUpdateUserAttributes.html) API and set `email_verified` or `phone_number_verified` to true.\n\nWhen `AttributesRequireVerificationBeforeUpdate` is false, your user pool doesn't require that your users verify attribute changes before Amazon Cognito updates them. In a user pool where `AttributesRequireVerificationBeforeUpdate` is false, API operations that change attribute values can immediately update a user\u2019s `email` or `phone_number` attribute." + } + }, "AWS::Cognito::UserPool.UserPoolAddOns": { "attributes": {}, "description": "The user pool add-ons type.", @@ -16876,6 +16884,82 @@ "Namespace": "The namespaces of the EKS cluster.\n\n*Minimum* : 1\n\n*Maximum* : 63\n\n*Pattern* : `[a-z0-9]([-a-z0-9]*[a-z0-9])?`" } }, + "AWS::EMRServerless::Application": { + "attributes": { + "ApplicationId": "The ID of the application, such as `ab4rp1abcs8xz47n3x0example` .", + "Arn": "The Amazon Resource Name (ARN) of the project.", + "Ref": "When you pass the logical ID of this resource to the intrinsic `Ref` function, `Ref` returns the ID of the application.\n\nFor more information about using the `Ref` function, see [`Ref`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html) ." + }, + "description": "The `AWS::EMRServerless::Application` resource specifies an EMR Serverless application. An application uses open source analytics frameworks to run jobs that process data. To create an application, you must specify the release version for the open source framework version you want to use and the type of application you want, such as Apache Spark or Apache Hive. After you create an application, you can submit data processing jobs or interactive requests to it.", + "properties": { + "AutoStartConfiguration": "The configuration for an application to automatically start on job submission.", + "AutoStopConfiguration": "The configuration for an application to automatically stop after a certain amount of time being idle.", + "InitialCapacity": "The initial capacity of the application.", + "MaximumCapacity": "The maximum capacity of the application. This is cumulative across all workers at any given point in time during the lifespan of the application is created. No new resources will be created once any one of the defined limits is hit.", + "Name": "The name of the application.\n\n*Minimum* : 1\n\n*Maximum* : 64\n\n*Pattern* : `^[A-Za-z0-9._\\\\/#-]+$`", + "NetworkConfiguration": "The network configuration for customer VPC connectivity for the application.", + "ReleaseLabel": "The EMR release version associated with the application.\n\n*Minimum* : 1\n\n*Maximum* : 64\n\n*Pattern* : `^[A-Za-z0-9._/-]+$`", + "Tags": "The tags assigned to the application.", + "Type": "The type of application, such as Spark or Hive." + } + }, + "AWS::EMRServerless::Application.AutoStartConfiguration": { + "attributes": {}, + "description": "The con\ufb01guration for an application to automatically start on job submission.", + "properties": { + "Enabled": "Enables the application to automatically start on job submission. Defaults to true." + } + }, + "AWS::EMRServerless::Application.AutoStopConfiguration": { + "attributes": {}, + "description": "The con\ufb01guration for an application to automatically stop after a certain amount of time being idle.", + "properties": { + "Enabled": "Enables the application to automatically stop after a certain amount of time being idle. Defaults to true.", + "IdleTimeoutMinutes": "The amount of idle time in minutes after which your application will automatically stop. Defaults to 15 minutes.\n\n*Minimum* : 1\n\n*Maximum* : 10080" + } + }, + "AWS::EMRServerless::Application.InitialCapacityConfig": { + "attributes": {}, + "description": "The initial capacity configuration per worker.", + "properties": { + "WorkerConfiguration": "The resource configuration of the initial capacity configuration.", + "WorkerCount": "The number of workers in the initial capacity configuration.\n\n*Minimum* : 1\n\n*Maximum* : 1000000" + } + }, + "AWS::EMRServerless::Application.InitialCapacityConfigKeyValuePair": { + "attributes": {}, + "description": "The initial capacity configuration per worker.", + "properties": { + "Key": "The worker type for an analytics framework. For Spark applications, the key can either be set to `Driver` or `Executor` . For Hive applications, it can be set to `HiveDriver` or `TezTask` .\n\n*Minimum* : 1\n\n*Maximum* : 50\n\n*Pattern* : `^[a-zA-Z]+[-_]*[a-zA-Z]+$`", + "Value": "The value for the initial capacity configuration per worker." + } + }, + "AWS::EMRServerless::Application.MaximumAllowedResources": { + "attributes": {}, + "description": "The maximum allowed cumulative resources for an application. No new resources will be created once the limit is hit.", + "properties": { + "Cpu": "The maximum allowed CPU for an application.\n\n*Minimum* : 1\n\n*Maximum* : 15\n\n*Pattern* : `^[1-9][0-9]*(\\\\s)?(vCPU|vcpu|VCPU)?$`", + "Disk": "The maximum allowed disk for an application.\n\n*Minimum* : 1\n\n*Maximum* : 15\n\n*Pattern* : `^[1-9][0-9]*(\\\\s)?(GB|gb|gB|Gb)$\"`", + "Memory": "The maximum allowed resources for an application.\n\n*Minimum* : 1\n\n*Maximum* : 15\n\n*Pattern* : `^[1-9][0-9]*(\\\\s)?(GB|gb|gB|Gb)?$`" + } + }, + "AWS::EMRServerless::Application.NetworkConfiguration": { + "attributes": {}, + "description": "The network configuration for customer VPC connectivity.", + "properties": { + "SecurityGroupIds": "The array of security group Ids for customer VPC connectivity.\n\n*Minimum* : 1\n\n*Maximum* : 32\n\n*Pattern* : `^[-0-9a-zA-Z]+`", + "SubnetIds": "The array of subnet Ids for customer VPC connectivity.\n\n*Minimum* : 1\n\n*Maximum* : 32\n\n*Pattern* : `^[-0-9a-zA-Z]+`" + } + }, + "AWS::EMRServerless::Application.WorkerConfiguration": { + "attributes": {}, + "description": "The resource configuration of the initial capacity configuration.", + "properties": { + "Cpu": "*Minimum* : 1\n\n*Maximum* : 15\n\n*Pattern* : `^[1-9][0-9]*(\\\\s)?(vCPU|vcpu|VCPU)?$`", + "Disk": "*Minimum* : 1\n\n*Maximum* : 15\n\n*Pattern* : `^[1-9][0-9]*(\\\\s)?(GB|gb|gB|Gb)$\"`", + "Memory": "*Minimum* : 1\n\n*Maximum* : 15\n\n*Pattern* : `^[1-9][0-9]*(\\\\s)?(GB|gb|gB|Gb)?$`" + } + }, "AWS::ElastiCache::CacheCluster": { "attributes": { "ConfigurationEndpoint.Address": "The DNS hostname of the cache node.\n\n> Redis (cluster mode disabled) replication groups don't have this attribute. Therefore, `Fn::GetAtt` returns a value for this attribute only if the replication group is clustered. Otherwise, `Fn::GetAtt` fails.", @@ -24955,6 +25039,21 @@ "RfRegion": "The frequency band (RFRegion) value." } }, + "AWS::IoTWireless::NetworkAnalyzerConfiguration": { + "attributes": { + "Arn": "", + "Ref": "" + }, + "description": "", + "properties": { + "Description": "", + "Name": "", + "Tags": "", + "TraceContent": "", + "WirelessDevices": "", + "WirelessGateways": "" + } + }, "AWS::IoTWireless::PartnerAccount": { "attributes": { "Arn": "The Amazon Resource Name (ARN) of the resource.", @@ -38532,7 +38631,7 @@ "Tags": "AWS CloudFormation resource tags to apply to the document. Use tags to help you identify and categorize resources.", "TargetType": "Specify a target type to define the kinds of resources the document can run on. For example, to run a document on EC2 instances, specify the following value: `/AWS::EC2::Instance` . If you specify a value of '/' the document can run on all types of resources. If you don't specify a value, the document can't run on any resources. For a list of valid resource types, see [AWS resource and property types reference](https://docs.aws.amazon.com//AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) in the *AWS CloudFormation User Guide* .", "UpdateMethod": "If the document resource you specify in your template already exists, this parameter determines whether a new version of the existing document is created, or the existing document is replaced. `Replace` is the default method. If you specify `NewVersion` for the `UpdateMethod` parameter, and the `Name` of the document does not match an existing resource, a new document is created. When you specify `NewVersion` , the default version of the document is changed to the newly created version.", - "VersionName": "An optional field specifying the version of the artifact you are creating with the document. For example, \"Release 12, Update 6\". This value is unique across all versions of a document, and can't be changed." + "VersionName": "An optional field specifying the version of the artifact you are creating with the document. For example, `Release12.1` . This value is unique across all versions of a document, and can't be changed." } }, "AWS::SSM::Document.AttachmentsSource": { From 777953106ac550b058fdaa3ccde25b62be07defa Mon Sep 17 00:00:00 2001 From: Cory Hall <43035978+corymhall@users.noreply.github.com> Date: Fri, 27 May 2022 08:46:36 -0400 Subject: [PATCH 03/25] feat(integ-runner): publish integ-runner cli (#20477) This PR switches the `integ-runner` package from private to public. ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/integ-runner/README.md | 7 ++++++ .../integ-runner/lib/runner/runner-base.ts | 4 ---- packages/@aws-cdk/integ-runner/package.json | 2 +- packages/cdk-cli-wrapper/lib/cdk-wrapper.ts | 4 ++-- .../cdk-cli-wrapper/test/cdk-wrapper.test.ts | 24 +++++++++---------- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/@aws-cdk/integ-runner/README.md b/packages/@aws-cdk/integ-runner/README.md index 3f983273a4a1e..db79cf866b9ca 100644 --- a/packages/@aws-cdk/integ-runner/README.md +++ b/packages/@aws-cdk/integ-runner/README.md @@ -19,6 +19,13 @@ ## Overview +This tool has been created to be used initially by this repo (aws/aws-cdk). Long term the goal is +for this tool to be a general tool that can be used for running CDK integration tests. We are +publishing this tool so that it can be used by the community and we would love to receive feedback +on use cases that the tool should support, or issues that prevent the tool from being used in your +library. + +This tool is meant to be used with the [integ-tests](https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/integ-tests) library. ## Usage diff --git a/packages/@aws-cdk/integ-runner/lib/runner/runner-base.ts b/packages/@aws-cdk/integ-runner/lib/runner/runner-base.ts index 18b8a3c6e9f64..6ebdce2eea40c 100644 --- a/packages/@aws-cdk/integ-runner/lib/runner/runner-base.ts +++ b/packages/@aws-cdk/integ-runner/lib/runner/runner-base.ts @@ -129,8 +129,6 @@ export abstract class IntegRunner { protected readonly profile?: string; - protected readonly cdkExecutable: string; - protected _destructiveChanges?: DestructiveChange[]; private legacyContext?: Record; @@ -153,9 +151,7 @@ export abstract class IntegRunner { this.sourceFilePath = path.join(this.directory, parsed.base); this.cdkContextPath = path.join(this.directory, 'cdk.context.json'); - this.cdkExecutable = require.resolve('aws-cdk/bin/cdk'); this.cdk = options.cdk ?? new CdkCliWrapper({ - cdkExecutable: this.cdkExecutable, directory: this.directory, env: { ...options.env, diff --git a/packages/@aws-cdk/integ-runner/package.json b/packages/@aws-cdk/integ-runner/package.json index 5f08391b5bf08..fdf44c3311b9a 100644 --- a/packages/@aws-cdk/integ-runner/package.json +++ b/packages/@aws-cdk/integ-runner/package.json @@ -2,7 +2,7 @@ "name": "@aws-cdk/integ-runner", "description": "CDK Integration Testing Tool", "version": "0.0.0", - "private": true, + "private": false, "main": "lib/index.js", "types": "lib/index.d.ts", "bin": { diff --git a/packages/cdk-cli-wrapper/lib/cdk-wrapper.ts b/packages/cdk-cli-wrapper/lib/cdk-wrapper.ts index fd45c4ae0c07b..069673582f195 100644 --- a/packages/cdk-cli-wrapper/lib/cdk-wrapper.ts +++ b/packages/cdk-cli-wrapper/lib/cdk-wrapper.ts @@ -109,9 +109,9 @@ export class CdkCliWrapper implements ICdk { this.directory = options.directory; this.env = options.env; try { - this.cdk = options.cdkExecutable ?? require.resolve('aws-cdk/bin/cdk'); + this.cdk = options.cdkExecutable ?? 'cdk'; } catch (e) { - throw new Error(`could not resolve path to cdk executable: "${options.cdkExecutable}"`); + throw new Error(`could not resolve path to cdk executable: "${options.cdkExecutable ?? 'cdk'}"`); } } diff --git a/packages/cdk-cli-wrapper/test/cdk-wrapper.test.ts b/packages/cdk-cli-wrapper/test/cdk-wrapper.test.ts index c0d8f75195ea9..0a9d370426aae 100644 --- a/packages/cdk-cli-wrapper/test/cdk-wrapper.test.ts +++ b/packages/cdk-cli-wrapper/test/cdk-wrapper.test.ts @@ -32,7 +32,7 @@ test('default deploy', () => { // THEN expect(spawnSyncMock).toHaveBeenCalledWith( - expect.stringMatching(/aws-cdk\/bin\/cdk/), + expect.stringMatching(/cdk/), ['deploy', '--app', 'node bin/my-app.js', 'test-stack1'], expect.objectContaining({ env: expect.anything(), @@ -86,7 +86,7 @@ test('deploy with all arguments', () => { // THEN expect(spawnSyncMock).toHaveBeenCalledWith( - expect.stringMatching(/aws-cdk\/bin\/cdk/), + expect.stringMatching(/cdk/), expect.arrayContaining([ 'deploy', '--no-strict', @@ -146,7 +146,7 @@ test('can parse boolean arguments', () => { // THEN expect(spawnSyncMock).toHaveBeenCalledWith( - expect.stringMatching(/aws-cdk\/bin\/cdk/), + expect.stringMatching(/cdk/), [ 'deploy', '--app', @@ -179,7 +179,7 @@ test('can parse parameters', () => { // THEN expect(spawnSyncMock).toHaveBeenCalledWith( - expect.stringMatching(/aws-cdk\/bin\/cdk/), + expect.stringMatching(/cdk/), [ 'deploy', '--parameters', 'myparam=test', @@ -211,7 +211,7 @@ test('can parse context', () => { // THEN expect(spawnSyncMock).toHaveBeenCalledWith( - expect.stringMatching(/aws-cdk\/bin\/cdk/), + expect.stringMatching(/cdk/), [ 'deploy', '--app', @@ -243,7 +243,7 @@ test('can parse array arguments', () => { // THEN expect(spawnSyncMock).toHaveBeenCalledWith( - expect.stringMatching(/aws-cdk\/bin\/cdk/), + expect.stringMatching(/cdk/), [ 'deploy', '--notification-arns', 'arn:aws:us-east-1:1111111111:some:resource', @@ -274,7 +274,7 @@ test('can provide additional environment', () => { // THEN expect(spawnSyncMock).toHaveBeenCalledWith( - expect.stringMatching(/aws-cdk\/bin\/cdk/), + expect.stringMatching(/cdk/), ['deploy', '--app', 'node bin/my-app.js', 'test-stack1'], expect.objectContaining({ env: expect.objectContaining({ @@ -300,7 +300,7 @@ test('default synth', () => { // THEN expect(spawnSyncMock).toHaveBeenCalledWith( - expect.stringMatching(/aws-cdk\/bin\/cdk/), + expect.stringMatching(/cdk/), ['synth', '--app', 'node bin/my-app.js', 'test-stack1'], expect.objectContaining({ env: expect.objectContaining({ @@ -326,7 +326,7 @@ test('synth arguments', () => { // THEN expect(spawnSyncMock).toHaveBeenCalledWith( - expect.stringMatching(/aws-cdk\/bin\/cdk/), + expect.stringMatching(/cdk/), ['destroy', '--app', 'node bin/my-app.js', 'test-stack1'], expect.objectContaining({ env: expect.objectContaining({ @@ -354,7 +354,7 @@ test('destroy arguments', () => { // THEN expect(spawnSyncMock).toHaveBeenCalledWith( - expect.stringMatching(/aws-cdk\/bin\/cdk/), + expect.stringMatching(/cdk/), ['destroy', '--force', '--no-exclusively', '--app', 'node bin/my-app.js', 'test-stack1'], expect.objectContaining({ env: expect.objectContaining({ @@ -380,7 +380,7 @@ test('default ls', () => { // THEN expect(spawnSyncMock).toHaveBeenCalledWith( - expect.stringMatching(/aws-cdk\/bin\/cdk/), + expect.stringMatching(/cdk/), ['ls', '--app', 'node bin/my-app.js', '*'], expect.objectContaining({ env: expect.objectContaining({ @@ -415,7 +415,7 @@ test('ls arguments', () => { // THEN expect(spawnSyncMock).toHaveBeenCalledWith( - expect.stringMatching(/aws-cdk\/bin\/cdk/), + expect.stringMatching(/cdk/), ['ls', '--long', '--app', 'node bin/my-app.js', '*'], expect.objectContaining({ env: expect.objectContaining({ From c274c2f983de2dfd20ed2886a3c50f7fd3f6b3f4 Mon Sep 17 00:00:00 2001 From: Cory Hall <43035978+corymhall@users.noreply.github.com> Date: Fri, 27 May 2022 09:32:37 -0400 Subject: [PATCH 04/25] fix(integ-runner): don't throw error if tests pass (#20511) If you run `integ-runner --update-on-failed` and the test succeeds, then the cli should not return an exit code. re #20384 ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/integ-runner/lib/cli.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/integ-runner/lib/cli.ts b/packages/@aws-cdk/integ-runner/lib/cli.ts index 1c7c7920375d5..6014d4997378a 100644 --- a/packages/@aws-cdk/integ-runner/lib/cli.ts +++ b/packages/@aws-cdk/integ-runner/lib/cli.ts @@ -49,9 +49,10 @@ async function main() { let failedSnapshots: IntegTestWorkerConfig[] = []; if (argv['max-workers'] < testRegions.length * (profiles ?? [1]).length) { - logger.warning('You are attempting to run %s tests in parallel, but only have %s workers. Not all of your profiles+regions will be utilized', argv.profiles*argv['parallel-regions'], argv['max-workers']); + logger.warning('You are attempting to run %s tests in parallel, but only have %s workers. Not all of your profiles+regions will be utilized', argv.profiles * argv['parallel-regions'], argv['max-workers']); } + let testsSucceeded = false; try { if (argv.list) { const tests = await new IntegrationTests(argv.directory).fromCliArgs(); @@ -99,6 +100,8 @@ async function main() { verbose: argv.verbose, updateWorkflow: !argv['disable-update-workflow'], }); + testsSucceeded = success; + if (argv.clean === false) { logger.warning('Not cleaning up stacks since "--no-clean" was used'); @@ -125,7 +128,9 @@ async function main() { if (!runUpdateOnFailed) { message = 'To re-run failed tests run: yarn integ-runner --update-on-failed'; } - throw new Error(`Some snapshot tests failed!\n${message}`); + if (!testsSucceeded) { + throw new Error(`Some tests failed!\n${message}`); + } } } From df419ba70a1ab5bca22baf104750dcc61e2cd4e7 Mon Sep 17 00:00:00 2001 From: Madeline Kusters Date: Fri, 27 May 2022 07:32:04 -0700 Subject: [PATCH 05/25] chore: empty commit to trigger pr build From 6f4aba805b93523958d5cb4c8db4c1c800f53806 Mon Sep 17 00:00:00 2001 From: Stephen Blackstone Date: Fri, 27 May 2022 11:05:59 -0400 Subject: [PATCH 06/25] Fix error message when creating a NodeJS function (#20524) Error message gives the incorrect field depsFileLockPath Should be depsLockFilePath ---- ### All Submissions: * [X] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: None. * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features None. * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* Yep. --- packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts index ee82e25ccca18..eea143f5713f9 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts @@ -147,7 +147,7 @@ function findLockFile(depsLockFilePath?: string): string { throw new Error('Cannot find a package lock file (`pnpm-lock.yaml`, `yarn.lock` or `package-lock.json`). Please specify it with `depsLockFilePath`.'); } if (lockFiles.length > 1) { - throw new Error(`Multiple package lock files found: ${lockFiles.join(', ')}. Please specify the desired one with \`depsFileLockPath\`.`); + throw new Error(`Multiple package lock files found: ${lockFiles.join(', ')}. Please specify the desired one with \`depsLockFilePath\`.`); } return lockFiles[0]; From b7bc10cc7a734fe3b4a9194dffbc017f2fe3ef43 Mon Sep 17 00:00:00 2001 From: Adam Brodziak Date: Fri, 27 May 2022 18:23:10 +0200 Subject: [PATCH 07/25] fix: Default username in RoleSessionName (#20188) In case user does not have entry in `/etc/passwd` the `os.userInfo()` call will throw `SystemError` exception as documented: https://nodejs.org/docs/latest-v16.x/api/os.html#osuserinfooptions Fixes #19401 issue. It can be tested inside Docker for ad-hoc 1234 user ID: ```sh docker run -u 1234 -e CDK_HOME=/tmp npm run cdk diff ``` The `CDK_HOME=/tmp` is a workaround for #7937 issue, where CDK complains that it can't write cached info in user homedir, because it does not exists. Once #7937 will be fixed then #19401 will most likely hit users. However above workaround is a viable option. Hence those two issues are related, but not duplicated. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) Yes, followed the guide. ### Adding new Unconventional Dependencies: * [x] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) No new dependencies. ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? No, it's a bugfix, not a feature. *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-cdk/lib/api/aws-auth/sdk-provider.ts | 6 +++- .../aws-cdk/test/api/sdk-provider.test.ts | 28 +++++++++++++++++++ packages/cdk-assets/lib/aws.ts | 6 +++- 3 files changed, 38 insertions(+), 2 deletions(-) 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 39f7cd2f2a1b1..200f6548c6554 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts @@ -459,7 +459,11 @@ function readIfPossible(filename: string): string | undefined { * @see https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html#API_AssumeRole_RequestParameters */ function safeUsername() { - return os.userInfo().username.replace(/[^\w+=,.@-]/g, '@'); + try { + return os.userInfo().username.replace(/[^\w+=,.@-]/g, '@'); + } catch (e) { + return 'noname'; + } } /** diff --git a/packages/aws-cdk/test/api/sdk-provider.test.ts b/packages/aws-cdk/test/api/sdk-provider.test.ts index c2e3d311af647..9c03a7be0beed 100644 --- a/packages/aws-cdk/test/api/sdk-provider.test.ts +++ b/packages/aws-cdk/test/api/sdk-provider.test.ts @@ -341,6 +341,34 @@ describe('with intercepted network calls', () => { }); }); + test('assuming a role does not fail when OS username cannot be read', async () => { + // GIVEN + prepareCreds({ + fakeSts, + config: { + default: { aws_access_key_id: 'foo', $account: '11111' }, + }, + }); + + await withMocked(os, 'userInfo', async (userInfo) => { + userInfo.mockImplementation(() => { + // SystemError thrown as documented: https://nodejs.org/docs/latest-v16.x/api/os.html#osuserinfooptions + throw new Error('SystemError on Linux: uv_os_get_passwd returned ENOENT. See #19401 issue.'); + }); + + // WHEN + const provider = await providerFromProfile(undefined); + + const sdk = (await provider.forEnvironment(env(uniq('88888')), Mode.ForReading, { assumeRoleArn: 'arn:aws:role' })).sdk as SDK; + await sdk.currentAccount(); + + // THEN + expect(fakeSts.assumedRoles[0]).toEqual(expect.objectContaining({ + roleSessionName: 'aws-cdk-noname', + })); + }); + }); + test('even if current credentials are for the wrong account, we will still use them to AssumeRole', async () => { // GIVEN prepareCreds({ diff --git a/packages/cdk-assets/lib/aws.ts b/packages/cdk-assets/lib/aws.ts index c35dedb38bbe2..4f79ead780227 100644 --- a/packages/cdk-assets/lib/aws.ts +++ b/packages/cdk-assets/lib/aws.ts @@ -150,6 +150,10 @@ export class DefaultAwsClient implements IAws { * @see https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html#API_AssumeRole_RequestParameters */ function safeUsername() { - return os.userInfo().username.replace(/[^\w+=,.@-]/g, '@'); + try { + return os.userInfo().username.replace(/[^\w+=,.@-]/g, '@'); + } catch (e) { + return 'noname'; + } } From dacefd6c4770f06390f853fdf4703d8662beb3f5 Mon Sep 17 00:00:00 2001 From: Joshua Weber <57131123+daschaa@users.noreply.github.com> Date: Fri, 27 May 2022 19:10:42 +0200 Subject: [PATCH 08/25] fix(ecs): canContainersAccessInstanceRole is ignored when passed in AsgCapacityProvider constructor (#20522) Fixes #20293 When adding an AsgCapacityProvider the property `canContainersAccessInstanceRole` is only checked when passed in via the method `addAsgCapacityProvider`. It is ignored when passing the property via the instantiation of an AsgCapacityProvider. In this PR I added, that if either one way (method or constructor) has got the property set - it is respected in the outcome. For more details please see the issue #20293 I decided **not** to omit the property on the class level because it would bring in breaking changes. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *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-ecs/lib/cluster.ts | 10 ++ .../@aws-cdk/aws-ecs/test/cluster.test.ts | 142 ++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index 8e48e2be59cec..188fa661944d3 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -370,6 +370,7 @@ export class Cluster extends Resource implements ICluster { machineImageType: provider.machineImageType, // Don't enable the instance-draining lifecycle hook if managed termination protection is enabled taskDrainTime: provider.enableManagedTerminationProtection ? Duration.seconds(0) : options.taskDrainTime, + canContainersAccessInstanceRole: options.canContainersAccessInstanceRole ?? provider.canContainersAccessInstanceRole, }); this._capacityProviderNames.push(provider.capacityProviderName); @@ -1109,6 +1110,13 @@ export class AsgCapacityProvider extends CoreConstruct { */ readonly enableManagedTerminationProtection?: boolean; + /** + * Specifies whether the containers can access the container instance role. + * + * @default false + */ + readonly canContainersAccessInstanceRole?: boolean; + constructor(scope: Construct, id: string, props: AsgCapacityProviderProps) { super(scope, id); @@ -1116,6 +1124,8 @@ export class AsgCapacityProvider extends CoreConstruct { this.machineImageType = props.machineImageType ?? MachineImageType.AMAZON_LINUX_2; + this.canContainersAccessInstanceRole = props.canContainersAccessInstanceRole; + this.enableManagedTerminationProtection = props.enableManagedTerminationProtection === undefined ? true : props.enableManagedTerminationProtection; diff --git a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts index d167c30989ded..45f9601728ef7 100644 --- a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts @@ -2306,3 +2306,145 @@ test('throws when ASG Capacity Provider with capacityProviderName starting with cluster.addAsgCapacityProvider(capacityProviderAl2); }).toThrow(/Invalid Capacity Provider Name: ecscp, If a name is specified, it cannot start with aws, ecs, or fargate./); }); + +describe('Accessing container instance role', function () { + + const addUserDataMock = jest.fn(); + const autoScalingGroup: autoscaling.AutoScalingGroup = { + addUserData: addUserDataMock, + addToRolePolicy: jest.fn(), + protectNewInstancesFromScaleIn: jest.fn(), + } as unknown as autoscaling.AutoScalingGroup; + + afterEach(() => { + addUserDataMock.mockClear(); + }); + + test('block ecs from accessing metadata service when canContainersAccessInstanceRole not set', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + + const capacityProvider = new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: autoScalingGroup, + }); + + cluster.addAsgCapacityProvider(capacityProvider); + + // THEN + expect(autoScalingGroup.addUserData).toHaveBeenCalledWith('sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP'); + expect(autoScalingGroup.addUserData).toHaveBeenCalledWith('sudo service iptables save'); + expect(autoScalingGroup.addUserData).toHaveBeenCalledWith('echo ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config'); + }); + + test('allow ecs accessing metadata service when canContainersAccessInstanceRole is set on addAsgCapacityProvider', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + const capacityProvider = new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: autoScalingGroup, + }); + + cluster.addAsgCapacityProvider(capacityProvider, { + canContainersAccessInstanceRole: true, + }); + + // THEN + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo service iptables save'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('echo ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config'); + }); + + test('allow ecs accessing metadata service when canContainersAccessInstanceRole is set on AsgCapacityProvider instantiation', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + const capacityProvider = new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: autoScalingGroup, + canContainersAccessInstanceRole: true, + }); + + cluster.addAsgCapacityProvider(capacityProvider); + + // THEN + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo service iptables save'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('echo ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config'); + }); + + test('allow ecs accessing metadata service when canContainersAccessInstanceRole is set on constructor and method', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + const capacityProvider = new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: autoScalingGroup, + canContainersAccessInstanceRole: true, + }); + + cluster.addAsgCapacityProvider(capacityProvider, { + canContainersAccessInstanceRole: true, + }); + + // THEN + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo service iptables save'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('echo ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config'); + }); + + test('block ecs from accessing metadata service when canContainersAccessInstanceRole set on constructor and not set on method', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + const capacityProvider = new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: autoScalingGroup, + canContainersAccessInstanceRole: true, + }); + + cluster.addAsgCapacityProvider(capacityProvider, { + canContainersAccessInstanceRole: false, + }); + + // THEN + expect(autoScalingGroup.addUserData).toHaveBeenCalledWith('sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP'); + expect(autoScalingGroup.addUserData).toHaveBeenCalledWith('sudo service iptables save'); + expect(autoScalingGroup.addUserData).toHaveBeenCalledWith('echo ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config'); + }); + + test('allow ecs accessing metadata service when canContainersAccessInstanceRole is not set on constructor and set on method', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + const capacityProvider = new ecs.AsgCapacityProvider(stack, 'Provider', { + autoScalingGroup: autoScalingGroup, + canContainersAccessInstanceRole: false, + }); + + cluster.addAsgCapacityProvider(capacityProvider, { + canContainersAccessInstanceRole: true, + }); + + // THEN + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('sudo service iptables save'); + expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('echo ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config'); + }); +}); + From f4439ceda079dd762ec30c6f4a893d6bcd7ed100 Mon Sep 17 00:00:00 2001 From: dafujii <41186511+dafujii@users.noreply.github.com> Date: Sat, 28 May 2022 10:26:48 +0900 Subject: [PATCH 09/25] fix(ecs): fix typo from fromServiceAtrributes to fromServiceAttributes (#20456) Fixed: #20458 I found `fromServiceAtrributes`. I fixed to `fromServiceAttributes` ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts | 2 +- packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts | 6 +++--- packages/@aws-cdk/aws-ecs/lib/external/external-service.ts | 6 +++--- packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts b/packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts index 8dfc272300d41..7a9cbc0d28563 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts @@ -27,7 +27,7 @@ export interface ServiceAttributes { readonly serviceName?: string; } -export function fromServiceAtrributes(scope: Construct, id: string, attrs: ServiceAttributes): IBaseService { +export function fromServiceAttributes(scope: Construct, id: string, attrs: ServiceAttributes): IBaseService { if ((attrs.serviceArn && attrs.serviceName) || (!attrs.serviceArn && !attrs.serviceName)) { throw new Error('You can only specify either serviceArn or serviceName.'); } diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index eca66a4db6ff3..c24a1780c8b48 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -2,7 +2,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import { ArnFormat, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { BaseService, BaseServiceOptions, DeploymentControllerType, IBaseService, IService, LaunchType } from '../base/base-service'; -import { fromServiceAtrributes } from '../base/from-service-attributes'; +import { fromServiceAttributes } from '../base/from-service-attributes'; import { NetworkMode, TaskDefinition } from '../base/task-definition'; import { ICluster } from '../cluster'; import { CfnService } from '../ecs.generated'; @@ -134,10 +134,10 @@ export class Ec2Service extends BaseService implements IEc2Service { } /** - * Imports from the specified service attrributes. + * Imports from the specified service attributes. */ public static fromEc2ServiceAttributes(scope: Construct, id: string, attrs: Ec2ServiceAttributes): IBaseService { - return fromServiceAtrributes(scope, id, attrs); + return fromServiceAttributes(scope, id, attrs); } private readonly constraints: CfnService.PlacementConstraintProperty[]; diff --git a/packages/@aws-cdk/aws-ecs/lib/external/external-service.ts b/packages/@aws-cdk/aws-ecs/lib/external/external-service.ts index 9bb1eaf0b8cef..ba3bb291d422b 100644 --- a/packages/@aws-cdk/aws-ecs/lib/external/external-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/external/external-service.ts @@ -5,7 +5,7 @@ import * as cloudmap from '@aws-cdk/aws-servicediscovery'; import { ArnFormat, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { AssociateCloudMapServiceOptions, BaseService, BaseServiceOptions, CloudMapOptions, DeploymentControllerType, EcsTarget, IBaseService, IEcsLoadBalancerTarget, IService, LaunchType, PropagatedTagSource } from '../base/base-service'; -import { fromServiceAtrributes } from '../base/from-service-attributes'; +import { fromServiceAttributes } from '../base/from-service-attributes'; import { ScalableTaskCount } from '../base/scalable-task-count'; import { Compatibility, LoadBalancerTargetOptions, TaskDefinition } from '../base/task-definition'; import { ICluster } from '../cluster'; @@ -79,10 +79,10 @@ export class ExternalService extends BaseService implements IExternalService { } /** - * Imports from the specified service attrributes. + * Imports from the specified service attributes. */ public static fromExternalServiceAttributes(scope: Construct, id: string, attrs: ExternalServiceAttributes): IBaseService { - return fromServiceAtrributes(scope, id, attrs); + return fromServiceAttributes(scope, id, attrs); } /** diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts index b654c87887dda..a1ae858d0be61 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts @@ -3,7 +3,7 @@ import * as cdk from '@aws-cdk/core'; import { ArnFormat } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { BaseService, BaseServiceOptions, DeploymentControllerType, IBaseService, IService, LaunchType } from '../base/base-service'; -import { fromServiceAtrributes } from '../base/from-service-attributes'; +import { fromServiceAttributes } from '../base/from-service-attributes'; import { TaskDefinition } from '../base/task-definition'; import { ICluster } from '../cluster'; @@ -111,10 +111,10 @@ export class FargateService extends BaseService implements IFargateService { } /** - * Imports from the specified service attrributes. + * Imports from the specified service attributes. */ public static fromFargateServiceAttributes(scope: Construct, id: string, attrs: FargateServiceAttributes): IBaseService { - return fromServiceAtrributes(scope, id, attrs); + return fromServiceAttributes(scope, id, attrs); } /** From 3a0077e5ebf8a146354e7f0be451ed4d973b2a38 Mon Sep 17 00:00:00 2001 From: AWS CDK Automation <43080478+aws-cdk-automation@users.noreply.github.com> Date: Mon, 30 May 2022 02:46:20 -0700 Subject: [PATCH 10/25] docs(cfnspec): update CloudFormation documentation (#20540) --- .../spec-source/cfn-docs/cfn-docs.json | 56 ++++++++++++++++--- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json b/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json index c99e5a9c1f3a7..65e4fda07bbc6 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json +++ b/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json @@ -14171,6 +14171,7 @@ }, "AWS::EC2::PlacementGroup": { "attributes": { + "GroupName": "", "Ref": "`Ref` returns the name of the placement group." }, "description": "Specifies a placement group in which to launch instances. The strategy of the placement group determines how the instances are organized within the group.\n\nA `cluster` placement group is a logical grouping of instances within a single Availability Zone that benefit from low network latency, high network throughput. A `spread` placement group places instances on distinct hardware. A `partition` placement group places groups of instances in different partitions, where instances in one partition do not share the same hardware with instances in another partition.\n\nFor more information, see [Placement Groups](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/placement-groups.html) in the *Amazon EC2 User Guide* .", @@ -14794,6 +14795,7 @@ }, "description": "Attaches a VPC to a transit gateway.\n\nIf you attach a VPC with a CIDR range that overlaps the CIDR range of a VPC that is already attached, the new VPC CIDR range is not propagated to the default propagation route table.\n\nTo send VPC traffic to an attached transit gateway, add a route to the VPC route table using [AWS::EC2::Route](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route.html) .", "properties": { + "Options": "", "SubnetIds": "The IDs of one or more subnets. You can specify only one subnet per Availability Zone. You must specify at least one subnet, but we recommend that you specify two subnets for better availability. The transit gateway uses one IP address from each specified subnet.", "Tags": "The tags for the attachment.", "TransitGatewayId": "The ID of the transit gateway.", @@ -15078,6 +15080,7 @@ }, "AWS::EC2::VPCPeeringConnection": { "attributes": { + "Id": "", "Ref": "`Ref` returns the ID of the VPC peering connection." }, "description": "Requests a VPC peering connection between two VPCs: a requester VPC that you own and an accepter VPC with which to create the connection. The accepter VPC can belong to another AWS account and can be in a different Region to the requester VPC.\n\nThe requester VPC and accepter VPC cannot have overlapping CIDR blocks. If you create a VPC peering connection request between VPCs with overlapping CIDR blocks, the VPC peering connection has a status of `failed` .\n\nFor more information, see [Walkthough: Peer with a VPC in another AWS account](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/peer-with-vpc-in-another-account.html) .", @@ -15781,7 +15784,7 @@ "attributes": {}, "description": "Information about the platform for the Amazon ECS service or task.\n\nFor more informataion about `RuntimePlatform` , see [RuntimePlatform](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#runtime-platform) in the *Amazon Elastic Container Service Developer Guide* .", "properties": { - "CpuArchitecture": "The CPU architecture.\n\nYou can run your Linux tasks on an ARM-based platform by setting the value to `ARM64` . This option is avaiable for tasks that run on Linuc Amazon EC2 instance or Linux containers on Fargate.", + "CpuArchitecture": "The CPU architecture.\n\nYou can run your Linux tasks on an ARM-based platform by setting the value to `ARM64` . This option is avaiable for tasks that run on Linux Amazon EC2 instance or Linux containers on Fargate.", "OperatingSystemFamily": "The operating system." } }, @@ -19501,7 +19504,7 @@ "description": "The `AWS::GameLift::Fleet` resource creates an Amazon GameLift (GameLift) fleet to host game servers. A fleet is a set of EC2 instances, each of which can host multiple game sessions.", "properties": { "BuildId": "A unique identifier for a build to be deployed on the new fleet. If you are deploying the fleet with a custom game build, you must specify this property. The build must have been successfully uploaded to Amazon GameLift and be in a `READY` status. This fleet setting cannot be changed once the fleet is created.", - "CertificateConfiguration": "Prompts GameLift to generate a TLS/SSL certificate for the fleet. TLS certificates are used for encrypting traffic between game clients and the game servers that are running on GameLift. By default, the `CertificateConfiguration` is set to `DISABLED` . This property cannot be changed after the fleet is created.\n\nNote: This feature requires the AWS Certificate Manager (ACM) service, which is not available in all AWS regions. When working in a region that does not support this feature, a fleet creation request with certificate generation fails with a 4xx error.", + "CertificateConfiguration": "Prompts GameLift to generate a TLS/SSL certificate for the fleet. GameLift uses the certificates to encrypt traffic between game clients and the game servers running on GameLift. By default, the `CertificateConfiguration` is `DISABLED` . You can't change this property after you create the fleet.\n\nAWS Certificate Manager (ACM) certificates expire after 13 months. Certificate expiration can cause fleets to fail, preventing players from connecting to instances in the fleet. We recommend you replace fleets before 13 months, consider using fleet aliases for a smooth transition.\n\n> ACM isn't available in all AWS regions. A fleet creation request with certificate generation enabled in an unsupported Region, fails with a 4xx error. For more information about the supported Regions, see [Supported Regions](https://docs.aws.amazon.com/acm/latest/userguide/acm-regions.html) in the *AWS Certificate Manager User Guide* .", "Description": "A human-readable description of the fleet.", "DesiredEC2Instances": "The number of EC2 instances that you want this fleet to host. When creating a new fleet, GameLift automatically sets this value to \"1\" and initiates a single instance. Once the fleet is active, update this value to trigger GameLift to add or remove instances from the fleet.", "EC2InboundPermissions": "The allowed IP address ranges and port settings that allow inbound traffic to access game sessions on this fleet. If the fleet is hosting a custom game build, this property must be set before players can connect to game sessions. For Realtime Servers fleets, GameLift automatically sets TCP and UDP ranges.", @@ -24499,6 +24502,7 @@ }, "description": "Creates an asset from an existing asset model. For more information, see [Creating assets](https://docs.aws.amazon.com/iot-sitewise/latest/userguide/create-assets.html) in the *AWS IoT SiteWise User Guide* .", "properties": { + "AssetDescription": "", "AssetHierarchies": "A list of asset hierarchies that each contain a `hierarchyLogicalId` . A hierarchy specifies allowed parent/child asset relationships.", "AssetModelId": "The ID of the asset model from which to create the asset.", "AssetName": "A unique, friendly name for the asset.\n\nThe maximum length is 256 characters with the pattern `[^\\u0000-\\u001F\\u007F]+` .", @@ -34013,6 +34017,7 @@ "AdditionalTreatments": "An array of requests that defines additional treatments for the campaign, in addition to the default treatment for the campaign.", "ApplicationId": "The unique identifier for the Amazon Pinpoint application that the campaign is associated with.", "CampaignHook": "Specifies the Lambda function to use as a code hook for a campaign.", + "CustomDeliveryConfiguration": "The delivery configuration settings for sending the treatment through a custom channel. This object is required if the `MessageConfiguration` object for the treatment specifies a `CustomMessage` object.", "Description": "A custom description of the campaign.", "HoldoutPercent": "The allocated percentage of users (segment members) who shouldn't receive messages from the campaign.", "IsPaused": "Specifies whether to pause the campaign. A paused campaign doesn't run unless you resume it by changing this value to `false` . If you restart a campaign, the campaign restarts from the beginning and not at the point you paused it.", @@ -34024,6 +34029,7 @@ "SegmentId": "The unique identifier for the segment to associate with the campaign.", "SegmentVersion": "The version of the segment to associate with the campaign.", "Tags": "An array of key-value pairs to apply to this resource.\n\nFor more information, see [Tag](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html) .", + "TemplateConfiguration": "The message template to use for the treatment.", "TreatmentDescription": "A custom description of the default treatment for the campaign.", "TreatmentName": "A custom name of the default treatment for the campaign, if the campaign has multiple treatments. A *treatment* is a variation of a campaign that's used for A/B testing." } @@ -34036,6 +34042,13 @@ "Values": "The criteria values to use for the segment dimension. Depending on the value of the `AttributeType` property, endpoints are included or excluded from the segment if their attribute values match the criteria values." } }, + "AWS::Pinpoint::Campaign.CampaignCustomMessage": { + "attributes": {}, + "description": "Specifies the contents of a message that's sent through a custom channel to recipients of a campaign.", + "properties": { + "Data": "The raw, JSON-formatted string to use as the payload for the message. The maximum size is 5 KB." + } + }, "AWS::Pinpoint::Campaign.CampaignEmailMessage": { "attributes": {}, "description": "Specifies the content and \"From\" address for an email message that's sent to recipients of a campaign.", @@ -34084,6 +34097,14 @@ "TemplateId": "The template ID received from the regulatory body for sending SMS in your country." } }, + "AWS::Pinpoint::Campaign.CustomDeliveryConfiguration": { + "attributes": {}, + "description": "Specifies the delivery configuration settings for sending a campaign or campaign treatment through a custom channel. This object is required if you use the `CampaignCustomMessage` object to define the message to send for the campaign or campaign treatment.", + "properties": { + "DeliveryUri": "The destination to send the campaign or treatment to. This value can be one of the following:\n\n- The name or Amazon Resource Name (ARN) of an AWS Lambda function to invoke to handle delivery of the campaign or treatment.\n- The URL for a web application or service that supports HTTPS and can receive the message. The URL has to be a full URL, including the HTTPS protocol.", + "EndpointTypes": "The types of endpoints to send the campaign or treatment to. Each valid value maps to a type of channel that you can associate with an endpoint by using the `ChannelType` property of an endpoint." + } + }, "AWS::Pinpoint::Campaign.DefaultButtonConfiguration": { "attributes": {}, "description": "Specifies the default behavior for a button that appears in an in-app message. You can optionally add button configurations that specifically apply to iOS, Android, or web browser users.", @@ -34181,6 +34202,7 @@ "ADMMessage": "The message that the campaign sends through the ADM (Amazon Device Messaging) channel. If specified, this message overrides the default message.", "APNSMessage": "The message that the campaign sends through the APNs (Apple Push Notification service) channel. If specified, this message overrides the default message.", "BaiduMessage": "The message that the campaign sends through the Baidu (Baidu Cloud Push) channel. If specified, this message overrides the default message.", + "CustomMessage": "The message that the campaign sends through a custom channel, as specified by the delivery configuration ( `CustomDeliveryConfiguration` ) settings for the campaign. If specified, this message overrides the default message.", "DefaultMessage": "The default message that the campaign sends through all the channels that are configured for the campaign.", "EmailMessage": "The message that the campaign sends through the email channel. If specified, this message overrides the default message.", "GCMMessage": "The message that the campaign sends through the GCM channel, which enables Amazon Pinpoint to send push notifications through the Firebase Cloud Messaging (FCM), formerly Google Cloud Messaging (GCM), service. If specified, this message overrides the default message.", @@ -34233,13 +34255,33 @@ "Values": "The criteria values to use for the segment dimension. Depending on the value of the `DimensionType` property, endpoints are included or excluded from the segment if their values match the criteria values." } }, + "AWS::Pinpoint::Campaign.Template": { + "attributes": {}, + "description": "Specifies the name and version of the message template to use for the message.", + "properties": { + "Name": "The name of the message template to use for the message. If specified, this value must match the name of an existing message template.", + "Version": "The unique identifier for the version of the message template to use for the message. If specified, this value must match the identifier for an existing template version. To retrieve a list of versions and version identifiers for a template, use the Template Versions resource.\n\nIf you don't specify a value for this property, Amazon Pinpoint uses the *active version* of the template. The *active version* is typically the version of a template that's been most recently reviewed and approved for use, depending on your workflow. It isn't necessarily the latest version of a template." + } + }, + "AWS::Pinpoint::Campaign.TemplateConfiguration": { + "attributes": {}, + "description": "Specifies the message template to use for the message, for each type of channel.", + "properties": { + "EmailTemplate": "The email template to use for the message.", + "PushTemplate": "The push notification template to use for the message.", + "SMSTemplate": "The SMS template to use for the message.", + "VoiceTemplate": "The voice template to use for the message. This object isn't supported for campaigns." + } + }, "AWS::Pinpoint::Campaign.WriteTreatmentResource": { "attributes": {}, "description": "Specifies the settings for a campaign treatment. A *treatment* is a variation of a campaign that's used for A/B testing of a campaign.", "properties": { + "CustomDeliveryConfiguration": "The delivery configuration settings for sending the treatment through a custom channel. This object is required if the `MessageConfiguration` object for the treatment specifies a `CustomMessage` object.", "MessageConfiguration": "The message configuration settings for the treatment.", "Schedule": "The schedule settings for the treatment.", "SizePercent": "The allocated percentage of users (segment members) to send the treatment to.", + "TemplateConfiguration": "The message template to use for the treatment.", "TreatmentDescription": "A custom description of the treatment.", "TreatmentName": "A custom name for the treatment." } @@ -34751,8 +34793,8 @@ }, "description": "The `AWS::QLDB::Ledger` resource specifies a new Amazon Quantum Ledger Database (Amazon QLDB) ledger in your AWS account . Amazon QLDB is a fully managed ledger database that provides a transparent, immutable, and cryptographically verifiable transaction log owned by a central trusted authority. You can use QLDB to track all application data changes, and maintain a complete and verifiable history of changes over time.\n\nFor more information, see [CreateLedger](https://docs.aws.amazon.com/qldb/latest/developerguide/API_CreateLedger.html) in the *Amazon QLDB API Reference* .", "properties": { - "DeletionProtection": "The flag that prevents a ledger from being deleted by any user. If not provided on ledger creation, this feature is enabled ( `true` ) by default.\n\nIf deletion protection is enabled, you must first disable it before you can delete the ledger. You can disable it by calling the `UpdateLedger` operation to set the flag to `false` .", - "KmsKey": "The key in AWS Key Management Service ( AWS KMS ) to use for encryption of data at rest in the ledger. For more information, see [Encryption at rest](https://docs.aws.amazon.com/qldb/latest/developerguide/encryption-at-rest.html) in the *Amazon QLDB Developer Guide* .\n\nUse one of the following options to specify this parameter:\n\n- `AWS_OWNED_KMS_KEY` : Use an AWS KMS key that is owned and managed by AWS on your behalf.\n- *Undefined* : By default, use an AWS owned KMS key.\n- *A valid symmetric customer managed KMS key* : Use the specified KMS key in your account that you create, own, and manage.\n\nAmazon QLDB does not support asymmetric keys. For more information, see [Using symmetric and asymmetric keys](https://docs.aws.amazon.com/kms/latest/developerguide/symmetric-asymmetric.html) in the *AWS Key Management Service Developer Guide* .\n\nTo specify a customer managed KMS key, you can use its key ID, Amazon Resource Name (ARN), alias name, or alias ARN. When using an alias name, prefix it with `\"alias/\"` . To specify a key in a different AWS account , you must use the key ARN or alias ARN.\n\nFor example:\n\n- Key ID: `1234abcd-12ab-34cd-56ef-1234567890ab`\n- Key ARN: `arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab`\n- Alias name: `alias/ExampleAlias`\n- Alias ARN: `arn:aws:kms:us-east-2:111122223333:alias/ExampleAlias`\n\nFor more information, see [Key identifiers (KeyId)](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id) in the *AWS Key Management Service Developer Guide* .", + "DeletionProtection": "Specifies whether the ledger is protected from being deleted by any user. If not defined during ledger creation, this feature is enabled ( `true` ) by default.\n\nIf deletion protection is enabled, you must first disable it before you can delete the ledger. You can disable it by calling the `UpdateLedger` operation to set the parameter to `false` .", + "KmsKey": "The key in AWS Key Management Service ( AWS KMS ) to use for encryption of data at rest in the ledger. For more information, see [Encryption at rest](https://docs.aws.amazon.com/qldb/latest/developerguide/encryption-at-rest.html) in the *Amazon QLDB Developer Guide* .\n\nUse one of the following options to specify this parameter:\n\n- `AWS_OWNED_KMS_KEY` : Use an AWS KMS key that is owned and managed by AWS on your behalf.\n- *Undefined* : By default, use an AWS owned KMS key.\n- *A valid symmetric customer managed KMS key* : Use the specified symmetric encryption KMS key in your account that you create, own, and manage.\n\nAmazon QLDB does not support asymmetric keys. For more information, see [Using symmetric and asymmetric keys](https://docs.aws.amazon.com/kms/latest/developerguide/symmetric-asymmetric.html) in the *AWS Key Management Service Developer Guide* .\n\nTo specify a customer managed KMS key, you can use its key ID, Amazon Resource Name (ARN), alias name, or alias ARN. When using an alias name, prefix it with `\"alias/\"` . To specify a key in a different AWS account , you must use the key ARN or alias ARN.\n\nFor example:\n\n- Key ID: `1234abcd-12ab-34cd-56ef-1234567890ab`\n- Key ARN: `arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab`\n- Alias name: `alias/ExampleAlias`\n- Alias ARN: `arn:aws:kms:us-east-2:111122223333:alias/ExampleAlias`\n\nFor more information, see [Key identifiers (KeyId)](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id) in the *AWS Key Management Service Developer Guide* .", "Name": "The name of the ledger that you want to create. The name must be unique among all of the ledgers in your AWS account in the current Region.\n\nNaming constraints for ledger names are defined in [Quotas in Amazon QLDB](https://docs.aws.amazon.com/qldb/latest/developerguide/limits.html#limits.naming) in the *Amazon QLDB Developer Guide* .", "PermissionsMode": "The permissions mode to assign to the ledger that you want to create. This parameter can have one of the following values:\n\n- `ALLOW_ALL` : A legacy permissions mode that enables access control with API-level granularity for ledgers.\n\nThis mode allows users who have the `SendCommand` API permission for this ledger to run all PartiQL commands (hence, `ALLOW_ALL` ) on any tables in the specified ledger. This mode disregards any table-level or command-level IAM permissions policies that you create for the ledger.\n- `STANDARD` : ( *Recommended* ) A permissions mode that enables access control with finer granularity for ledgers, tables, and PartiQL commands.\n\nBy default, this mode denies all user requests to run any PartiQL commands on any tables in this ledger. To allow PartiQL commands to run, you must create IAM permissions policies for specific table resources and PartiQL actions, in addition to the `SendCommand` API permission for the ledger. For information, see [Getting started with the standard permissions mode](https://docs.aws.amazon.com/qldb/latest/developerguide/getting-started-standard-mode.html) in the *Amazon QLDB Developer Guide* .\n\n> We strongly recommend using the `STANDARD` permissions mode to maximize the security of your ledger data.", "Tags": "An array of key-value pairs to apply to this resource.\n\nFor more information, see [Tag](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html) ." @@ -37844,8 +37886,8 @@ "NoncurrentVersionExpirationInDays": "(Deprecated.) For buckets with versioning enabled (or suspended), specifies the time, in days, between when a new version of the object is uploaded to the bucket and when old versions of the object expire. When object versions expire, Amazon S3 permanently deletes them. If you specify a transition and expiration time, the expiration time must be later than the transition time.", "NoncurrentVersionTransition": "(Deprecated.) For buckets with versioning enabled (or suspended), specifies when non-current objects transition to a specified storage class. If you specify a transition and expiration time, the expiration time must be later than the transition time. If you specify this property, don't specify the `NoncurrentVersionTransitions` property.", "NoncurrentVersionTransitions": "For buckets with versioning enabled (or suspended), one or more transition rules that specify when non-current objects transition to a specified storage class. If you specify a transition and expiration time, the expiration time must be later than the transition time. If you specify this property, don't specify the `NoncurrentVersionTransition` property.", - "ObjectSizeGreaterThan": "Specifies the minimum object size in bytes for this rule to apply to. For more information about size based rules, see [Lifecycle configuration using size-based rules](https://docs.aws.amazon.com/AmazonS3/latest/userguide/lifecycle-configuration-examples.html#lc-size-rules) in the *Amazon S3 User Guide* .", - "ObjectSizeLessThan": "Specifies the maximum object size in bytes for this rule to apply to. For more information about sized based rules, see [Lifecycle configuration using size-based rules](https://docs.aws.amazon.com/AmazonS3/latest/userguide/lifecycle-configuration-examples.html#lc-size-rules) in the *Amazon S3 User Guide* .", + "ObjectSizeGreaterThan": "Specifies the minimum object size in bytes for this rule to apply to. Objects must be larger than this value in bytes. For more information about size based rules, see [Lifecycle configuration using size-based rules](https://docs.aws.amazon.com/AmazonS3/latest/userguide/lifecycle-configuration-examples.html#lc-size-rules) in the *Amazon S3 User Guide* .", + "ObjectSizeLessThan": "Specifies the maximum object size in bytes for this rule to apply to. Objects must be smaller than this value in bytes. For more information about sized based rules, see [Lifecycle configuration using size-based rules](https://docs.aws.amazon.com/AmazonS3/latest/userguide/lifecycle-configuration-examples.html#lc-size-rules) in the *Amazon S3 User Guide* .", "Prefix": "Object key prefix that identifies one or more objects to which this rule applies.\n\n> Replacement must be made for object keys containing special characters (such as carriage returns) when using XML requests. For more information, see [XML related object key constraints](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html#object-key-xml-related-constraints) .", "Status": "If `Enabled` , the rule is currently being applied. If `Disabled` , the rule is not currently being applied.", "TagFilters": "Tags to use to identify a subset of objects to which the lifecycle rule applies.", @@ -37855,7 +37897,7 @@ }, "AWS::S3::Bucket.S3KeyFilter": { "attributes": {}, - "description": "A container for object key name prefix and suffix filtering rules.\n\n> The same type of filter rule cannot be used more than once. For example, you cannot specify two prefix rules.", + "description": "A container for object key name prefix and suffix filtering rules. For more information about object key name filtering, see [Configuring event notifications using object key name filtering](https://docs.aws.amazon.com/AmazonS3/latest/userguide/notification-how-to-filtering.html) in the *Amazon S3 User Guide* .\n\n> The same type of filter rule cannot be used more than once. For example, you cannot specify two prefix rules.", "properties": { "Rules": "A list of containers for the key-value pair that defines the criteria for the filter rule." } From d814293b8092d975977005b3e28a0a8ac4c6389f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 May 2022 11:11:10 +0000 Subject: [PATCH 11/25] chore(deps): Bump awscli from 1.24.5 to 1.24.10 in /packages/@aws-cdk/lambda-layer-awscli (#20542) Bumps [awscli](https://github.com/aws/aws-cli) from 1.24.5 to 1.24.10.
Changelog

Sourced from awscli's changelog.

1.24.10

  • api-change:appflow: Adding the following features/changes: Parquet output that preserves typing from the source connector, Failed executions threshold before deactivation for scheduled flows, increasing max size of access and refresh token from 2048 to 4096
  • api-change:sagemaker: Amazon SageMaker Notebook Instances now allows configuration of Instance Metadata Service version and Amazon SageMaker Studio now supports G5 instance types.
  • api-change:datasync: AWS DataSync now supports TLS encryption in transit, file system policies and access points for EFS locations.
  • api-change:emr-serverless: This release adds support for Amazon EMR Serverless, a serverless runtime environment that simplifies running analytics applications using the latest open source frameworks such as Apache Spark and Apache Hive.

1.24.9

  • api-change:ec2: C7g instances, powered by the latest generation AWS Graviton3 processors, provide the best price performance in Amazon EC2 for compute-intensive workloads.
  • api-change:emr-serverless: This release adds support for Amazon EMR Serverless, a serverless runtime environment that simplifies running analytics applications using the latest open source frameworks such as Apache Spark and Apache Hive.
  • api-change:forecast: Introduced a new field in Auto Predictor as Time Alignment Boundary. It helps in aligning the timestamps generated during Forecast exports
  • api-change:lightsail: Amazon Lightsail now supports the ability to configure a Lightsail Container Service to pull images from Amazon ECR private repositories in your account.

1.24.8

  • api-change:secretsmanager: Documentation updates for Secrets Manager
  • api-change:sagemaker: Amazon SageMaker Autopilot adds support for manually selecting features from the input dataset using the CreateAutoMLJob API.
  • api-change:apprunner: Documentation-only update added for CodeConfiguration.
  • api-change:apigateway: Documentation updates for Amazon API Gateway
  • api-change:fsx: This release adds root squash support to FSx for Lustre to restrict root level access from clients by mapping root users to a less-privileged user/group with limited permissions.
  • api-change:lookoutmetrics: Adding AthenaSourceConfig for MetricSet APIs to support Athena as a data source.
  • api-change:voice-id: VoiceID will now automatically expire Speakers if they haven't been accessed for Enrollment, Re-enrollment or Successful Auth for three years. The Speaker APIs now return a "LastAccessedAt" time for Speakers, and the EvaluateSession API returns "SPEAKER_EXPIRED" Auth Decision for EXPIRED Speakers.
  • api-change:cloudformation: Add a new parameter statusReason to DescribeStackSetOperation output for additional details

1.24.7

  • api-change:ec2: Stop Protection feature enables customers to protect their instances from accidental stop actions.
  • api-change:cognito-idp: Amazon Cognito now supports requiring attribute verification (ex. email and phone number) before update.
  • api-change:mediaconvert: AWS Elemental MediaConvert SDK has added support for rules that constrain Automatic-ABR rendition selection when generating ABR package ladders.
  • api-change:networkmanager: This release adds Multi Account API support for a TGW Global Network, to enable and disable AWSServiceAccess with AwsOrganizations for Network Manager service and dependency CloudFormation StackSets service.
  • api-change:ivschat: Doc-only update. For MessageReviewHandler structure, added timeout period in the description of the fallbackResult field

1.24.6

  • api-change:forecast: New APIs for Monitor that help you understand how your predictors perform over time.
  • api-change:elasticache: Added support for encryption in transit for Memcached clusters. Customers can now launch Memcached cluster with encryption in transit enabled when using Memcached version 1.6.12 or later.
  • api-change:personalize: Adding modelMetrics as part of DescribeRecommender API response for Personalize.
Commits
  • 61acbef Merge branch 'release-1.24.10'
  • fd11aff Bumping version to 1.24.10
  • 4db48c4 Update changelog based on model updates
  • c9cedb1 Merge pull request #3822 from mkrn/develop
  • 7f04197 Merge branch 'release-1.24.9'
  • faafcb1 Merge branch 'release-1.24.9' into develop
  • 4c24413 Bumping version to 1.24.9
  • 2db57f7 Update changelog based on model updates
  • d9f828c Add support for aws cloudformation package of AWS::AppSync::FunctionConfigura...
  • 20348b2 Merge pull request #4353 from yoshiken/delete_unused_variable
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=awscli&package-manager=pip&previous-version=1.24.5&new-version=1.24.10)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- packages/@aws-cdk/lambda-layer-awscli/layer/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/lambda-layer-awscli/layer/requirements.txt b/packages/@aws-cdk/lambda-layer-awscli/layer/requirements.txt index 4142bbbaacc8f..9b84c7758b66b 100644 --- a/packages/@aws-cdk/lambda-layer-awscli/layer/requirements.txt +++ b/packages/@aws-cdk/lambda-layer-awscli/layer/requirements.txt @@ -1 +1 @@ -awscli==1.24.5 +awscli==1.24.10 From cedfde8cb07eb879ee384bda93bba813ede91699 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 31 May 2022 16:32:21 +0200 Subject: [PATCH 12/25] fix(integ-runner): catch snapshot errors, treat `--from-file` as command-line (#20523) Snapshot errors --------------- The constructor of `IntegSnapshotRunner` calls `loadManifest()`, which on my computer happened to fail and stopped the entire test suite because this error happened outside the `try/catch` block. Same for the integ runner itself. Move it inside the try/catch block, needed a bit of refactoring to make sure we could still get at the test name. `--from-file` ------------- Instead of having `--from-file` require a JSON file with its own structure, interpret it as a text file which gets treated exactly the same as the `[TEST [..]]` arguments on the command line. This still allows for the `--exclude` behavior by setting that flag on the command-line. Also be very liberal on the pattern (file name or test name or display name) that we accept, encoded in the `IntegTest.matches()` class. Refactoring ----------- Moved the logic around determining test names and directories into a class (`IntegTest`) which is a convenience class on top of a static data record (`IntegTestInfo`). The split between data and logic is so that we can pass the data to worker threads where we can hydrate the helper class on top again. I tried to be consistent: in memory, all the fields are with respect to `process.cwd()`, so valid file paths in the current process. Only when they are passed to the CLI wrapper are the paths made relative to the CLI wrapper directory. Print snapshot validations that are running for a long time (1 minute). This helps diagnose what is stuck, if anything is. On my machine, it was tests using Docker because there was some issue with it, and this lost me a day. Also change the test reporting formatting slightly. -------------- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/integ-runner/lib/cli.ts | 26 +-- .../lib/runner/integ-test-runner.ts | 24 ++- .../lib/runner/integration-tests.ts | 154 ++++++++++++++---- .../integ-runner/lib/runner/runner-base.ts | 72 +++----- .../lib/runner/snapshot-test-runner.ts | 4 +- packages/@aws-cdk/integ-runner/lib/utils.ts | 58 +++++++ .../integ-runner/lib/workers/common.ts | 43 ++++- .../lib/workers/extract/extract_worker.ts | 72 ++++---- .../lib/workers/integ-snapshot-worker.ts | 21 ++- .../lib/workers/integ-test-worker.ts | 4 +- .../test/runner/integ-test-runner.test.ts | 92 +++++++---- .../test/runner/integration-tests.test.ts | 55 ------- .../test/runner/snapshot-test-runner.test.ts | 40 +++-- .../test/workers/integ-worker.test.ts | 60 +++---- .../test/workers/mock-extract_worker.ts | 4 +- .../test/workers/snapshot-worker.test.ts | 6 +- 16 files changed, 453 insertions(+), 282 deletions(-) diff --git a/packages/@aws-cdk/integ-runner/lib/cli.ts b/packages/@aws-cdk/integ-runner/lib/cli.ts index 6014d4997378a..148d5e456f21c 100644 --- a/packages/@aws-cdk/integ-runner/lib/cli.ts +++ b/packages/@aws-cdk/integ-runner/lib/cli.ts @@ -1,9 +1,10 @@ // Exercise all integ stacks and if they deploy, update the expected synth files +import { promises as fs } from 'fs'; import * as path from 'path'; import * as chalk from 'chalk'; import * as workerpool from 'workerpool'; import * as logger from './logger'; -import { IntegrationTests, IntegTestConfig } from './runner/integration-tests'; +import { IntegrationTests, IntegTestInfo, IntegTest } from './runner/integration-tests'; import { runSnapshotTests, runIntegrationTests, IntegRunnerMetrics, IntegTestWorkerConfig, DestructiveChange } from './workers'; // https://github.com/yargs/yargs/issues/1929 @@ -25,8 +26,8 @@ async function main() { .options('directory', { type: 'string', default: 'test', desc: 'starting directory to discover integration tests. Tests will be discovered recursively from this directory' }) .options('profiles', { type: 'array', desc: 'list of AWS profiles to use. Tests will be run in parallel across each profile+regions', nargs: 1, default: [] }) .options('max-workers', { type: 'number', desc: 'The max number of workerpool workers to use when running integration tests in parallel', default: 16 }) - .options('exclude', { type: 'boolean', desc: 'All tests should be run, except for the list of tests provided', default: false }) - .options('from-file', { type: 'string', desc: 'Import tests to include or exclude from a file' }) + .options('exclude', { type: 'boolean', desc: 'Run all tests in the directory, except the specified TESTs', default: false }) + .options('from-file', { type: 'string', desc: 'Read TEST names from a file (one TEST per line)' }) .option('inspect-failures', { type: 'boolean', desc: 'Keep the integ test cloud assembly if a failure occurs for inspection', default: false }) .option('disable-update-workflow', { type: 'boolean', default: false, desc: 'If this is "true" then the stack update workflow will be disabled' }) .strict() @@ -39,7 +40,7 @@ async function main() { // list of integration tests that will be executed const testsToRun: IntegTestWorkerConfig[] = []; const destructiveChanges: DestructiveChange[] = []; - const testsFromArgs: IntegTestConfig[] = []; + const testsFromArgs: IntegTest[] = []; const parallelRegions = arrayFromYargs(argv['parallel-regions']); const testRegions: string[] = parallelRegions ?? ['us-east-1', 'us-east-2', 'us-west-2']; const profiles = arrayFromYargs(argv.profiles); @@ -56,19 +57,18 @@ async function main() { try { if (argv.list) { const tests = await new IntegrationTests(argv.directory).fromCliArgs(); - process.stdout.write(tests.map(t => t.fileName).join('\n') + '\n'); + process.stdout.write(tests.map(t => t.discoveryRelativeFileName).join('\n') + '\n'); return; } if (argv._.length > 0 && fromFile) { throw new Error('A list of tests cannot be provided if "--from-file" is provided'); - } else if (argv._.length === 0 && !fromFile) { - testsFromArgs.push(...(await new IntegrationTests(path.resolve(argv.directory)).fromCliArgs())); - } else if (fromFile) { - testsFromArgs.push(...(await new IntegrationTests(path.resolve(argv.directory)).fromFile(path.resolve(fromFile)))); - } else { - testsFromArgs.push(...(await new IntegrationTests(path.resolve(argv.directory)).fromCliArgs(argv._.map((x: any) => x.toString()), exclude))); } + const requestedTests = fromFile + ? (await fs.readFile(fromFile, { encoding: 'utf8' })).split('\n').filter(x => x) + : (argv._.length > 0 ? argv._ : undefined); // 'undefined' means no request + + testsFromArgs.push(...(await new IntegrationTests(path.resolve(argv.directory)).fromCliArgs(requestedTests, exclude))); // always run snapshot tests, but if '--force' is passed then // run integration tests on all failed tests, not just those that @@ -85,7 +85,7 @@ async function main() { } else { // if any of the test failed snapshot tests, keep those results // and merge with the rest of the tests from args - testsToRun.push(...mergeTests(testsFromArgs, failedSnapshots)); + testsToRun.push(...mergeTests(testsFromArgs.map(t => t.info), failedSnapshots)); } // run integration tests if `--update-on-failed` OR `--force` is used @@ -174,7 +174,7 @@ function arrayFromYargs(xs: string[]): string[] | undefined { * tests that failed snapshot tests. The failed snapshot tests have additional * information that we want to keep so this should override any test from args */ -function mergeTests(testFromArgs: IntegTestConfig[], failedSnapshotTests: IntegTestWorkerConfig[]): IntegTestWorkerConfig[] { +function mergeTests(testFromArgs: IntegTestInfo[], failedSnapshotTests: IntegTestWorkerConfig[]): IntegTestWorkerConfig[] { const failedTestNames = new Set(failedSnapshotTests.map(test => test.fileName)); const final: IntegTestWorkerConfig[] = failedSnapshotTests; final.push(...testFromArgs.filter(test => !failedTestNames.has(test.fileName))); diff --git a/packages/@aws-cdk/integ-runner/lib/runner/integ-test-runner.ts b/packages/@aws-cdk/integ-runner/lib/runner/integ-test-runner.ts index 3c325d34c3700..fe1e6cadf1e61 100644 --- a/packages/@aws-cdk/integ-runner/lib/runner/integ-test-runner.ts +++ b/packages/@aws-cdk/integ-runner/lib/runner/integ-test-runner.ts @@ -72,7 +72,8 @@ export class IntegTestRunner extends IntegRunner { * all branches and we then search for one that starts with `HEAD branch: ` */ private checkoutSnapshot(): void { - const cwd = path.dirname(this.snapshotDir); + const cwd = this.directory; + // https://git-scm.com/docs/git-merge-base let baseBranch: string | undefined = undefined; // try to find the base branch that the working branch was created from @@ -98,17 +99,19 @@ export class IntegTestRunner extends IntegRunner { // if we found the base branch then get the merge-base (most recent common commit) // and checkout the snapshot using that commit if (baseBranch) { + const relativeSnapshotDir = path.relative(this.directory, this.snapshotDir); + try { const base = exec(['git', 'merge-base', 'HEAD', baseBranch], { cwd, }); - exec(['git', 'checkout', base, '--', this.relativeSnapshotDir], { + exec(['git', 'checkout', base, '--', relativeSnapshotDir], { cwd, }); } catch (e) { logger.warning('%s\n%s', `Could not checkout snapshot directory ${this.snapshotDir} using these commands: `, - `git merge-base HEAD ${baseBranch} && git checkout {merge-base} -- ${this.relativeSnapshotDir}`, + `git merge-base HEAD ${baseBranch} && git checkout {merge-base} -- ${relativeSnapshotDir}`, ); logger.warning('error: %s', e); } @@ -129,6 +132,9 @@ export class IntegTestRunner extends IntegRunner { public runIntegTestCase(options: RunOptions): AssertionResults | undefined { let assertionResults: AssertionResults | undefined; const actualTestCase = this.actualTestSuite.testSuite[options.testCaseName]; + if (!actualTestCase) { + throw new Error(`Did not find test case name '${options.testCaseName}' in '${Object.keys(this.actualTestSuite.testSuite)}'`); + } const clean = options.clean ?? true; const updateWorkflowEnabled = (options.updateWorkflow ?? true) && (actualTestCase.stackUpdateWorkflow ?? true); @@ -151,7 +157,7 @@ export class IntegTestRunner extends IntegRunner { this.cdk.synthFast({ execCmd: this.cdkApp.split(' '), env, - output: this.cdkOutDir, + output: path.relative(this.directory, this.cdkOutDir), }); } // only create the snapshot if there are no assertion assertion results @@ -170,7 +176,7 @@ export class IntegTestRunner extends IntegRunner { all: true, force: true, app: this.cdkApp, - output: this.cdkOutDir, + output: path.relative(this.directory, this.cdkOutDir), ...actualTestCase.cdkCommandOptions?.destroy?.args, context: this.getContext(actualTestCase.cdkCommandOptions?.destroy?.args?.context), }); @@ -241,7 +247,7 @@ export class IntegTestRunner extends IntegRunner { stacks: expectedTestCase.stacks, ...expectedTestCase?.cdkCommandOptions?.deploy?.args, context: this.getContext(expectedTestCase?.cdkCommandOptions?.deploy?.args?.context), - app: this.relativeSnapshotDir, + app: path.relative(this.directory, this.snapshotDir), lookups: this.expectedTestSuite?.enableLookups, }); } @@ -255,9 +261,9 @@ export class IntegTestRunner extends IntegRunner { ...actualTestCase.assertionStack ? [actualTestCase.assertionStack] : [], ], rollback: false, - output: this.cdkOutDir, + output: path.relative(this.directory, this.cdkOutDir), ...actualTestCase?.cdkCommandOptions?.deploy?.args, - ...actualTestCase.assertionStack ? { outputsFile: path.join(this.cdkOutDir, 'assertion-results.json') } : undefined, + ...actualTestCase.assertionStack ? { outputsFile: path.relative(this.directory, path.join(this.cdkOutDir, 'assertion-results.json')) } : undefined, context: this.getContext(actualTestCase?.cdkCommandOptions?.deploy?.args?.context), app: this.cdkApp, }); @@ -270,7 +276,7 @@ export class IntegTestRunner extends IntegRunner { if (actualTestCase.assertionStack) { return this.processAssertionResults( - path.join(this.directory, this.cdkOutDir, 'assertion-results.json'), + path.join(this.cdkOutDir, 'assertion-results.json'), actualTestCase.assertionStack, ); } diff --git a/packages/@aws-cdk/integ-runner/lib/runner/integration-tests.ts b/packages/@aws-cdk/integ-runner/lib/runner/integration-tests.ts index 98511a76daff8..ac139187c500a 100644 --- a/packages/@aws-cdk/integ-runner/lib/runner/integration-tests.ts +++ b/packages/@aws-cdk/integ-runner/lib/runner/integration-tests.ts @@ -1,22 +1,118 @@ import * as path from 'path'; import * as fs from 'fs-extra'; +const CDK_OUTDIR_PREFIX = 'cdk-integ.out'; + /** * Represents a single integration test + * + * This type is a data-only structure, so it can trivially be passed to workers. + * Derived attributes are calculated using the `IntegTest` class. */ -export interface IntegTestConfig { +export interface IntegTestInfo { /** - * The name of the file that contains the - * integration tests. This will be in the format - * of integ.{test-name}.js + * Path to the file to run + * + * Path is relative to the current working directory. */ readonly fileName: string; /** - * The base directory where the tests are - * discovered from + * The root directory we discovered this test from + * + * Path is relative to the current working directory. */ - readonly directory: string; + readonly discoveryRoot: string; +} + +/** + * Derived information for IntegTests + */ +export class IntegTest { + /** + * The name of the file to run + * + * Path is relative to the current working directory. + */ + public readonly fileName: string; + + /** + * Relative path to the file to run + * + * Relative from the "discovery root". + */ + public readonly discoveryRelativeFileName: string; + + /** + * The absolute path to the file + */ + public readonly absoluteFileName: string; + + /** + * Directory the test is in + */ + public readonly directory: string; + + /** + * Display name for the test + * + * Depends on the discovery directory. + * + * Looks like `integ.mytest` or `package/test/integ.mytest`. + */ + public readonly testName: string; + + /** + * Path of the snapshot directory for this test + */ + public readonly snapshotDir: string; + + /** + * Path to the temporary output directory for this test + */ + public readonly temporaryOutputDir: string; + + constructor(public readonly info: IntegTestInfo) { + this.absoluteFileName = path.resolve(info.fileName); + this.fileName = path.relative(process.cwd(), info.fileName); + + const parsed = path.parse(this.fileName); + this.discoveryRelativeFileName = path.relative(info.discoveryRoot, info.fileName); + this.directory = parsed.dir; + + // if we are running in a package directory then just use the fileName + // as the testname, but if we are running in a parent directory with + // multiple packages then use the directory/filename as the testname + // + // Looks either like `integ.mytest` or `package/test/integ.mytest`. + const relDiscoveryRoot = path.relative(process.cwd(), info.discoveryRoot); + this.testName = this.directory === path.join(relDiscoveryRoot, 'test') || this.directory === path.join(relDiscoveryRoot) + ? parsed.name + : path.join(path.relative(this.info.discoveryRoot, parsed.dir), parsed.name); + + const nakedTestName = parsed.name.slice(6); // Leave name without 'integ.' and '.ts' + this.snapshotDir = path.join(this.directory, `${nakedTestName}.integ.snapshot`); + this.temporaryOutputDir = path.join(this.directory, `${CDK_OUTDIR_PREFIX}.${nakedTestName}`); + } + + /** + * Whether this test matches the user-given name + * + * We are very lenient here. A name matches if it matches: + * + * - The CWD-relative filename + * - The discovery root-relative filename + * - The suite name + * - The absolute filename + */ + public matches(name: string) { + return [ + this.fileName, + this.discoveryRelativeFileName, + this.testName, + this.absoluteFileName, + ].includes(name); + } } /** @@ -49,7 +145,7 @@ export class IntegrationTests { * Takes a file name of a file that contains a list of test * to either run or exclude and returns a list of Integration Tests to run */ - public async fromFile(fileName: string): Promise { + public async fromFile(fileName: string): Promise { const file: IntegrationTestFileConfig = JSON.parse(fs.readFileSync(fileName, { encoding: 'utf-8' })); const foundTests = await this.discover(); @@ -65,32 +161,27 @@ export class IntegrationTests { * If they have provided a test name that we don't find, then we write out that error message. * - If it is a list of tests to exclude, then we discover all available tests and filter out the tests that were provided by the user. */ - private filterTests(discoveredTests: IntegTestConfig[], requestedTests?: string[], exclude?: boolean): IntegTestConfig[] { - if (!requestedTests || requestedTests.length === 0) { + private filterTests(discoveredTests: IntegTest[], requestedTests?: string[], exclude?: boolean): IntegTest[] { + if (!requestedTests) { return discoveredTests; } - const all = discoveredTests.map(x => { - return path.relative(x.directory, x.fileName); - }); - let foundAll = true; - // Pare down found tests to filter + + const allTests = discoveredTests.filter(t => { - if (exclude) { - return (!requestedTests.includes(path.relative(t.directory, t.fileName))); - } - return (requestedTests.includes(path.relative(t.directory, t.fileName))); + const matches = requestedTests.some(pattern => t.matches(pattern)); + return matches !== !!exclude; // Looks weird but is equal to (matches && !exclude) || (!matches && exclude) }); + // If not excluding, all patterns must have matched at least one test if (!exclude) { - const selectedNames = allTests.map(t => path.relative(t.directory, t.fileName)); - for (const unmatched of requestedTests.filter(t => !selectedNames.includes(t))) { + const unmatchedPatterns = requestedTests.filter(pattern => !discoveredTests.some(t => t.matches(pattern))); + for (const unmatched of unmatchedPatterns) { process.stderr.write(`No such integ test: ${unmatched}\n`); - foundAll = false; } - } - if (!foundAll) { - process.stderr.write(`Available tests: ${all.join(' ')}\n`); - return []; + if (unmatchedPatterns.length > 0) { + process.stderr.write(`Available tests: ${discoveredTests.map(t => t.discoveryRelativeFileName).join(' ')}\n`); + return []; + } } return allTests; @@ -99,8 +190,11 @@ export class IntegrationTests { /** * Takes an optional list of tests to look for, otherwise * it will look for all tests from the directory + * + * @param tests Tests to include or exclude, undefined means include all tests. + * @param exclude Whether the 'tests' list is inclusive or exclusive (inclusive by default). */ - public async fromCliArgs(tests?: string[], exclude?: boolean): Promise { + public async fromCliArgs(tests?: string[], exclude?: boolean): Promise { const discoveredTests = await this.discover(); const allTests = this.filterTests(discoveredTests, tests, exclude); @@ -108,14 +202,14 @@ export class IntegrationTests { return allTests; } - private async discover(): Promise { + private async discover(): Promise { const files = await this.readTree(); const integs = files.filter(fileName => path.basename(fileName).startsWith('integ.') && path.basename(fileName).endsWith('.js')); return this.request(integs); } - private request(files: string[]): IntegTestConfig[] { - return files.map(fileName => { return { directory: this.directory, fileName }; }); + private request(files: string[]): IntegTest[] { + return files.map(fileName => new IntegTest({ discoveryRoot: this.directory, fileName })); } private async readTree(): Promise { diff --git a/packages/@aws-cdk/integ-runner/lib/runner/runner-base.ts b/packages/@aws-cdk/integ-runner/lib/runner/runner-base.ts index 6ebdce2eea40c..1a74184078769 100644 --- a/packages/@aws-cdk/integ-runner/lib/runner/runner-base.ts +++ b/packages/@aws-cdk/integ-runner/lib/runner/runner-base.ts @@ -6,9 +6,9 @@ import * as fs from 'fs-extra'; import { flatten } from '../utils'; import { DestructiveChange } from '../workers/common'; import { IntegTestSuite, LegacyIntegTestSuite } from './integ-test-suite'; +import { IntegTest } from './integration-tests'; import { AssemblyManifestReader, ManifestTrace } from './private/cloud-assembly'; -const CDK_OUTDIR_PREFIX = 'cdk-integ.out'; const DESTRUCTIVE_CHANGES = '!!DESTRUCTIVE_CHANGES:'; /** @@ -16,16 +16,9 @@ const DESTRUCTIVE_CHANGES = '!!DESTRUCTIVE_CHANGES:'; */ export interface IntegRunnerOptions { /** - * The name of the file that contains the integration test - * This should be a JavaScript file + * Information about the test to run */ - readonly fileName: string, - - /** - * The base directory where the tests are - * discovered from. - */ - readonly directory: string, + readonly test: IntegTest; /** * The AWS profile to use when invoking the CDK CLI @@ -76,13 +69,10 @@ export abstract class IntegRunner { */ public readonly testName: string; - /** - * The path to the integration test file - */ - protected readonly sourceFilePath: string; - /** * The value used in the '--app' CLI parameter + * + * Path to the integ test source file, relative to `this.directory`. */ protected readonly cdkApp: string; @@ -92,11 +82,6 @@ export abstract class IntegRunner { */ protected readonly cdkContextPath: string; - /** - * The relative path from the cwd to the snapshot directory - */ - protected readonly relativeSnapshotDir: string; - /** * The test suite from the existing snapshot */ @@ -113,6 +98,11 @@ export abstract class IntegRunner { */ protected readonly directory: string; + /** + * The test to run + */ + protected readonly test: IntegTest; + /** * Default options to pass to the CDK CLI */ @@ -124,6 +114,8 @@ export abstract class IntegRunner { /** * The directory where the CDK will be synthed to + * + * Relative to cwd. */ protected readonly cdkOutDir: string; @@ -133,22 +125,10 @@ export abstract class IntegRunner { private legacyContext?: Record; constructor(options: IntegRunnerOptions) { - const parsed = path.parse(options.fileName); - this.directory = parsed.dir; - const testName = parsed.name.slice(6); - - // if we are running in a package directory then juse use the fileName - // as the testname, but if we are running in a parent directory with - // multiple packages then use the directory/filename as the testname - if (parsed.dir === 'test') { - this.testName = testName; - } else { - const relativePath = path.relative(options.directory, parsed.dir); - this.testName = `${relativePath ? relativePath + '/' : ''}${parsed.name}`; - } - this.snapshotDir = path.join(this.directory, `${testName}.integ.snapshot`); - this.relativeSnapshotDir = `${testName}.integ.snapshot`; - this.sourceFilePath = path.join(this.directory, parsed.base); + this.test = options.test; + this.directory = this.test.directory; + this.testName = this.test.testName; + this.snapshotDir = this.test.snapshotDir; this.cdkContextPath = path.join(this.directory, 'cdk.context.json'); this.cdk = options.cdk ?? new CdkCliWrapper({ @@ -157,8 +137,8 @@ export abstract class IntegRunner { ...options.env, }, }); - this.cdkOutDir = options.integOutDir ?? `${CDK_OUTDIR_PREFIX}.${testName}`; - this.cdkApp = `node ${parsed.base}`; + this.cdkOutDir = options.integOutDir ?? this.test.temporaryOutputDir; + this.cdkApp = `node ${path.relative(this.directory, this.test.fileName)}`; this.profile = options.profile; if (this.hasSnapshot()) { this.expectedTestSuite = this.loadManifest(); @@ -194,9 +174,9 @@ export abstract class IntegRunner { // use the "expected" context. This is only run in order to read the manifest CDK_CONTEXT_JSON: JSON.stringify(this.getContext(this.expectedTestSuite?.synthContext)), }, - output: this.cdkOutDir, + output: path.relative(this.directory, this.cdkOutDir), }); - return this.loadManifest(path.join(this.directory, this.cdkOutDir)); + return this.loadManifest(this.cdkOutDir); } /** @@ -221,22 +201,22 @@ export abstract class IntegRunner { const testCases = LegacyIntegTestSuite.fromLegacy({ cdk: this.cdk, testName: this.testName, - integSourceFilePath: this.sourceFilePath, + integSourceFilePath: this.test.fileName, listOptions: { ...this.defaultArgs, all: true, app: this.cdkApp, profile: this.profile, - output: this.cdkOutDir, + output: path.relative(this.directory, this.cdkOutDir), }, }); - this.legacyContext = LegacyIntegTestSuite.getPragmaContext(this.sourceFilePath); + this.legacyContext = LegacyIntegTestSuite.getPragmaContext(this.test.fileName); return testCases; } } protected cleanup(): void { - const cdkOutPath = path.join(this.directory, this.cdkOutDir); + const cdkOutPath = this.cdkOutDir; if (fs.existsSync(cdkOutPath)) { fs.removeSync(cdkOutPath); } @@ -330,10 +310,10 @@ export abstract class IntegRunner { ...DEFAULT_SYNTH_OPTIONS.env, CDK_CONTEXT_JSON: JSON.stringify(this.getContext()), }, - output: this.relativeSnapshotDir, + output: path.relative(this.directory, this.snapshotDir), }); } else { - fs.moveSync(path.join(this.directory, this.cdkOutDir), this.snapshotDir, { overwrite: true }); + fs.moveSync(this.cdkOutDir, this.snapshotDir, { overwrite: true }); } this.cleanupSnapshot(); diff --git a/packages/@aws-cdk/integ-runner/lib/runner/snapshot-test-runner.ts b/packages/@aws-cdk/integ-runner/lib/runner/snapshot-test-runner.ts index e7c95beff0a56..956d392cbea2a 100644 --- a/packages/@aws-cdk/integ-runner/lib/runner/snapshot-test-runner.ts +++ b/packages/@aws-cdk/integ-runner/lib/runner/snapshot-test-runner.ts @@ -47,11 +47,11 @@ export class IntegSnapshotRunner extends IntegRunner { this.cdk.synthFast({ execCmd: this.cdkApp.split(' '), env, - output: this.cdkOutDir, + output: path.relative(this.directory, this.cdkOutDir), }); // read the "actual" snapshot - const actualDir = path.join(this.directory, this.cdkOutDir); + const actualDir = this.cdkOutDir; const actualStacks = this.readAssembly(actualDir); // only diff stacks that are part of the test case const actualStacksToDiff: Record = {}; diff --git a/packages/@aws-cdk/integ-runner/lib/utils.ts b/packages/@aws-cdk/integ-runner/lib/utils.ts index 4eaef31727867..74e5eb2154ae7 100644 --- a/packages/@aws-cdk/integ-runner/lib/utils.ts +++ b/packages/@aws-cdk/integ-runner/lib/utils.ts @@ -40,3 +40,61 @@ export function flatten(xs: T[][]): T[] { export function chain(commands: string[]): string { return commands.filter(c => !!c).join(' && '); } + + +/** + * A class holding a set of items which are being crossed off in time + * + * If it takes too long to cross off a new item, print the list. + */ +export class WorkList { + private readonly remaining = new Set(this.items); + private readonly timeout: number; + private timer?: NodeJS.Timeout; + + constructor(private readonly items: A[], private readonly options: WorkListOptions = {}) { + this.timeout = options.timeout ?? 60_000; + this.scheduleTimer(); + } + + public crossOff(item: A) { + this.remaining.delete(item); + this.stopTimer(); + if (this.remaining.size > 0) { + this.scheduleTimer(); + } + } + + public done() { + this.remaining.clear(); + } + + private stopTimer() { + if (this.timer) { + clearTimeout(this.timer); + this.timer = undefined; + } + } + + private scheduleTimer() { + this.timer = setTimeout(() => this.report(), this.timeout); + } + + private report() { + this.options.onTimeout?.(this.remaining); + } +} + +export interface WorkListOptions { + /** + * When to reply with remaining items + * + * @default 60000 + */ + readonly timeout?: number; + + /** + * Function to call when timeout hits + */ + readonly onTimeout?: (x: Set) => void; +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/lib/workers/common.ts b/packages/@aws-cdk/integ-runner/lib/workers/common.ts index 9b10c6b195f93..0ceb3bfa5786e 100644 --- a/packages/@aws-cdk/integ-runner/lib/workers/common.ts +++ b/packages/@aws-cdk/integ-runner/lib/workers/common.ts @@ -2,7 +2,7 @@ import { format } from 'util'; import { ResourceImpact } from '@aws-cdk/cloudformation-diff'; import * as chalk from 'chalk'; import * as logger from '../logger'; -import { IntegTestConfig } from '../runner/integration-tests'; +import { IntegTestInfo } from '../runner/integration-tests'; /** * The aggregate results from running assertions on a test case @@ -28,7 +28,7 @@ export interface AssertionResult { /** * Config for an integration test */ -export interface IntegTestWorkerConfig extends IntegTestConfig { +export interface IntegTestWorkerConfig extends IntegTestInfo { /** * A list of any destructive changes * @@ -112,7 +112,7 @@ export interface IntegBatchResponse { /** * List of failed tests */ - readonly failedTests: IntegTestConfig[]; + readonly failedTests: IntegTestInfo[]; /** * List of Integration test metrics. Each entry in the @@ -178,12 +178,22 @@ export enum DiagnosticReason { */ TEST_FAILED = 'TEST_FAILED', + /** + * There was an error running the integration test + */ + TEST_ERROR = 'TEST_ERROR', + /** * The snapshot test failed because the actual * snapshot was different than the expected snapshot */ SNAPSHOT_FAILED = 'SNAPSHOT_FAILED', + /** + * The snapshot test failed because there was an error executing it + */ + SNAPSHOT_ERROR = 'SNAPSHOT_ERROR', + /** * The snapshot test succeeded */ @@ -255,24 +265,39 @@ export function formatAssertionResults(results: AssertionResults): string { export function printResults(diagnostic: Diagnostic): void { switch (diagnostic.reason) { case DiagnosticReason.SNAPSHOT_SUCCESS: - logger.success(' %s No Change! %s', diagnostic.testName, chalk.gray(`${diagnostic.duration}s`)); + logger.success(' UNCHANGED %s %s', diagnostic.testName, chalk.gray(`${diagnostic.duration}s`)); break; case DiagnosticReason.TEST_SUCCESS: - logger.success(' %s Test Succeeded! %s', diagnostic.testName, chalk.gray(`${diagnostic.duration}s`)); + logger.success(' SUCCESS %s %s', diagnostic.testName, chalk.gray(`${diagnostic.duration}s`)); break; case DiagnosticReason.NO_SNAPSHOT: - logger.error(' %s - No Snapshot! %s', diagnostic.testName, chalk.gray(`${diagnostic.duration}s`)); + logger.error(' NEW %s %s', diagnostic.testName, chalk.gray(`${diagnostic.duration}s`)); break; case DiagnosticReason.SNAPSHOT_FAILED: - logger.error(' %s - Snapshot changed! %s\n%s', diagnostic.testName, chalk.gray(`${diagnostic.duration}s`), diagnostic.message); + logger.error(' CHANGED %s %s\n %s', diagnostic.testName, chalk.gray(`${diagnostic.duration}s`), diagnostic.message); + break; + case DiagnosticReason.SNAPSHOT_ERROR: + case DiagnosticReason.TEST_ERROR: + logger.error(' ERROR %s %s\n %s', diagnostic.testName, chalk.gray(`${diagnostic.duration}s`), diagnostic.message); break; case DiagnosticReason.TEST_FAILED: - logger.error(' %s - Failed! %s\n%s', diagnostic.testName, chalk.gray(`${diagnostic.duration}s`), diagnostic.message); + logger.error(' FAILED %s %s\n %s', diagnostic.testName, chalk.gray(`${diagnostic.duration}s`), diagnostic.message); break; case DiagnosticReason.ASSERTION_FAILED: - logger.error(' %s - Assertions Failed! %s\n%s', diagnostic.testName, chalk.gray(`${diagnostic.duration}s`), diagnostic.message); + logger.error(' ASSERT %s %s\n %s', diagnostic.testName, chalk.gray(`${diagnostic.duration}s`), diagnostic.message); + break; } for (const addl of diagnostic.additionalMessages ?? []) { logger.print(` ${addl}`); } } + +export function printLaggards(testNames: Set) { + const parts = [ + ' ', + `Waiting for ${testNames.size} more`, + testNames.size < 10 ? ['(', Array.from(testNames).join(', '), ')'].join('') : '', + ]; + + logger.print(chalk.grey(parts.filter(x => x).join(' '))); +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/lib/workers/extract/extract_worker.ts b/packages/@aws-cdk/integ-runner/lib/workers/extract/extract_worker.ts index 957341647e90f..595dc3a323172 100644 --- a/packages/@aws-cdk/integ-runner/lib/workers/extract/extract_worker.ts +++ b/packages/@aws-cdk/integ-runner/lib/workers/extract/extract_worker.ts @@ -1,6 +1,6 @@ import * as workerpool from 'workerpool'; import { IntegSnapshotRunner, IntegTestRunner } from '../../runner'; -import { IntegTestConfig } from '../../runner/integration-tests'; +import { IntegTest, IntegTestInfo } from '../../runner/integration-tests'; import { DiagnosticReason, IntegTestWorkerConfig, SnapshotVerificationOptions, Diagnostic, formatAssertionResults } from '../common'; import { IntegTestBatchRequest } from '../integ-test-worker'; @@ -13,20 +13,22 @@ import { IntegTestBatchRequest } from '../integ-test-worker'; * If the tests succeed it will then save the snapshot */ export function integTestWorker(request: IntegTestBatchRequest): IntegTestWorkerConfig[] { - const failures: IntegTestConfig[] = []; - for (const test of request.tests) { - const runner = new IntegTestRunner({ - directory: test.directory, - fileName: test.fileName, - profile: request.profile, - env: { - AWS_REGION: request.region, - }, - }, test.destructiveChanges); - + const failures: IntegTestInfo[] = []; + for (const testInfo of request.tests) { + const test = new IntegTest(testInfo); // Hydrate from data const start = Date.now(); - const tests = runner.actualTests(); + try { + const runner = new IntegTestRunner({ + test, + profile: request.profile, + env: { + AWS_REGION: request.region, + }, + }, testInfo.destructiveChanges); + + const tests = runner.actualTests(); + if (!tests || Object.keys(tests).length === 0) { throw new Error(`No tests defined for ${runner.testName}`); } @@ -39,7 +41,7 @@ export function integTestWorker(request: IntegTestBatchRequest): IntegTestWorker updateWorkflow: request.updateWorkflow, }); if (results) { - failures.push(test); + failures.push(testInfo); workerpool.workerEmit({ reason: DiagnosticReason.ASSERTION_FAILED, testName: `${runner.testName}-${testCaseName} (${request.profile}/${request.region})`, @@ -55,7 +57,7 @@ export function integTestWorker(request: IntegTestBatchRequest): IntegTestWorker }); } } catch (e) { - failures.push(test); + failures.push(testInfo); workerpool.workerEmit({ reason: DiagnosticReason.TEST_FAILED, testName: `${runner.testName}-${testCaseName} (${request.profile}/${request.region})`, @@ -65,11 +67,11 @@ export function integTestWorker(request: IntegTestBatchRequest): IntegTestWorker } } } catch (e) { - failures.push(test); + failures.push(testInfo); workerpool.workerEmit({ - reason: DiagnosticReason.TEST_FAILED, - testName: `${test.fileName} (${request.profile}/${request.region})`, - message: `Integration test failed: ${e}`, + reason: DiagnosticReason.TEST_ERROR, + testName: `${testInfo.fileName} (${request.profile}/${request.region})`, + message: `Error during integration test: ${e}`, duration: (Date.now() - start) / 1000, }); } @@ -84,19 +86,30 @@ export function integTestWorker(request: IntegTestBatchRequest): IntegTestWorker * if there is an existing snapshot, and if there is will * check if there are any changes */ -export function snapshotTestWorker(test: IntegTestConfig, options: SnapshotVerificationOptions = {}): IntegTestWorkerConfig[] { +export function snapshotTestWorker(testInfo: IntegTestInfo, options: SnapshotVerificationOptions = {}): IntegTestWorkerConfig[] { const failedTests = new Array(); - const runner = new IntegSnapshotRunner({ fileName: test.fileName, directory: test.directory }); const start = Date.now(); + const test = new IntegTest(testInfo); // Hydrate the data record again + + const timer = setTimeout(() => { + workerpool.workerEmit({ + reason: DiagnosticReason.SNAPSHOT_ERROR, + testName: test.testName, + message: 'Test is taking a very long time', + duration: (Date.now() - start) / 1000, + }); + }, 60_000); + try { + const runner = new IntegSnapshotRunner({ test }); if (!runner.hasSnapshot()) { workerpool.workerEmit({ reason: DiagnosticReason.NO_SNAPSHOT, - testName: runner.testName, + testName: test.testName, message: 'No Snapshot', duration: (Date.now() - start) / 1000, }); - failedTests.push(test); + failedTests.push(test.info); } else { const { diagnostics, destructiveChanges } = runner.testSnapshot(options); if (diagnostics.length > 0) { @@ -105,27 +118,28 @@ export function snapshotTestWorker(test: IntegTestConfig, options: SnapshotVerif duration: (Date.now() - start) / 1000, } as Diagnostic)); failedTests.push({ - fileName: test.fileName, - directory: test.directory, + ...test.info, destructiveChanges, }); } else { workerpool.workerEmit({ reason: DiagnosticReason.SNAPSHOT_SUCCESS, - testName: runner.testName, + testName: test.testName, message: 'Success', duration: (Date.now() - start) / 1000, } as Diagnostic); } } } catch (e) { - failedTests.push(test); + failedTests.push(test.info); workerpool.workerEmit({ message: e.message, - testName: runner.testName, - reason: DiagnosticReason.SNAPSHOT_FAILED, + testName: test.testName, + reason: DiagnosticReason.SNAPSHOT_ERROR, duration: (Date.now() - start) / 1000, } as Diagnostic); + } finally { + clearTimeout(timer); } return failedTests; diff --git a/packages/@aws-cdk/integ-runner/lib/workers/integ-snapshot-worker.ts b/packages/@aws-cdk/integ-runner/lib/workers/integ-snapshot-worker.ts index 1ff88e6cba5d6..072b6c28544b4 100644 --- a/packages/@aws-cdk/integ-runner/lib/workers/integ-snapshot-worker.ts +++ b/packages/@aws-cdk/integ-runner/lib/workers/integ-snapshot-worker.ts @@ -1,8 +1,8 @@ import * as workerpool from 'workerpool'; import * as logger from '../logger'; -import { IntegTestConfig } from '../runner/integration-tests'; -import { flatten } from '../utils'; -import { printSummary, printResults, IntegTestWorkerConfig, SnapshotVerificationOptions } from './common'; +import { IntegTest } from '../runner/integration-tests'; +import { flatten, WorkList } from '../utils'; +import { printSummary, printResults, IntegTestWorkerConfig, SnapshotVerificationOptions, printLaggards } from './common'; /** * Run Snapshot tests @@ -11,19 +11,28 @@ import { printSummary, printResults, IntegTestWorkerConfig, SnapshotVerification */ export async function runSnapshotTests( pool: workerpool.WorkerPool, - tests: IntegTestConfig[], + tests: IntegTest[], options: SnapshotVerificationOptions, ): Promise { logger.highlight('\nVerifying integration test snapshots...\n'); + const todo = new WorkList(tests.map(t => t.testName), { + onTimeout: printLaggards, + }); + const failedTests: IntegTestWorkerConfig[][] = await Promise.all( - tests.map((test) => pool.exec('snapshotTestWorker', [test, options], { - on: printResults, + tests.map((test) => pool.exec('snapshotTestWorker', [test.info /* Dehydrate class -> data */, options], { + on: (x) => { + todo.crossOff(x.testName); + printResults(x); + }, })), ); + todo.done(); const testsToRun = flatten(failedTests); logger.highlight('\nSnapshot Results: \n'); printSummary(tests.length, testsToRun.length); return testsToRun; + } diff --git a/packages/@aws-cdk/integ-runner/lib/workers/integ-test-worker.ts b/packages/@aws-cdk/integ-runner/lib/workers/integ-test-worker.ts index 9c6b4fb2465c6..8ce8fb658b5d5 100644 --- a/packages/@aws-cdk/integ-runner/lib/workers/integ-test-worker.ts +++ b/packages/@aws-cdk/integ-runner/lib/workers/integ-test-worker.ts @@ -1,6 +1,6 @@ import * as workerpool from 'workerpool'; import * as logger from '../logger'; -import { IntegTestConfig } from '../runner/integration-tests'; +import { IntegTestInfo } from '../runner/integration-tests'; import { flatten } from '../utils'; import { printResults, printSummary, IntegBatchResponse, IntegTestOptions, IntegRunnerMetrics } from './common'; @@ -127,7 +127,7 @@ export async function runIntegrationTestsInParallel( if (!test) break; const testStart = Date.now(); logger.highlight(`Running test ${test.fileName} in ${worker.profile ? worker.profile + '/' : ''}${worker.region}`); - const response: IntegTestConfig[][] = await options.pool.exec('integTestWorker', [{ + const response: IntegTestInfo[][] = await options.pool.exec('integTestWorker', [{ region: worker.region, profile: worker.profile, tests: [test], diff --git a/packages/@aws-cdk/integ-runner/test/runner/integ-test-runner.test.ts b/packages/@aws-cdk/integ-runner/test/runner/integ-test-runner.test.ts index db4c4a434554d..c2b0987c3ba2b 100644 --- a/packages/@aws-cdk/integ-runner/test/runner/integ-test-runner.test.ts +++ b/packages/@aws-cdk/integ-runner/test/runner/integ-test-runner.test.ts @@ -3,7 +3,7 @@ import { Manifest } from '@aws-cdk/cloud-assembly-schema'; import { AVAILABILITY_ZONE_FALLBACK_CONTEXT_KEY } from '@aws-cdk/cx-api'; import { SynthFastOptions, DestroyOptions, ListOptions, SynthOptions, DeployOptions } from 'cdk-cli-wrapper'; import * as fs from 'fs-extra'; -import { IntegTestRunner } from '../../lib/runner'; +import { IntegTestRunner, IntegTest } from '../../lib/runner'; import { MockCdkProvider } from '../helpers'; let cdkMock: MockCdkProvider; @@ -55,8 +55,10 @@ describe('IntegTest runIntegTests', () => { // WHEN const integTest = new IntegTestRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.test-with-snapshot.js', - directory: 'test/test-data', + test: new IntegTest({ + fileName: 'test/test-data/integ.test-with-snapshot.js', + discoveryRoot: 'test/test-data', + }), }); integTest.runIntegTestCase({ testCaseName: 'integ.test-with-snapshot', @@ -107,8 +109,10 @@ describe('IntegTest runIntegTests', () => { // WHEN const integTest = new IntegTestRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.integ-test1.js', - directory: 'test/test-data', + test: new IntegTest({ + fileName: 'test/test-data/integ.integ-test1.js', + discoveryRoot: 'test/test-data', + }), }); integTest.runIntegTestCase({ testCaseName: 'integ.integ-test1', @@ -149,8 +153,10 @@ describe('IntegTest runIntegTests', () => { // WHEN const integTest = new IntegTestRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.test-with-snapshot-assets-diff.js', - directory: 'test/test-data', + test: new IntegTest({ + fileName: 'test/test-data/integ.test-with-snapshot-assets-diff.js', + discoveryRoot: 'test/test-data', + }), }); integTest.runIntegTestCase({ testCaseName: 'integ.test-with-snapshot-assets-diff', @@ -206,8 +212,10 @@ describe('IntegTest runIntegTests', () => { // WHEN const integTest = new IntegTestRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.integ-test1.js', - directory: 'test/test-data', + test: new IntegTest({ + fileName: 'test/test-data/integ.integ-test1.js', + discoveryRoot: 'test/test-data', + }), }); integTest.runIntegTestCase({ testCaseName: 'integ.integ-test1', @@ -224,8 +232,10 @@ describe('IntegTest runIntegTests', () => { // WHEN const integTest = new IntegTestRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.integ-test1.js', - directory: 'test/test-data', + test: new IntegTest({ + fileName: 'test/test-data/integ.integ-test1.js', + discoveryRoot: 'test/test-data', + }), }); integTest.runIntegTestCase({ testCaseName: 'integ.integ-test1', @@ -242,8 +252,10 @@ describe('IntegTest runIntegTests', () => { // WHEN const integTest = new IntegTestRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.integ-test1.js', - directory: 'test', + test: new IntegTest({ + fileName: 'test/test-data/integ.integ-test1.js', + discoveryRoot: 'test', + }), }); // THEN @@ -261,8 +273,10 @@ describe('IntegTest runIntegTests', () => { // WHEN new IntegTestRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.integ-test1.js', - directory: 'test/test-data', + test: new IntegTest({ + fileName: 'test/test-data/integ.integ-test1.js', + discoveryRoot: 'test/test-data', + }), }); // THEN @@ -280,8 +294,10 @@ describe('IntegTest runIntegTests', () => { // WHEN const integTest = new IntegTestRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.integ-test2.js', - directory: 'test', + test: new IntegTest({ + fileName: 'test/test-data/integ.integ-test2.js', + discoveryRoot: 'test', + }), }); // THEN @@ -307,9 +323,11 @@ describe('IntegTest runIntegTests', () => { // WHEN const integTest = new IntegTestRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.integ-test1.js', + test: new IntegTest({ + fileName: 'test/test-data/integ.integ-test1.js', + discoveryRoot: 'test/test-data', + }), profile: 'test-profile', - directory: 'test/test-data', }); integTest.runIntegTestCase({ testCaseName: 'integ.integ-test1', @@ -348,8 +366,10 @@ describe('IntegTest runIntegTests', () => { test('with hooks', () => { const integTest = new IntegTestRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.test-with-snapshot-assets.js', - directory: 'test/test-data', + test: new IntegTest({ + fileName: 'test/test-data/integ.test-with-snapshot-assets.js', + discoveryRoot: 'test/test-data', + }), }); integTest.runIntegTestCase({ testCaseName: 'integ.test-with-snapshot-assets', @@ -393,8 +413,10 @@ describe('IntegTest runIntegTests', () => { // WHEN const integTest = new IntegTestRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.test-with-snapshot.js', - directory: 'test/test-data', + test: new IntegTest({ + fileName: 'test/test-data/integ.test-with-snapshot.js', + discoveryRoot: 'test/test-data', + }), }); integTest.runIntegTestCase({ testCaseName: 'integ.test-with-snapshot', @@ -429,8 +451,10 @@ describe('IntegTest runIntegTests', () => { // WHEN const integTest = new IntegTestRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.test-with-snapshot.js', - directory: 'test/test-data', + test: new IntegTest({ + fileName: 'test/test-data/integ.test-with-snapshot.js', + discoveryRoot: 'test/test-data', + }), }); integTest.runIntegTestCase({ testCaseName: 'integ.test-with-snapshot', @@ -466,8 +490,10 @@ describe('IntegTest runIntegTests', () => { // WHEN const integTest = new IntegTestRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.test-with-snapshot.js', - directory: 'test/test-data', + test: new IntegTest({ + fileName: 'test/test-data/integ.test-with-snapshot.js', + discoveryRoot: 'test/test-data', + }), }); integTest.runIntegTestCase({ testCaseName: 'integ.test-with-snapshot', @@ -484,8 +510,10 @@ describe('IntegTest runIntegTests', () => { test('with assets manifest, assets are removed if stackUpdateWorkflow is disabled', () => { const integTest = new IntegTestRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.test-with-snapshot-assets.js', - directory: 'test/test-data', + test: new IntegTest({ + fileName: 'test/test-data/integ.test-with-snapshot-assets.js', + discoveryRoot: 'test/test-data', + }), }); integTest.runIntegTestCase({ testCaseName: 'integ.test-with-snapshot-assets', @@ -503,8 +531,10 @@ describe('IntegTest runIntegTests', () => { test('with assembly manifest, assets are removed if stackUpdateWorkflow is disabled', () => { const integTest = new IntegTestRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.test-with-snapshot-assets-diff.js', - directory: 'test/test-data', + test: new IntegTest({ + fileName: 'test/test-data/integ.test-with-snapshot-assets-diff.js', + discoveryRoot: 'test/test-data', + }), }); integTest.runIntegTestCase({ testCaseName: 'integ.test-with-snapshot-assets-diff', diff --git a/packages/@aws-cdk/integ-runner/test/runner/integration-tests.test.ts b/packages/@aws-cdk/integ-runner/test/runner/integration-tests.test.ts index 1185f848a232a..22b796b9fe52e 100644 --- a/packages/@aws-cdk/integ-runner/test/runner/integration-tests.test.ts +++ b/packages/@aws-cdk/integ-runner/test/runner/integration-tests.test.ts @@ -2,8 +2,6 @@ import * as mockfs from 'mock-fs'; import { IntegrationTests } from '../../lib/runner/integration-tests'; describe('IntegrationTests', () => { - const testsFile = '/tmp/foo/bar/does/not/exist/tests.json'; - const testsFileExclude = '/tmp/foo/bar/does/not/exist/tests-exclude.json'; const tests = new IntegrationTests('test'); let stderrMock: jest.SpyInstance; stderrMock = jest.spyOn(process.stderr, 'write').mockImplementation(() => { return true; }); @@ -14,17 +12,6 @@ describe('IntegrationTests', () => { 'integ.integ-test2.js': 'content', 'integ.integ-test3.js': 'content', }, - [testsFileExclude]: JSON.stringify({ - exclude: true, - tests: [ - 'test-data/integ.integ-test1.js', - ], - }), - [testsFile]: JSON.stringify({ - tests: [ - 'test-data/integ.integ-test1.js', - ], - }), }); }); @@ -60,46 +47,4 @@ describe('IntegrationTests', () => { 'test/test-data/integ.integ-test1.js', ); }); - - test('from file', async () => { - const integTests = await tests.fromFile(testsFile); - - const fileNames = integTests.map(test => test.fileName); - expect(integTests.length).toEqual(1); - expect(fileNames).toContain( - 'test/test-data/integ.integ-test1.js', - ); - }); - - test('from file, test not found', async () => { - mockfs({ - 'test/test-data': { - 'integ.integ-test1.js': 'content', - }, - [testsFile]: JSON.stringify({ - tests: [ - 'test-data/integ.integ-test16.js', - ], - }), - }); - const integTests = await tests.fromFile(testsFile); - - expect(integTests.length).toEqual(0); - expect(stderrMock.mock.calls[0][0]).toContain( - 'No such integ test: test-data/integ.integ-test16.js', - ); - expect(stderrMock.mock.calls[1][0]).toContain( - 'Available tests: test-data/integ.integ-test1.js test-data/integ.integ-test2.js test-data/integ.integ-test3.js', - ); - }); - - test('from file exclude', async () => { - const integTests = await tests.fromFile(testsFileExclude); - - const fileNames = integTests.map(test => test.fileName); - expect(integTests.length).toEqual(2); - expect(fileNames).not.toContain( - 'test/test-data/integ.integ-test1.js', - ); - }); }); diff --git a/packages/@aws-cdk/integ-runner/test/runner/snapshot-test-runner.test.ts b/packages/@aws-cdk/integ-runner/test/runner/snapshot-test-runner.test.ts index 28d9e80c79477..1370589d36252 100644 --- a/packages/@aws-cdk/integ-runner/test/runner/snapshot-test-runner.test.ts +++ b/packages/@aws-cdk/integ-runner/test/runner/snapshot-test-runner.test.ts @@ -2,7 +2,7 @@ import * as child_process from 'child_process'; import * as path from 'path'; import { SynthFastOptions, DestroyOptions, ListOptions, SynthOptions, DeployOptions } from 'cdk-cli-wrapper'; import * as fs from 'fs-extra'; -import { IntegSnapshotRunner } from '../../lib/runner'; +import { IntegSnapshotRunner, IntegTest } from '../../lib/runner'; import { DiagnosticReason } from '../../lib/workers/common'; import { MockCdkProvider } from '../helpers'; @@ -46,9 +46,11 @@ describe('IntegTest runSnapshotTests', () => { // WHEN const integTest = new IntegSnapshotRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.test-with-snapshot.js', - directory: 'test/test-data', - integOutDir: 'test-with-snapshot.integ.snapshot', + test: new IntegTest({ + fileName: 'test/test-data/integ.test-with-snapshot.js', + discoveryRoot: 'test/test-data', + }), + integOutDir: 'test/test-data/test-with-snapshot.integ.snapshot', }); const results = integTest.testSnapshot(); @@ -69,8 +71,10 @@ describe('IntegTest runSnapshotTests', () => { // WHEN const integTest = new IntegSnapshotRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.test-with-snapshot.js', - directory: 'test/test-data', + test: new IntegTest({ + fileName: 'test/test-data/integ.test-with-snapshot.js', + discoveryRoot: 'test/test-data', + }), }); const results = integTest.testSnapshot(); @@ -95,9 +99,11 @@ describe('IntegTest runSnapshotTests', () => { // WHEN const integTest = new IntegSnapshotRunner({ cdk: cdkMock.cdk, - fileName: 'test/test-data/integ.test-with-snapshot.js', - directory: 'test/test-data', - integOutDir: 'test-with-snapshot-diff.integ.snapshot', + test: new IntegTest({ + fileName: 'test/test-data/integ.test-with-snapshot.js', + discoveryRoot: 'test/test-data', + }), + integOutDir: 'test/test-data/test-with-snapshot-diff.integ.snapshot', }); const results = integTest.testSnapshot(); @@ -134,9 +140,11 @@ describe('IntegTest runSnapshotTests', () => { // WHEN const integTest = new IntegSnapshotRunner({ cdk: cdkMock.cdk, - fileName: path.join(__dirname, '../test-data/integ.test-with-snapshot-assets-diff.js'), - directory: 'test/test-data', - integOutDir: 'test-with-snapshot-assets.integ.snapshot', + test: new IntegTest({ + fileName: path.join(__dirname, '../test-data/integ.test-with-snapshot-assets-diff.js'), + discoveryRoot: 'test/test-data', + }), + integOutDir: 'test/test-data/test-with-snapshot-assets.integ.snapshot', }); expect(() => { integTest.testSnapshot(); @@ -158,9 +166,11 @@ describe('IntegTest runSnapshotTests', () => { // WHEN const integTest = new IntegSnapshotRunner({ cdk: cdkMock.cdk, - fileName: path.join(__dirname, '../test-data/integ.test-with-snapshot-assets.js'), - directory: 'test/test-data', - integOutDir: 'test-with-snapshot-assets-diff.integ.snapshot', + test: new IntegTest({ + fileName: path.join(__dirname, '../test-data/integ.test-with-snapshot-assets.js'), + discoveryRoot: 'test/test-data', + }), + integOutDir: 'test/test-data/test-with-snapshot-assets-diff.integ.snapshot', }); const results = integTest.testSnapshot(); diff --git a/packages/@aws-cdk/integ-runner/test/workers/integ-worker.test.ts b/packages/@aws-cdk/integ-runner/test/workers/integ-worker.test.ts index 54864d2421b4a..0c1634faf759e 100644 --- a/packages/@aws-cdk/integ-runner/test/workers/integ-worker.test.ts +++ b/packages/@aws-cdk/integ-runner/test/workers/integ-worker.test.ts @@ -65,7 +65,7 @@ describe('test runner', () => { // WHEN const test = { fileName: 'test/test-data/integ.integ-test1.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }; integTestWorker({ tests: [test], @@ -89,7 +89,7 @@ describe('test runner', () => { // WHEN const test = { fileName: 'test/test-data/integ.integ-test2.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }; spawnSyncMock.mockReset(); jest.spyOn(child_process, 'spawnSync').mockReturnValue({ @@ -108,7 +108,7 @@ describe('test runner', () => { // THEN expect(results[0]).toEqual({ fileName: expect.stringMatching(/integ.integ-test2.js$/), - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }); }); @@ -116,7 +116,7 @@ describe('test runner', () => { // WHEN const test = { fileName: 'test/test-data/integ.test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }; const results = integTestWorker({ tests: [test], @@ -154,7 +154,7 @@ describe('test runner', () => { // WHEN const test = { fileName: 'test/test-data/integ.test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }; jest.spyOn(child_process, 'spawnSync').mockReturnValue({ status: 1, @@ -171,7 +171,7 @@ describe('test runner', () => { expect(results[0]).toEqual({ fileName: 'test/test-data/integ.test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }); }); }); @@ -181,11 +181,11 @@ describe('parallel worker', () => { const tests = [ { fileName: 'integ.test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, { fileName: 'integ.another-test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, ]; await runIntegrationTests({ @@ -211,7 +211,7 @@ describe('parallel worker', () => { test('run tests', async () => { const tests = [{ fileName: 'integ.test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }]; const results = await runIntegrationTestsInParallel({ pool, @@ -226,7 +226,7 @@ describe('parallel worker', () => { failedTests: expect.arrayContaining([ { fileName: 'integ.test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, ]), metrics: expect.arrayContaining([ @@ -245,19 +245,19 @@ describe('parallel worker', () => { const tests = [ { fileName: 'integ.test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, { fileName: 'integ.another-test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, { fileName: 'integ.another-test-with-snapshot2.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, { fileName: 'integ.another-test-with-snapshot3.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, ]; const results = await runIntegrationTestsInParallel({ @@ -283,19 +283,19 @@ describe('parallel worker', () => { failedTests: expect.arrayContaining([ { fileName: 'integ.test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, { fileName: 'integ.another-test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, { fileName: 'integ.another-test-with-snapshot2.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, { fileName: 'integ.another-test-with-snapshot3.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, ]), metrics: expect.arrayContaining([ @@ -339,11 +339,11 @@ describe('parallel worker', () => { const tests = [ { fileName: 'integ.test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, { fileName: 'integ.another-test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, ]; const results = await runIntegrationTestsInParallel({ @@ -362,11 +362,11 @@ describe('parallel worker', () => { failedTests: expect.arrayContaining([ { fileName: 'integ.test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, { fileName: 'integ.another-test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, ]), metrics: expect.arrayContaining([ @@ -392,11 +392,11 @@ describe('parallel worker', () => { const tests = [ { fileName: 'integ.test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, { fileName: 'integ.another-test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, ]; const results = await runIntegrationTestsInParallel({ @@ -415,11 +415,11 @@ describe('parallel worker', () => { failedTests: expect.arrayContaining([ { fileName: 'integ.another-test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, { fileName: 'integ.test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, ]), metrics: expect.arrayContaining([ @@ -439,11 +439,11 @@ describe('parallel worker', () => { const tests = [ { fileName: 'integ.test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, { fileName: 'integ.another-test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, ]; const results = await runIntegrationTestsInParallel({ @@ -462,11 +462,11 @@ describe('parallel worker', () => { failedTests: expect.arrayContaining([ { fileName: 'integ.test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, { fileName: 'integ.another-test-with-snapshot.js', - directory: 'test/test-data', + discoveryRoot: 'test/test-data', }, ]), metrics: expect.arrayContaining([ diff --git a/packages/@aws-cdk/integ-runner/test/workers/mock-extract_worker.ts b/packages/@aws-cdk/integ-runner/test/workers/mock-extract_worker.ts index 18f517e5f4dee..f761b5a2a4429 100644 --- a/packages/@aws-cdk/integ-runner/test/workers/mock-extract_worker.ts +++ b/packages/@aws-cdk/integ-runner/test/workers/mock-extract_worker.ts @@ -1,9 +1,9 @@ import * as workerpool from 'workerpool'; -import { IntegTestConfig } from '../../lib/runner'; +import { IntegTestInfo } from '../../lib/runner'; import { IntegTestBatchRequest } from '../../lib/workers/integ-test-worker'; -function integTestWorker(request: IntegTestBatchRequest): IntegTestConfig[] { +function integTestWorker(request: IntegTestBatchRequest): IntegTestInfo[] { return request.tests; } diff --git a/packages/@aws-cdk/integ-runner/test/workers/snapshot-worker.test.ts b/packages/@aws-cdk/integ-runner/test/workers/snapshot-worker.test.ts index 5b47873fd9987..ac9d4cda13f60 100644 --- a/packages/@aws-cdk/integ-runner/test/workers/snapshot-worker.test.ts +++ b/packages/@aws-cdk/integ-runner/test/workers/snapshot-worker.test.ts @@ -22,7 +22,7 @@ describe('Snapshot tests', () => { // WHEN const test = { fileName: path.join(directory, 'integ.integ-test1.js'), - directory: directory, + discoveryRoot: directory, }; const result = snapshotTestWorker(test); @@ -36,7 +36,7 @@ describe('Snapshot tests', () => { jest.spyOn(child_process, 'spawnSync').mockResolvedValue; const test = { fileName: path.join(directory, 'integ.test-with-snapshot.js'), - directory: directory, + discoveryRoot: directory, }; const result = snapshotTestWorker(test); @@ -49,7 +49,7 @@ describe('Snapshot tests', () => { jest.spyOn(child_process, 'spawnSync').mockRejectedValue; const test = { fileName: path.join(directory, 'integ.test-with-snapshot-assets.js'), - directory, + discoveryRoot: directory, destructiveChanges: [], }; const result = snapshotTestWorker(test); From 33acc7cc03c4a6700c05e840393ef90e5d8f68dc Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 31 May 2022 17:17:22 +0200 Subject: [PATCH 13/25] feat(cognito): OpenID Connect identity provider (#20241) Add the `UserPoolIdentityProviderOidc` class to create an OpenID Connect identity provider for user pools. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *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-cognito/README.md | 1 + .../aws-cognito/lib/user-pool-idps/index.ts | 3 +- .../aws-cognito/lib/user-pool-idps/oidc.ts | 157 +++++++++++++ packages/@aws-cdk/aws-cognito/package.json | 3 +- .../test/integ.user-pool-idp.oidc.ts | 45 ++++ .../user-pool-idp.oidc.integ.snapshot/cdk.out | 1 + .../integ-user-pool-idp-google.template.json | 121 ++++++++++ .../integ.json | 14 ++ .../manifest.json | 52 +++++ .../tree.json | 202 +++++++++++++++++ .../test/user-pool-idps/oidc.test.ts | 214 ++++++++++++++++++ 11 files changed, 811 insertions(+), 2 deletions(-) create mode 100644 packages/@aws-cdk/aws-cognito/lib/user-pool-idps/oidc.ts create mode 100644 packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.oidc.ts create mode 100644 packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/integ-user-pool-idp-google.template.json create mode 100644 packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/tree.json create mode 100644 packages/@aws-cdk/aws-cognito/test/user-pool-idps/oidc.test.ts diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index a052d97824a5c..8e2720be896f3 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -503,6 +503,7 @@ The following third-party identity providers are currently supported in the CDK - [Facebook Login](https://developers.facebook.com/docs/facebook-login/) - [Google Login](https://developers.google.com/identity/sign-in/web/sign-in) - [Sign In With Apple](https://developer.apple.com/sign-in-with-apple/get-started/) +- [OpenID Connect](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-oidc-idp.html) The following code configures a user pool to federate with the third party provider, 'Login with Amazon'. The identity provider needs to be configured with a set of credentials that the Cognito backend can use to federate with the diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts index 321ee0ecad5d9..fd7ad04af70fe 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts @@ -2,4 +2,5 @@ export * from './base'; export * from './apple'; export * from './amazon'; export * from './facebook'; -export * from './google'; \ No newline at end of file +export * from './google'; +export * from './oidc'; diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/oidc.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/oidc.ts new file mode 100644 index 0000000000000..f23e80adef4de --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/oidc.ts @@ -0,0 +1,157 @@ +import { Names, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnUserPoolIdentityProvider } from '../cognito.generated'; +import { UserPoolIdentityProviderProps } from './base'; +import { UserPoolIdentityProviderBase } from './private/user-pool-idp-base'; + +/** + * Properties to initialize UserPoolIdentityProviderOidc + */ +export interface UserPoolIdentityProviderOidcProps extends UserPoolIdentityProviderProps { + /** + * The client id + */ + readonly clientId: string; + + /** + * The client secret + */ + readonly clientSecret: string; + + /** + * Issuer URL + */ + readonly issuerUrl: string; + + /** + * The name of the provider + * + * @default - the unique ID of the construct + */ + readonly name?: string; + + /** + * The OAuth 2.0 scopes that you will request from OpenID Connect. Scopes are + * groups of OpenID Connect user attributes to exchange with your app. + * + * @default ['openid'] + */ + readonly scopes?: string[]; + + /** + * Identifiers + * + * Identifiers can be used to redirect users to the correct IdP in multitenant apps. + * + * @default - no identifiers used + */ + readonly identifiers?: string[] + + /** + * The method to use to request attributes + * + * @default OidcAttributeRequestMethod.GET + */ + readonly attributeRequestMethod?: OidcAttributeRequestMethod + + /** + * OpenID connect endpoints + * + * @default - auto discovered with issuer URL + */ + readonly endpoints?: OidcEndpoints; +} + +/** + * OpenID Connect endpoints + */ +export interface OidcEndpoints { + /** + * Authorization endpoint + */ + readonly authorization: string; + + /** + * Token endpoint + */ + readonly token: string; + + /** + * UserInfo endpoint + */ + readonly userInfo: string; + + /** + * Jwks_uri endpoint + */ + readonly jwksUri: string; +} + +/** + * The method to use to request attributes + */ +export enum OidcAttributeRequestMethod { + /** GET */ + GET = 'GET', + /** POST */ + POST = 'POST' +} + +/** + * Represents a identity provider that integrates with OpenID Connect + * @resource AWS::Cognito::UserPoolIdentityProvider + */ +export class UserPoolIdentityProviderOidc extends UserPoolIdentityProviderBase { + public readonly providerName: string; + + constructor(scope: Construct, id: string, props: UserPoolIdentityProviderOidcProps) { + super(scope, id, props); + + if (props.name && !Token.isUnresolved(props.name) && (props.name.length < 3 || props.name.length > 32)) { + throw new Error(`Expected provider name to be between 3 and 32 characters, received ${props.name} (${props.name.length} characters)`); + } + + const scopes = props.scopes ?? ['openid']; + + const resource = new CfnUserPoolIdentityProvider(this, 'Resource', { + userPoolId: props.userPool.userPoolId, + providerName: this.getProviderName(props.name), + providerType: 'OIDC', + providerDetails: { + client_id: props.clientId, + client_secret: props.clientSecret, + authorize_scopes: scopes.join(' '), + attributes_request_method: props.attributeRequestMethod ?? OidcAttributeRequestMethod.GET, + oidc_issuer: props.issuerUrl, + authorize_url: props.endpoints?.authorization, + token_url: props.endpoints?.token, + attributes_url: props.endpoints?.userInfo, + jwks_uri: props.endpoints?.jwksUri, + }, + idpIdentifiers: props.identifiers, + attributeMapping: super.configureAttributeMapping(), + }); + + this.providerName = super.getResourceNameAttribute(resource.ref); + } + + private getProviderName(name?: string): string { + if (name) { + if (!Token.isUnresolved(name) && (name.length < 3 || name.length > 32)) { + throw new Error(`Expected provider name to be between 3 and 32 characters, received ${name} (${name.length} characters)`); + } + return name; + } + + const uniqueId = Names.uniqueId(this); + + if (uniqueId.length < 3) { + return `${uniqueId}oidc`; + } + + if (uniqueId.length > 32) { + return uniqueId.substring(0, 16) + uniqueId.substring(uniqueId.length - 16); + } + return uniqueId; + } +} diff --git a/packages/@aws-cdk/aws-cognito/package.json b/packages/@aws-cdk/aws-cognito/package.json index 44d19ecf0ce92..506d0cd1935bc 100644 --- a/packages/@aws-cdk/aws-cognito/package.json +++ b/packages/@aws-cdk/aws-cognito/package.json @@ -122,7 +122,8 @@ "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderFacebookProps", "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderAmazonProps", "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderGoogleProps", - "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderAppleProps" + "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderAppleProps", + "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderOidcProps" ] }, "stability": "stable", diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.oidc.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.oidc.ts new file mode 100644 index 0000000000000..159c7e305d2ff --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.oidc.ts @@ -0,0 +1,45 @@ +import { App, CfnOutput, RemovalPolicy, Stack } from '@aws-cdk/core'; +import { ProviderAttribute, UserPool, UserPoolIdentityProviderOidc } from '../lib'; + +/* + * Stack verification steps + * * Visit the URL provided by stack output 'SignInLink' in a browser, and verify the 'cdk' sign in link shows up. + */ +const app = new App(); +const stack = new Stack(app, 'integ-user-pool-idp-google'); + +const userpool = new UserPool(stack, 'pool', { + removalPolicy: RemovalPolicy.DESTROY, +}); + +new UserPoolIdentityProviderOidc(stack, 'cdk', { + userPool: userpool, + name: 'cdk', + clientId: 'client-id', + clientSecret: 'client-secret', + issuerUrl: 'https://www.issuer-url.com', + endpoints: { + authorization: 'https://www.issuer-url.com/authorize', + token: 'https://www.issuer-url.com/token', + userInfo: 'https://www.issuer-url.com/userinfo', + jwksUri: 'https://www.issuer-url.com/jwks', + }, + scopes: ['openid', 'phone'], + attributeMapping: { + phoneNumber: ProviderAttribute.other('phone_number'), + }, +}); + +const client = userpool.addClient('client'); + +const domain = userpool.addDomain('domain', { + cognitoDomain: { + domainPrefix: 'cdk-test-pool', + }, +}); + +new CfnOutput(stack, 'SignInLink', { + value: domain.signInUrl(client, { + redirectUri: 'https://example.com', + }), +}); diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..2efc89439fab8 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"18.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/integ-user-pool-idp-google.template.json b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/integ-user-pool-idp-google.template.json new file mode 100644 index 0000000000000..f5a02bb5abf1f --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/integ-user-pool-idp-google.template.json @@ -0,0 +1,121 @@ +{ + "Resources": { + "pool056F3F7E": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { + "Name": "verified_phone_number", + "Priority": 1 + }, + { + "Name": "verified_email", + "Priority": 2 + } + ] + }, + "AdminCreateUserConfig": { + "AllowAdminCreateUserOnly": true + }, + "EmailVerificationMessage": "The verification code to your new account is {####}", + "EmailVerificationSubject": "Verify your new account", + "SmsVerificationMessage": "The verification code to your new account is {####}", + "VerificationMessageTemplate": { + "DefaultEmailOption": "CONFIRM_WITH_CODE", + "EmailMessage": "The verification code to your new account is {####}", + "EmailSubject": "Verify your new account", + "SmsMessage": "The verification code to your new account is {####}" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "poolclient2623294C": { + "Type": "AWS::Cognito::UserPoolClient", + "Properties": { + "UserPoolId": { + "Ref": "pool056F3F7E" + }, + "AllowedOAuthFlows": [ + "implicit", + "code" + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin" + ], + "CallbackURLs": [ + "https://example.com" + ], + "SupportedIdentityProviders": [ + { + "Ref": "cdk52888317" + }, + "COGNITO" + ] + } + }, + "pooldomain430FA744": { + "Type": "AWS::Cognito::UserPoolDomain", + "Properties": { + "Domain": "cdk-test-pool", + "UserPoolId": { + "Ref": "pool056F3F7E" + } + } + }, + "cdk52888317": { + "Type": "AWS::Cognito::UserPoolIdentityProvider", + "Properties": { + "ProviderName": "cdk", + "ProviderType": "OIDC", + "UserPoolId": { + "Ref": "pool056F3F7E" + }, + "AttributeMapping": { + "phone_number": "phone_number" + }, + "ProviderDetails": { + "client_id": "client-id", + "client_secret": "client-secret", + "authorize_scopes": "openid phone", + "attributes_request_method": "GET", + "oidc_issuer": "https://www.issuer-url.com", + "authorize_url": "https://www.issuer-url.com/authorize", + "token_url": "https://www.issuer-url.com/token", + "attributes_url": "https://www.issuer-url.com/userinfo", + "jwks_uri": "https://www.issuer-url.com/jwks" + } + } + } + }, + "Outputs": { + "SignInLink": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "pooldomain430FA744" + }, + ".auth.", + { + "Ref": "AWS::Region" + }, + ".amazoncognito.com/login?client_id=", + { + "Ref": "poolclient2623294C" + }, + "&response_type=code&redirect_uri=https://example.com" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/integ.json b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/integ.json new file mode 100644 index 0000000000000..6c7dfa2d2f275 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/integ.json @@ -0,0 +1,14 @@ +{ + "version": "18.0.0", + "testCases": { + "integ.user-pool-idp.oidc": { + "stacks": [ + "integ-user-pool-idp-google" + ], + "diffAssets": false, + "stackUpdateWorkflow": true + } + }, + "synthContext": {}, + "enableLookups": false +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..ff3038ce4b3e3 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/manifest.json @@ -0,0 +1,52 @@ +{ + "version": "18.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "integ-user-pool-idp-google": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integ-user-pool-idp-google.template.json", + "validateOnSynth": false + }, + "metadata": { + "/integ-user-pool-idp-google/pool/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "pool056F3F7E" + } + ], + "/integ-user-pool-idp-google/pool/client/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "poolclient2623294C" + } + ], + "/integ-user-pool-idp-google/pool/domain/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "pooldomain430FA744" + } + ], + "/integ-user-pool-idp-google/cdk/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "cdk52888317" + } + ], + "/integ-user-pool-idp-google/SignInLink": [ + { + "type": "aws:cdk:logicalId", + "data": "SignInLink" + } + ] + }, + "displayName": "integ-user-pool-idp-google" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/tree.json b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/tree.json new file mode 100644 index 0000000000000..ded3b9a167598 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.oidc.integ.snapshot/tree.json @@ -0,0 +1,202 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "@aws-cdk/core.Construct", + "version": "0.0.0" + } + }, + "integ-user-pool-idp-google": { + "id": "integ-user-pool-idp-google", + "path": "integ-user-pool-idp-google", + "children": { + "pool": { + "id": "pool", + "path": "integ-user-pool-idp-google/pool", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-user-pool-idp-google/pool/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Cognito::UserPool", + "aws:cdk:cloudformation:props": { + "accountRecoverySetting": { + "recoveryMechanisms": [ + { + "name": "verified_phone_number", + "priority": 1 + }, + { + "name": "verified_email", + "priority": 2 + } + ] + }, + "adminCreateUserConfig": { + "allowAdminCreateUserOnly": true + }, + "emailVerificationMessage": "The verification code to your new account is {####}", + "emailVerificationSubject": "Verify your new account", + "smsVerificationMessage": "The verification code to your new account is {####}", + "verificationMessageTemplate": { + "defaultEmailOption": "CONFIRM_WITH_CODE", + "emailMessage": "The verification code to your new account is {####}", + "emailSubject": "Verify your new account", + "smsMessage": "The verification code to your new account is {####}" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.CfnUserPool", + "version": "0.0.0" + } + }, + "client": { + "id": "client", + "path": "integ-user-pool-idp-google/pool/client", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-user-pool-idp-google/pool/client/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Cognito::UserPoolClient", + "aws:cdk:cloudformation:props": { + "userPoolId": { + "Ref": "pool056F3F7E" + }, + "allowedOAuthFlows": [ + "implicit", + "code" + ], + "allowedOAuthFlowsUserPoolClient": true, + "allowedOAuthScopes": [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin" + ], + "callbackUrLs": [ + "https://example.com" + ], + "supportedIdentityProviders": [ + { + "Ref": "cdk52888317" + }, + "COGNITO" + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.CfnUserPoolClient", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.UserPoolClient", + "version": "0.0.0" + } + }, + "domain": { + "id": "domain", + "path": "integ-user-pool-idp-google/pool/domain", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-user-pool-idp-google/pool/domain/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Cognito::UserPoolDomain", + "aws:cdk:cloudformation:props": { + "domain": "cdk-test-pool", + "userPoolId": { + "Ref": "pool056F3F7E" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.CfnUserPoolDomain", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.UserPoolDomain", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.UserPool", + "version": "0.0.0" + } + }, + "cdk": { + "id": "cdk", + "path": "integ-user-pool-idp-google/cdk", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-user-pool-idp-google/cdk/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Cognito::UserPoolIdentityProvider", + "aws:cdk:cloudformation:props": { + "providerName": "cdk", + "providerType": "OIDC", + "userPoolId": { + "Ref": "pool056F3F7E" + }, + "attributeMapping": { + "phone_number": "phone_number" + }, + "providerDetails": { + "client_id": "client-id", + "client_secret": "client-secret", + "authorize_scopes": "openid phone", + "attributes_request_method": "GET", + "oidc_issuer": "https://www.issuer-url.com", + "authorize_url": "https://www.issuer-url.com/authorize", + "token_url": "https://www.issuer-url.com/token", + "attributes_url": "https://www.issuer-url.com/userinfo", + "jwks_uri": "https://www.issuer-url.com/jwks" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.CfnUserPoolIdentityProvider", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cognito.UserPoolIdentityProviderOidc", + "version": "0.0.0" + } + }, + "SignInLink": { + "id": "SignInLink", + "path": "integ-user-pool-idp-google/SignInLink", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idps/oidc.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/oidc.test.ts new file mode 100644 index 0000000000000..c28feb073ae13 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/oidc.test.ts @@ -0,0 +1,214 @@ +import { Template } from '@aws-cdk/assertions'; +import { Stack } from '@aws-cdk/core'; +import { ProviderAttribute, UserPool, UserPoolIdentityProviderOidc } from '../../lib'; + +describe('UserPoolIdentityProvider', () => { + describe('oidc', () => { + test('defaults', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderOidc(stack, 'userpoolidp', { + userPool: pool, + clientId: 'client-id', + clientSecret: 'client-secret', + issuerUrl: 'https://my-issuer-url.com', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'userpoolidp', + ProviderType: 'OIDC', + ProviderDetails: { + client_id: 'client-id', + client_secret: 'client-secret', + authorize_scopes: 'openid', + attributes_request_method: 'GET', + oidc_issuer: 'https://my-issuer-url.com', + }, + }); + }); + + test('endpoints', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderOidc(stack, 'userpoolidp', { + userPool: pool, + clientId: 'client-id', + clientSecret: 'client-secret', + issuerUrl: 'https://my-issuer-url.com', + endpoints: { + authorization: 'https://my-issuer-url.com/authorize', + token: 'https://my-issuer-url.com/token', + userInfo: 'https://my-issuer-url.com/userinfo', + jwksUri: 'https://my-issuer-url.com/jwks', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ProviderDetails: { + client_id: 'client-id', + client_secret: 'client-secret', + authorize_scopes: 'openid', + attributes_request_method: 'GET', + oidc_issuer: 'https://my-issuer-url.com', + authorize_url: 'https://my-issuer-url.com/authorize', + token_url: 'https://my-issuer-url.com/token', + attributes_url: 'https://my-issuer-url.com/userinfo', + jwks_uri: 'https://my-issuer-url.com/jwks', + }, + }); + }); + + test('scopes', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderOidc(stack, 'userpoolidp', { + userPool: pool, + clientId: 'client-id', + clientSecret: 'client-secret', + issuerUrl: 'https://my-issuer-url.com', + scopes: ['scope1', 'scope2'], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ProviderDetails: { + client_id: 'client-id', + client_secret: 'client-secret', + authorize_scopes: 'scope1 scope2', + attributes_request_method: 'GET', + oidc_issuer: 'https://my-issuer-url.com', + }, + }); + }); + + test('registered with user pool', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + const provider = new UserPoolIdentityProviderOidc(stack, 'userpoolidp', { + userPool: pool, + clientId: 'client-id', + clientSecret: 'client-secret', + issuerUrl: 'https://my-issuer-url.com', + }); + + // THEN + expect(pool.identityProviders).toContain(provider); + }); + + test('attribute mapping', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderOidc(stack, 'userpoolidp', { + userPool: pool, + clientId: 'client-id', + clientSecret: 'client-secret', + issuerUrl: 'https://my-issuer-url.com', + attributeMapping: { + familyName: ProviderAttribute.other('family_name'), + givenName: ProviderAttribute.other('given_name'), + custom: { + customAttr1: ProviderAttribute.other('email'), + customAttr2: ProviderAttribute.other('sub'), + }, + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + AttributeMapping: { + family_name: 'family_name', + given_name: 'given_name', + customAttr1: 'email', + customAttr2: 'sub', + }, + }); + }); + + test('with provider name', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderOidc(stack, 'userpoolidp', { + userPool: pool, + name: 'my-provider', + clientId: 'client-id', + clientSecret: 'client-secret', + issuerUrl: 'https://my-issuer-url.com', + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'my-provider', + }); + }); + + test('throws with invalid provider name', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // THEN + expect(() => new UserPoolIdentityProviderOidc(stack, 'userpoolidp', { + userPool: pool, + name: 'xy', + clientId: 'client-id', + clientSecret: 'client-secret', + issuerUrl: 'https://my-issuer-url.com', + })).toThrow(/Expected provider name to be between 3 and 32 characters/); + }); + + test('generates a valid name when unique id is too short', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderOidc(stack, 'xy', { + userPool: pool, + clientId: 'client-id', + clientSecret: 'client-secret', + issuerUrl: 'https://my-issuer-url.com', + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'xyoidc', + }); + }); + + test('generates a valid name when unique id is too long', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProviderOidc(stack, `${'oidc'.repeat(10)}xyz`, { + userPool: pool, + clientId: 'client-id', + clientSecret: 'client-secret', + issuerUrl: 'https://my-issuer-url.com', + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'oidcoidcoidcoidccoidcoidcoidcxyz', + }); + }); + }); +}); From 27fdaf1be55b1057a59a690484ce8c4891780134 Mon Sep 17 00:00:00 2001 From: Ashok <1902568+ashokm@users.noreply.github.com> Date: Tue, 31 May 2022 18:01:08 +0200 Subject: [PATCH 14/25] chore(lambda-nodejs): fix typo (#20557) --- packages/@aws-cdk/aws-lambda-nodejs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index a21e5c9a6001d..61e25f81f8ffe 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -102,7 +102,7 @@ If `esbuild` is available it will be used to bundle your code in your environmen bundling will happen in a [Lambda compatible Docker container](https://gallery.ecr.aws/sam/build-nodejs12.x) with the Docker platform based on the target architecture of the Lambda function. -For macOS the recommendend approach is to install `esbuild` as Docker volume performance is really poor. +For macOS the recommended approach is to install `esbuild` as Docker volume performance is really poor. `esbuild` can be installed with: From 2bf30df223cc5bb43c2fcfaaf32669a8438ad19a Mon Sep 17 00:00:00 2001 From: Joshua Weber <57131123+daschaa@users.noreply.github.com> Date: Tue, 31 May 2022 18:47:05 +0200 Subject: [PATCH 15/25] feat(s3): adds objectSizeLessThan property for s3 lifecycle rule (#20429) Fixes (other half - see #20425) of #20372. This implements the `objectSizeLessThan` property for a S3 lifecycle rule. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *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-s3/README.md | 1 + packages/@aws-cdk/aws-s3/lib/bucket.ts | 1 + packages/@aws-cdk/aws-s3/lib/rule.ts | 8 +++++++- packages/@aws-cdk/aws-s3/test/integ.lifecycle.ts | 1 + .../lifecycle.integ.snapshot/aws-cdk-s3.template.json | 1 + packages/@aws-cdk/aws-s3/test/rules.test.ts | 4 +++- 6 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-s3/README.md b/packages/@aws-cdk/aws-s3/README.md index 988b360c01d3c..e34dd8067f0ef 100644 --- a/packages/@aws-cdk/aws-s3/README.md +++ b/packages/@aws-cdk/aws-s3/README.md @@ -578,6 +578,7 @@ const bucket = new s3.Bucket(this, 'MyBucket', { }], objectSizeGreaterThan: 500, prefix: 'prefix', + objectSizeLessThan: 10000, transitions: [{ storageClass: s3.StorageClass.GLACIER, diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index d51d79165f61c..d2be56356ae7f 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -1923,6 +1923,7 @@ export class Bucket extends BucketBase { })), expiredObjectDeleteMarker: rule.expiredObjectDeleteMarker, tagFilters: self.parseTagFilters(rule.tagFilters), + objectSizeLessThan: rule.objectSizeLessThan, objectSizeGreaterThan: rule.objectSizeGreaterThan, }; diff --git a/packages/@aws-cdk/aws-s3/lib/rule.ts b/packages/@aws-cdk/aws-s3/lib/rule.ts index b79af075ed848..943130c1a20f9 100644 --- a/packages/@aws-cdk/aws-s3/lib/rule.ts +++ b/packages/@aws-cdk/aws-s3/lib/rule.ts @@ -120,7 +120,13 @@ export interface LifecycleRule { readonly expiredObjectDeleteMarker?: boolean; /** - * Specifies the minimum object size in bytes for this rule to apply to. + * Specifies the maximum object size in bytes for this rule to apply to. + * + * @default - No rule + */ + readonly objectSizeLessThan?: number; + + /** Specifies the minimum object size in bytes for this rule to apply to. * * @default - No rule */ diff --git a/packages/@aws-cdk/aws-s3/test/integ.lifecycle.ts b/packages/@aws-cdk/aws-s3/test/integ.lifecycle.ts index 0fe92eda9a7eb..973a0e562232c 100644 --- a/packages/@aws-cdk/aws-s3/test/integ.lifecycle.ts +++ b/packages/@aws-cdk/aws-s3/test/integ.lifecycle.ts @@ -12,6 +12,7 @@ new Bucket(stack, 'MyBucket', { expirationDate: new Date('2019-10-01'), }, { + objectSizeLessThan: 500, objectSizeGreaterThan: 500, }, ], diff --git a/packages/@aws-cdk/aws-s3/test/lifecycle.integ.snapshot/aws-cdk-s3.template.json b/packages/@aws-cdk/aws-s3/test/lifecycle.integ.snapshot/aws-cdk-s3.template.json index df2d4d718d3cf..d811988919f3f 100644 --- a/packages/@aws-cdk/aws-s3/test/lifecycle.integ.snapshot/aws-cdk-s3.template.json +++ b/packages/@aws-cdk/aws-s3/test/lifecycle.integ.snapshot/aws-cdk-s3.template.json @@ -10,6 +10,7 @@ "Status": "Enabled" }, { + "ObjectSizeLessThan": "500", "ObjectSizeGreaterThan": "500", "Status": "Enabled" } diff --git a/packages/@aws-cdk/aws-s3/test/rules.test.ts b/packages/@aws-cdk/aws-s3/test/rules.test.ts index 5be1225ebc4d1..b327494d19267 100644 --- a/packages/@aws-cdk/aws-s3/test/rules.test.ts +++ b/packages/@aws-cdk/aws-s3/test/rules.test.ts @@ -292,13 +292,14 @@ describe('rules', () => { }); }); - test('Bucket with objectSizeGreaterThan', () => { + test('Bucket with object size rules', () => { // GIVEN const stack = new Stack(); // WHEN new Bucket(stack, 'Bucket', { lifecycleRules: [{ + objectSizeLessThan: 0, objectSizeGreaterThan: 0, }], }); @@ -307,6 +308,7 @@ describe('rules', () => { Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { LifecycleConfiguration: { Rules: [{ + ObjectSizeLessThan: 0, ObjectSizeGreaterThan: 0, Status: 'Enabled', }], From 68761dc3ceadbe77e241fb85544e48544149568a Mon Sep 17 00:00:00 2001 From: Vincent Voyer Date: Tue, 31 May 2022 19:33:56 +0200 Subject: [PATCH 16/25] feat(lambda): add insights version 1.0.135.0 (#19588) This commits adds Lambda Insights layer version 1.0.135.0 for x86 and arm. Ref: - https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-extension-versionsx86-64.html#Lambda-Insights-extension-1.0.135.0 - https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-extension-versionsARM.html#Lambda-Insights-extension-ARM-1.0.135.0 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `cdk-integ` to deploy the infrastructure and generate the snapshot (i.e. `cdk-integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-lambda/lib/lambda-insights.ts | 5 ++ .../region-info/build-tools/fact-tables.ts | 72 +++++++++++++++++++ .../__snapshots__/region-info.test.js.snap | 60 ++++++++++++++++ 3 files changed, 137 insertions(+) diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-insights.ts b/packages/@aws-cdk/aws-lambda/lib/lambda-insights.ts index 4d1eee91cf10a..265e72535f812 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-insights.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda-insights.ts @@ -47,6 +47,11 @@ export abstract class LambdaInsightsVersion { */ public static readonly VERSION_1_0_119_0 = LambdaInsightsVersion.fromInsightsVersion('1.0.119.0'); + /** + * Version 1.0.135.0 + */ + public static readonly VERSION_1_0_135_0 = LambdaInsightsVersion.fromInsightsVersion('1.0.135.0'); + /** * Use the insights extension associated with the provided ARN. Make sure the ARN is associated * with same region as your function diff --git a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts index c5de5d2e6e785..502618d1edc16 100644 --- a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts +++ b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts @@ -187,6 +187,78 @@ export const APPMESH_ECR_ACCOUNTS: { [region: string]: string } = { // https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-extension-versions.html export const CLOUDWATCH_LAMBDA_INSIGHTS_ARNS: { [key: string]: any } = { + '1.0.135.0': { + arm64: { + // US East (N. Virginia) + 'us-east-1': 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension-Arm64:2', + // US East (Ohio) + 'us-east-2': 'arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension-Arm64:2', + // US West (Oregon) + 'us-west-2': 'arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:2', + // Asia Pacific (Mumbai) + 'ap-south-1': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension-Arm64:2', + // Asia Pacific (Singapore) + 'ap-southeast-1': 'arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension-Arm64:2', + // Asia Pacific (Sydney) + 'ap-southeast-2': 'arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension-Arm64:2', + // Asia Pacific (Tokyo) + 'ap-northeast-1': 'arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension-Arm64:2', + // Europe (Frankfurt) + 'eu-central-1': 'arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension-Arm64:2', + // Europe (Ireland) + 'eu-west-1': 'arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension-Arm64:2', + // Europe (London) + 'eu-west-2': 'arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:2', + }, + x86_64: { + // US East (N. Virginia) + 'us-east-1': 'arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:18', + // US East (Ohio) + 'us-east-2': 'arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:18', + // US West (N. California) + 'us-west-1': 'arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:18', + // US West (Oregon) + 'us-west-2': 'arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:18', + // Africa (Cape Town) + 'af-south-1': 'arn:aws:lambda:af-south-1:012438385374:layer:LambdaInsightsExtension:11', + // Asia Pacific (Hong Kong) + 'ap-east-1': 'arn:aws:lambda:ap-east-1:519774774795:layer:LambdaInsightsExtension:11', + // Asia Pacific (Mumbai) + 'ap-south-1': 'arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:18', + // Asia Pacific (Oskaka) + 'ap-northeast-3': 'arn:aws:lambda:ap-northeast-3:194566237122:layer:LambdaInsightsExtension:1', + // Asia Pacific (Seoul) + 'ap-northeast-2': 'arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:18', + // Asia Pacific (Singapore) + 'ap-southeast-1': 'arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:18', + // Asia Pacific (Sydney) + 'ap-southeast-2': 'arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:18', + // Asia Pacific (Tokyo) + 'ap-northeast-1': 'arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:25', + // Canada (Central) + 'ca-central-1': 'arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:18', + // China (Beijing) + 'cn-north-1': 'arn:aws-cn:lambda:cn-north-1:488211338238:layer:LambdaInsightsExtension:11', + // China (Ningxia) + 'cn-northwest-1': 'arn:aws-cn:lambda:cn-northwest-1:488211338238:layer:LambdaInsightsExtension:11', + // Europe (Frankfurt) + 'eu-central-1': 'arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:18', + // Europe (Ireland) + 'eu-west-1': 'arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:18', + // Europe (London) + 'eu-west-2': 'arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:18', + // Europe (Milan) + 'eu-south-1': 'arn:aws:lambda:eu-south-1:339249233099:layer:LambdaInsightsExtension:11', + // Europe (Paris) + 'eu-west-3': 'arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:18', + // Europe (Stockholm) + 'eu-north-1': 'arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:18', + // Middle East (Bahrain) + 'me-south-1': 'arn:aws:lambda:me-south-1:285320876703:layer:LambdaInsightsExtension:11', + // South America (Sao Paulo) + 'sa-east-1': 'arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:18', + }, + }, '1.0.119.0': { arm64: { // US East (N. Virginia) diff --git a/packages/@aws-cdk/region-info/test/__snapshots__/region-info.test.js.snap b/packages/@aws-cdk/region-info/test/__snapshots__/region-info.test.js.snap index 5a3c0e2683c2c..dae9b4dda9d16 100644 --- a/packages/@aws-cdk/region-info/test/__snapshots__/region-info.test.js.snap +++ b/packages/@aws-cdk/region-info/test/__snapshots__/region-info.test.js.snap @@ -7,9 +7,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:af-south-1:012438385374:layer:LambdaInsightsExtension:9", + "1.0.135.0": "arn:aws:lambda:af-south-1:012438385374:layer:LambdaInsightsExtension:11", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -38,9 +40,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-east-1:519774774795:layer:LambdaInsightsExtension:9", + "1.0.135.0": "arn:aws:lambda:ap-east-1:519774774795:layer:LambdaInsightsExtension:11", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -69,9 +73,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension-Arm64:1", + "1.0.135.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension-Arm64:2", }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:23", + "1.0.135.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:25", "1.0.54.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:12", @@ -100,9 +106,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:16", + "1.0.135.0": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:18", "1.0.54.0": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ap-northeast-2:580247275435:layer:LambdaInsightsExtension:12", @@ -131,9 +139,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": "arn:aws:lambda:ap-northeast-3:194566237122:layer:LambdaInsightsExtension:1", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -162,9 +172,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension-Arm64:1", + "1.0.135.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension-Arm64:2", }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:16", + "1.0.135.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:18", "1.0.54.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ap-south-1:580247275435:layer:LambdaInsightsExtension:12", @@ -193,9 +205,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension-Arm64:1", + "1.0.135.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension-Arm64:2", }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:16", + "1.0.135.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:18", "1.0.54.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ap-southeast-1:580247275435:layer:LambdaInsightsExtension:12", @@ -224,9 +238,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension-Arm64:1", + "1.0.135.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension-Arm64:2", }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:16", + "1.0.135.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:18", "1.0.54.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ap-southeast-2:580247275435:layer:LambdaInsightsExtension:12", @@ -255,9 +271,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -286,9 +304,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:16", + "1.0.135.0": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:18", "1.0.54.0": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:ca-central-1:580247275435:layer:LambdaInsightsExtension:12", @@ -317,9 +337,11 @@ Object { "domainSuffix": "amazonaws.com.cn", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws-cn:lambda:cn-north-1:488211338238:layer:LambdaInsightsExtension:9", + "1.0.135.0": "arn:aws-cn:lambda:cn-north-1:488211338238:layer:LambdaInsightsExtension:11", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -348,9 +370,11 @@ Object { "domainSuffix": "amazonaws.com.cn", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws-cn:lambda:cn-northwest-1:488211338238:layer:LambdaInsightsExtension:9", + "1.0.135.0": "arn:aws-cn:lambda:cn-northwest-1:488211338238:layer:LambdaInsightsExtension:11", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -379,9 +403,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension-Arm64:1", + "1.0.135.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension-Arm64:2", }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:16", + "1.0.135.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:18", "1.0.54.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:eu-central-1:580247275435:layer:LambdaInsightsExtension:12", @@ -410,9 +436,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:16", + "1.0.135.0": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:18", "1.0.54.0": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:eu-north-1:580247275435:layer:LambdaInsightsExtension:12", @@ -441,9 +469,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:eu-south-1:339249233099:layer:LambdaInsightsExtension:9", + "1.0.135.0": "arn:aws:lambda:eu-south-1:339249233099:layer:LambdaInsightsExtension:11", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -472,9 +502,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -503,9 +535,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension-Arm64:1", + "1.0.135.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension-Arm64:2", }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:16", + "1.0.135.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:18", "1.0.54.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:12", @@ -534,9 +568,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:1", + "1.0.135.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:2", }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:16", + "1.0.135.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:18", "1.0.54.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:12", @@ -565,9 +601,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:16", + "1.0.135.0": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:18", "1.0.54.0": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:eu-west-3:580247275435:layer:LambdaInsightsExtension:12", @@ -596,9 +634,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:me-south-1:285320876703:layer:LambdaInsightsExtension:9", + "1.0.135.0": "arn:aws:lambda:me-south-1:285320876703:layer:LambdaInsightsExtension:11", "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -627,9 +667,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:16", + "1.0.135.0": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:18", "1.0.54.0": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:sa-east-1:580247275435:layer:LambdaInsightsExtension:12", @@ -658,9 +700,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension-Arm64:1", + "1.0.135.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension-Arm64:2", }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:16", + "1.0.135.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:18", "1.0.54.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:12", @@ -689,9 +733,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension-Arm64:1", + "1.0.135.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension-Arm64:2", }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:16", + "1.0.135.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:18", "1.0.54.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:us-east-2:580247275435:layer:LambdaInsightsExtension:12", @@ -720,9 +766,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -751,9 +799,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -782,9 +832,11 @@ Object { "domainSuffix": "c2s.ic.gov", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -813,9 +865,11 @@ Object { "domainSuffix": "c2s.ic.gov", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -844,9 +898,11 @@ Object { "domainSuffix": "sc2s.sgov.gov", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, "1.0.54.0": undefined, "1.0.86.0": undefined, "1.0.89.0": undefined, @@ -875,9 +931,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": undefined, + "1.0.135.0": undefined, }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:16", + "1.0.135.0": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:18", "1.0.54.0": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:us-west-1:580247275435:layer:LambdaInsightsExtension:12", @@ -906,9 +964,11 @@ Object { "domainSuffix": "amazonaws.com", "lambdaInsightsArmVersions": Object { "1.0.119.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:1", + "1.0.135.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:2", }, "lambdaInsightsVersions": Object { "1.0.119.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:16", + "1.0.135.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:18", "1.0.54.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:2", "1.0.86.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:11", "1.0.89.0": "arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:12", From f19ecefcdde712dfd951106bec3b1f850b66f2a8 Mon Sep 17 00:00:00 2001 From: Kaizen Conroy <36202692+kaizencc@users.noreply.github.com> Date: Tue, 31 May 2022 13:26:23 -0500 Subject: [PATCH 17/25] fix(lambda): function version ignores layer version changes (#20150) Fixes #19098. This introduces two bug fixes that are hidden behind a feature flag to preserve the current hash: - lambda layer order is ignored by the hash now - lambda layer version is included in the hash (along with other lambda layer attributes) I also added a few more tests around this area to confirm the current behavior which should help demonstrate what the feature flag will change. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../cdk.out | 2 +- .../integ-distribution-lambda.template.json | 4 +- .../integ.json | 4 +- .../manifest.json | 13 +- .../tree.json | 2 +- .../aws-cdk-codedeploy-lambda.template.json | 4 +- .../deployment-group.integ.snapshot/cdk.out | 2 +- .../integ.json | 4 +- .../manifest.json | 13 +- .../deployment-group.integ.snapshot/tree.json | 2 +- packages/@aws-cdk/aws-lambda/README.md | 43 +++++- .../@aws-cdk/aws-lambda/lib/function-hash.ts | 35 ++++- packages/@aws-cdk/aws-lambda/lib/function.ts | 48 +++++-- packages/@aws-cdk/aws-lambda/lib/layers.ts | 4 +- .../aws-lambda/rosetta/default.ts-fixture | 3 +- .../aws-lambda-autoscaling.template.json | 5 +- .../autoscaling.lit.integ.snapshot/cdk.out | 2 +- .../autoscaling.lit.integ.snapshot/integ.json | 4 +- .../manifest.json | 13 +- .../autoscaling.lit.integ.snapshot/tree.json | 2 +- .../current-version.integ.snapshot/cdk.out | 2 +- .../current-version.integ.snapshot/integ.json | 4 +- .../lambda-test-current-version.template.json | 7 +- .../manifest.json | 18 ++- .../current-version.integ.snapshot/tree.json | 4 +- .../aws-lambda/test/function-hash.test.ts | 135 +++++++++++++++++- .../@aws-cdk/aws-lambda/test/function.test.ts | 90 +++++++++++- .../aws-lambda/test/integ.autoscaling.lit.ts | 7 +- .../aws-lambda/test/integ.current-version.ts | 9 +- .../test/integ.lambda.prov.concurrent.ts | 5 + .../@aws-cdk/aws-lambda/test/integ.lambda.ts | 5 + .../aws-cdk-lambda-1.template.json | 5 +- .../test/lambda.integ.snapshot/cdk.out | 2 +- .../test/lambda.integ.snapshot/integ.json | 4 +- .../test/lambda.integ.snapshot/manifest.json | 13 +- .../test/lambda.integ.snapshot/tree.json | 2 +- .../aws-cdk-lambda-pce-1.template.json | 10 +- .../cdk.out | 2 +- .../integ.json | 4 +- .../manifest.json | 24 +++- .../tree.json | 4 +- packages/@aws-cdk/cx-api/lib/features.ts | 3 + .../MyStack.template.json | 6 +- .../test/triggers.integ.snapshot/cdk.out | 2 +- .../test/triggers.integ.snapshot/integ.json | 4 +- .../triggers.integ.snapshot/manifest.json | 13 +- 46 files changed, 507 insertions(+), 86 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/cdk.out index 90bef2e09ad39..588d7b269d34f 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"17.0.0"} \ No newline at end of file +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/integ-distribution-lambda.template.json b/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/integ-distribution-lambda.template.json index bc53883f90d56..a67a9c58206d1 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/integ-distribution-lambda.template.json +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/integ-distribution-lambda.template.json @@ -53,7 +53,7 @@ "LambdaServiceRoleA8ED4D3B" ] }, - "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d": { + "LambdaCurrentVersionDF706F6A1ee13d0fa54e9f5621e8c7b616fc53fc": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -72,7 +72,7 @@ { "EventType": "origin-request", "LambdaFunctionARN": { - "Ref": "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d" + "Ref": "LambdaCurrentVersionDF706F6A1ee13d0fa54e9f5621e8c7b616fc53fc" } } ], diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/integ.json b/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/integ.json index a66e912a752e3..18e730b879c13 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/integ.json @@ -1,7 +1,7 @@ { - "version": "18.0.0", + "version": "20.0.0", "testCases": { - "aws-cloudfront/test/integ.distribution-lambda": { + "integ.distribution-lambda": { "stacks": [ "integ-distribution-lambda" ], diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/manifest.json index 8e8ff45bad838..77db96a39724b 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "17.0.0", + "version": "20.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -30,7 +30,7 @@ "/integ-distribution-lambda/Lambda/CurrentVersion/Resource": [ { "type": "aws:cdk:logicalId", - "data": "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d" + "data": "LambdaCurrentVersionDF706F6A1ee13d0fa54e9f5621e8c7b616fc53fc" } ], "/integ-distribution-lambda/Dist/Resource": [ @@ -38,6 +38,15 @@ "type": "aws:cdk:logicalId", "data": "DistB3B78991" } + ], + "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d": [ + { + "type": "aws:cdk:logicalId", + "data": "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } ] }, "displayName": "integ-distribution-lambda" diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/tree.json b/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/tree.json index 69962c261ce0b..7c6b25d4f82fc 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution-lambda.integ.snapshot/tree.json @@ -166,7 +166,7 @@ "lambdaFunctionAssociations": [ { "lambdaFunctionArn": { - "Ref": "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d" + "Ref": "LambdaCurrentVersionDF706F6A1ee13d0fa54e9f5621e8c7b616fc53fc" }, "eventType": "origin-request" } diff --git a/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/aws-cdk-codedeploy-lambda.template.json b/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/aws-cdk-codedeploy-lambda.template.json index 70a1779a5ea6b..53bc85fe76aa9 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/aws-cdk-codedeploy-lambda.template.json +++ b/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/aws-cdk-codedeploy-lambda.template.json @@ -85,7 +85,7 @@ "HandlerServiceRoleFCDC14AE" ] }, - "HandlerCurrentVersion93FB80BFb2a9ce598bf2730613c07e406cddb6b6": { + "HandlerCurrentVersion93FB80BFf2e6129c63154d1f37c0092df295ab51": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -101,7 +101,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "HandlerCurrentVersion93FB80BFb2a9ce598bf2730613c07e406cddb6b6", + "HandlerCurrentVersion93FB80BFf2e6129c63154d1f37c0092df295ab51", "Version" ] }, diff --git a/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/cdk.out index 90bef2e09ad39..588d7b269d34f 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"17.0.0"} \ No newline at end of file +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/integ.json b/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/integ.json index 75997c02f7318..9e8866896e5c8 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/integ.json @@ -1,7 +1,7 @@ { - "version": "18.0.0", + "version": "20.0.0", "testCases": { - "aws-codedeploy/test/lambda/integ.deployment-group": { + "lambda/integ.deployment-group": { "stacks": [ "aws-cdk-codedeploy-lambda" ], diff --git a/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/manifest.json index 01c947d34ebd3..7cd3a1d6fac26 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "17.0.0", + "version": "20.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -68,7 +68,7 @@ "/aws-cdk-codedeploy-lambda/Handler/CurrentVersion/Resource": [ { "type": "aws:cdk:logicalId", - "data": "HandlerCurrentVersion93FB80BFb2a9ce598bf2730613c07e406cddb6b6" + "data": "HandlerCurrentVersion93FB80BFf2e6129c63154d1f37c0092df295ab51" } ], "/aws-cdk-codedeploy-lambda/AssetParameters/edb7466707eb899fbaee22c1e67f9443e9edcc2eeda0b58d8448f7c4157746b3/S3Bucket": [ @@ -202,6 +202,15 @@ "type": "aws:cdk:logicalId", "data": "ServiceprincipalMap" } + ], + "HandlerCurrentVersion93FB80BFb2a9ce598bf2730613c07e406cddb6b6": [ + { + "type": "aws:cdk:logicalId", + "data": "HandlerCurrentVersion93FB80BFb2a9ce598bf2730613c07e406cddb6b6", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } ] }, "displayName": "aws-cdk-codedeploy-lambda" diff --git a/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/tree.json b/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/tree.json index 3c8a6e8b0cd6f..71475f602c3a4 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-codedeploy/test/lambda/deployment-group.integ.snapshot/tree.json @@ -313,7 +313,7 @@ }, "functionVersion": { "Fn::GetAtt": [ - "HandlerCurrentVersion93FB80BFb2a9ce598bf2730613c07e406cddb6b6", + "HandlerCurrentVersion93FB80BFf2e6129c63154d1f37c0092df295ab51", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index 6002699b5f982..cbb22cbd4a090 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -288,11 +288,20 @@ This has been fixed in the AWS CDK but *existing* users need to opt-in via a [feature flag]. Users who have run `cdk init` since this fix will be opted in, by default. -Existing users will need to enable the [feature flag] +Otherwise, you will need to enable the [feature flag] `@aws-cdk/aws-lambda:recognizeVersionProps`. Since CloudFormation does not -allow duplicate versions, they will also need to make some modification to -their function so that a new version can be created. Any trivial change such as -a whitespace change in the code or a no-op environment variable will suffice. +allow duplicate versions, you will also need to make some modification to +your function so that a new version can be created. To efficiently and trivially +modify all your lambda functions at once, you can attach the +`FunctionVersionUpgrade` aspect to the stack, which slightly alters the +function description. This aspect is intended for one-time use to upgrade the +version of all your functions at the same time, and can safely be removed after +deploying once. + +```ts +const stack = new Stack(); +Aspects.of(stack).add(new lambda.FunctionVersionUpgrade(LAMBDA_RECOGNIZE_VERSION_PROPS)); +``` When the new logic is in effect, you may rarely come across the following error: `The following properties are not recognized as version properties`. This will @@ -304,6 +313,32 @@ record whether a new version should be generated when this property is changed. This can be typically determined by checking whether the property can be modified using the *[UpdateFunctionConfiguration]* API or not. +### `currentVersion`: Updated hashing logic for layer versions + +An additional update to the hashing logic fixes two issues surrounding layers. +Prior to this change, updating the lambda layer version would have no effect on +the function version. Also, the order of lambda layers provided to the function +was unnecessarily baked into the hash. + +This has been fixed in the AWS CDK starting with version 2.27. If you ran +`cdk init` with an earlier version, you will need to opt-in via a [feature flag]. +If you run `cdk init` with v2.27 or later, this fix will be opted in, by default. + +Existing users will need to enable the [feature flag] +`@aws-cdk/aws-lambda:recognizeLayerVersion`. Since CloudFormation does not +allow duplicate versions, they will also need to make some modification to +their function so that a new version can be created. To efficiently and trivially +modify all your lambda functions at once, users can attach the +`FunctionVersionUpgrade` aspect to the stack, which slightly alters the +function description. This aspect is intended for one-time use to upgrade the +version of all your functions at the same time, and can safely be removed after +deploying once. + +```ts +const stack = new Stack(); +Aspects.of(stack).add(new lambda.FunctionVersionUpgrade(LAMBDA_RECOGNIZE_LAYER_VERSION)); +``` + [feature flag]: https://docs.aws.amazon.com/cdk/latest/guide/featureflags.html [property overrides]: https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html#cfn_layer_raw [UpdateFunctionConfiguration]: https://docs.aws.amazon.com/lambda/latest/dg/API_UpdateFunctionConfiguration.html diff --git a/packages/@aws-cdk/aws-lambda/lib/function-hash.ts b/packages/@aws-cdk/aws-lambda/lib/function-hash.ts index 0d16849763fec..952f8f43eed8a 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-hash.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-hash.ts @@ -1,7 +1,8 @@ import * as crypto from 'crypto'; import { CfnResource, FeatureFlags, Stack } from '@aws-cdk/core'; -import { LAMBDA_RECOGNIZE_VERSION_PROPS } from '@aws-cdk/cx-api'; +import { LAMBDA_RECOGNIZE_LAYER_VERSION, LAMBDA_RECOGNIZE_VERSION_PROPS } from '@aws-cdk/cx-api'; import { Function as LambdaFunction } from './function'; +import { ILayerVersion } from './layers'; export function calculateFunctionHash(fn: LambdaFunction) { const stack = Stack.of(fn); @@ -29,6 +30,10 @@ export function calculateFunctionHash(fn: LambdaFunction) { stringifiedConfig = JSON.stringify(config); } + if (FeatureFlags.of(fn).isEnabled(LAMBDA_RECOGNIZE_LAYER_VERSION)) { + stringifiedConfig = stringifiedConfig + calculateLayersHash(fn._layers); + } + const hash = crypto.createHash('md5'); hash.update(stringifiedConfig); return hash.digest('hex'); @@ -117,3 +122,31 @@ function sortProperties(properties: any) { } return ret; } + +function calculateLayersHash(layers: ILayerVersion[]): string { + const layerConfig: {[key: string]: any } = {}; + for (const layer of layers) { + const stack = Stack.of(layer); + const layerResource = layer.node.defaultChild as CfnResource; + // if there is no layer resource, then the layer was imported + // and we will include the layer arn and runtimes in the hash + if (layerResource === undefined) { + layerConfig[layer.layerVersionArn] = layer.compatibleRuntimes; + continue; + } + const config = stack.resolve((layerResource as any)._toCloudFormation()); + const resources = config.Resources; + const resourceKeys = Object.keys(resources); + if (resourceKeys.length !== 1) { + throw new Error(`Expected one rendered CloudFormation resource but found ${resourceKeys.length}`); + } + const logicalId = resourceKeys[0]; + const properties = resources[logicalId].Properties; + // all properties require replacement, so they are all version locked. + layerConfig[layer.node.id] = properties; + } + + const hash = crypto.createHash('md5'); + hash.update(JSON.stringify(layerConfig)); + return hash.digest('hex'); +} diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index ba6ddcf414f82..2dfe5c7ffacdd 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -6,8 +6,10 @@ import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as sns from '@aws-cdk/aws-sns'; import * as sqs from '@aws-cdk/aws-sqs'; -import { Annotations, ArnFormat, CfnResource, Duration, Fn, Lazy, Names, Size, Stack, Token } from '@aws-cdk/core'; +import { Annotations, ArnFormat, CfnResource, Duration, FeatureFlags, Fn, IAspect, IConstruct, Lazy, Names, Size, Stack, Token } from '@aws-cdk/core'; +import { LAMBDA_RECOGNIZE_LAYER_VERSION } from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; +import { AliasOptions, Alias } from './alias'; import { Architecture } from './architecture'; import { Code, CodeConfig } from './code'; import { ICodeSigningConfig } from './code-signing-config'; @@ -26,7 +28,6 @@ import { Runtime } from './runtime'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main // eslint-disable-next-line import { LogRetentionRetryOptions } from './log-retention'; -import { AliasOptions, Alias } from './alias'; import { addAlias } from './util'; /** @@ -637,10 +638,10 @@ export class Function extends FunctionBase { public readonly permissionsNode = this.node; - protected readonly canCreatePermissions = true; - private readonly layers: ILayerVersion[] = []; + /** @internal */ + public readonly _layers: ILayerVersion[] = []; private _logGroup?: logs.ILogGroup; @@ -777,7 +778,7 @@ export class Function extends FunctionBase { zipFile: code.inlineCode, imageUri: code.image?.imageUri, }, - layers: Lazy.list({ produce: () => this.layers.map(layer => layer.layerVersionArn) }, { omitEmpty: true }), // Evaluated on synthesis + layers: Lazy.list({ produce: () => this.renderLayers() }), // Evaluated on synthesis handler: props.handler === Handler.FROM_IMAGE ? undefined : props.handler, timeout: props.timeout && props.timeout.toSeconds(), packageType: props.runtime === Runtime.FROM_IMAGE ? 'Image' : undefined, @@ -909,7 +910,7 @@ export class Function extends FunctionBase { */ public addLayers(...layers: ILayerVersion[]): void { for (const layer of layers) { - if (this.layers.length === 5) { + if (this._layers.length === 5) { throw new Error('Unable to add layer: this lambda function already uses 5 layers.'); } if (layer.compatibleRuntimes && !layer.compatibleRuntimes.find(runtime => runtime.runtimeEquals(this.runtime))) { @@ -920,8 +921,7 @@ export class Function extends FunctionBase { // Currently no validations for compatible architectures since Lambda service // allows layers configured with one architecture to be used with a Lambda function // from another architecture. - - this.layers.push(layer); + this._layers.push(layer); } } @@ -1050,6 +1050,18 @@ Environment variables can be marked for removal when used in Lambda@Edge by sett this.role?.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchLambdaInsightsExecutionRolePolicy')); } + private renderLayers() { + if (!this._layers || this._layers.length === 0) { + return undefined; + } + + if (FeatureFlags.of(this).isEnabled(LAMBDA_RECOGNIZE_LAYER_VERSION)) { + this._layers.sort(); + } + + return this._layers.map(layer => layer.layerVersionArn); + } + private renderEnvironment() { if (!this.environment || Object.keys(this.environment).length === 0) { return undefined; @@ -1269,3 +1281,23 @@ function undefinedIfNoKeys(struct: A): A | undefined { const allUndefined = Object.values(struct).every(val => val === undefined); return allUndefined ? undefined : struct; } + +/** + * Aspect for upgrading function versions when the feature flag + * provided feature flag present. This can be necessary when the feature flag + * changes the function hash, as such changes must be associated with a new + * version. This aspect will change the function description in these cases, + * which "validates" the new function hash. + */ +export class FunctionVersionUpgrade implements IAspect { + constructor(private readonly featureFlag: string, private readonly enabled=true) {} + + public visit(node: IConstruct): void { + if (node instanceof Function && + this.enabled === FeatureFlags.of(node).isEnabled(this.featureFlag)) { + const cfnFunction = node.node.defaultChild as CfnFunction; + const desc = cfnFunction.description ? `${cfnFunction.description} ` : ''; + cfnFunction.addPropertyOverride('Description', `${desc}version-hash:${calculateFunctionHash(node)}`); + } + }; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/lib/layers.ts b/packages/@aws-cdk/aws-lambda/lib/layers.ts index 7176badb0ee42..83c548522600f 100644 --- a/packages/@aws-cdk/aws-lambda/lib/layers.ts +++ b/packages/@aws-cdk/aws-lambda/lib/layers.ts @@ -189,9 +189,7 @@ export class LayerVersion extends LayerVersionBase { if (props.compatibleRuntimes && props.compatibleRuntimes.length === 0) { throw new Error('Attempted to define a Lambda layer that supports no runtime!'); } - if (props.code.isInline) { - throw new Error('Lambda layers cannot be created from inline code'); - } + // Allow usage of the code in this context... const code = props.code.bind(this); if (code.inlineCode) { diff --git a/packages/@aws-cdk/aws-lambda/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-lambda/rosetta/default.ts-fixture index 603fe399c1d3e..3519cb4d48595 100644 --- a/packages/@aws-cdk/aws-lambda/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-lambda/rosetta/default.ts-fixture @@ -1,9 +1,10 @@ // Fixture with packages imported, but nothing else import * as path from 'path'; import { Construct } from 'constructs'; -import { CfnOutput, DockerImage, Duration, RemovalPolicy, Stack } from '@aws-cdk/core'; +import { Aspects, CfnOutput, DockerImage, Duration, RemovalPolicy, Stack } from '@aws-cdk/core'; import * as lambda from '@aws-cdk/aws-lambda'; import * as iam from '@aws-cdk/aws-iam'; +import { LAMBDA_RECOGNIZE_VERSION_PROPS, LAMBDA_RECOGNIZE_LAYER_VERSION } from '@aws-cdk/cx-api'; class Fixture extends Stack { constructor(scope: Construct, id: string) { diff --git a/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/aws-lambda-autoscaling.template.json b/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/aws-lambda-autoscaling.template.json index 9fd6b7e176cc1..b01be18dd2aa5 100644 --- a/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/aws-lambda-autoscaling.template.json +++ b/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/aws-lambda-autoscaling.template.json @@ -43,6 +43,7 @@ "Arn" ] }, + "Description": "version-hash:7c384e097d7f7605c9405f788cdf9f7b", "Handler": "index.handler", "Runtime": "nodejs14.x" }, @@ -50,7 +51,7 @@ "MyLambdaServiceRole4539ECB6" ] }, - "MyLambdaCurrentVersionE7A382CCc9b5d5d60612e848a9b7c670d8802822": { + "MyLambdaCurrentVersionE7A382CC0be4c7f7b82230afaf72d130ee920da9": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -66,7 +67,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaCurrentVersionE7A382CCc9b5d5d60612e848a9b7c670d8802822", + "MyLambdaCurrentVersionE7A382CC0be4c7f7b82230afaf72d130ee920da9", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/cdk.out index 90bef2e09ad39..588d7b269d34f 100644 --- a/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"17.0.0"} \ No newline at end of file +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/integ.json b/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/integ.json index e9dd8f6f0f119..969a25e58ee55 100644 --- a/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/integ.json @@ -1,7 +1,7 @@ { - "version": "18.0.0", + "version": "20.0.0", "testCases": { - "aws-lambda/test/integ.autoscaling.lit": { + "integ.autoscaling.lit": { "stacks": [ "aws-lambda-autoscaling" ], diff --git a/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/manifest.json index 272bf82e48729..64264d0be91ac 100644 --- a/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "17.0.0", + "version": "20.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -30,7 +30,7 @@ "/aws-lambda-autoscaling/MyLambda/CurrentVersion/Resource": [ { "type": "aws:cdk:logicalId", - "data": "MyLambdaCurrentVersionE7A382CCc9b5d5d60612e848a9b7c670d8802822" + "data": "MyLambdaCurrentVersionE7A382CC0be4c7f7b82230afaf72d130ee920da9" } ], "/aws-lambda-autoscaling/Alias/Resource": [ @@ -56,6 +56,15 @@ "type": "aws:cdk:logicalId", "data": "FunctionName" } + ], + "MyLambdaCurrentVersionE7A382CCa1b19066cadcfb5b078bffbfb5b1e687": [ + { + "type": "aws:cdk:logicalId", + "data": "MyLambdaCurrentVersionE7A382CCa1b19066cadcfb5b078bffbfb5b1e687", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } ] }, "displayName": "aws-lambda-autoscaling" diff --git a/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/tree.json b/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/tree.json index 6493369fc0dc1..d83bb4b12de15 100644 --- a/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-lambda/test/autoscaling.lit.integ.snapshot/tree.json @@ -140,7 +140,7 @@ }, "functionVersion": { "Fn::GetAtt": [ - "MyLambdaCurrentVersionE7A382CCc9b5d5d60612e848a9b7c670d8802822", + "MyLambdaCurrentVersionE7A382CC0be4c7f7b82230afaf72d130ee920da9", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/cdk.out index 90bef2e09ad39..588d7b269d34f 100644 --- a/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"17.0.0"} \ No newline at end of file +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/integ.json b/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/integ.json index 2b9ccbdbc02cf..45b5d74847cf9 100644 --- a/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/integ.json @@ -1,7 +1,7 @@ { - "version": "18.0.0", + "version": "20.0.0", "testCases": { - "aws-lambda/test/integ.current-version": { + "integ.current-version": { "stacks": [ "lambda-test-current-version" ], diff --git a/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/lambda-test-current-version.template.json b/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/lambda-test-current-version.template.json index a1161bda27260..366be468de3fc 100644 --- a/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/lambda-test-current-version.template.json +++ b/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/lambda-test-current-version.template.json @@ -78,6 +78,7 @@ "Arn" ] }, + "Description": "version-hash:dc9dd00a4c2f3ab3541720d352f0f09e", "Handler": "index.main", "Runtime": "python3.8" }, @@ -85,7 +86,7 @@ "MyLambdaServiceRole4539ECB6" ] }, - "MyLambdaCurrentVersionE7A382CC132baf6493c3210af4c8c0e36b416660": { + "MyLambdaCurrentVersionE7A382CC1c5f33e29b34a521d6a59697cc440454": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -103,7 +104,7 @@ }, "Qualifier": { "Fn::GetAtt": [ - "MyLambdaCurrentVersionE7A382CC132baf6493c3210af4c8c0e36b416660", + "MyLambdaCurrentVersionE7A382CC1c5f33e29b34a521d6a59697cc440454", "Version" ] }, @@ -118,7 +119,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaCurrentVersionE7A382CC132baf6493c3210af4c8c0e36b416660", + "MyLambdaCurrentVersionE7A382CC1c5f33e29b34a521d6a59697cc440454", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/manifest.json index 5a584ddfbc9d1..c76ae363745ea 100644 --- a/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "17.0.0", + "version": "20.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -44,13 +44,16 @@ "/lambda-test-current-version/MyLambda/CurrentVersion/Resource": [ { "type": "aws:cdk:logicalId", - "data": "MyLambdaCurrentVersionE7A382CC132baf6493c3210af4c8c0e36b416660" + "data": "MyLambdaCurrentVersionE7A382CC1c5f33e29b34a521d6a59697cc440454" } ], "/lambda-test-current-version/MyLambda/CurrentVersion/EventInvokeConfig/Resource": [ { "type": "aws:cdk:logicalId", - "data": "MyLambdaCurrentVersionEventInvokeConfigD120DC68" + "data": "MyLambdaCurrentVersionEventInvokeConfigD120DC68", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_REPLACE" + ] } ], "/lambda-test-current-version/MyLambda/CurrentVersion/Aliaslive/Resource": [ @@ -76,6 +79,15 @@ "type": "aws:cdk:logicalId", "data": "AssetParameters8811a2632ac5564a08fd269e159298f7e497f259578b0dc5e927a1f48ab24d34ArtifactHash70E274C4" } + ], + "MyLambdaCurrentVersionE7A382CC132baf6493c3210af4c8c0e36b416660": [ + { + "type": "aws:cdk:logicalId", + "data": "MyLambdaCurrentVersionE7A382CC132baf6493c3210af4c8c0e36b416660", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } ] }, "displayName": "lambda-test-current-version" diff --git a/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/tree.json b/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/tree.json index 4a38c0f7a8bd4..4ca00596e83cd 100644 --- a/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-lambda/test/current-version.integ.snapshot/tree.json @@ -189,7 +189,7 @@ }, "qualifier": { "Fn::GetAtt": [ - "MyLambdaCurrentVersionE7A382CC132baf6493c3210af4c8c0e36b416660", + "MyLambdaCurrentVersionE7A382CC1c5f33e29b34a521d6a59697cc440454", "Version" ] }, @@ -222,7 +222,7 @@ }, "functionVersion": { "Fn::GetAtt": [ - "MyLambdaCurrentVersionE7A382CC132baf6493c3210af4c8c0e36b416660", + "MyLambdaCurrentVersionE7A382CC1c5f33e29b34a521d6a59697cc440454", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts b/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts index de07180924ef5..4fcc5b4ed33f9 100644 --- a/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import { resourceSpecification } from '@aws-cdk/cfnspec'; import { App, CfnOutput, CfnResource, Stack } from '@aws-cdk/core'; -import { LAMBDA_RECOGNIZE_VERSION_PROPS } from '@aws-cdk/cx-api'; +import { LAMBDA_RECOGNIZE_LAYER_VERSION, LAMBDA_RECOGNIZE_VERSION_PROPS } from '@aws-cdk/cx-api'; import * as lambda from '../lib'; import { calculateFunctionHash, trimFromStart, VERSION_LOCKED } from '../lib/function-hash'; @@ -123,8 +123,139 @@ describe('function hash', () => { expect(calculateFunctionHash(fn2)).toEqual('ffedf6424a18a594a513129dc97bf53c'); }); - describe('impact of env variables order on hash', () => { + describe('lambda layers', () => { + let stack1: Stack; + let layer1: lambda.LayerVersion; + let layer2: lambda.LayerVersion; + beforeAll(() => { + stack1 = new Stack(); + layer1 = new lambda.LayerVersion(stack1, 'MyLayer', { + code: lambda.Code.fromAsset(path.join(__dirname, 'layer-code')), + compatibleRuntimes: [lambda.Runtime.NODEJS_12_X], + license: 'Apache-2.0', + description: 'A layer to test the L2 construct', + }); + layer2 = new lambda.LayerVersion(stack1, 'MyLayer2', { + code: lambda.Code.fromAsset(path.join(__dirname, 'layer-code')), + compatibleRuntimes: [lambda.Runtime.NODEJS_12_X], + license: 'Apache-2.0', + description: 'A layer to test the L2 construct', + }); + }); + + test('same configuration yields the same hash', () => { + const stack2 = new Stack(); + const fn1 = new lambda.Function(stack2, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromInline('foo'), + handler: 'index.handler', + layers: [layer1], + }); + + const stack3 = new Stack(); + const fn2 = new lambda.Function(stack3, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromInline('foo'), + handler: 'index.handler', + layers: [layer1], + }); + + expect(calculateFunctionHash(fn1)).toEqual(calculateFunctionHash(fn2)); + expect(calculateFunctionHash(fn1)).toEqual('028f8a4cb1c719f29e70b7b3c0f2a9d7'); + }); + + test('different layers impacts hash', () => { + const stack2 = new Stack(); + const fn1 = new lambda.Function(stack2, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromInline('foo'), + handler: 'index.handler', + layers: [layer1], + }); + + const stack3 = new Stack(); + const fn2 = new lambda.Function(stack3, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromInline('foo'), + handler: 'index.handler', + layers: [layer2], + }); + + expect(calculateFunctionHash(fn1)).toEqual('028f8a4cb1c719f29e70b7b3c0f2a9d7'); + expect(calculateFunctionHash(fn2)).toEqual('e74647bf81c4d532137545c8234726f3'); + }); + + describe('impact of lambda layer order on hash', () => { + test('without feature flag, preserve old behavior to avoid unnecessary invalidation of templates', () => { + const stack2 = new Stack(); + const fn1 = new lambda.Function(stack2, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromInline('foo'), + handler: 'index.handler', + layers: [layer1, layer2], + }); + + const stack3 = new Stack(); + const fn2 = new lambda.Function(stack3, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromInline('foo'), + handler: 'index.handler', + layers: [layer2, layer1], + }); + + expect(calculateFunctionHash(fn1)).toEqual('b6cade45d8f9c77f29f0ab169004113c'); + expect(calculateFunctionHash(fn2)).toEqual('0d79a0b6bcac599b278e63b173eca170'); + }); + test('with feature flag, we sort layers so order is consistent', () => { + const app = new App({ context: { [LAMBDA_RECOGNIZE_LAYER_VERSION]: true } }); + + const stack2 = new Stack(app, 'stack2'); + const fn1 = new lambda.Function(stack2, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromInline('foo'), + handler: 'index.handler', + layers: [layer1, layer2], + }); + + const stack3 = new Stack(app, 'stack3'); + const fn2 = new lambda.Function(stack3, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromInline('foo'), + handler: 'index.handler', + layers: [layer2, layer1], + }); + + expect(calculateFunctionHash(fn1)).toEqual(calculateFunctionHash(fn2)); + }); + }); + + test('with feature flag, imported lambda layers can be distinguished', () => { + const app = new App({ context: { [LAMBDA_RECOGNIZE_LAYER_VERSION]: true } }); + + const stack2 = new Stack(app, 'stack2'); + const importedLayer1 = lambda.LayerVersion.fromLayerVersionArn(stack2, 'imported-layer', 'arn:aws:lambda:::layer::'); + const fn1 = new lambda.Function(stack2, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromInline('foo'), + handler: 'index.handler', + layers: [importedLayer1], + }); + + const stack3 = new Stack(app, 'stack3'); + const importedLayer2 = lambda.LayerVersion.fromLayerVersionArn(stack3, 'imported-layer', 'arn:aws:lambda:::layer::'); + const fn2 = new lambda.Function(stack3, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromInline('foo'), + handler: 'index.handler', + layers: [importedLayer2], + }); + + expect(calculateFunctionHash(fn1)).not.toEqual(calculateFunctionHash(fn2)); + }); + }); + + describe('impact of env variables order on hash', () => { test('without "currentVersion", we preserve old behavior to avoid unnecessary invalidation of templates', () => { const stack1 = new Stack(); const fn1 = new lambda.Function(stack1, 'MyFunction', { diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index 187fa061b2e19..c644e59a70e03 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -12,10 +12,12 @@ import * as sns from '@aws-cdk/aws-sns'; import * as sqs from '@aws-cdk/aws-sqs'; import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; -import { Lazy, Size } from '@aws-cdk/core'; +import { Aspects, Lazy, Size } from '@aws-cdk/core'; +import { LAMBDA_RECOGNIZE_LAYER_VERSION } from '@aws-cdk/cx-api'; import * as constructs from 'constructs'; import * as _ from 'lodash'; import * as lambda from '../lib'; +import { calculateFunctionHash } from '../lib/function-hash'; describe('function', () => { test('default function', () => { @@ -1437,6 +1439,69 @@ describe('function', () => { expect(bindTarget).toEqual(fn); }); + test('layer is baked into the function version', () => { + // GIVEN + const stack = new cdk.Stack(undefined, 'TestStack'); + const bucket = new s3.Bucket(stack, 'Bucket'); + const code = new lambda.S3Code(bucket, 'ObjectKey'); + + const fn = new lambda.Function(stack, 'fn', { + runtime: lambda.Runtime.NODEJS_14_X, + code: lambda.Code.fromInline('exports.main = function() { console.log("DONE"); }'), + handler: 'index.main', + }); + + const fnHash = calculateFunctionHash(fn); + + // WHEN + const layer = new lambda.LayerVersion(stack, 'LayerVersion', { + code, + compatibleRuntimes: [lambda.Runtime.NODEJS_14_X], + }); + + fn.addLayers(layer); + + const newFnHash = calculateFunctionHash(fn); + + expect(fnHash).not.toEqual(newFnHash); + }); + + test('with feature flag, layer version is baked into function version', () => { + // GIVEN + const app = new cdk.App({ context: { [LAMBDA_RECOGNIZE_LAYER_VERSION]: true } }); + const stack = new cdk.Stack(app, 'TestStack'); + const bucket = new s3.Bucket(stack, 'Bucket'); + const code = new lambda.S3Code(bucket, 'ObjectKey'); + const layer = new lambda.LayerVersion(stack, 'LayerVersion', { + code, + compatibleRuntimes: [lambda.Runtime.NODEJS_14_X], + }); + + // function with layer + const fn = new lambda.Function(stack, 'fn', { + runtime: lambda.Runtime.NODEJS_14_X, + code: lambda.Code.fromInline('exports.main = function() { console.log("DONE"); }'), + handler: 'index.main', + layers: [layer], + }); + + const fnHash = calculateFunctionHash(fn); + + // use escape hatch to change the content of the layer + // this simulates updating the layer code which changes the version. + const cfnLayer = layer.node.defaultChild as lambda.CfnLayerVersion; + const newCode = (new lambda.S3Code(bucket, 'NewObjectKey')).bind(layer); + cfnLayer.content = { + s3Bucket: newCode.s3Location!.bucketName, + s3Key: newCode.s3Location!.objectKey, + s3ObjectVersion: newCode.s3Location!.objectVersion, + }; + + const newFnHash = calculateFunctionHash(fn); + + expect(fnHash).not.toEqual(newFnHash); + }); + test('using an incompatible layer', () => { // GIVEN const stack = new cdk.Stack(undefined, 'TestStack'); @@ -2895,6 +2960,29 @@ test('ephemeral storage allows unresolved tokens', () => { }).not.toThrow(); }); +test('FunctionVersionUpgrade adds new description to function', () => { + const app = new cdk.App({ context: { [LAMBDA_RECOGNIZE_LAYER_VERSION]: true } }); + const stack = new cdk.Stack(app); + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'bar', + runtime: lambda.Runtime.NODEJS_14_X, + description: 'my description', + }); + + Aspects.of(stack).add(new lambda.FunctionVersionUpgrade(LAMBDA_RECOGNIZE_LAYER_VERSION)); + + Template.fromStack(stack).hasResource('AWS::Lambda::Function', { + Properties: + { + Code: { ZipFile: 'foo' }, + Handler: 'bar', + Runtime: 'nodejs14.x', + Description: 'my description version-hash:de36a94cfdb5cb58f7c7b4cd975120ba', + }, + }); +}); + function newTestLambda(scope: constructs.Construct) { return new lambda.Function(scope, 'MyLambda', { code: new lambda.InlineCode('foo'), diff --git a/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.ts b/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.ts index ec6387c230c1a..6203e718caafc 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.autoscaling.lit.ts @@ -1,5 +1,6 @@ import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as cdk from '@aws-cdk/core'; +import { LAMBDA_RECOGNIZE_LAYER_VERSION } from '@aws-cdk/cx-api'; import * as lambda from '../lib'; /** @@ -48,6 +49,10 @@ class TestStack extends cdk.Stack { const app = new cdk.App(); -new TestStack(app, 'aws-lambda-autoscaling'); +const stack = new TestStack(app, 'aws-lambda-autoscaling'); + +// Changes the function description when the feature flag is present +// to validate the changed function hash. +cdk.Aspects.of(stack).add(new lambda.FunctionVersionUpgrade(LAMBDA_RECOGNIZE_LAYER_VERSION)); app.synth(); diff --git a/packages/@aws-cdk/aws-lambda/test/integ.current-version.ts b/packages/@aws-cdk/aws-lambda/test/integ.current-version.ts index a6f4e4dae32c0..441f687d7d0cc 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.current-version.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.current-version.ts @@ -1,5 +1,6 @@ import * as path from 'path'; -import { App, RemovalPolicy, Stack } from '@aws-cdk/core'; +import { Aspects, App, RemovalPolicy, Stack } from '@aws-cdk/core'; +import { LAMBDA_RECOGNIZE_LAYER_VERSION } from '@aws-cdk/cx-api'; import * as lambda from '../lib'; class TestStack extends Stack { @@ -22,6 +23,10 @@ class TestStack extends Stack { const app = new App(); -new TestStack(app, 'lambda-test-current-version'); +const stack = new TestStack(app, 'lambda-test-current-version'); + +// Changes the function description when the feature flag is present +// to validate the changed function hash. +Aspects.of(stack).add(new lambda.FunctionVersionUpgrade(LAMBDA_RECOGNIZE_LAYER_VERSION)); app.synth(); diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.ts b/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.ts index dde4a27d84ceb..a0d576926ded7 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.prov.concurrent.ts @@ -1,5 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; +import { LAMBDA_RECOGNIZE_LAYER_VERSION } from '@aws-cdk/cx-api'; import * as lambda from '../lib'; const app = new cdk.App(); @@ -61,4 +62,8 @@ alias2.addPermission('AliasPermission2', { principal: new iam.ServicePrincipal('cloudformation.amazonaws.com'), }); +// Changes the function description when the feature flag is present +// to validate the changed function hash. +cdk.Aspects.of(stack).add(new lambda.FunctionVersionUpgrade(LAMBDA_RECOGNIZE_LAYER_VERSION)); + app.synth(); diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts b/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts index 91aa827178dd4..c00e9d141a4f6 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts @@ -1,5 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; +import { LAMBDA_RECOGNIZE_LAYER_VERSION } from '@aws-cdk/cx-api'; import * as lambda from '../lib'; const app = new cdk.App(); @@ -31,4 +32,8 @@ alias.addFunctionUrl({ authType: lambda.FunctionUrlAuthType.NONE, }); +// Changes the function description when the feature flag is present +// to validate the changed function hash. +cdk.Aspects.of(stack).add(new lambda.FunctionVersionUpgrade(LAMBDA_RECOGNIZE_LAYER_VERSION)); + app.synth(); diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/aws-cdk-lambda-1.template.json b/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/aws-cdk-lambda-1.template.json index c53bfeebcfdbb..1bb221f35344f 100644 --- a/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/aws-cdk-lambda-1.template.json +++ b/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/aws-cdk-lambda-1.template.json @@ -64,6 +64,7 @@ "Arn" ] }, + "Description": "version-hash:1786de9fc1bc4cb2fd5b64d612628c6f", "Handler": "index.handler", "Runtime": "nodejs14.x" }, @@ -84,7 +85,7 @@ } } }, - "MyLambdaCurrentVersionE7A382CCaab0ffd2d3271bb29338c3fe7c7f3151": { + "MyLambdaCurrentVersionE7A382CCac1a0c5f52d7e233eaf45e0c00b74629": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -100,7 +101,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaCurrentVersionE7A382CCaab0ffd2d3271bb29338c3fe7c7f3151", + "MyLambdaCurrentVersionE7A382CCac1a0c5f52d7e233eaf45e0c00b74629", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/cdk.out index 90bef2e09ad39..588d7b269d34f 100644 --- a/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"17.0.0"} \ No newline at end of file +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/integ.json b/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/integ.json index 8dcb9904fbcd8..0ab510ff8ab8f 100644 --- a/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/integ.json @@ -1,7 +1,7 @@ { - "version": "18.0.0", + "version": "20.0.0", "testCases": { - "aws-lambda/test/integ.lambda": { + "integ.lambda": { "stacks": [ "aws-cdk-lambda-1" ], diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/manifest.json index 6b8aaa9c4ab1d..6f86b84603474 100644 --- a/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "17.0.0", + "version": "20.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -42,7 +42,7 @@ "/aws-cdk-lambda-1/MyLambda/CurrentVersion/Resource": [ { "type": "aws:cdk:logicalId", - "data": "MyLambdaCurrentVersionE7A382CCaab0ffd2d3271bb29338c3fe7c7f3151" + "data": "MyLambdaCurrentVersionE7A382CCac1a0c5f52d7e233eaf45e0c00b74629" } ], "/aws-cdk-lambda-1/Alias/Resource": [ @@ -68,6 +68,15 @@ "type": "aws:cdk:logicalId", "data": "Aliasinvokefunctionurl4CA9917B" } + ], + "MyLambdaCurrentVersionE7A382CC7e2eef94b9010f0c2a5e7db748a3833a": [ + { + "type": "aws:cdk:logicalId", + "data": "MyLambdaCurrentVersionE7A382CC7e2eef94b9010f0c2a5e7db748a3833a", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } ] }, "displayName": "aws-cdk-lambda-1" diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/tree.json b/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/tree.json index 17bb14e72105f..0c403b8704fc2 100644 --- a/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-lambda/test/lambda.integ.snapshot/tree.json @@ -209,7 +209,7 @@ }, "functionVersion": { "Fn::GetAtt": [ - "MyLambdaCurrentVersionE7A382CCaab0ffd2d3271bb29338c3fe7c7f3151", + "MyLambdaCurrentVersionE7A382CCac1a0c5f52d7e233eaf45e0c00b74629", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/aws-cdk-lambda-pce-1.template.json b/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/aws-cdk-lambda-pce-1.template.json index 8527e8c9021f3..87e7bcb71d172 100644 --- a/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/aws-cdk-lambda-pce-1.template.json +++ b/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/aws-cdk-lambda-pce-1.template.json @@ -64,6 +64,7 @@ "Arn" ] }, + "Description": "version-hash:22935888161d542257ba1aa7d6060c95", "Handler": "index.handler", "Runtime": "nodejs14.x" }, @@ -72,7 +73,7 @@ "MyLambdaAliasPCEServiceRoleF7C9F212" ] }, - "MyLambdaAliasPCECurrentVersion072335D3f742c0f8cc0b7f48bb32fb34b63bc22c": { + "MyLambdaAliasPCECurrentVersion072335D3224448d87cb592b450e76b3259c1d874": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -88,7 +89,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaAliasPCECurrentVersion072335D3f742c0f8cc0b7f48bb32fb34b63bc22c", + "MyLambdaAliasPCECurrentVersion072335D3224448d87cb592b450e76b3259c1d874", "Version" ] }, @@ -172,6 +173,7 @@ "Arn" ] }, + "Description": "version-hash:fa78bcfe53ab85fefd72b3ac4cc5679c", "Handler": "index.handler", "Runtime": "nodejs14.x" }, @@ -180,7 +182,7 @@ "MyLambdaVersionPCEServiceRole2ACFB73E" ] }, - "MyLambdaVersionPCECurrentVersion27FC3932fbc6188ae863cb6dc15d61f96ad00420": { + "MyLambdaVersionPCECurrentVersion27FC3932d779c9115684f8f405cbd7282b1508f5": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -199,7 +201,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaVersionPCECurrentVersion27FC3932fbc6188ae863cb6dc15d61f96ad00420", + "MyLambdaVersionPCECurrentVersion27FC3932d779c9115684f8f405cbd7282b1508f5", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/cdk.out index 90bef2e09ad39..588d7b269d34f 100644 --- a/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"17.0.0"} \ No newline at end of file +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/integ.json b/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/integ.json index 0ae14379f1ad9..9c97cfe4b427e 100644 --- a/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/integ.json @@ -1,7 +1,7 @@ { - "version": "18.0.0", + "version": "20.0.0", "testCases": { - "aws-lambda/test/integ.lambda.prov.concurrent": { + "integ.lambda.prov.concurrent": { "stacks": [ "aws-cdk-lambda-pce-1" ], diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/manifest.json index a9884c51f90b1..148240ef61612 100644 --- a/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "17.0.0", + "version": "20.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -36,7 +36,7 @@ "/aws-cdk-lambda-pce-1/MyLambdaAliasPCE/CurrentVersion/Resource": [ { "type": "aws:cdk:logicalId", - "data": "MyLambdaAliasPCECurrentVersion072335D3f742c0f8cc0b7f48bb32fb34b63bc22c" + "data": "MyLambdaAliasPCECurrentVersion072335D3224448d87cb592b450e76b3259c1d874" } ], "/aws-cdk-lambda-pce-1/Alias/Resource": [ @@ -72,7 +72,7 @@ "/aws-cdk-lambda-pce-1/MyLambdaVersionPCE/CurrentVersion/Resource": [ { "type": "aws:cdk:logicalId", - "data": "MyLambdaVersionPCECurrentVersion27FC3932fbc6188ae863cb6dc15d61f96ad00420" + "data": "MyLambdaVersionPCECurrentVersion27FC3932d779c9115684f8f405cbd7282b1508f5" } ], "/aws-cdk-lambda-pce-1/Alias2/Resource": [ @@ -86,6 +86,24 @@ "type": "aws:cdk:logicalId", "data": "Alias2AliasPermission2448514B6" } + ], + "MyLambdaAliasPCECurrentVersion072335D369bedb3f5cf56c66c63ccfdba59d5d84": [ + { + "type": "aws:cdk:logicalId", + "data": "MyLambdaAliasPCECurrentVersion072335D369bedb3f5cf56c66c63ccfdba59d5d84", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } + ], + "MyLambdaVersionPCECurrentVersion27FC3932fc9df6df91e1aac821ec45a32cf17e32": [ + { + "type": "aws:cdk:logicalId", + "data": "MyLambdaVersionPCECurrentVersion27FC3932fc9df6df91e1aac821ec45a32cf17e32", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } ] }, "displayName": "aws-cdk-lambda-pce-1" diff --git a/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/tree.json b/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/tree.json index 1e3519beec613..f1e633686f53b 100644 --- a/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-lambda/test/lambda.prov.concurrent.integ.snapshot/tree.json @@ -179,7 +179,7 @@ }, "functionVersion": { "Fn::GetAtt": [ - "MyLambdaAliasPCECurrentVersion072335D3f742c0f8cc0b7f48bb32fb34b63bc22c", + "MyLambdaAliasPCECurrentVersion072335D3224448d87cb592b450e76b3259c1d874", "Version" ] }, @@ -392,7 +392,7 @@ }, "functionVersion": { "Fn::GetAtt": [ - "MyLambdaVersionPCECurrentVersion27FC3932fbc6188ae863cb6dc15d61f96ad00420", + "MyLambdaVersionPCECurrentVersion27FC3932d779c9115684f8f405cbd7282b1508f5", "Version" ] }, diff --git a/packages/@aws-cdk/cx-api/lib/features.ts b/packages/@aws-cdk/cx-api/lib/features.ts index 21918585ae183..d1dd205efc06f 100644 --- a/packages/@aws-cdk/cx-api/lib/features.ts +++ b/packages/@aws-cdk/cx-api/lib/features.ts @@ -173,6 +173,8 @@ export const EFS_DEFAULT_ENCRYPTION_AT_REST = '@aws-cdk/aws-efs:defaultEncryptio */ export const LAMBDA_RECOGNIZE_VERSION_PROPS = '@aws-cdk/aws-lambda:recognizeVersionProps'; +export const LAMBDA_RECOGNIZE_LAYER_VERSION = '@aws-cdk/aws-lambda:recognizeLayerVersion'; + /** * Enable this feature flag to have cloudfront distributions use the security policy TLSv1.2_2021 by default. * @@ -255,6 +257,7 @@ export const FUTURE_FLAGS: { [key: string]: boolean } = { [RDS_LOWERCASE_DB_IDENTIFIER]: true, [EFS_DEFAULT_ENCRYPTION_AT_REST]: true, [LAMBDA_RECOGNIZE_VERSION_PROPS]: true, + [LAMBDA_RECOGNIZE_LAYER_VERSION]: true, [CLOUDFRONT_DEFAULT_SECURITY_POLICY_TLS_V1_2_2021]: true, [ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER]: true, [EC2_UNIQUE_IMDSV2_LAUNCH_TEMPLATE_NAME]: true, diff --git a/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/MyStack.template.json b/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/MyStack.template.json index 188f4cdd055a0..112695ca01809 100644 --- a/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/MyStack.template.json +++ b/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/MyStack.template.json @@ -69,7 +69,7 @@ ] }, "HandlerArn": { - "Ref": "MyFunctionCurrentVersion197490AF776ea8de2edf446759649703b18110a4" + "Ref": "MyFunctionCurrentVersion197490AFd41a8aa4109c7b22dd39d6bef408da46" } }, "DependsOn": [ @@ -78,7 +78,7 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "MyFunctionCurrentVersion197490AF776ea8de2edf446759649703b18110a4": { + "MyFunctionCurrentVersion197490AFd41a8aa4109c7b22dd39d6bef408da46": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -119,7 +119,7 @@ ], "Resource": [ { - "Ref": "MyFunctionCurrentVersion197490AF776ea8de2edf446759649703b18110a4" + "Ref": "MyFunctionCurrentVersion197490AFd41a8aa4109c7b22dd39d6bef408da46" } ] } diff --git a/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/cdk.out b/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/cdk.out index 90bef2e09ad39..588d7b269d34f 100644 --- a/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"17.0.0"} \ No newline at end of file +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/integ.json b/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/integ.json index c6e52debf5128..bccfdf584b074 100644 --- a/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/integ.json +++ b/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/integ.json @@ -1,7 +1,7 @@ { - "version": "18.0.0", + "version": "20.0.0", "testCases": { - "triggers/test/integ.triggers": { + "integ.triggers": { "stacks": [ "MyStack" ], diff --git a/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/manifest.json b/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/manifest.json index f362fc350ddae..a71ae08f02057 100644 --- a/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/triggers/test/triggers.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "17.0.0", + "version": "20.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -62,7 +62,7 @@ "/MyStack/MyFunction/CurrentVersion/Resource": [ { "type": "aws:cdk:logicalId", - "data": "MyFunctionCurrentVersion197490AF776ea8de2edf446759649703b18110a4" + "data": "MyFunctionCurrentVersion197490AFd41a8aa4109c7b22dd39d6bef408da46" } ], "/MyStack/AWSCDK.TriggerCustomResourceProviderCustomResourceProvider/Role": [ @@ -94,6 +94,15 @@ "type": "aws:cdk:logicalId", "data": "AssetParameters6b78a08a66c707ed36509dde1cebf8e7d5244a3b039122c2c00a5137efb845e2ArtifactHash29DBC1FA" } + ], + "MyFunctionCurrentVersion197490AF776ea8de2edf446759649703b18110a4": [ + { + "type": "aws:cdk:logicalId", + "data": "MyFunctionCurrentVersion197490AF776ea8de2edf446759649703b18110a4", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } ] }, "displayName": "MyStack" From e44c2c436d41a9993714d7e9ff5a9ed95b5677f1 Mon Sep 17 00:00:00 2001 From: Cory Hall <43035978+corymhall@users.noreply.github.com> Date: Wed, 1 Jun 2022 06:04:08 -0400 Subject: [PATCH 18/25] fix(core): logicalId is consumed prior to being overridden (#20560) When using `Stack.exportValue` to manually create a CloudFormation export, the logicalId of the referenced resource is used to generate the logicalId of the `CfnExport`. Because `exportValue` creates a `CfnExport` _and_ returns an `importValue` it needs to _resolve_ the logicalId at call time. If the user later overrides the logicalId of the referenced resource, that override is reflected in the export/import that was created earlier. There doesn't seem to be a way to solve this without incurring a breaking change so this PR attempts to smooth a rough edge by "locking" the `logicalId` when `exportValue` is called. If the user attempts to override the id _after_ that point, an error message will be thrown closes #14335 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *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-element.ts | 40 +++++++++++++++++++- packages/@aws-cdk/core/lib/stack.ts | 8 ++++ packages/@aws-cdk/core/test/stack.test.ts | 46 +++++++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/core/lib/cfn-element.ts b/packages/@aws-cdk/core/lib/cfn-element.ts index 84126add4e13c..feac7bd320dac 100644 --- a/packages/@aws-cdk/core/lib/cfn-element.ts +++ b/packages/@aws-cdk/core/lib/cfn-element.ts @@ -47,6 +47,14 @@ export abstract class CfnElement extends CoreConstruct { */ private _logicalIdOverride?: string; + /** + * If the logicalId is locked then it can no longer be overridden. + * This is needed for cases where the logicalId is consumed prior to synthesis + * (i.e. Stack.exportValue). + */ + private _logicalIdLocked?: boolean; + + /** * Creates an entity and binds it to a tree. * Note that the root of the tree must be a Stack object (not just any Root). @@ -75,7 +83,37 @@ export abstract class CfnElement extends CoreConstruct { * @param newLogicalId The new logical ID to use for this stack element. */ public overrideLogicalId(newLogicalId: string) { - this._logicalIdOverride = newLogicalId; + if (this._logicalIdLocked) { + throw new Error(`The logicalId for resource at path ${Node.of(this).path} has been locked and cannot be overridden\n` + + 'Make sure you are calling "overrideLogicalId" before Stack.exportValue'); + } else { + this._logicalIdOverride = newLogicalId; + } + } + + /** + * Lock the logicalId of the element and do not allow + * any updates (e.g. via overrideLogicalId) + * + * This is needed in cases where you are consuming the LogicalID + * of an element prior to synthesis and you need to not allow future + * changes to the id since doing so would cause the value you just + * consumed to differ from the synth time value of the logicalId. + * + * For example: + * + * const bucket = new Bucket(stack, 'Bucket'); + * stack.exportValue(bucket.bucketArn) <--- consuming the logicalId + * bucket.overrideLogicalId('NewLogicalId') <--- updating logicalId + * + * You should most likely never need to use this method, and if + * you are implementing a feature that requires this, make sure + * you actually require it. + * + * @internal + */ + public _lockLogicalId(): void { + this._logicalIdLocked = true; } /** diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index 4e6287f72fc2b..6a9274b4bc1f0 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -907,6 +907,14 @@ export class Stack extends CoreConstruct implements ITaggable { throw new Error('exportValue: either supply \'name\' or make sure to export a resource attribute (like \'bucket.bucketName\')'); } + // if exportValue is being called manually (which is pre onPrepare) then the logicalId + // could potentially be changed by a call to overrideLogicalId. This would cause our Export/Import + // to have an incorrect id. For a better user experience, lock the logicalId and throw an error + // if the user tries to override the id _after_ calling exportValue + if (CfnElement.isCfnElement(resolvable.target)) { + resolvable.target._lockLogicalId(); + } + // "teleport" the value here, in case it comes from a nested stack. This will also // ensure the value is from our own scope. const exportable = referenceNestedStackValueInParent(resolvable, this); diff --git a/packages/@aws-cdk/core/test/stack.test.ts b/packages/@aws-cdk/core/test/stack.test.ts index d55352601503b..f0f1637ed54ad 100644 --- a/packages/@aws-cdk/core/test/stack.test.ts +++ b/packages/@aws-cdk/core/test/stack.test.ts @@ -576,6 +576,52 @@ describe('stack', () => { expect(templateA).toEqual(templateM); }); + test('throw error if overrideLogicalId is used and logicalId is locked', () => { + // GIVEN: manual + const appM = new App(); + const producerM = new Stack(appM, 'Producer'); + const resourceM = new CfnResource(producerM, 'ResourceXXX', { type: 'AWS::Resource' }); + producerM.exportValue(resourceM.getAtt('Att')); + + // THEN - producers are the same + expect(() => { + resourceM.overrideLogicalId('OVERRIDE_LOGICAL_ID'); + }).toThrow(/The logicalId for resource at path Producer\/ResourceXXX has been locked and cannot be overridden/); + }); + + test('do not throw error if overrideLogicalId is used and logicalId is not locked', () => { + // GIVEN: manual + const appM = new App(); + const producerM = new Stack(appM, 'Producer'); + const resourceM = new CfnResource(producerM, 'ResourceXXX', { type: 'AWS::Resource' }); + + // THEN - producers are the same + resourceM.overrideLogicalId('OVERRIDE_LOGICAL_ID'); + producerM.exportValue(resourceM.getAtt('Att')); + + const template = appM.synth().getStackByName(producerM.stackName).template; + expect(template).toEqual({ + Outputs: { + ExportsOutputFnGetAttOVERRIDELOGICALIDAtt2DD28019: { + Export: { + Name: 'Producer:ExportsOutputFnGetAttOVERRIDELOGICALIDAtt2DD28019', + }, + Value: { + 'Fn::GetAtt': [ + 'OVERRIDE_LOGICAL_ID', + 'Att', + ], + }, + }, + }, + Resources: { + OVERRIDE_LOGICAL_ID: { + Type: 'AWS::Resource', + }, + }, + }); + }); + test('automatic cross-stack references and manual exports look the same: nested stack edition', () => { // GIVEN: automatic const appA = new App(); From 9a23575f4590a170caf79f4141c16adf431e7c40 Mon Sep 17 00:00:00 2001 From: Joe Flateau Date: Wed, 1 Jun 2022 06:48:36 -0400 Subject: [PATCH 19/25] fix(ecr-assets): cannot build ARM images using modern stack synthesis (#20563) #20439 added support for the --platform option for docker build but the platform prop was not properly passed through when using the new DefaultStackSynthesizer. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-ecr-assets/test/image-asset.test.ts | 16 ++++++++++++++++ .../_asset-manifest-builder.ts | 1 + 2 files changed, 17 insertions(+) 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 4fe6299ab9c87..89a5aeb3d0309 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 @@ -170,6 +170,22 @@ describe('image asset', () => { expect(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).platform).toEqual('linux/arm64'); }); + testFutureBehavior('with platform: default synth edition', flags, App, (app) => { + // GIVEN + const stack = new Stack(app, 'Stack', { synthesizer: new DefaultStackSynthesizer() }); + // WHEN + const asset = new DockerImageAsset(stack, 'Image', { + directory: path.join(__dirname, 'demo-image'), + platform: Platform.LINUX_ARM64, + }); + + // THEN + const asm = app.synth(); + const stackAssets = JSON.parse(fs.readFileSync(path.join(asm.directory, 'Stack.assets.json'), { encoding: 'utf-8' })); + const dockerImageAsset = stackAssets.dockerImages[asset.assetHash]; + expect(dockerImageAsset.source.platform).toEqual('linux/arm64'); + }); + testFutureBehavior('asset.repository.grantPull can be used to grant a principal permissions to use the image', flags, App, (app) => { // GIVEN const stack = new Stack(app); diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/_asset-manifest-builder.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/_asset-manifest-builder.ts index b1e121399cf4a..3d0d833245b8d 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/_asset-manifest-builder.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/_asset-manifest-builder.ts @@ -93,6 +93,7 @@ export class AssetManifestBuilder { dockerBuildTarget: asset.dockerBuildTarget, dockerFile: asset.dockerFile, networkMode: asset.networkMode, + platform: asset.platform, }, destinations: { [this.manifestEnvName(stack)]: { From 264c02e6014552cd73f38acef0df2205811d6c86 Mon Sep 17 00:00:00 2001 From: Cory Hall <43035978+corymhall@users.noreply.github.com> Date: Wed, 1 Jun 2022 10:45:05 -0400 Subject: [PATCH 20/25] fix(iam): referencing the same immutable role twice makes it mutable (#20497) The solution I went with in this PR was to try and keep the provided id set on the `ImmutableRole` instead of the `Import` role. This should also keep backwards compatibility by only changing the id of the `Import` role if we are returning an `ImmutableRole`. fixes #7255 ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *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-iam/lib/role.ts | 13 +++-- .../aws-iam/test/immutable-role.test.ts | 55 ++++++++++++++++++- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-iam/lib/role.ts b/packages/@aws-cdk/aws-iam/lib/role.ts index 4b19fb7a93c99..0308899dc10ec 100644 --- a/packages/@aws-cdk/aws-iam/lib/role.ts +++ b/packages/@aws-cdk/aws-iam/lib/role.ts @@ -277,15 +277,20 @@ export class Role extends Resource implements IRole { throw new Error('\'addGrantsToResources\' can only be passed if \'mutable: false\''); } - const importedRole = new Import(scope, id); - const roleArnAndScopeStackAccountComparison = Token.compareStrings(importedRole.env.account, scopeStack.account); + const roleArnAndScopeStackAccountComparison = Token.compareStrings(roleAccount ?? '', scopeStack.account); const equalOrAnyUnresolved = roleArnAndScopeStackAccountComparison === TokenComparison.SAME || roleArnAndScopeStackAccountComparison === TokenComparison.BOTH_UNRESOLVED || roleArnAndScopeStackAccountComparison === TokenComparison.ONE_UNRESOLVED; + + // if we are returning an immutable role then the 'importedRole' is just a throwaway construct + // so give it a different id + const mutableRoleId = (options.mutable !== false && equalOrAnyUnresolved) ? id : `MutableRole${id}`; + const importedRole = new Import(scope, mutableRoleId); + // we only return an immutable Role if both accounts were explicitly provided, and different return options.mutable !== false && equalOrAnyUnresolved ? importedRole - : new ImmutableRole(scope, `ImmutableRole${id}`, importedRole, options.addGrantsToResources ?? false); + : new ImmutableRole(scope, id, importedRole, options.addGrantsToResources ?? false); } /** @@ -655,4 +660,4 @@ export interface WithoutPolicyUpdatesOptions { * @default false */ readonly addGrantsToResources?: boolean; -} \ No newline at end of file +} 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 12cb38dc9542b..b79dd3a55c23d 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 { Template, Match } from '@aws-cdk/assertions'; import { Stack } from '@aws-cdk/core'; import * as iam from '../lib'; @@ -53,6 +53,57 @@ describe('ImmutableRole', () => { }); }); + test('id of mutable role remains unchanged', () => { + iam.Role.fromRoleName(stack, 'TestRole123', 'my-role'); + expect(stack.node.tryFindChild('TestRole123')).not.toBeUndefined(); + expect(stack.node.tryFindChild('MutableRoleTestRole123')).toBeUndefined(); + }); + + test('remains mutable when called multiple times', () => { + const user = new iam.User(stack, 'User'); + const policy = new iam.Policy(stack, 'Policy', { + statements: [new iam.PolicyStatement({ + resources: ['*'], + actions: ['s3:*'], + })], + users: [user], + }); + + function findRole(): iam.IRole { + const foundRole = stack.node.tryFindChild('MyRole') as iam.IRole; + if (foundRole) { + return foundRole; + } + return iam.Role.fromRoleArn(stack, 'MyRole', 'arn:aws:iam::12345:role/role-name', { mutable: false }); + } + + let foundRole = findRole(); + foundRole.attachInlinePolicy(policy); + foundRole = findRole(); + foundRole.attachInlinePolicy(policy); + + expect(stack.node.tryFindChild('MutableRoleMyRole')).not.toBeUndefined(); + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': 's3:*', + 'Resource': '*', + 'Effect': 'Allow', + }, + ], + 'Version': '2012-10-17', + }, + 'PolicyName': 'Policy23B91518', + 'Roles': Match.absent(), + 'Users': [ + { + 'Ref': 'User00B015A1', + }, + ], + }); + }); + test('ignores calls to addManagedPolicy', () => { mutableRole.addManagedPolicy({ managedPolicyArn: 'Arn1' }); @@ -117,4 +168,4 @@ describe('ImmutableRole', () => { new Construct(immutableRole as unknown as Construct, 'Child'); new Construct(mutableRole.withoutPolicyUpdates() as unknown as Construct, 'Child2'); }); -}); \ No newline at end of file +}); From 74318c7d22bfc00de9e005f68a0a6aaa58c7db39 Mon Sep 17 00:00:00 2001 From: Calvin Combs <66279577+comcalvi@users.noreply.github.com> Date: Wed, 1 Jun 2022 09:44:32 -0600 Subject: [PATCH 21/25] fix(events-targets): EventBus IAM statements are only added for the first target (#20479) If the `EventBus` constructor is called with no arguments, then attaching more than a single target to its policy will silently fail to add them. This is because of a strange edge case in the implementation that was not accounted for previously; it is possible for `props.role` to be `undefined`, yet `singletonEventRole()` is still capable of finding the desired role. `singletonEventRole()` does not add the new statements to any IAM policies that it finds, so as a result adding multiple targets does not add any of them. Fixes #19407. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-events-targets/lib/api-destination.ts | 11 ++- .../aws-events-targets/lib/api-gateway.ts | 18 +++-- .../@aws-cdk/aws-events-targets/lib/batch.ts | 23 +++--- .../aws-events-targets/lib/codebuild.ts | 13 ++-- .../aws-events-targets/lib/codepipeline.ts | 11 ++- .../aws-events-targets/lib/ecs-task.ts | 9 +-- .../aws-events-targets/lib/event-bus.ts | 6 +- .../lib/kinesis-firehose-stream.ts | 8 +- .../aws-events-targets/lib/kinesis-stream.ts | 7 +- .../aws-events-targets/lib/state-machine.ts | 5 +- .../@aws-cdk/aws-events-targets/lib/util.ts | 4 +- .../test/event-bus/event-rule-target.test.ts | 78 +++++++++++++++++++ 12 files changed, 138 insertions(+), 55 deletions(-) diff --git a/packages/@aws-cdk/aws-events-targets/lib/api-destination.ts b/packages/@aws-cdk/aws-events-targets/lib/api-destination.ts index 8f2bc936a6d28..5ec83563ade94 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/api-destination.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/api-destination.ts @@ -83,13 +83,16 @@ export class ApiDestination implements events.IRuleTarget { addToDeadLetterQueueResourcePolicy(_rule, this.props.deadLetterQueue); } + const role = this.props?.eventRole ?? singletonEventRole(this.apiDestination); + role.addToPrincipalPolicy(new iam.PolicyStatement({ + resources: [this.apiDestination.apiDestinationArn], + actions: ['events:InvokeApiDestination'], + })); + return { ...(this.props ? bindBaseTargetConfig(this.props) : {}), arn: this.apiDestination.apiDestinationArn, - role: this.props?.eventRole ?? singletonEventRole(this.apiDestination, [new iam.PolicyStatement({ - resources: [this.apiDestination.apiDestinationArn], - actions: ['events:InvokeApiDestination'], - })]), + role, input: this.props.event, targetResource: this.apiDestination, httpParameters, diff --git a/packages/@aws-cdk/aws-events-targets/lib/api-gateway.ts b/packages/@aws-cdk/aws-events-targets/lib/api-gateway.ts index 168c5a65a386d..04f073ad82fd6 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/api-gateway.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/api-gateway.ts @@ -98,16 +98,20 @@ export class ApiGateway implements events.IRuleTarget { this.props?.path || '/', this.props?.stage || this.restApi.deploymentStage.stageName, ); + + const role = this.props?.eventRole || singletonEventRole(this.restApi); + role.addToPrincipalPolicy(new iam.PolicyStatement({ + resources: [restApiArn], + actions: [ + 'execute-api:Invoke', + 'execute-api:ManageConnections', + ], + })); + return { ...(this.props ? bindBaseTargetConfig(this.props) : {}), arn: restApiArn, - role: this.props?.eventRole || singletonEventRole(this.restApi, [new iam.PolicyStatement({ - resources: [restApiArn], - actions: [ - 'execute-api:Invoke', - 'execute-api:ManageConnections', - ], - })]), + role, deadLetterConfig: this.props?.deadLetterQueue && { arn: this.props.deadLetterQueue?.queueArn }, input: this.props?.postBody, targetResource: this.restApi, diff --git a/packages/@aws-cdk/aws-events-targets/lib/batch.ts b/packages/@aws-cdk/aws-events-targets/lib/batch.ts index f0186efe6089d..369f36517aea6 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/batch.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/batch.ts @@ -87,20 +87,21 @@ export class BatchJob implements events.IRuleTarget { addToDeadLetterQueueResourcePolicy(rule, this.props.deadLetterQueue); } + // When scoping resource-level access for job submission, you must provide both job queue and job definition resource types. + // https://docs.aws.amazon.com/batch/latest/userguide/ExamplePolicies_BATCH.html#iam-example-restrict-job-def + const role = singletonEventRole(this.jobDefinitionScope); + role.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['batch:SubmitJob'], + resources: [ + this.jobDefinitionArn, + this.jobQueueArn, + ], + })); + return { ...bindBaseTargetConfig(this.props), arn: this.jobQueueArn, - // When scoping resource-level access for job submission, you must provide both job queue and job definition resource types. - // https://docs.aws.amazon.com/batch/latest/userguide/ExamplePolicies_BATCH.html#iam-example-restrict-job-def - role: singletonEventRole(this.jobDefinitionScope, [ - new iam.PolicyStatement({ - actions: ['batch:SubmitJob'], - resources: [ - this.jobDefinitionArn, - this.jobQueueArn, - ], - }), - ]), + role, input: this.props.event, targetResource: this.jobQueueScope, batchParameters, diff --git a/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts b/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts index a9da8719cfef7..14f9dd4c0c626 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts @@ -44,15 +44,16 @@ export class CodeBuildProject implements events.IRuleTarget { addToDeadLetterQueueResourcePolicy(_rule, this.props.deadLetterQueue); } + const role = this.props.eventRole || singletonEventRole(this.project); + role.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['codebuild:StartBuild'], + resources: [this.project.projectArn], + })); + return { ...bindBaseTargetConfig(this.props), arn: this.project.projectArn, - role: this.props.eventRole || singletonEventRole(this.project, [ - new iam.PolicyStatement({ - actions: ['codebuild:StartBuild'], - resources: [this.project.projectArn], - }), - ]), + role, input: this.props.event, targetResource: this.project, }; diff --git a/packages/@aws-cdk/aws-events-targets/lib/codepipeline.ts b/packages/@aws-cdk/aws-events-targets/lib/codepipeline.ts index 8d2006378f121..27d80a55e33f9 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/codepipeline.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/codepipeline.ts @@ -26,14 +26,17 @@ export class CodePipeline implements events.IRuleTarget { } public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig { + const role = this.options.eventRole || singletonEventRole(this.pipeline); + role.addToPrincipalPolicy(new iam.PolicyStatement({ + resources: [this.pipeline.pipelineArn], + actions: ['codepipeline:StartPipelineExecution'], + })); + return { ...bindBaseTargetConfig(this.options), id: '', arn: this.pipeline.pipelineArn, - role: this.options.eventRole || singletonEventRole(this.pipeline, [new iam.PolicyStatement({ - resources: [this.pipeline.pipelineArn], - actions: ['codepipeline:StartPipelineExecution'], - })]), + role, targetResource: this.pipeline, }; } diff --git a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts index 13a08dbd8d4eb..80147f71b8b40 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts @@ -118,12 +118,9 @@ export class EcsTask implements events.IRuleTarget { this.taskCount = props.taskCount ?? 1; this.platformVersion = props.platformVersion; - if (props.role) { - const role = props.role; - this.createEventRolePolicyStatements().forEach(role.addToPrincipalPolicy.bind(role)); - this.role = role; - } else { - this.role = singletonEventRole(this.taskDefinition, this.createEventRolePolicyStatements()); + this.role = props.role ?? singletonEventRole(this.taskDefinition); + for (const stmt of this.createEventRolePolicyStatements()) { + this.role.addToPrincipalPolicy(stmt); } // Security groups are only configurable with the "awsvpc" network mode. diff --git a/packages/@aws-cdk/aws-events-targets/lib/event-bus.ts b/packages/@aws-cdk/aws-events-targets/lib/event-bus.ts index 7026273ef5330..eb739c0b37296 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/event-bus.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/event-bus.ts @@ -36,10 +36,8 @@ export class EventBus implements events.IRuleTarget { constructor(private readonly eventBus: events.IEventBus, private readonly props: EventBusProps = {}) { } bind(rule: events.IRule, _id?: string): events.RuleTargetConfig { - if (this.props.role) { - this.props.role.addToPrincipalPolicy(this.putEventStatement()); - } - const role = this.props.role ?? singletonEventRole(rule, [this.putEventStatement()]); + const role = this.props.role ?? singletonEventRole(rule); + role.addToPrincipalPolicy(this.putEventStatement()); if (this.props.deadLetterQueue) { addToDeadLetterQueueResourcePolicy(rule, this.props.deadLetterQueue); diff --git a/packages/@aws-cdk/aws-events-targets/lib/kinesis-firehose-stream.ts b/packages/@aws-cdk/aws-events-targets/lib/kinesis-firehose-stream.ts index b52f7bf423df2..2a454f5164d77 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/kinesis-firehose-stream.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/kinesis-firehose-stream.ts @@ -31,14 +31,16 @@ export class KinesisFirehoseStream implements events.IRuleTarget { * result from a Event Bridge event. */ public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig { - const policyStatements = [new iam.PolicyStatement({ + const role = singletonEventRole(this.stream); + role.addToPrincipalPolicy(new iam.PolicyStatement({ actions: ['firehose:PutRecord', 'firehose:PutRecordBatch'], resources: [this.stream.attrArn], - })]; + })); + return { arn: this.stream.attrArn, - role: singletonEventRole(this.stream, policyStatements), + role, input: this.props.message, targetResource: this.stream, }; diff --git a/packages/@aws-cdk/aws-events-targets/lib/kinesis-stream.ts b/packages/@aws-cdk/aws-events-targets/lib/kinesis-stream.ts index 743b197e19d52..9114561a6ca97 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/kinesis-stream.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/kinesis-stream.ts @@ -45,14 +45,15 @@ export class KinesisStream implements events.IRuleTarget { * result from a CloudWatch event. */ public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig { - const policyStatements = [new iam.PolicyStatement({ + const role = singletonEventRole(this.stream); + role.addToPrincipalPolicy(new iam.PolicyStatement({ actions: ['kinesis:PutRecord', 'kinesis:PutRecords'], resources: [this.stream.streamArn], - })]; + })); return { arn: this.stream.streamArn, - role: singletonEventRole(this.stream, policyStatements), + role, input: this.props.message, targetResource: this.stream, kinesisParameters: this.props.partitionKeyPath ? { partitionKeyPath: this.props.partitionKeyPath } : undefined, diff --git a/packages/@aws-cdk/aws-events-targets/lib/state-machine.ts b/packages/@aws-cdk/aws-events-targets/lib/state-machine.ts index ce780bf99d2d2..c328f16884930 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/state-machine.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/state-machine.ts @@ -29,11 +29,8 @@ export class SfnStateMachine implements events.IRuleTarget { private readonly role: iam.IRole; constructor(public readonly machine: sfn.IStateMachine, private readonly props: SfnStateMachineProps = {}) { - if (props.role) { - props.role.grant(new iam.ServicePrincipal('events.amazonaws.com')); - } // no statements are passed because we are configuring permissions by using grant* helper below - this.role = props.role ?? singletonEventRole(machine, []); + this.role = props.role ?? singletonEventRole(machine); machine.grantStartExecution(this.role); } diff --git a/packages/@aws-cdk/aws-events-targets/lib/util.ts b/packages/@aws-cdk/aws-events-targets/lib/util.ts index 086c63b4c2224..8ed77ba548d66 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/util.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/util.ts @@ -71,7 +71,7 @@ export function bindBaseTargetConfig(props: TargetBaseProps) { * events have the same target, they will share a role. * @internal */ -export function singletonEventRole(scope: IConstruct, policyStatements: iam.PolicyStatement[]): iam.IRole { +export function singletonEventRole(scope: IConstruct): iam.IRole { const id = 'EventsRole'; const existing = scope.node.tryFindChild(id) as iam.IRole; if (existing) { return existing; } @@ -81,8 +81,6 @@ export function singletonEventRole(scope: IConstruct, policyStatements: iam.Poli assumedBy: new iam.ServicePrincipal('events.amazonaws.com'), }); - policyStatements.forEach(role.addToPolicy.bind(role)); - return role; } diff --git a/packages/@aws-cdk/aws-events-targets/test/event-bus/event-rule-target.test.ts b/packages/@aws-cdk/aws-events-targets/test/event-bus/event-rule-target.test.ts index cb2940fdb0cd7..554d3b753be62 100644 --- a/packages/@aws-cdk/aws-events-targets/test/event-bus/event-rule-target.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/event-bus/event-rule-target.test.ts @@ -167,3 +167,81 @@ test('with a Dead Letter Queue specified', () => { ], }); }); + +test('event buses are correctly added to the rule\'s principal policy', () => { + const stack = new Stack(); + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.expression('rate(1 min)'), + }); + + const bus1 = new events.EventBus(stack, 'bus' + 1); + const bus2 = new events.EventBus(stack, 'bus' + 2); + + rule.addTarget(new targets.EventBus(bus1)); + rule.addTarget(new targets.EventBus(bus2)); + + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + Targets: [ + { + Arn: { + 'Fn::GetAtt': [ + 'bus110C385DC', + 'Arn', + ], + }, + Id: 'Target0', + RoleArn: { + 'Fn::GetAtt': [ + 'RuleEventsRoleC51A4248', + 'Arn', + ], + }, + }, + { + Arn: { + 'Fn::GetAtt': [ + 'bus22D01F126', + 'Arn', + ], + }, + Id: 'Target1', + RoleArn: { + 'Fn::GetAtt': [ + 'RuleEventsRoleC51A4248', + 'Arn', + ], + }, + }, + ], + }); + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Effect: 'Allow', + Action: 'events:PutEvents', + Resource: { + 'Fn::GetAtt': [ + 'bus110C385DC', + 'Arn', + ], + }, + }, + { + Effect: 'Allow', + Action: 'events:PutEvents', + Resource: { + 'Fn::GetAtt': [ + 'bus22D01F126', + 'Arn', + ], + }, + }, + ], + Version: '2012-10-17', + }, + Roles: [{ + Ref: 'RuleEventsRoleC51A4248', + }], + }); +}); From e9e886462190393e508e261a12cbc8bed2f4542d Mon Sep 17 00:00:00 2001 From: madeline-k Date: Wed, 1 Jun 2022 16:57:54 +0000 Subject: [PATCH 22/25] automatic pkglint fixes --- packages/@aws-cdk/integ-runner/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/integ-runner/package.json b/packages/@aws-cdk/integ-runner/package.json index 474d700ac5d4b..81adf1e1c0790 100644 --- a/packages/@aws-cdk/integ-runner/package.json +++ b/packages/@aws-cdk/integ-runner/package.json @@ -2,7 +2,6 @@ "name": "@aws-cdk/integ-runner", "description": "CDK Integration Testing Tool", "version": "0.0.0", - "private": false, "main": "lib/index.js", "types": "lib/index.d.ts", "bin": { @@ -99,5 +98,6 @@ "maturity": "experimental", "publishConfig": { "tag": "latest" - } + }, + "private": true } From 22678f8872f41e10c9917b11a265de8393f50490 Mon Sep 17 00:00:00 2001 From: Madeline Kusters Date: Wed, 1 Jun 2022 10:57:06 -0700 Subject: [PATCH 23/25] core: fix unit test --- packages/@aws-cdk/core/test/stack.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/core/test/stack.test.ts b/packages/@aws-cdk/core/test/stack.test.ts index 51b2afbbfd8ca..dd83e954ed17b 100644 --- a/packages/@aws-cdk/core/test/stack.test.ts +++ b/packages/@aws-cdk/core/test/stack.test.ts @@ -593,7 +593,7 @@ describe('stack', () => { producerM.exportValue(resourceM.getAtt('Att')); const template = appM.synth().getStackByName(producerM.stackName).template; - expect(template).toEqual({ + expect(template).toMatchObject({ Outputs: { ExportsOutputFnGetAttOVERRIDELOGICALIDAtt2DD28019: { Export: { From 93d4d2736de8bd9d0336249212f330fc4bead6d1 Mon Sep 17 00:00:00 2001 From: Madeline Kusters Date: Wed, 1 Jun 2022 11:14:50 -0700 Subject: [PATCH 24/25] lambda: use constructs.IConstruct --- packages/@aws-cdk/aws-lambda/lib/function.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 2dfe5c7ffacdd..1d26e85576f27 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -6,9 +6,9 @@ import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; import * as sns from '@aws-cdk/aws-sns'; import * as sqs from '@aws-cdk/aws-sqs'; -import { Annotations, ArnFormat, CfnResource, Duration, FeatureFlags, Fn, IAspect, IConstruct, Lazy, Names, Size, Stack, Token } from '@aws-cdk/core'; +import { Annotations, ArnFormat, CfnResource, Duration, FeatureFlags, Fn, IAspect, Lazy, Names, Size, Stack, Token } from '@aws-cdk/core'; import { LAMBDA_RECOGNIZE_LAYER_VERSION } from '@aws-cdk/cx-api'; -import { Construct } from 'constructs'; +import { Construct, IConstruct } from 'constructs'; import { AliasOptions, Alias } from './alias'; import { Architecture } from './architecture'; import { Code, CodeConfig } from './code'; From 4b0f8fff003b2e46800d46a8232a4865c8611056 Mon Sep 17 00:00:00 2001 From: Madeline Kusters Date: Wed, 1 Jun 2022 11:44:00 -0700 Subject: [PATCH 25/25] lambda: update unit tests for v2 branch --- .../@aws-cdk/aws-lambda/test/function-hash.test.ts | 10 +++++----- packages/@aws-cdk/aws-lambda/test/function.test.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts b/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts index 06de38ebe1e47..1b38b9c8a88f6 100644 --- a/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts @@ -181,7 +181,7 @@ describe('function hash', () => { }); expect(calculateFunctionHash(fn1)).toEqual(calculateFunctionHash(fn2)); - expect(calculateFunctionHash(fn1)).toEqual('028f8a4cb1c719f29e70b7b3c0f2a9d7'); + expect(calculateFunctionHash(fn1)).toEqual('8633cf3f4f019915d1f5d2c0c34c080a'); }); test('different layers impacts hash', () => { @@ -201,8 +201,8 @@ describe('function hash', () => { layers: [layer2], }); - expect(calculateFunctionHash(fn1)).toEqual('028f8a4cb1c719f29e70b7b3c0f2a9d7'); - expect(calculateFunctionHash(fn2)).toEqual('e74647bf81c4d532137545c8234726f3'); + expect(calculateFunctionHash(fn1)).toEqual('8633cf3f4f019915d1f5d2c0c34c080a'); + expect(calculateFunctionHash(fn2)).toEqual('22a2173d7ae9608c12f4d93703d28b03'); }); describe('impact of lambda layer order on hash', () => { @@ -223,8 +223,8 @@ describe('function hash', () => { layers: [layer2, layer1], }); - expect(calculateFunctionHash(fn1)).toEqual('b6cade45d8f9c77f29f0ab169004113c'); - expect(calculateFunctionHash(fn2)).toEqual('0d79a0b6bcac599b278e63b173eca170'); + expect(calculateFunctionHash(fn1)).toEqual('f6d16d9486308ab39801029f742bf4d2'); + expect(calculateFunctionHash(fn2)).toEqual('93b2e5f48356020117f72c248ec1e0da'); }); test('with feature flag, we sort layers so order is consistent', () => { diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index ce351976c1681..7c3c2c2d5aacf 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -2983,7 +2983,7 @@ test('FunctionVersionUpgrade adds new description to function', () => { Code: { ZipFile: 'foo' }, Handler: 'bar', Runtime: 'nodejs14.x', - Description: 'my description version-hash:de36a94cfdb5cb58f7c7b4cd975120ba', + Description: 'my description version-hash:54f18c47346ed84843c2dac547de81fa', }, }); });