Skip to content

Commit

Permalink
feat(lambda): Support AWS Lambda Layers (#1411)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
RomainMuller authored Jan 24, 2019
1 parent 23c9afc commit 036cfdf
Show file tree
Hide file tree
Showing 12 changed files with 633 additions and 33 deletions.
21 changes: 14 additions & 7 deletions packages/@aws-cdk/aws-lambda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,24 @@ When deploying a stack that contains this code, the directory will be zip
archived and then uploaded to an S3 bucket, then the exact location of the S3
objects will be passed when the stack is deployed.

### Layers

The `lambda.LayerVersion` class can be used to define Lambda layers and manage
granting permissions to other AWS accounts or organizations.

[Example of Lambda Layer usage](test/integ.layer-version.lit.ts)

### Event Sources

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

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

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

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

### Lambda with DLQ
### Lambda with DLQ

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

### Lambda with X-Ray Tracing
### Lambda with X-Ray Tracing

```ts
import lambda = require('@aws-cdk/aws-lambda');
Expand Down
43 changes: 29 additions & 14 deletions packages/@aws-cdk/aws-lambda/lib/code.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assets = require('@aws-cdk/assets');
import s3 = require('@aws-cdk/aws-s3');
import cdk = require('@aws-cdk/cdk');
import fs = require('fs');
import { Function as Func } from './lambda';
import { CfnFunction } from './lambda.generated';

export abstract class Code {
Expand Down Expand Up @@ -50,17 +50,24 @@ export abstract class Code {
return new AssetCode(filePath, assets.AssetPackaging.File);
}

/**
* Determines whether this Code is inline code or not.
*/
public abstract readonly isInline: boolean;

/**
* Called during stack synthesis to render the CodePropery for the
* Lambda function.
*
* @param resource the resource to which the code will be attached (a CfnFunction, or a CfnLayerVersion).
*/
public abstract toJSON(resource: CfnFunction): CfnFunction.CodeProperty;
public abstract _toJSON(resource?: cdk.Resource): CfnFunction.CodeProperty;

/**
* Called when the lambda is initialized to allow this object to
* Called when the lambda or layer is initialized to allow this object to
* bind to the stack, add resources and have fun.
*/
public bind(_lambda: Func) {
public bind(_construct: cdk.Construct) {
return;
}
}
Expand All @@ -69,6 +76,7 @@ export abstract class Code {
* Lambda code from an S3 archive.
*/
export class S3Code extends Code {
public readonly isInline = false;
private bucketName: string;

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

public toJSON(_: CfnFunction): CfnFunction.CodeProperty {
public _toJSON(_?: cdk.Resource): CfnFunction.CodeProperty {
return {
s3Bucket: this.bucketName,
s3Key: this.key,
Expand All @@ -94,6 +102,8 @@ export class S3Code extends Code {
* Lambda code from an inline string (limited to 4KiB).
*/
export class InlineCode extends Code {
public readonly isInline = true;

constructor(private code: string) {
super();

Expand All @@ -102,13 +112,14 @@ export class InlineCode extends Code {
}
}

public bind(lambda: Func) {
if (!lambda.runtime.supportsInlineCode) {
throw new Error(`Inline source not allowed for ${lambda.runtime.name}`);
public bind(construct: cdk.Construct) {
const runtime = (construct as any).runtime;
if (!runtime.supportsInlineCode) {
throw new Error(`Inline source not allowed for ${runtime && runtime.name}`);
}
}

public toJSON(_: CfnFunction): CfnFunction.CodeProperty {
public _toJSON(_?: cdk.Resource): CfnFunction.CodeProperty {
return {
zipFile: this.code
};
Expand All @@ -119,6 +130,8 @@ export class InlineCode extends Code {
* Lambda code from a local directory.
*/
export class AssetCode extends Code {
public readonly isInline = false;

/**
* The asset packaging.
*/
Expand All @@ -142,10 +155,10 @@ export class AssetCode extends Code {
}
}

public bind(lambda: Func) {
public bind(construct: cdk.Construct) {
// If the same AssetCode is used multiple times, retain only the first instantiation.
if (!this.asset) {
this.asset = new assets.Asset(lambda, 'Code', {
this.asset = new assets.Asset(construct, 'Code', {
path: this.path,
packaging: this.packaging
});
Expand All @@ -156,9 +169,11 @@ export class AssetCode extends Code {
}
}

public toJSON(resource: CfnFunction): CfnFunction.CodeProperty {
// https://github.com/awslabs/aws-cdk/issues/1432
this.asset!.addResourceMetadata(resource, 'Code');
public _toJSON(resource?: cdk.Resource): CfnFunction.CodeProperty {
if (resource) {
// https://github.com/awslabs/aws-cdk/issues/1432
this.asset!.addResourceMetadata(resource, 'Code');
}

return {
s3Bucket: this.asset!.s3BucketName,
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-lambda/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './alias';
export * from './lambda-ref';
export * from './lambda';
export * from './layers';
export * from './permission';
export * from './pipeline-action';
export * from './runtime';
Expand Down
45 changes: 41 additions & 4 deletions packages/@aws-cdk/aws-lambda/lib/lambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Code } from './code';
import { FunctionBase, FunctionImportProps, IFunction } from './lambda-ref';
import { Version } from './lambda-version';
import { CfnFunction } from './lambda.generated';
import { ILayerVersion } from './layers';
import { Runtime } from './runtime';

/**
Expand Down Expand Up @@ -173,6 +174,15 @@ export interface FunctionProps {
*/
tracing?: Tracing;

/**
* A list of layers to add to the function's execution environment. You can configure your Lambda function to pull in
* additional code during initialization in the form of layers. Layers are packages of libraries or other dependencies
* that can be used by mulitple functions.
*
* @default no layers
*/
layers?: ILayerVersion[];

/**
* The maximum of concurrent executions you want to reserve for the function.
*
Expand Down Expand Up @@ -306,6 +316,8 @@ export class Function extends FunctionBase {

protected readonly canCreatePermissions = true;

private readonly layers: ILayerVersion[] = [];

/**
* Environment variables for this function
*/
Expand Down Expand Up @@ -338,7 +350,8 @@ export class Function extends FunctionBase {
const resource = new CfnFunction(this, 'Resource', {
functionName: props.functionName,
description: props.description,
code: new cdk.Token(() => props.code.toJSON(resource)),
code: new cdk.Token(() => props.code._toJSON(resource)),
layers: new cdk.Token(() => this.layers.length > 0 ? this.layers.map(layer => layer.layerVersionArn) : undefined),
handler: props.handler,
timeout: props.timeout,
runtime: props.runtime.name,
Expand All @@ -360,6 +373,10 @@ export class Function extends FunctionBase {

// allow code to bind to stack.
props.code.bind(this);

for (const layer of props.layers || []) {
this.addLayer(layer);
}
}

/**
Expand All @@ -380,12 +397,32 @@ export class Function extends FunctionBase {
* @param key The environment variable key.
* @param value The environment variable's value.
*/
public addEnvironment(key: string, value: any) {
public addEnvironment(key: string, value: any): this {
if (!this.environment) {
// TODO: add metadata
return;
return this;
}
this.environment[key] = value;
return this;
}

/**
* Adds a Lambda Layer to this Lambda function.
*
* @param layer the layer to be added.
*
* @throws if there are already 5 layers on this function, or the layer is incompatible with this function's runtime.
*/
public addLayer(layer: ILayerVersion): this {
if (this.layers.length === 5) {
throw new Error('Unable to add layer: this lambda function already uses 5 layers.');
}
if (layer.compatibleRuntimes && layer.compatibleRuntimes.indexOf(this.runtime) === -1) {
const runtimes = layer.compatibleRuntimes.map(runtime => runtime.name).join(', ');
throw new Error(`This lambda function uses a runtime that is incompatible with this layer (${this.runtime.name} is not in [${runtimes}])`);
}
this.layers.push(layer);
return this;
}

/**
Expand Down Expand Up @@ -542,4 +579,4 @@ export class ImportedFunction extends FunctionBase {
*/
function extractNameFromArn(arn: string) {
return cdk.Fn.select(6, cdk.Fn.split(':', arn));
}
}
Loading

0 comments on commit 036cfdf

Please sign in to comment.