Skip to content

Commit

Permalink
lambda: Use assets for handler code
Browse files Browse the repository at this point in the history
Enables the use of CDK assets for Lambda handler code.
It is now possible to reference a directory on disk, and the CDK
will zip archive, upload and reference it automatically.
  • Loading branch information
Elad Ben-Israel committed Jul 18, 2018
1 parent 5142934 commit 708040b
Show file tree
Hide file tree
Showing 9 changed files with 350 additions and 62 deletions.
62 changes: 20 additions & 42 deletions packages/@aws-cdk/lambda/README.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,33 @@
## AWS Lambda Construct Library

This construct library allows you to define AWS Lambda functions.

```ts
const fn = new Lambda(this, 'MyFunction', {
runtime: LambdaRuntime.DOTNETCORE_2,
code: new LambdaS3Code(bucket, 'myKey'),
runtime: LambdaRuntime.NodeJS810,
handler: 'index.handler'
code: LambdaCode.inline('exports.handler = function(event, ctx, cb) { return cb(null, "hi"); }'),
});

fn.role.addToPolicy(new PolicyStatement()....);
```

### Inline node.js Lambda Functions

The subclass called `LambdaInlineNodeJS` allows embedding the function's handler
as a JavaScript function within the construct code.
### Handler Code

The following example defines a node.js Lambda and an S3 bucket. When invoked,
a file named "myfile.txt" will be uploaded to the bucket with the string "hello, world".
The `LambdaCode` class includes static convenience methods for various types of
runtime code.

A few things to note:
* `LambdaCode.bucket(bucket, key[, objectVersion])` - specify an S3 object that
contains the archive of your runtime code.
* `LambdaCode.inline(code)` - inline the handle code as a string. This is
limited to 4KB. The class `InlineJavaScriptLambda` can be used to simplify
inlining JavaScript functions.
* `LambdaCode.asset(directory)` - specify a directory in the local filesystem
which will be zipped and uploaded to S3 before deployment.

- The function's execution role is granted read/write permissions on the
bucket.
- The require statement for `aws-sdk` is invoked within the function's body. Due to
node.js' module caching, this is equivalent in performance to requiring
outside.
- The bucket name is passed to the function via the environment variable
`BUCKET_NAME`.
The following example shows how to define a Python function and deploy the code from the
local directory `my-lambda-handler` to it:

```ts
const bucket = new Bucket(this, 'MyBucket');

const lambda = new InlineJavaScriptLambda(this, 'MyLambda', {
environment: {
BUCKET_NAME: bucket.bucketName
},
handler: {
fn: (_event: any, _context: any, callback: any) => {
const S3 = require('aws-sdk').S3;
const s3 = new S3();
const bucketName = process.env.BUCKET_NAME;
s3.upload({ Bucket: bucketName, Key: 'myfile.txt', Body: 'Hello, world' }, (err, data) => {
if (err) {
return callback(err);
}
console.log(data);
return callback();
});
}
}
});
[Example of Lambda Code from Local Assets](test/integ.assets.lit.ts)

bucket.grantReadWrite(lambda.role);
```
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.
90 changes: 81 additions & 9 deletions packages/@aws-cdk/lambda/lib/code.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,58 @@
import { BucketName, BucketRef } from '@aws-cdk/s3';
import assets = require('@aws-cdk/cdk-assets');
import s3 = require('@aws-cdk/s3');
import { Lambda } from './lambda';
import { cloudformation } from './lambda.generated';
import { LambdaRuntime } from './runtime';

export abstract class LambdaCode {
public abstract toJSON(runtime: LambdaRuntime): cloudformation.FunctionResource.CodeProperty;
/**
* @returns `LambdaCodeS3` associated with the specified S3 object.
* @param bucket The S3 bucket
* @param key The object key
* @param objectVersion Optional S3 object version
*/
public static bucket(bucket: s3.BucketRef, key: string, objectVersion?: string) {
return new LambdaS3Code(bucket, key, objectVersion);
}

/**
* @returns `LambdaCodeInline` with inline code.
* @param code The actual handler code (limited to 4KiB)
*/
public static inline(code: string) {
return new LambdaInlineCode(code);
}

/**
* @returns `LambdaCodeAsset`
* @param directoryToZip
*/
public static asset(directoryToZip: string) {
return new LambdaAssetCode(directoryToZip);
}

/**
* Called during stack synthesis to render the CodePropery for the
* Lambda function.
*/

public abstract toJSON(): cloudformation.FunctionResource.CodeProperty;

/**
* Called when the lambda is initialized to allow this object to
* bind to the stack, add resources and have fun.
*/
public bind(_parent: Lambda) {
return;
}
}

