Skip to content

Commit 9d6c458

Browse files
author
Elad Ben-Israel
committed
feat(lambda): add alias to latest version through code hash
It is a common use case to define an alias for the latest version of a function. In order to do that, one needs to define a Version resource that points to the latest version and then associate an Alias with it. This was not trivial to do so far. Now it is: fn.addAlias('foo') This will define a lambda.Version with a name the derives from the hash of the lambda's source code and then define a lambda.Alias associated with this version object. Since the name of the version resource is based on the hash, when the code changes, a new version will be created automatically. To support this, `lambda.Code.bind()` now returns an optional `codeHash` attribute. It is supported for `lambda.Code.fromAsset` and `lambda.Code.fromInline`, for which we can calculate the source hash based on the content. Resolves #6750 Resolves #5334
1 parent 7a10f0f commit 9d6c458

9 files changed

+399
-23
lines changed

packages/@aws-cdk/aws-lambda/README.md

+29
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,35 @@ to our CDK project directory. This is especially important when we want to share
4949
this construct through a library. Different programming languages will have
5050
different techniques for bundling resources into libraries.
5151

52+
When using `fromAsset` or `fromInline`, you can obtain the hash of source
53+
through the `function.codeHash` property. This property will return `undefined`
54+
if the code hash cannot be calculated during synthesis (e.g. when using code
55+
from an S3 bucket).
56+
57+
### Versions and Aliases
58+
59+
You can define one or more
60+
[aliases](https://docs.aws.amazon.com/lambda/latest/dg/configuration-aliases.html)
61+
for your AWS Lambda function. A Lambda alias is like a pointer to a specific
62+
Lambda function version. Users can access the function version using the alias
63+
ARN.
64+
65+
To define an alias that points to the latest version of your function's code,
66+
you can use the `function.addAlias` method like so:
67+
68+
```ts
69+
const alias = lambdaFunction.addAlias('latest');
70+
```
71+
72+
This will define an alias that points to a
73+
[Version](https://docs.aws.amazon.com/lambda/latest/dg/configuration-versions.html)
74+
resource that will get updated every time your lambda code changes based on your code's hash.
75+
76+
> Automatically creating version objects based on the code's hash is only supported
77+
> for `lambda.Code.fromAsset` and `lambda.Code.fromInline`. Other types of code
78+
> providers require that you will maintain the `versionName` explicitly when you
79+
> define the alias (and update it manually when your code changes).
80+
5281
### Layers
5382

5483
The `lambda.LayerVersion` class can be used to define Lambda layers and manage

packages/@aws-cdk/aws-lambda/lib/alias.ts

+19-14
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,16 @@ export interface IAlias extends IFunction {
2020
}
2121

2222
/**
23-
* Properties for a new Lambda alias
23+
* Options for `lambda.Alias`.
2424
*/
25-
export interface AliasProps extends EventInvokeConfigOptions {
25+
export interface AliasOptions extends EventInvokeConfigOptions {
2626
/**
2727
* Description for the alias
2828
*
2929
* @default No description
3030
*/
3131
readonly description?: string;
3232

33-
/**
34-
* Function version this alias refers to
35-
*
36-
* Use lambda.addVersion() to obtain a new lambda version to refer to.
37-
*/
38-
readonly version: IVersion;
39-
40-
/**
41-
* Name of this alias
42-
*/
43-
readonly aliasName: string;
44-
4533
/**
4634
* Additional versions with individual weights this alias points to
4735
*
@@ -69,6 +57,23 @@ export interface AliasProps extends EventInvokeConfigOptions {
6957
readonly provisionedConcurrentExecutions?: number;
7058
}
7159

60+
/**
61+
* Properties for a new Lambda alias
62+
*/
63+
export interface AliasProps extends AliasOptions {
64+
/**
65+
* Name of this alias
66+
*/
67+
readonly aliasName: string;
68+
69+
/**
70+
* Function version this alias refers to
71+
*
72+
* Use lambda.addVersion() to obtain a new lambda version to refer to.
73+
*/
74+
readonly version: IVersion;
75+
}
76+
7277
export interface AliasAttributes {
7378
readonly aliasName: string;
7479
readonly aliasVersion: IVersion;

packages/@aws-cdk/aws-lambda/lib/code.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as s3 from '@aws-cdk/aws-s3';
22
import * as s3_assets from '@aws-cdk/aws-s3-assets';
33
import * as cdk from '@aws-cdk/core';
4+
import * as crypto from 'crypto';
45

56
export abstract class Code {
67
/**
@@ -104,13 +105,19 @@ export interface CodeConfig {
104105
* Inline code (mutually exclusive with `s3Location`).
105106
*/
106107
readonly inlineCode?: string;
108+
109+
/**
110+
* The hash of the lambda code (if applicable).
111+
*/
112+
readonly codeHash?: string;
107113
}
108114

109115
/**
110116
* Lambda code from an S3 archive.
111117
*/
112118
export class S3Code extends Code {
113119
public readonly isInline = false;
120+
114121
private bucketName: string;
115122

116123
constructor(bucket: s3.IBucket, private key: string, private objectVersion?: string) {
@@ -139,6 +146,7 @@ export class S3Code extends Code {
139146
*/
140147
export class InlineCode extends Code {
141148
public readonly isInline = true;
149+
private readonly codeHash: string;
142150

143151
constructor(private code: string) {
144152
super();
@@ -150,11 +158,14 @@ export class InlineCode extends Code {
150158
if (code.length > 4096) {
151159
throw new Error("Lambda source is too large, must be <= 4096 but is " + code.length);
152160
}
161+
162+
this.codeHash = crypto.createHash('sha256').update(code).digest('hex');
153163
}
154164

155165
public bind(_scope: cdk.Construct): CodeConfig {
156166
return {
157-
inlineCode: this.code
167+
inlineCode: this.code,
168+
codeHash: this.codeHash
158169
};
159170
}
160171
}
@@ -164,6 +175,7 @@ export class InlineCode extends Code {
164175
*/
165176
export class AssetCode extends Code {
166177
public readonly isInline = false;
178+
167179
private asset?: s3_assets.Asset;
168180

169181
/**
@@ -187,6 +199,7 @@ export class AssetCode extends Code {
187199
}
188200

189201
return {
202+
codeHash: this.asset.sourceHash,
190203
s3Location: {
191204
bucketName: this.asset.s3BucketName,
192205
objectKey: this.asset.s3ObjectKey
@@ -246,6 +259,7 @@ export interface CfnParametersCodeProps {
246259
*/
247260
export class CfnParametersCode extends Code {
248261
public readonly isInline = false;
262+
249263
private _bucketNameParam?: cdk.CfnParameter;
250264
private _objectKeyParam?: cdk.CfnParameter;
251265

packages/@aws-cdk/aws-lambda/lib/function.ts

+73-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as iam from '@aws-cdk/aws-iam';
44
import * as logs from '@aws-cdk/aws-logs';
55
import * as sqs from '@aws-cdk/aws-sqs';
66
import { Construct, Duration, Fn, Lazy } from '@aws-cdk/core';
7+
import { Alias, AliasOptions } from './alias';
78
import { Code, CodeConfig } from './code';
89
import { EventInvokeConfigOptions } from './event-invoke-config';
910
import { IEventSource } from './event-source';
@@ -414,6 +415,14 @@ export class Function extends FunctionBase {
414415

415416
public readonly permissionsNode = this.node;
416417

418+
/**
419+
* The hash of the AWS Lambda code.
420+
*
421+
* This only applies to `lambda.Code.fromAsset` and `lambda.Code.fromInline`.
422+
* For other code types this will return `undefined`.
423+
*/
424+
public readonly codeHash?: string;
425+
417426
protected readonly canCreatePermissions = true;
418427

419428
private readonly layers: ILayerVersion[] = [];
@@ -456,6 +465,7 @@ export class Function extends FunctionBase {
456465
verifyCodeConfig(code, props.runtime);
457466

458467
this.deadLetterQueue = this.buildDeadLetterQueue(props);
468+
this.codeHash = code.codeHash;
459469

460470
const resource: CfnFunction = new CfnFunction(this, 'Resource', {
461471
functionName: this.physicalName,
@@ -557,26 +567,43 @@ export class Function extends FunctionBase {
557567
* Add a new version for this Lambda
558568
*
559569
* If you want to deploy through CloudFormation and use aliases, you need to
560-
* add a new version (with a new name) to your Lambda every time you want
561-
* to deploy an update. An alias can then refer to the newly created Version.
570+
* add a new version (with a new name) to your Lambda every time you want to
571+
* deploy an update. An alias can then refer to the newly created Version.
562572
*
563573
* All versions should have distinct names, and you should not delete versions
564574
* as long as your Alias needs to refer to them.
565575
*
566-
* @param name A unique name for this version
567-
* @param codeSha256 The SHA-256 hash of the most recently deployed Lambda source code, or
568-
* omit to skip validation.
576+
* @param name A unique name for this version. The version name is not
577+
* specified, a name will automatically be generated based on the code hash of
578+
* the lambda. This means that a new version will automatically be created
579+
* every time the lambda code changes. This is only supported for
580+
* `lambda.Code.fromAsset` and `lambda.Code.fromInline`. Otherwise `name` is
581+
* required.
582+
*
583+
* @param codeSha256 The SHA-256 hash of the most recently deployed Lambda
584+
* source code, or omit to skip validation.
569585
* @param description A description for this version.
570-
* @param provisionedExecutions A provisioned concurrency configuration for a function's version.
571-
* @param asyncInvokeConfig configuration for this version when it is invoked asynchronously.
586+
* @param provisionedExecutions A provisioned concurrency configuration for a
587+
* function's version.
588+
* @param asyncInvokeConfig configuration for this version when it is invoked
589+
* asynchronously.
572590
* @returns A new Version object.
573591
*/
574592
public addVersion(
575-
name: string,
593+
name?: string,
576594
codeSha256?: string,
577595
description?: string,
578596
provisionedExecutions?: number,
579597
asyncInvokeConfig: EventInvokeConfigOptions = {}): Version {
598+
599+
if (!name) {
600+
if (!this.codeHash) {
601+
throw new Error(`version name must be provided because the the lambda code hash cannot be calculated. Only "lambda.Code.fromAsset" and "lambda.Code.fromInline" support code hash`);
602+
}
603+
604+
name = this.codeHash;
605+
}
606+
580607
return new Version(this, 'Version' + name, {
581608
lambda: this,
582609
codeSha256,
@@ -586,6 +613,23 @@ export class Function extends FunctionBase {
586613
});
587614
}
588615

616+
/**
617+
* Defines an alias for this function associated with the latest version of
618+
* the function.
619+
*
620+
* @param name The name for this alias.
621+
* @param options Options for the alias. If this function uses assets or
622+
* inline code, do not specify `versionName`. Otherwise, `versionName` is
623+
* required.
624+
*/
625+
public addAlias(name: string, options: AddAliasOptions = { }) {
626+
return new Alias(this, `Alias${name}`, {
627+
version: this.addVersion(options.versionName),
628+
aliasName: name,
629+
...options
630+
});
631+
}
632+
589633
/**
590634
* The LogGroup where the Lambda function's logs are made available.
591635
*
@@ -750,3 +794,24 @@ export function verifyCodeConfig(code: CodeConfig, runtime: Runtime) {
750794
throw new Error(`Inline source not allowed for ${runtime.name}`);
751795
}
752796
}
797+
798+
/**
799+
* Options for `function.addAlias`.
800+
*/
801+
export interface AddAliasOptions extends AliasOptions {
802+
/**
803+
* An explicit name for this version.
804+
*
805+
* It is not recommeneded to specify a name for the version because you will
806+
* need to explicitly update it every time your lambda code changes. If you
807+
* leave this undefined, the framework will automatically create a new version
808+
* every time your lambda code changes.
809+
*
810+
* @default - a name will automatically be generated based on the code hash of
811+
* the lambda. This means that a new version will automatically be created
812+
* every time the lambda code changes. This is only supported for
813+
* `lambda.Code.fromAsset` and `lambda.Code.fromInline`. Otherwise `name` is
814+
* required.
815+
*/
816+
readonly versionName?: string;
817+
}

0 commit comments

Comments
 (0)