Skip to content

Commit 036cfdf

Browse files
authored
feat(lambda): Support AWS Lambda Layers (#1411)
Add support for the AWS Lambda Layers feature, enabling users to define reusable and shareable packages of runtime code that can be shared across multiple lambda functions.
1 parent 23c9afc commit 036cfdf

File tree

12 files changed

+633
-33
lines changed

12 files changed

+633
-33
lines changed

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,24 @@ When deploying a stack that contains this code, the directory will be zip
3333
archived and then uploaded to an S3 bucket, then the exact location of the S3
3434
objects will be passed when the stack is deployed.
3535

36+
### Layers
37+
38+
The `lambda.LayerVersion` class can be used to define Lambda layers and manage
39+
granting permissions to other AWS accounts or organizations.
40+
41+
[Example of Lambda Layer usage](test/integ.layer-version.lit.ts)
42+
3643
### Event Sources
3744

3845
AWS Lambda supports a [variety of event sources](https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html).
3946

40-
In most cases, it is possible to trigger a function as a result of an event by
41-
using one of the `onXxx` methods on the source construct. For example, the `s3.Bucket`
42-
construct has an `onEvent` method which can be used to trigger a Lambda when an event,
47+
In most cases, it is possible to trigger a function as a result of an event by
48+
using one of the `onXxx` methods on the source construct. For example, the `s3.Bucket`
49+
construct has an `onEvent` method which can be used to trigger a Lambda when an event,
4350
such as PutObject occurs on an S3 bucket.
4451

45-
An alternative way to add event sources to a function is to use `function.addEventSource(source)`.
46-
This method accepts an `IEventSource` object. The module __@aws-cdk/aws-lambda-event-sources__
52+
An alternative way to add event sources to a function is to use `function.addEventSource(source)`.
53+
This method accepts an `IEventSource` object. The module __@aws-cdk/aws-lambda-event-sources__
4754
includes classes for the various event sources supported by AWS Lambda.
4855

4956
For example, the following code adds an SQS queue as an event source for a function:
@@ -109,7 +116,7 @@ lambdaAction.outputArtifact('Out2'); // returns the named output Artifact, or th
109116
See [the AWS documentation](https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html)
110117
on how to write a Lambda function invoked from CodePipeline.
111118

112-
### Lambda with DLQ
119+
### Lambda with DLQ
113120

114121
```ts
115122
import lambda = require('@aws-cdk/aws-lambda');
@@ -124,7 +131,7 @@ const fn = new lambda.Function(this, 'MyFunction', {
124131
See [the AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/dlq.html)
125132
to learn more about AWS Lambdas and DLQs.
126133

127-
### Lambda with X-Ray Tracing
134+
### Lambda with X-Ray Tracing
128135

129136
```ts
130137
import lambda = require('@aws-cdk/aws-lambda');

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

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import assets = require('@aws-cdk/assets');
22
import s3 = require('@aws-cdk/aws-s3');
3+
import cdk = require('@aws-cdk/cdk');
34
import fs = require('fs');
4-
import { Function as Func } from './lambda';
55
import { CfnFunction } from './lambda.generated';
66

77
export abstract class Code {
@@ -50,17 +50,24 @@ export abstract class Code {
5050
return new AssetCode(filePath, assets.AssetPackaging.File);
5151
}
5252

53+
/**
54+
* Determines whether this Code is inline code or not.
55+
*/
56+
public abstract readonly isInline: boolean;
57+
5358
/**
5459
* Called during stack synthesis to render the CodePropery for the
5560
* Lambda function.
61+
*
62+
* @param resource the resource to which the code will be attached (a CfnFunction, or a CfnLayerVersion).
5663
*/
57-
public abstract toJSON(resource: CfnFunction): CfnFunction.CodeProperty;
64+
public abstract _toJSON(resource?: cdk.Resource): CfnFunction.CodeProperty;
5865

5966
/**
60-
* Called when the lambda is initialized to allow this object to
67+
* Called when the lambda or layer is initialized to allow this object to
6168
* bind to the stack, add resources and have fun.
6269
*/
63-
public bind(_lambda: Func) {
70+
public bind(_construct: cdk.Construct) {
6471
return;
6572
}
6673
}
@@ -69,6 +76,7 @@ export abstract class Code {
6976
* Lambda code from an S3 archive.
7077
*/
7178
export class S3Code extends Code {
79+
public readonly isInline = false;
7280
private bucketName: string;
7381

7482
constructor(bucket: s3.IBucket, private key: string, private objectVersion?: string) {
@@ -81,7 +89,7 @@ export class S3Code extends Code {
8189
this.bucketName = bucket.bucketName;
8290
}
8391

84-
public toJSON(_: CfnFunction): CfnFunction.CodeProperty {
92+
public _toJSON(_?: cdk.Resource): CfnFunction.CodeProperty {
8593
return {
8694
s3Bucket: this.bucketName,
8795
s3Key: this.key,
@@ -94,6 +102,8 @@ export class S3Code extends Code {
94102
* Lambda code from an inline string (limited to 4KiB).
95103
*/
96104
export class InlineCode extends Code {
105+
public readonly isInline = true;
106+
97107
constructor(private code: string) {
98108
super();
99109

@@ -102,13 +112,14 @@ export class InlineCode extends Code {
102112
}
103113
}
104114

105-
public bind(lambda: Func) {
106-
if (!lambda.runtime.supportsInlineCode) {
107-
throw new Error(`Inline source not allowed for ${lambda.runtime.name}`);
115+
public bind(construct: cdk.Construct) {
116+
const runtime = (construct as any).runtime;
117+
if (!runtime.supportsInlineCode) {
118+
throw new Error(`Inline source not allowed for ${runtime && runtime.name}`);
108119
}
109120
}
110121

111-
public toJSON(_: CfnFunction): CfnFunction.CodeProperty {
122+
public _toJSON(_?: cdk.Resource): CfnFunction.CodeProperty {
112123
return {
113124
zipFile: this.code
114125
};
@@ -119,6 +130,8 @@ export class InlineCode extends Code {
119130
* Lambda code from a local directory.
120131
*/
121132
export class AssetCode extends Code {
133+
public readonly isInline = false;
134+
122135
/**
123136
* The asset packaging.
124137
*/
@@ -142,10 +155,10 @@ export class AssetCode extends Code {
142155
}
143156
}
144157

145-
public bind(lambda: Func) {
158+
public bind(construct: cdk.Construct) {
146159
// If the same AssetCode is used multiple times, retain only the first instantiation.
147160
if (!this.asset) {
148-
this.asset = new assets.Asset(lambda, 'Code', {
161+
this.asset = new assets.Asset(construct, 'Code', {
149162
path: this.path,
150163
packaging: this.packaging
151164
});
@@ -156,9 +169,11 @@ export class AssetCode extends Code {
156169
}
157170
}
158171

159-
public toJSON(resource: CfnFunction): CfnFunction.CodeProperty {
160-
// https://github.com/awslabs/aws-cdk/issues/1432
161-
this.asset!.addResourceMetadata(resource, 'Code');
172+
public _toJSON(resource?: cdk.Resource): CfnFunction.CodeProperty {
173+
if (resource) {
174+
// https://github.com/awslabs/aws-cdk/issues/1432
175+
this.asset!.addResourceMetadata(resource, 'Code');
176+
}
162177

163178
return {
164179
s3Bucket: this.asset!.s3BucketName,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * from './alias';
22
export * from './lambda-ref';
33
export * from './lambda';
4+
export * from './layers';
45
export * from './permission';
56
export * from './pipeline-action';
67
export * from './runtime';

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

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Code } from './code';
77
import { FunctionBase, FunctionImportProps, IFunction } from './lambda-ref';
88
import { Version } from './lambda-version';
99
import { CfnFunction } from './lambda.generated';
10+
import { ILayerVersion } from './layers';
1011
import { Runtime } from './runtime';
1112

1213
/**
@@ -173,6 +174,15 @@ export interface FunctionProps {
173174
*/
174175
tracing?: Tracing;
175176

177+
/**
178+
* A list of layers to add to the function's execution environment. You can configure your Lambda function to pull in
179+
* additional code during initialization in the form of layers. Layers are packages of libraries or other dependencies
180+
* that can be used by mulitple functions.
181+
*
182+
* @default no layers
183+
*/
184+
layers?: ILayerVersion[];
185+
176186
/**
177187
* The maximum of concurrent executions you want to reserve for the function.
178188
*
@@ -306,6 +316,8 @@ export class Function extends FunctionBase {
306316

307317
protected readonly canCreatePermissions = true;
308318

319+
private readonly layers: ILayerVersion[] = [];
320+
309321
/**
310322
* Environment variables for this function
311323
*/
@@ -338,7 +350,8 @@ export class Function extends FunctionBase {
338350
const resource = new CfnFunction(this, 'Resource', {
339351
functionName: props.functionName,
340352
description: props.description,
341-
code: new cdk.Token(() => props.code.toJSON(resource)),
353+
code: new cdk.Token(() => props.code._toJSON(resource)),
354+
layers: new cdk.Token(() => this.layers.length > 0 ? this.layers.map(layer => layer.layerVersionArn) : undefined),
342355
handler: props.handler,
343356
timeout: props.timeout,
344357
runtime: props.runtime.name,
@@ -360,6 +373,10 @@ export class Function extends FunctionBase {
360373

361374
// allow code to bind to stack.
362375
props.code.bind(this);
376+
377+
for (const layer of props.layers || []) {
378+
this.addLayer(layer);
379+
}
363380
}
364381

365382
/**
@@ -380,12 +397,32 @@ export class Function extends FunctionBase {
380397
* @param key The environment variable key.
381398
* @param value The environment variable's value.
382399
*/
383-
public addEnvironment(key: string, value: any) {
400+
public addEnvironment(key: string, value: any): this {
384401
if (!this.environment) {
385402
// TODO: add metadata
386-
return;
403+
return this;
387404
}
388405
this.environment[key] = value;
406+
return this;
407+
}
408+
409+
/**
410+
* Adds a Lambda Layer to this Lambda function.
411+
*
412+
* @param layer the layer to be added.
413+
*
414+
* @throws if there are already 5 layers on this function, or the layer is incompatible with this function's runtime.
415+
*/
416+
public addLayer(layer: ILayerVersion): this {
417+
if (this.layers.length === 5) {
418+
throw new Error('Unable to add layer: this lambda function already uses 5 layers.');
419+
}
420+
if (layer.compatibleRuntimes && layer.compatibleRuntimes.indexOf(this.runtime) === -1) {
421+
const runtimes = layer.compatibleRuntimes.map(runtime => runtime.name).join(', ');
422+
throw new Error(`This lambda function uses a runtime that is incompatible with this layer (${this.runtime.name} is not in [${runtimes}])`);
423+
}
424+
this.layers.push(layer);
425+
return this;
389426
}
390427

391428
/**
@@ -542,4 +579,4 @@ export class ImportedFunction extends FunctionBase {
542579
*/
543580
function extractNameFromArn(arn: string) {
544581
return cdk.Fn.select(6, cdk.Fn.split(':', arn));
545-
}
582+
}

0 commit comments

Comments
 (0)