From e721d62e583581549c2d6ed798ba61e070de5c6a Mon Sep 17 00:00:00 2001 From: AWS CDK Team Date: Tue, 24 Mar 2020 13:16:39 +0000 Subject: [PATCH 1/5] chore(release): 1.31.0 --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ lerna.json | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dbe2328e395c..e2b8bb5380ea1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,36 @@ 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.31.0](https://github.com/aws/aws-cdk/compare/v1.30.0...v1.31.0) (2020-03-24) + + +### ⚠ BREAKING CHANGES + +* .NET Core v3.1 is required with JSII v1.1 + +Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> + +### Features + +* **cloud9:** Support Cloud9 EC2 Environment ([#6298](https://github.com/aws/aws-cdk/issues/6298)) ([f50b876](https://github.com/aws/aws-cdk/commit/f50b8769548c34a66cf05079d1ac721c83992840)) +* **codepipeline:** add experimental support for the BitBucket source action ([#6756](https://github.com/aws/aws-cdk/issues/6756)) ([95bb1ad](https://github.com/aws/aws-cdk/commit/95bb1ad60e600007421acd6d160e0d7fb9bc0389)), closes [#6710](https://github.com/aws/aws-cdk/issues/6710) +* **eks:** KubernetesPatch ([#6753](https://github.com/aws/aws-cdk/issues/6753)) ([c7fab5b](https://github.com/aws/aws-cdk/commit/c7fab5b29aca518fb6e1c8f2868d915885fedf04)), closes [#6723](https://github.com/aws/aws-cdk/issues/6723) +* **events:** AWS Batch event target ([#6570](https://github.com/aws/aws-cdk/issues/6570)) ([73899a9](https://github.com/aws/aws-cdk/commit/73899a95ffe52c51ff77155fd654c2b4cdef7241)) + + +### Bug Fixes + +* **acm:** Allow tokens as a part of the hosted zone name ([#6685](https://github.com/aws/aws-cdk/issues/6685)) ([acfb6ef](https://github.com/aws/aws-cdk/commit/acfb6ef8b5f94c04206c3afc8d12bfaf87c1a650)), closes [#6133](https://github.com/aws/aws-cdk/issues/6133) +* **aws-ecs-patterns:** only create an A record if LB is public ([#6895](https://github.com/aws/aws-cdk/issues/6895)) ([f31f4e1](https://github.com/aws/aws-cdk/commit/f31f4e128d5f9dd8d673ac2a3c28d792d1427dda)), closes [#6702](https://github.com/aws/aws-cdk/issues/6702) +* **cdk-assets:** context path not honored by Docker asset build ([#6957](https://github.com/aws/aws-cdk/issues/6957)) ([1edd507](https://github.com/aws/aws-cdk/commit/1edd5076e8a5b4e2194c73e395d1712f74cd2ba1)), closes [#6954](https://github.com/aws/aws-cdk/issues/6954) [#6814](https://github.com/aws/aws-cdk/issues/6814) +* **cloudwatch:** unhelpful error when reusing metric IDs ([#6892](https://github.com/aws/aws-cdk/issues/6892)) ([60253a3](https://github.com/aws/aws-cdk/commit/60253a319d6f185cf807ca45dac4ce0be4ab5777)) +* **cognito:** user pool - link style email verification fails to deploy ([#6938](https://github.com/aws/aws-cdk/issues/6938)) ([b5c60d5](https://github.com/aws/aws-cdk/commit/b5c60d50a6c4fb7e93185c5874a2651ba40d0247)), closes [#6811](https://github.com/aws/aws-cdk/issues/6811) +* **ec2:** spelling error in Instance's subnet selection logic. ([#6752](https://github.com/aws/aws-cdk/issues/6752)) ([564561a](https://github.com/aws/aws-cdk/commit/564561a5462b78bc29cd6d6968abe6b05a670df2)) +* **iam:** immutable role cannot be used as a construct ([#6920](https://github.com/aws/aws-cdk/issues/6920)) ([56be032](https://github.com/aws/aws-cdk/commit/56be032149f3e698120f7653e36ef1fea565f952)), closes [#6885](https://github.com/aws/aws-cdk/issues/6885) + + +* .NET Core 3.1 is required with JSII v1.1 ([#6951](https://github.com/aws/aws-cdk/issues/6951)) ([24f12d6](https://github.com/aws/aws-cdk/commit/24f12d6931fc107cc959404516e5c33001c6f7f5)) + ## [1.30.0](https://github.com/aws/aws-cdk/compare/v1.29.0...v1.30.0) (2020-03-18) diff --git a/lerna.json b/lerna.json index 64ed5058b057f..84f8a4fabb7f1 100644 --- a/lerna.json +++ b/lerna.json @@ -10,5 +10,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.30.0" + "version": "1.31.0" } From 73a6693894965e72df12c28a337b4db7b1a6e481 Mon Sep 17 00:00:00 2001 From: Shiv Lakshminarayan Date: Tue, 24 Mar 2020 08:40:09 -0700 Subject: [PATCH 2/5] Update CHANGELOG.md --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2b8bb5380ea1..d34f1d024c14d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,6 @@ All notable changes to this project will be documented in this file. See [standa * .NET Core v3.1 is required with JSII v1.1 -Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> - ### Features * **cloud9:** Support Cloud9 EC2 Environment ([#6298](https://github.com/aws/aws-cdk/issues/6298)) ([f50b876](https://github.com/aws/aws-cdk/commit/f50b8769548c34a66cf05079d1ac721c83992840)) From 5cd2018743a5946ba9c26497ddaf25678766a303 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2020 08:23:21 +0000 Subject: [PATCH 3/5] chore(deps): bump aws-sdk from 2.644.0 to 2.645.0 (#6980) Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.644.0 to 2.645.0. - [Release notes](https://github.com/aws/aws-sdk-js/releases) - [Changelog](https://github.com/aws/aws-sdk-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js/compare/v2.644.0...v2.645.0) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- packages/@aws-cdk/aws-cloudfront/package.json | 2 +- packages/@aws-cdk/aws-cloudtrail/package.json | 2 +- packages/@aws-cdk/aws-codebuild/package.json | 2 +- packages/@aws-cdk/aws-codecommit/package.json | 2 +- packages/@aws-cdk/aws-dynamodb/package.json | 2 +- packages/@aws-cdk/aws-eks/package.json | 2 +- packages/@aws-cdk/aws-events-targets/package.json | 2 +- packages/@aws-cdk/aws-lambda/package.json | 2 +- packages/@aws-cdk/aws-route53/package.json | 2 +- packages/@aws-cdk/aws-sqs/package.json | 2 +- packages/@aws-cdk/custom-resources/package.json | 2 +- packages/aws-cdk/package.json | 2 +- packages/cdk-assets/package.json | 2 +- yarn.lock | 8 ++++---- 14 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index 243b966dff147..d620e5e60979f 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.30", - "aws-sdk": "^2.644.0", + "aws-sdk": "^2.645.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index edadc683d6fbe..50c415fe73c7d 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.30", - "aws-sdk": "^2.644.0", + "aws-sdk": "^2.645.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index 30a541169da81..2563ee72a5fab 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -70,7 +70,7 @@ "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sqs": "0.0.0", "@types/nodeunit": "^0.0.30", - "aws-sdk": "^2.644.0", + "aws-sdk": "^2.645.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index 541181d239444..f273d2335fe58 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -70,7 +70,7 @@ "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@types/nodeunit": "^0.0.30", - "aws-sdk": "^2.644.0", + "aws-sdk": "^2.645.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index ff233dc6aef6a..2e3505b5d7bbe 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.30", - "aws-sdk": "^2.644.0", + "aws-sdk": "^2.645.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index a7b33c8aa660f..7c83727dee60b 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.30", - "aws-sdk": "^2.644.0", + "aws-sdk": "^2.645.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index aac2639895c79..89ff7d9735b32 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -86,7 +86,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", - "aws-sdk": "^2.644.0", + "aws-sdk": "^2.645.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index 35894a23abbef..1166871f8495c 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -71,7 +71,7 @@ "@types/lodash": "^4.14.149", "@types/nodeunit": "^0.0.30", "@types/sinon": "^7.5.2", - "aws-sdk": "^2.644.0", + "aws-sdk": "^2.645.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 67164679f1325..41945a00ccfd8 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.30", - "aws-sdk": "^2.644.0", + "aws-sdk": "^2.645.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index 8e09dc3919602..31116564b44e6 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -65,7 +65,7 @@ "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@types/nodeunit": "^0.0.30", - "aws-sdk": "^2.644.0", + "aws-sdk": "^2.645.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index 7541b32e53ba0..5c80ff13f4e2e 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -73,7 +73,7 @@ "@types/aws-lambda": "^8.10.39", "@types/fs-extra": "^8.1.0", "@types/sinon": "^7.5.2", - "aws-sdk": "^2.644.0", + "aws-sdk": "^2.645.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index d9a0de58da93d..805397c8036b3 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -69,7 +69,7 @@ "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/region-info": "0.0.0", "archiver": "^3.1.1", - "aws-sdk": "^2.644.0", + "aws-sdk": "^2.645.0", "camelcase": "^5.3.1", "cdk-assets": "0.0.0", "colors": "^1.4.0", diff --git a/packages/cdk-assets/package.json b/packages/cdk-assets/package.json index c8be8821cdb45..1742180c9603a 100644 --- a/packages/cdk-assets/package.json +++ b/packages/cdk-assets/package.json @@ -44,7 +44,7 @@ "dependencies": { "@aws-cdk/cdk-assets-schema": "0.0.0", "archiver": "^3.1.1", - "aws-sdk": "^2.644.0", + "aws-sdk": "^2.645.0", "glob": "^7.1.6", "yargs": "^15.3.1" }, diff --git a/yarn.lock b/yarn.lock index 9099312d02c9b..f2c4b0065b561 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2619,10 +2619,10 @@ aws-sdk-mock@^5.1.0: sinon "^9.0.1" traverse "^0.6.6" -aws-sdk@^2.637.0, aws-sdk@^2.644.0: - version "2.644.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.644.0.tgz#a7422be28b5952955db8a0e09defb5341082b252" - integrity sha512-FkglRU5fvWHwT2Ll0caXS/QzRNzh5ZCS5cs4pF2nP0OjdD7VNJhNtu5lOnSzvMHAtZQJMsG3k0em8PoI8vk1AQ== +aws-sdk@^2.637.0, aws-sdk@^2.645.0: + version "2.645.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.645.0.tgz#20865d693a5150fb6d5770ba3ce1efd558d4bd0a" + integrity sha512-zElxkYl5lRxf1wiByd6C3kBKBNtA04ltC++DhcL3OypNBAn/LjnHLR1r7TOn6XyM9xgM7wPwm7MS43n0AS2qYg== dependencies: buffer "4.9.1" events "1.1.1" From c94ce62bc71387d031cf291dbce40243feb50e83 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Wed, 25 Mar 2020 11:44:05 +0200 Subject: [PATCH 4/5] feat(lambda): currentVersion, version.addAlias() (#6771) It is common for AWS services to require an explicit AWS Lambda Version when referencing functions. When an `AWS::Lambda::Version` resource is defined in CloudFormation is captures the AWS Lambda configuration *at the time of the creation of the version resource. This means that if the function's configuration or code is updated, the Version resource will no longer point to the function defined in the stack. To address this, we introduce a property `function.currentVersion` which will create a new AWS::Lambda::Version resource every time the function's configuration changes. This is done by encoding a hash of the function's CloudFormation properties into the logical ID of the version resource. Additionally, this change adds `version.addAlias` which makes it easier to define an AWS Lambda alias for a version. The result is this: fn.currentVersion.addAlias('live'); We employ an approach similar to apigateway's "Deployment" resource in order to implement `currentVersion`: during "prepare", we synthesize the CloudFormation template snippet of the AWS::Lambda::Function resource, calculate an MD5 for it and append it to the logical ID of the version resource. Resolves #6750 Resolves #5334 --- packages/@aws-cdk/aws-lambda/README.md | 63 ++++++ packages/@aws-cdk/aws-lambda/lib/alias.ts | 33 +-- .../@aws-cdk/aws-lambda/lib/function-base.ts | 17 +- .../@aws-cdk/aws-lambda/lib/function-hash.ts | 23 +++ packages/@aws-cdk/aws-lambda/lib/function.ts | 103 ++++++++-- .../@aws-cdk/aws-lambda/lib/lambda-version.ts | 61 +++++- packages/@aws-cdk/aws-lambda/lib/util.ts | 11 + .../test/integ.current-version.expected.json | 143 +++++++++++++ .../aws-lambda/test/integ.current-version.ts | 27 +++ .../aws-lambda/test/test.function-hash.ts | 194 ++++++++++++++++++ .../@aws-cdk/aws-lambda/test/test.function.ts | 54 ++++- .../aws-lambda/test/test.lambda-version.ts | 29 +++ 12 files changed, 720 insertions(+), 38 deletions(-) create mode 100644 packages/@aws-cdk/aws-lambda/lib/function-hash.ts create mode 100644 packages/@aws-cdk/aws-lambda/lib/util.ts create mode 100644 packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json create mode 100644 packages/@aws-cdk/aws-lambda/test/integ.current-version.ts create mode 100644 packages/@aws-cdk/aws-lambda/test/test.function-hash.ts diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index c9e8433930f8a..1ddc674c07da5 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -49,6 +49,69 @@ to our CDK project directory. This is especially important when we want to share this construct through a library. Different programming languages will have different techniques for bundling resources into libraries. +When using `fromAsset` or `fromInline`, you can obtain the hash of source +through the `function.codeHash` property. This property will return `undefined` +if the code hash cannot be calculated during synthesis (e.g. when using code +from an S3 bucket). + +### Versions and Aliases + +You can use +[versions](https://docs.aws.amazon.com/lambda/latest/dg/configuration-versions.html) +to manage the deployment of your AWS Lambda functions. For example, you can +publish a new version of a function for beta testing without affecting users of +the stable production version. + +The function version includes the following information: + +- The function code and all associated dependencies. +- The Lambda runtime that executes the function. +- All of the function settings, including the environment variables. +- A unique Amazon Resource Name (ARN) to identify this version of the function. + +You can define one or more +[aliases](https://docs.aws.amazon.com/lambda/latest/dg/configuration-aliases.html) +for your AWS Lambda function. A Lambda alias is like a pointer to a specific +Lambda function version. Users can access the function version using the alias +ARN. + +The `fn.currentVersion` property can be used to obtain a `lambda.Version` +resource that represents the AWS Lambda function defined in your application. +Any change to your function's code or configuration will result in the creation +of a new version resource. You can specify options for this version through the +`currentVersionOptions` property. + +> The `currentVersion` property is only supported when your AWS Lambda function +> uses either `lambda.Code.fromAsset` or `lambda.Code.fromInline`. Other types +> of code providers (such as `lambda.Code.fromBucket`) require that you define a +> `lambda.Version` resource directly since the CDK is unable to determine if +> their contents had changed. + +The `version.addAlias()` method can be used to define an AWS Lambda alias that +points to a specific version. + +The following example defines an alias named `live` which will always point to a +version that represents the function as defined in your CDK app. When you change +your lambda code or configuration, a new resource will be created. You can +specify options for the current version through the `currentVersionOptions` +property. + +```ts +const fn = new lambda.Function(this, 'MyFunction', { + currentVersionOptions: { + removalPolicy: RemovalPolicy.RETAIN, // retain old versions + retryAttempts: 1 // async retry attempts + } +}); + +fn.currentVersion.addAlias('live'); +``` + +> NOTE: The `fn.latestVersion` property returns a `lambda.IVersion` which +> represents the `$LATEST` pseudo-version. Most AWS services require a specific +> AWS Lambda version, and won't allow you to use `$LATEST`. Therefore, you would +> normally want to use `lambda.currentVersion`. + ### Layers The `lambda.LayerVersion` class can be used to define Lambda layers and manage diff --git a/packages/@aws-cdk/aws-lambda/lib/alias.ts b/packages/@aws-cdk/aws-lambda/lib/alias.ts index d4dc36cb670c3..f533d19a45e17 100644 --- a/packages/@aws-cdk/aws-lambda/lib/alias.ts +++ b/packages/@aws-cdk/aws-lambda/lib/alias.ts @@ -20,9 +20,9 @@ export interface IAlias extends IFunction { } /** - * Properties for a new Lambda alias + * Options for `lambda.Alias`. */ -export interface AliasProps extends EventInvokeConfigOptions { +export interface AliasOptions extends EventInvokeConfigOptions { /** * Description for the alias * @@ -30,18 +30,6 @@ export interface AliasProps extends EventInvokeConfigOptions { */ readonly description?: string; - /** - * Function version this alias refers to - * - * Use lambda.addVersion() to obtain a new lambda version to refer to. - */ - readonly version: IVersion; - - /** - * Name of this alias - */ - readonly aliasName: string; - /** * Additional versions with individual weights this alias points to * @@ -69,6 +57,23 @@ export interface AliasProps extends EventInvokeConfigOptions { readonly provisionedConcurrentExecutions?: number; } +/** + * Properties for a new Lambda alias + */ +export interface AliasProps extends AliasOptions { + /** + * Name of this alias + */ + readonly aliasName: string; + + /** + * Function version this alias refers to + * + * Use lambda.addVersion() to obtain a new lambda version to refer to. + */ + readonly version: IVersion; +} + export interface AliasAttributes { readonly aliasName: string; readonly aliasVersion: IVersion; diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index 2207db443f1b4..c8d9a49764231 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -2,12 +2,14 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import { ConstructNode, IResource, Resource } from '@aws-cdk/core'; +import { AliasOptions } from './alias'; import { EventInvokeConfig, EventInvokeConfigOptions } from './event-invoke-config'; import { IEventSource } from './event-source'; import { EventSourceMapping, EventSourceMappingOptions } from './event-source-mapping'; import { IVersion } from './lambda-version'; import { CfnPermission } from './lambda.generated'; import { Permission } from './permission'; +import { addAlias } from './util'; export interface IFunction extends IResource, ec2.IConnectable, iam.IGrantable { @@ -39,6 +41,13 @@ export interface IFunction extends IResource, ec2.IConnectable, iam.IGrantable { /** * The `$LATEST` version of this function. + * + * Note that this is reference to a non-specific AWS Lambda version, which + * means the function this version refers to can return different results in + * different invocations. + * + * To obtain a reference to an explicit version which references the current + * function configuration, use `lambdaFunction.currentVersion` instead. */ readonly latestVersion: IVersion; @@ -102,7 +111,7 @@ export interface IFunction extends IResource, ec2.IConnectable, iam.IGrantable { /** * Configures options for asynchronous invocation. */ - configureAsyncInvoke(options: EventInvokeConfigOptions): void + configureAsyncInvoke(options: EventInvokeConfigOptions): void; } /** @@ -235,7 +244,7 @@ export abstract class FunctionBase extends Resource implements IFunction { } public get latestVersion(): IVersion { - // Dynamic to avoid invinite recursion when creating the LatestVersion instance... + // Dynamic to avoid infinite recursion when creating the LatestVersion instance... return new LatestVersion(this); } @@ -393,4 +402,8 @@ class LatestVersion extends FunctionBase implements IVersion { public get role() { return this.lambda.role; } + + public addAlias(aliasName: string, options: AliasOptions = {}) { + return addAlias(this, this, aliasName, options); + } } diff --git a/packages/@aws-cdk/aws-lambda/lib/function-hash.ts b/packages/@aws-cdk/aws-lambda/lib/function-hash.ts new file mode 100644 index 0000000000000..a0ca9b8becd92 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/lib/function-hash.ts @@ -0,0 +1,23 @@ +import { CfnResource, Stack } from "@aws-cdk/core"; +import * as crypto from 'crypto'; +import { Function as LambdaFunction } from "./function"; + +export function calculateFunctionHash(fn: LambdaFunction) { + const stack = Stack.of(fn); + + const functionResource = fn.node.defaultChild as CfnResource; + + // render the cloudformation resource from this function + const config = stack.resolve((functionResource as any)._toCloudFormation()); + + const hash = crypto.createHash('md5'); + hash.update(JSON.stringify(config)); + + return hash.digest('hex'); +} + +export function trimFromStart(s: string, maxLength: number) { + const desiredLength = Math.min(maxLength, s.length); + const newStart = s.length - desiredLength; + return s.substring(newStart); +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 2b76f8a21bd80..d659a4fede6f0 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -3,12 +3,13 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; import * as sqs from '@aws-cdk/aws-sqs'; -import { Construct, Duration, Fn, Lazy } from '@aws-cdk/core'; +import { CfnResource, Construct, Duration, Fn, Lazy, Stack } from '@aws-cdk/core'; import { Code, CodeConfig } from './code'; import { EventInvokeConfigOptions } from './event-invoke-config'; import { IEventSource } from './event-source'; import { FunctionAttributes, FunctionBase, IFunction } from './function-base'; -import { Version } from './lambda-version'; +import { calculateFunctionHash, trimFromStart } from './function-hash'; +import { Version, VersionOptions } from './lambda-version'; import { CfnFunction } from './lambda.generated'; import { ILayerVersion } from './layers'; import { LogRetention } from './log-retention'; @@ -224,6 +225,13 @@ export interface FunctionOptions extends EventInvokeConfigOptions { * @default - A new role is created. */ readonly logRetentionRole?: iam.IRole; + + /** + * Options for the `lambda.Version` resource automatically created by the + * `fn.currentVersion` method. + * @default - default options as described in `VersionOptions` + */ + readonly currentVersionOptions?: VersionOptions; } export interface FunctionProps extends FunctionOptions { @@ -266,6 +274,28 @@ export interface FunctionProps extends FunctionOptions { * library. */ export class Function extends FunctionBase { + + /** + * Returns a `lambda.Version` which represents the current version of this + * Lambda function. A new version will be created every time the function's + * configuration changes. + * + * You can specify options for this version using the `currentVersionOptions` + * prop when initializing the `lambda.Function`. + */ + public get currentVersion(): Version { + if (this._currentVersion) { + return this._currentVersion; + } + + this._currentVersion = new Version(this, `CurrentVersion`, { + lambda: this, + ...this.currentVersionOptions + }); + + return this._currentVersion; + } + public static fromFunctionArn(scope: Construct, id: string, functionArn: string): IFunction { return Function.fromFunctionAttributes(scope, id, { functionArn }); } @@ -425,6 +455,9 @@ export class Function extends FunctionBase { */ private readonly environment: { [key: string]: string }; + private readonly currentVersionOptions?: VersionOptions; + private _currentVersion?: Version; + constructor(scope: Construct, id: string, props: FunctionProps) { super(scope, id, { physicalName: props.functionName, @@ -520,6 +553,8 @@ export class Function extends FunctionBase { retryAttempts: props.retryAttempts, }); } + + this.currentVersionOptions = props.currentVersionOptions; } /** @@ -557,19 +592,27 @@ export class Function extends FunctionBase { * Add a new version for this Lambda * * If you want to deploy through CloudFormation and use aliases, you need to - * add a new version (with a new name) to your Lambda every time you want - * to deploy an update. An alias can then refer to the newly created Version. + * add a new version (with a new name) to your Lambda every time you want to + * deploy an update. An alias can then refer to the newly created Version. * * All versions should have distinct names, and you should not delete versions * as long as your Alias needs to refer to them. * - * @param name A unique name for this version - * @param codeSha256 The SHA-256 hash of the most recently deployed Lambda source code, or - * omit to skip validation. + * @param name A unique name for this version. + * @param codeSha256 The SHA-256 hash of the most recently deployed Lambda + * source code, or omit to skip validation. * @param description A description for this version. - * @param provisionedExecutions A provisioned concurrency configuration for a function's version. - * @param asyncInvokeConfig configuration for this version when it is invoked asynchronously. + * @param provisionedExecutions A provisioned concurrency configuration for a + * function's version. + * @param asyncInvokeConfig configuration for this version when it is invoked + * asynchronously. * @returns A new Version object. + * + * @deprecated This method will create an AWS::Lambda::Version resource which + * snapshots the AWS Lambda function *at the time of its creation* and it + * won't get updated when the function changes. Instead, use + * `this.currentVersion` to obtain a reference to a version resource that gets + * automatically recreated when the function configuration (or code) changes. */ public addVersion( name: string, @@ -577,6 +620,7 @@ export class Function extends FunctionBase { description?: string, provisionedExecutions?: number, asyncInvokeConfig: EventInvokeConfigOptions = {}): Version { + return new Version(this, 'Version' + name, { lambda: this, codeSha256, @@ -607,14 +651,47 @@ export class Function extends FunctionBase { return this._logGroup; } + protected prepare() { + super.prepare(); + + // if we have a current version resource, override it's logical id + // so that it includes the hash of the function code and it's configuration. + if (this._currentVersion) { + const stack = Stack.of(this); + const cfn = this._currentVersion.node.defaultChild as CfnResource; + const originalLogicalId: string = stack.resolve(cfn.logicalId); + + const hash = calculateFunctionHash(this); + + const logicalId = trimFromStart(originalLogicalId, 255 - 32); + cfn.overrideLogicalId(`${logicalId}${hash}`); + } + } + private renderEnvironment() { if (!this.environment || Object.keys(this.environment).length === 0) { return undefined; } - return { - variables: this.environment - }; + // for backwards compatibility we do not sort environment variables in case + // _currentVersion is not defined. otherwise, this would have invalidated + // the template, and for example, may cause unneeded updates for nested + // stacks. + if (!this._currentVersion) { + return { + variables: this.environment + }; + } + + // sort environment so the hash of the function used to create + // `currentVersion` is not affected by key order (this is how lambda does + // it). + const variables: { [key: string]: string } = { }; + for (const key of Object.keys(this.environment).sort()) { + variables[key] = this.environment[key]; + } + + return { variables }; } /** @@ -749,4 +826,4 @@ export function verifyCodeConfig(code: CodeConfig, runtime: Runtime) { if (code.inlineCode && !runtime.supportsInlineCode) { throw new Error(`Inline source not allowed for ${runtime.name}`); } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts index bd5c132f89e44..b04fbace206e9 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts @@ -1,9 +1,11 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; -import { Construct, Fn } from '@aws-cdk/core'; +import { Construct, Fn, RemovalPolicy } from '@aws-cdk/core'; +import { Alias, AliasOptions } from './alias'; import { EventInvokeConfigOptions } from './event-invoke-config'; import { Function } from './function'; import { IFunction, QualifiedFunctionBase } from './function-base'; import { CfnVersion } from './lambda.generated'; +import { addAlias } from './util'; export interface IVersion extends IFunction { /** @@ -16,12 +18,19 @@ export interface IVersion extends IFunction { * The underlying AWS Lambda function. */ readonly lambda: IFunction; + + /** + * Defines an alias for this version. + * @param aliasName The name of the alias + * @param options Alias options + */ + addAlias(aliasName: string, options?: AliasOptions): Alias; } /** - * Properties for a new Lambda version + * Options for `lambda.Version` */ -export interface VersionProps extends EventInvokeConfigOptions { +export interface VersionOptions extends EventInvokeConfigOptions { /** * SHA256 of the version of the Lambda source code * @@ -38,17 +47,30 @@ export interface VersionProps extends EventInvokeConfigOptions { */ readonly description?: string; - /** - * Function to get the value of - */ - readonly lambda: IFunction; - /** * Specifies a provisioned concurrency configuration for a function's version. * * @default No provisioned concurrency */ readonly provisionedConcurrentExecutions?: number; + + /** + * Whether to retain old versions of this function when a new version is + * created. + * + * @default RemovalPolicy.DESTROY + */ + readonly removalPolicy?: RemovalPolicy; +} + +/** + * Properties for a new Lambda version + */ +export interface VersionProps extends VersionOptions { + /** + * Function to get the value of + */ + readonly lambda: IFunction; } export interface VersionAttributes { @@ -102,6 +124,10 @@ export class Version extends QualifiedFunctionBase implements IVersion { protected readonly qualifier = version; protected readonly canCreatePermissions = false; + + public addAlias(name: string, opts: AliasOptions = { }): Alias { + return addAlias(this, this, name, opts); + } } return new Import(scope, id); } @@ -117,6 +143,10 @@ export class Version extends QualifiedFunctionBase implements IVersion { protected readonly qualifier = attrs.version; protected readonly canCreatePermissions = false; + + public addAlias(name: string, opts: AliasOptions = { }): Alias { + return addAlias(this, this, name, opts); + } } return new Import(scope, id); } @@ -141,6 +171,12 @@ export class Version extends QualifiedFunctionBase implements IVersion { provisionedConcurrencyConfig: this.determineProvisionedConcurrency(props) }); + if (props.removalPolicy) { + version.applyRemovalPolicy(props.removalPolicy, { + default: RemovalPolicy.DESTROY + }); + } + this.version = version.attrVersion; this.functionArn = version.ref; this.functionName = `${this.lambda.functionName}:${this.version}`; @@ -178,6 +214,15 @@ export class Version extends QualifiedFunctionBase implements IVersion { }); } + /** + * Defines an alias for this version. + * @param aliasName The name of the alias (e.g. "live") + * @param options Alias options + */ + public addAlias(aliasName: string, options: AliasOptions = { }): Alias { + return addAlias(this, this, aliasName, options); + } + /** * Validate that the provisionedConcurrentExecutions makes sense * diff --git a/packages/@aws-cdk/aws-lambda/lib/util.ts b/packages/@aws-cdk/aws-lambda/lib/util.ts new file mode 100644 index 0000000000000..317699450b436 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/lib/util.ts @@ -0,0 +1,11 @@ +import { Construct } from "@aws-cdk/core"; +import { Alias, AliasOptions } from "./alias"; +import { IVersion } from "./lambda-version"; + +export function addAlias(scope: Construct, version: IVersion, aliasName: string, options: AliasOptions = {}) { + return new Alias(scope, `Alias${aliasName}`, { + aliasName, + version, + ...options + }); +} diff --git a/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json new file mode 100644 index 0000000000000..96d17d375e9c1 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json @@ -0,0 +1,143 @@ +{ + "Resources": { + "MyLambdaServiceRole4539ECB6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "MyLambdaCCE802FB": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dS3Bucket34E3DBD0" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dS3VersionKey585C4BED" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dS3VersionKey585C4BED" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.main", + "Role": { + "Fn::GetAtt": [ + "MyLambdaServiceRole4539ECB6", + "Arn" + ] + }, + "Runtime": "python3.8" + }, + "DependsOn": [ + "MyLambdaServiceRole4539ECB6" + ] + }, + "MyLambdaCurrentVersionE7A382CC1a5358ec9d2d5ef45baeba2fbb9fa9bd": { + "Type": "AWS::Lambda::Version", + "Properties": { + "FunctionName": { + "Ref": "MyLambdaCCE802FB" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "MyLambdaCurrentVersionEventInvokeConfigD120DC68": { + "Type": "AWS::Lambda::EventInvokeConfig", + "Properties": { + "FunctionName": { + "Ref": "MyLambdaCCE802FB" + }, + "Qualifier": { + "Fn::GetAtt": [ + "MyLambdaCurrentVersionE7A382CC1a5358ec9d2d5ef45baeba2fbb9fa9bd", + "Version" + ] + }, + "MaximumRetryAttempts": 1 + } + }, + "MyLambdaCurrentVersionAliaslive9151E913": { + "Type": "AWS::Lambda::Alias", + "Properties": { + "FunctionName": { + "Ref": "MyLambdaCCE802FB" + }, + "FunctionVersion": { + "Fn::GetAtt": [ + "MyLambdaCurrentVersionE7A382CC1a5358ec9d2d5ef45baeba2fbb9fa9bd", + "Version" + ] + }, + "Name": "live" + } + } + }, + "Parameters": { + "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dS3Bucket34E3DBD0": { + "Type": "String", + "Description": "S3 bucket for asset \"45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2d\"" + }, + "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dS3VersionKey585C4BED": { + "Type": "String", + "Description": "S3 key for asset version \"45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2d\"" + }, + "AssetParameters45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2dArtifactHash20CDD3D4": { + "Type": "String", + "Description": "Artifact hash for asset \"45f085ecc03a1a22cf003fba3fab28e660c92bcfcd4d0c01b62c7cd191070a2d\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/integ.current-version.ts b/packages/@aws-cdk/aws-lambda/test/integ.current-version.ts new file mode 100644 index 0000000000000..4a37f486e53f8 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/integ.current-version.ts @@ -0,0 +1,27 @@ +import { App, RemovalPolicy, Stack } from '@aws-cdk/core'; +import * as path from 'path'; +import * as lambda from '../lib'; + +class TestStack extends Stack { + constructor(scope: App, id: string) { + super(scope, id); + + const handler = new lambda.Function(this, 'MyLambda', { + code: lambda.Code.fromAsset(path.join(__dirname, 'layer-code')), + handler: 'index.main', + runtime: lambda.Runtime.PYTHON_3_8, + currentVersionOptions: { + removalPolicy: RemovalPolicy.RETAIN, + retryAttempts: 1 + } + }); + + handler.currentVersion.addAlias('live'); + } +} + +const app = new App(); + +new TestStack(app, 'lambda-test-current-version'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-lambda/test/test.function-hash.ts b/packages/@aws-cdk/aws-lambda/test/test.function-hash.ts new file mode 100644 index 0000000000000..651884b83e575 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/test.function-hash.ts @@ -0,0 +1,194 @@ +import { CfnOutput, Stack } from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import * as path from 'path'; +import * as lambda from '../lib'; +import { calculateFunctionHash, trimFromStart } from '../lib/function-hash'; + +export = { + "trimFromStart": { + + 'trim not needed'(test: Test) { + test.deepEqual(trimFromStart('foo', 100), 'foo'); + test.deepEqual(trimFromStart('foo', 3), 'foo'); + test.deepEqual(trimFromStart('', 3), ''); + test.done(); + }, + + 'trim required'(test: Test) { + test.deepEqual(trimFromStart('hello', 3), 'llo'); + test.deepEqual(trimFromStart('hello', 4), 'ello'); + test.deepEqual(trimFromStart('hello', 1), 'o'); + test.done(); + } + + }, + + "calcHash": { + 'same configuration and code yields the same hash'(test: Test) { + const stack1 = new Stack(); + const fn1 = new lambda.Function(stack1, 'MyFunction1', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'handler.zip')), + handler: 'index.handler' + }); + + const stack2 = new Stack(); + const fn2 = new lambda.Function(stack2, 'MyFunction1', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'handler.zip')), + handler: 'index.handler' + }); + + test.deepEqual(calculateFunctionHash(fn1), calculateFunctionHash(fn2)); + test.deepEqual(calculateFunctionHash(fn1), 'aea5463dba236007afe91d2832b3c836'); + test.done(); + }, + }, + + 'code impacts hash'(test: Test) { + const stack1 = new Stack(); + const fn1 = new lambda.Function(stack1, 'MyFunction1', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')), + handler: 'index.handler' + }); + + test.notDeepEqual(calculateFunctionHash(fn1), 'aea5463dba236007afe91d2832b3c836'); + test.deepEqual(calculateFunctionHash(fn1), '979b4a14c6f174c745cdbcd1036cf844'); + test.done(); + }, + + 'environment variables impact hash'(test: Test) { + const stack1 = new Stack(); + const fn1 = new lambda.Function(stack1, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')), + handler: 'index.handler', + environment: { + Foo: 'bar' + } + }); + + const stack2 = new Stack(); + const fn2 = new lambda.Function(stack2, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')), + handler: 'index.handler', + environment: { + Foo: 'beer' + } + }); + + test.deepEqual(calculateFunctionHash(fn1), 'd1bc824ac5022b7d62d8b12dbae6580c'); + test.deepEqual(calculateFunctionHash(fn2), '3b683d05465012b0aa9c4ff53b32f014'); + test.done(); + }, + + 'runtime impacts hash'(test: Test) { + const stack1 = new Stack(); + const fn1 = new lambda.Function(stack1, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')), + handler: 'index.handler', + environment: { + Foo: 'bar' + } + }); + + const stack2 = new Stack(); + const fn2 = new lambda.Function(stack2, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')), + handler: 'index.handler', + environment: { + Foo: 'beer' + } + }); + + test.deepEqual(calculateFunctionHash(fn1), 'd1bc824ac5022b7d62d8b12dbae6580c'); + test.deepEqual(calculateFunctionHash(fn2), '0f168f0772463e8e547bb3800937e54d'); + test.done(); + }, + + 'inline code change impacts the hash'(test: Test) { + const stack1 = new Stack(); + const fn1 = new lambda.Function(stack1, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromInline('foo'), + handler: 'index.handler', + }); + + const stack2 = new Stack(); + const fn2 = new lambda.Function(stack2, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_10_X, + code: lambda.Code.fromInline('foo bar'), + handler: 'index.handler', + }); + + test.deepEqual(calculateFunctionHash(fn1), 'ebf2e871fc6a3062e8bdcc5ebe16db3f'); + test.deepEqual(calculateFunctionHash(fn2), 'ffedf6424a18a594a513129dc97bf53c'); + test.done(); + }, + + 'impact of env variables order on hash': { + + 'without "currentVersion", we preserve old behavior to avoid unnesesary invalidation of templates'(test: Test) { + const stack1 = new Stack(); + const fn1 = new lambda.Function(stack1, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')), + handler: 'index.handler', + environment: { + Foo: 'bar', + Bar: 'foo', + } + }); + + const stack2 = new Stack(); + const fn2 = new lambda.Function(stack2, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')), + handler: 'index.handler', + environment: { + Bar: 'foo', + Foo: 'bar', + } + }); + + test.notDeepEqual(calculateFunctionHash(fn1), calculateFunctionHash(fn2)); + test.done(); + }, + + 'with "currentVersion", we sort env keys so order is consistent'(test: Test) { + const stack1 = new Stack(); + const fn1 = new lambda.Function(stack1, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')), + handler: 'index.handler', + environment: { + Foo: 'bar', + Bar: 'foo', + } + }); + + new CfnOutput(stack1, 'VersionArn', { value: fn1.currentVersion.functionArn }); + + const stack2 = new Stack(); + const fn2 = new lambda.Function(stack2, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')), + handler: 'index.handler', + environment: { + Bar: 'foo', + Foo: 'bar', + } + }); + + new CfnOutput(stack2, 'VersionArn', { value: fn2.currentVersion.functionArn }); + + test.deepEqual(calculateFunctionHash(fn1), calculateFunctionHash(fn2)); + test.done(); + } + + }, +}; diff --git a/packages/@aws-cdk/aws-lambda/test/test.function.ts b/packages/@aws-cdk/aws-lambda/test/test.function.ts index 6a9e2fa343090..fd9316c822b54 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.function.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.function.ts @@ -1,9 +1,11 @@ +import { expect, haveOutput } from '@aws-cdk/assert'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as sqs from '@aws-cdk/aws-sqs'; import * as cdk from '@aws-cdk/core'; import * as _ from 'lodash'; import {Test, testCase} from 'nodeunit'; +import * as path from 'path'; import * as lambda from '../lib'; export = testCase({ @@ -178,5 +180,55 @@ export = testCase({ code: lambda.Code.fromInline('foo') }), /Inline source not allowed for/); test.done(); - } + }, + + 'currentVersion': { + + // see test.function-hash.ts for more coverage for this + 'logical id of version is based on the function hash'(test: Test) { + // GIVEN + const stack1 = new cdk.Stack(); + const fn1 = new lambda.Function(stack1, 'MyFunction', { + handler: 'foo', + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'handler.zip')), + environment: { + FOO: 'bar' + } + }); + const stack2 = new cdk.Stack(); + const fn2 = new lambda.Function(stack2, 'MyFunction', { + handler: 'foo', + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'handler.zip')), + environment: { + FOO: 'bear' + } + }); + + // WHEN + new cdk.CfnOutput(stack1, 'CurrentVersionArn', { + value: fn1.currentVersion.functionArn + }); + new cdk.CfnOutput(stack2, 'CurrentVersionArn', { + value: fn2.currentVersion.functionArn + }); + + // THEN + expect(stack1).to(haveOutput({ + outputName: 'CurrentVersionArn', + outputValue: { + Ref: "MyFunctionCurrentVersion197490AF1a9a73cf5c46aec5e40fb202042eb60b" + } + })); + expect(stack2).to(haveOutput({ + outputName: 'CurrentVersionArn', + outputValue: { + Ref: "MyFunctionCurrentVersion197490AF8360a045031060e3117269037b7bffd6" + } + })); + test.done(); + } + }, + }); diff --git a/packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts b/packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts index 80c02e56c9966..b667e85e2b779 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts @@ -111,6 +111,35 @@ export = { MaximumRetryAttempts: 0 })); + test.done(); + }, + + 'addAlias can be used to add an alias that points to a version'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'Fn', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.fromInline('foo'), + }); + const version = fn.currentVersion; + + // WHEN + version.addAlias('foo'); + + // THEN + expect(stack).to(haveResource('AWS::Lambda::Alias', { + "FunctionName": { + "Ref": "Fn9270CBC0" + }, + "FunctionVersion": { + "Fn::GetAtt": [ + "FnCurrentVersion17A89ABB19ed45993ff69fd011ae9fd4ab6e2005", + "Version" + ] + }, + "Name": "foo" + })); test.done(); } }; From 53f82f0c33b913c1cad05d6e5d9797f9243d37e9 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Wed, 25 Mar 2020 13:28:41 +0200 Subject: [PATCH 5/5] chore(monocdk): support barrel imports (#6996) chore(monocdk): support barrel imports (#6996) This change enables an alternative syntax for importing submodules from monocdk: import * as s3 from 'monocdk-experiment/aws-s3'; In addition to the currently supported syntax: import { aws_s3 as s3 } from 'monocdk-experiment'; The new syntax will make it easy to migrate from existing CDK codebase and also makes it easier to namespace submodule imports by consumers. To that end, we hoisted the submodules from `src` to be direct subdirectories of the package (otherwise, the barrel import would have been `monocdk-experiment/src/aws-s3`). Credits: @CaerusKaru --- packages/@monocdk-experiment/assert/clone.sh | 5 ++++- packages/@monocdk-experiment/assert/package.json | 3 ++- packages/monocdk-experiment/.gitignore | 2 +- packages/monocdk-experiment/build.sh | 6 +++--- packages/monocdk-experiment/deps.js | 2 -- packages/monocdk-experiment/gen.js | 10 ++++------ packages/monocdk-experiment/package.json | 4 ++-- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/@monocdk-experiment/assert/clone.sh b/packages/@monocdk-experiment/assert/clone.sh index b290c36b16945..e4fb1658ce38f 100755 --- a/packages/@monocdk-experiment/assert/clone.sh +++ b/packages/@monocdk-experiment/assert/clone.sh @@ -15,4 +15,7 @@ done npx rewrite-imports {lib,test}/*.ts jest.ts - +# symlink the full staged monocdk from the staging directory to node_modules +rm -fr node_modules/monocdk-experiment +mkdir -p node_modules +ln -s $PWD/../../monocdk-experiment/staging node_modules/monocdk-experiment diff --git a/packages/@monocdk-experiment/assert/package.json b/packages/@monocdk-experiment/assert/package.json index 619767e5a7b46..3aabbf3af0062 100644 --- a/packages/@monocdk-experiment/assert/package.json +++ b/packages/@monocdk-experiment/assert/package.json @@ -5,7 +5,7 @@ "main": "lib/index.js", "types": "lib/index.d.ts", "scripts": { - "build": "./clone.sh && cdk-build", + "build": "cdk-build", "watch": "cdk-watch", "lint": "cdk-lint", "test": "cdk-test", @@ -15,6 +15,7 @@ "build+test": "npm run build && npm test" }, "cdk-build": { + "pre": [ "./clone.sh" ], "eslint": { "disable": true }, diff --git a/packages/monocdk-experiment/.gitignore b/packages/monocdk-experiment/.gitignore index f35964ce50b46..6febf4a0ff341 100644 --- a/packages/monocdk-experiment/.gitignore +++ b/packages/monocdk-experiment/.gitignore @@ -2,4 +2,4 @@ *.d.ts !deps.js !gen.js -src/ \ No newline at end of file +staging/ \ No newline at end of file diff --git a/packages/monocdk-experiment/build.sh b/packages/monocdk-experiment/build.sh index 5199751871258..0d1742e577ff8 100755 --- a/packages/monocdk-experiment/build.sh +++ b/packages/monocdk-experiment/build.sh @@ -33,6 +33,6 @@ cd ${scriptdir} mkdir -p dist/js cp ${tarball} dist/js -# copying src/ so this module will also work as a local dependency (e.g. for modules under @monocdk-experiment/*). -rm -fr src -rsync -av ${unpacked}/src/ src/ +# so this module will also work as a local dependency (e.g. for modules under @monocdk-experiment/*). +rm -fr staging +mv ${unpacked} staging diff --git a/packages/monocdk-experiment/deps.js b/packages/monocdk-experiment/deps.js index 2068ce124c019..034bcc33944c3 100644 --- a/packages/monocdk-experiment/deps.js +++ b/packages/monocdk-experiment/deps.js @@ -31,8 +31,6 @@ for (const dir of modules) { continue; } - - if (!exists) { console.error(`missing dependency: ${meta.name}`); errors = true; diff --git a/packages/monocdk-experiment/gen.js b/packages/monocdk-experiment/gen.js index 3196aedf7eeb7..3c7e3e090f193 100644 --- a/packages/monocdk-experiment/gen.js +++ b/packages/monocdk-experiment/gen.js @@ -34,8 +34,6 @@ const exclude_files = [ async function main() { const outdir = path.join(await fs.mkdtemp(path.join(os.tmpdir(), 'monocdk-')), 'package'); - const srcdir = path.join(outdir, 'src'); - console.error(`generating monocdk at ${outdir}`); const reexports = []; @@ -89,7 +87,7 @@ async function main() { const basename = path.basename(moduledir); const files = await fs.readdir(moduledir); - const targetdir = path.join(srcdir, basename); + const targetdir = path.join(outdir, basename); for (const file of files) { const source = path.join(moduledir, file); @@ -150,12 +148,12 @@ async function main() { } } - await fs.writeFile(path.join(srcdir, 'index.ts'), reexports.join('\n')); + await fs.writeFile(path.join(outdir, 'index.ts'), reexports.join('\n')); console.error(`rewriting "import" statements...`); - const sourceFiles = await findSources(srcdir); + const sourceFiles = await findSources(outdir); for (const source of sourceFiles) { - await rewriteImports(srcdir, source); + await rewriteImports(outdir, source); } // copy tsconfig.json and .npmignore diff --git a/packages/monocdk-experiment/package.json b/packages/monocdk-experiment/package.json index 90c1efe859655..b12845679a9f2 100644 --- a/packages/monocdk-experiment/package.json +++ b/packages/monocdk-experiment/package.json @@ -2,8 +2,8 @@ "name": "monocdk-experiment", "version": "0.0.0", "description": "An experiment to bundle the entire CDK into a single module", - "main": "src/index.js", - "types": "src/index.d.ts", + "main": "index.js", + "types": "index.d.ts", "repository": { "type": "git", "url": "https://github.com/aws/aws-cdk.git"