/**
* Lambda code from an S3 archive.
*/
export class LambdaS3Code extends LambdaCode {
private bucketName: BucketName;
private bucketName: s3.BucketName;

constructor(bucket: BucketRef, private key: string, private objectVersion?: string) {
constructor(bucket: s3.BucketRef, private key: string, private objectVersion?: string) {
super();

if (!bucket.bucketName) {
Expand All @@ -19,7 +62,7 @@ export class LambdaS3Code extends LambdaCode {
this.bucketName = bucket.bucketName;
}

public toJSON(_runtime: LambdaRuntime): cloudformation.FunctionResource.CodeProperty {
public toJSON(): cloudformation.FunctionResource.CodeProperty {
return {
s3Bucket: this.bucketName,
s3Key: this.key,
Expand All @@ -28,6 +71,9 @@ export class LambdaS3Code extends LambdaCode {
}
}

/**
* Lambda code from an inline string (limited to 4KiB).
*/
export class LambdaInlineCode extends LambdaCode {
constructor(private code: string) {
super();
Expand All @@ -37,13 +83,39 @@ export class LambdaInlineCode extends LambdaCode {
}
}

public toJSON(runtime: LambdaRuntime): cloudformation.FunctionResource.CodeProperty {
if (!runtime.supportsInlineCode) {
throw new Error(`Inline source not supported for: ${runtime.name}`);
public bind(parent: Lambda) {
if (!parent.runtime.supportsInlineCode) {
throw new Error(`Inline source not allowed for ${parent.runtime.name}`);
}
}

public toJSON(): cloudformation.FunctionResource.CodeProperty {

return {
zipFile: this.code
};
}
}

/**
* Lambda code from a local directory.
*/
export class LambdaAssetCode extends LambdaCode {
private asset?: assets.Asset;

constructor(private readonly directory: string) {
super();
}

public bind(parent: Lambda) {
this.asset = new assets.ZipDirectoryAsset(parent, 'Code', { path: this.directory });
this.asset.grantRead(parent.role);
}

public toJSON(): cloudformation.FunctionResource.CodeProperty {
return {
s3Bucket: this.asset!.s3BucketName,
s3Key: this.asset!.s3ObjectKey
};
}
}
17 changes: 16 additions & 1 deletion packages/@aws-cdk/lambda/lib/lambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ export class Lambda extends LambdaRef {
*/
public readonly role?: Role;

/**
* The runtime configured for this lambda.
*/
public readonly runtime: LambdaRuntime;

/**
* The name of the handler configured for this lambda.
*/
public readonly handler: string;

protected readonly canCreatePermissions = true;

/**
Expand Down Expand Up @@ -149,7 +159,7 @@ export class Lambda extends LambdaRef {
const resource = new cloudformation.FunctionResource(this, 'Resource', {
functionName: props.functionName,
description: props.description,
code: props.code.toJSON(props.runtime),
code: new Token(() => props.code.toJSON()),
handler: props.handler,
timeout: props.timeout,
runtime: props.runtime.name,
Expand All @@ -162,6 +172,11 @@ export class Lambda extends LambdaRef {

this.functionName = resource.ref;
this.functionArn = resource.functionArn;
this.handler = props.handler;
this.runtime = props.runtime;

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

/**
Expand Down
4 changes: 3 additions & 1 deletion packages/@aws-cdk/lambda/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@
"dependencies": {
"@aws-cdk/cloudwatch": "^0.7.3-beta",
"@aws-cdk/core": "^0.7.3-beta",
"@aws-cdk/cdk-assets": "^0.7.3-beta",
"@aws-cdk/events": "^0.7.3-beta",
"@aws-cdk/iam": "^0.7.3-beta",
"@aws-cdk/s3": "^0.7.3-beta",
"@aws-cdk/logs": "^0.7.3-beta"
"@aws-cdk/logs": "^0.7.3-beta",
"@aws-cdk/cx-api": "^0.7.3-beta"
}
}
Loading

0 comments on commit 708040b

Please sign in to comment.