diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index e122151c3a28c..df932369c0955 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -8,7 +8,7 @@ import lambda = require('@aws-cdk/aws-lambda'); const fn = new lambda.Function(this, 'MyFunction', { runtime: lambda.Runtime.NodeJS810, handler: 'index.handler', - code: lambda.Code.inline('exports.handler = function(event, ctx, cb) { return cb(null, "hi"); }'), + code: lambda.Code.asset('./lambda-handler'), }); ``` @@ -17,18 +17,15 @@ const fn = new lambda.Function(this, 'MyFunction', { The `lambda.Code` class includes static convenience methods for various types of runtime code. - * `lambda.Code.bucket(bucket, key[, objectVersion])` - specify an S3 object that - contains the archive of your runtime code. + * `lambda.Code.bucket(bucket, key[, objectVersion])` - specify an S3 object + that contains the archive of your runtime code. * `lambda.Code.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. - * `lambda.Code.directory(directory)` - specify a directory in the local filesystem - which will be zipped and uploaded to S3 before deployment. - * `lambda.Code.file(path)` - specify a file to be used for Lambda code. This can - be, for example a JAR or a ZIP file, based on the runtime used. - -The following example shows how to define a Python function and deploy the code from the -local directory `my-lambda-handler` to it: + limited to 4KB. + * `lambda.Code.asset(path)` - specify a directory or a .zip file in the local + filesystem which will be zipped and uploaded to S3 before deployment. + +The following example shows how to define a Python function and deploy the code +from the local directory `my-lambda-handler` to it: [Example of Lambda Code from Local Assets](test/integ.assets.lit.ts) diff --git a/packages/@aws-cdk/aws-lambda/lib/code.ts b/packages/@aws-cdk/aws-lambda/lib/code.ts index def3300b41f64..29b8aef957522 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code.ts @@ -1,5 +1,6 @@ import assets = require('@aws-cdk/assets'); import s3 = require('@aws-cdk/aws-s3'); +import fs = require('fs'); import { Function as Func } from './lambda'; import { cloudformation } from './lambda.generated'; @@ -22,10 +23,19 @@ export abstract class Code { return new InlineCode(code); } + /** + * Loads the function code from a local disk asset. + * @param path Either a directory with the Lambda code bundle or a .zip file + */ + public static asset(path: string) { + return new AssetCode(path); + } + /** * @returns Zip archives the contents of a directory on disk and uses this * as the lambda handler's code. * @param directoryToZip The directory to zip + * @deprecated use `lambda.Code.asset(path)` (no need to specify if it's a file or a directory) */ public static directory(directoryToZip: string) { return new AssetCode(directoryToZip, assets.AssetPackaging.ZipDirectory); @@ -34,6 +44,7 @@ export abstract class Code { /** * @returns Uses a file on disk as a lambda handler's code. * @param filePath The file path + * @deprecated use `lambda.Code.asset(path)` (no need to specify if it's a file or a directory) */ public static file(filePath: string) { return new AssetCode(filePath, assets.AssetPackaging.File); @@ -108,16 +119,27 @@ export class InlineCode extends Code { * Lambda code from a local directory. */ export class AssetCode extends Code { + /** + * The asset packaging. + */ + public readonly packaging: assets.AssetPackaging; + private asset?: assets.Asset; /** * @param path The path to the asset file or directory. - * @param packaging The asset packaging format + * @param packaging The asset packaging format (optional, determined automatically) */ - constructor( - private readonly path: string, - private readonly packaging: assets.AssetPackaging) { + constructor(public readonly path: string, packaging?: assets.AssetPackaging) { super(); + + if (packaging !== undefined) { + this.packaging = packaging; + } else { + this.packaging = fs.lstatSync(path).isDirectory() + ? assets.AssetPackaging.ZipDirectory + : assets.AssetPackaging.File; + } } public bind(lambda: Func) { @@ -126,7 +148,9 @@ export class AssetCode extends Code { packaging: this.packaging }); - this.asset.grantRead(lambda.role); + if (!this.asset.isZipArchive) { + throw new Error(`Asset must be a .zip file or a directory (${this.path})`); + } } public toJSON(): cloudformation.FunctionResource.CodeProperty { diff --git a/packages/@aws-cdk/aws-lambda/lib/index.ts b/packages/@aws-cdk/aws-lambda/lib/index.ts index c512f7fc0c1ef..2874f05f53a90 100644 --- a/packages/@aws-cdk/aws-lambda/lib/index.ts +++ b/packages/@aws-cdk/aws-lambda/lib/index.ts @@ -5,7 +5,6 @@ export * from './permission'; export * from './pipeline-action'; export * from './runtime'; export * from './code'; -export * from './inline'; export * from './lambda-version'; export * from './singleton-lambda'; diff --git a/packages/@aws-cdk/aws-lambda/lib/inline.ts b/packages/@aws-cdk/aws-lambda/lib/inline.ts deleted file mode 100644 index e1afbcbd43564..0000000000000 --- a/packages/@aws-cdk/aws-lambda/lib/inline.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { Construct } from '@aws-cdk/cdk'; -import { InlineCode } from './code'; -import { Function } from './lambda'; -import { InlinableJavaScriptRuntime, Runtime } from './runtime'; - -/** - * Defines the handler code for an inline JavaScript lambda function. - * - * AWS Lambda invokes your Lambda function via a handler object. A handler - * represents the name of your Lambda function (and serves as the entry point - * that AWS Lambda uses to execute your function code. For example: - */ -export interface IJavaScriptLambdaHandler { - /** - * The main Lambda entrypoint. - * - * @param event Event sources can range from a supported AWS service or - * custom applications that invoke your Lambda function. For examples, see - * [Sample Events Published by Event - * Sources](https://docs.aws.amazon.com/lambda/latest/dg/eventsources.html). - * - * @param context AWS Lambda uses this parameter to provide details of your - * Lambda function's execution. For more information, see [The Context - * Object](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html). - * - * @param callback The Node.js runtimes v6.10 and v8.10 support the optional - * callback parameter. You can use it to explicitly return information back - * to the caller. Signature is `callback(err, response)`. - */ - fn(event: any, context: any, callback: any): void; -} - -export interface InlineJavaScriptLambdaProps { - /** - * The lambda handler as a javascript function. - * - * This must be a javascript function object. The reason it is `any` is due - * to limitations of the jsii compiler. - */ - handler: IJavaScriptLambdaHandler; - - /** - * A description of the function. - */ - description?: string; - - /** - * The function execution time (in seconds) after which Lambda terminates - * the function. Because the execution time affects cost, set this value - * based on the function's expected execution time. - * - * @default 30 seconds. - */ - timeout?: number; - - /** - * Key-value pairs that Lambda caches and makes available for your Lambda - * functions. Use environment variables to apply configuration changes, such - * as test and production environment configurations, without changing your - * Lambda function source code. - */ - environment?: { [key: string]: any }; - - /** - * The runtime environment for the Lambda function that you are uploading. - * For valid values, see the Runtime property in the AWS Lambda Developer - * Guide. - * - * @default NodeJS810 - */ - runtime?: InlinableJavaScriptRuntime; - - /** - * A name for the function. If you don't specify a name, AWS CloudFormation - * generates a unique physical ID and uses that ID for the function's name. - * For more information, see Name Type. - */ - functionName?: string; - - /** - * The amount of memory, in MB, that is allocated to your Lambda function. - * Lambda uses this value to proportionally allocate the amount of CPU - * power. For more information, see Resource Model in the AWS Lambda - * Developer Guide. - * - * @default The default value is 128 MB - */ - memorySize?: number; -} - -/** - * A lambda function with inline node.js code. - * - * Usage: - * - * new InlineJavaScriptLambda(this, 'MyFunc', { - * handler: { - * fn: (event, context, callback) => { - * console.log('hello, world'); - * callback(); - * } - * } - * }); - * - * This will define a node.js 6.10 function with the provided function has - * the handler code. - */ -export class InlineJavaScriptFunction extends Function { - constructor(parent: Construct, name: string, props: InlineJavaScriptLambdaProps) { - const code = new InlineCode(renderCode(props.handler)); - const runtime: InlinableJavaScriptRuntime = props.runtime || Runtime.NodeJS610; - const handler = 'index.handler'; - const timeout = props.timeout || 30; - super(parent, name, { - code, - runtime, - handler, - timeout, - description: props.description, - memorySize: props.memorySize, - functionName: props.functionName, - environment: props.environment, - }); - } -} - -function renderCode(handler: IJavaScriptLambdaHandler) { - return `exports.handler = ${handler.fn.toString()}`; -} diff --git a/packages/@aws-cdk/aws-lambda/lib/runtime.ts b/packages/@aws-cdk/aws-lambda/lib/runtime.ts index bc01358d38193..c79763d336446 100644 --- a/packages/@aws-cdk/aws-lambda/lib/runtime.ts +++ b/packages/@aws-cdk/aws-lambda/lib/runtime.ts @@ -3,58 +3,59 @@ export interface LambdaRuntimeProps { * Whether the ``ZipFile`` (aka inline code) property can be used with this runtime. * @default false */ - readonly supportsInlineCode?: boolean; + supportsInlineCode?: boolean; +} + +export enum RuntimeFamily { + NodeJS, + Java, + Python, + DotNetCore, + Go } /** * Lambda function runtime environment. + * + * If you need to use a runtime name that doesn't exist as a static member, you + * can instantiate a `Runtime` object, e.g: `new Runtime('nodejs99.99')`. */ -export class Runtime implements InlinableRuntime, InlinableJavaScriptRuntime { - /* tslint:disable variable-name */ - public static readonly NodeJS = new Runtime('nodejs', { supportsInlineCode: true }) as InlinableJavaScriptRuntime; - // Using ``as InlinableLambdaRuntime`` because that class cannot be defined just yet - public static readonly NodeJS43 = new Runtime('nodejs4.3', { supportsInlineCode: true }) as InlinableJavaScriptRuntime; - public static readonly NodeJS43Edge = new Runtime('nodejs4.3-edge'); - // Using ``as InlinableLambdaRuntime`` because that class cannot be defined just yet - public static readonly NodeJS610 = new Runtime('nodejs6.10', { supportsInlineCode: true }) as InlinableJavaScriptRuntime; - public static readonly NodeJS810 = new Runtime('nodejs8.10'); - public static readonly Java8 = new Runtime('java8'); - // Using ``as InlinableLambdaRuntime`` because that class cannot be defined just yet - public static readonly Python27 = new Runtime('python2.7', { supportsInlineCode: true }) as InlinableRuntime; - // Using ``as InlinableLambdaRuntime`` because that class cannot be defined just yet - public static readonly Python36 = new Runtime('python3.6', { supportsInlineCode: true }) as InlinableRuntime; - public static readonly DotNetCore1 = new Runtime('dotnetcore1.0'); - public static readonly DotNetCore2 = new Runtime('dotnetcore2.0'); - public static readonly DotNetCore21 = new Runtime('dotnetcore2.1'); - public static readonly Go1x = new Runtime('go1.x'); - /* tslint:enable variable-name */ +export class Runtime { + public static readonly NodeJS = new Runtime('nodejs', RuntimeFamily.NodeJS, { supportsInlineCode: true }); + public static readonly NodeJS43 = new Runtime('nodejs4.3', RuntimeFamily.NodeJS, { supportsInlineCode: true }); + public static readonly NodeJS610 = new Runtime('nodejs6.10', RuntimeFamily.NodeJS, { supportsInlineCode: true }); + public static readonly NodeJS810 = new Runtime('nodejs8.10', RuntimeFamily.NodeJS, { supportsInlineCode: true }); + public static readonly Python27 = new Runtime('python2.7', RuntimeFamily.Python, { supportsInlineCode: true }); + public static readonly Python36 = new Runtime('python3.6', RuntimeFamily.Python, { supportsInlineCode: true }); + public static readonly Java8 = new Runtime('java8', RuntimeFamily.Java); + public static readonly DotNetCore1 = new Runtime('dotnetcore1.0', RuntimeFamily.DotNetCore); + public static readonly DotNetCore2 = new Runtime('dotnetcore2.0', RuntimeFamily.DotNetCore); + public static readonly DotNetCore21 = new Runtime('dotnetcore2.1', RuntimeFamily.DotNetCore); + public static readonly Go1x = new Runtime('go1.x', RuntimeFamily.Go); - /** The name of this runtime, as expected by the Lambda resource. */ + /** + * The name of this runtime, as expected by the Lambda resource. + */ public readonly name: string; - /** Whether the ``ZipFile`` (aka inline code) property can be used with this runtime. */ + + /** + * Whether the ``ZipFile`` (aka inline code) property can be used with this + * runtime. + */ public readonly supportsInlineCode: boolean; - constructor(name: string, props: LambdaRuntimeProps = {}) { + /** + * The runtime family. + */ + public readonly family?: RuntimeFamily; + + constructor(name: string, family?: RuntimeFamily, props: LambdaRuntimeProps = { }) { this.name = name; this.supportsInlineCode = !!props.supportsInlineCode; + this.family = family; } public toString(): string { return this.name; } } - -/** - * A ``LambdaRuntime`` that can be used in conjunction with the ``ZipFile`` - * property of the ``AWS::Lambda::Function`` resource. - */ -export interface InlinableRuntime { - readonly name: string; - readonly supportsInlineCode: boolean; -} - -/** - * A ``LambdaRuntime`` that can be used for inlining JavaScript. - */ -// tslint:disable-next-line:no-empty-interface this is a marker to allow type-safe declarations -export interface InlinableJavaScriptRuntime extends InlinableRuntime {} diff --git a/packages/@aws-cdk/aws-lambda/test/integ.assets.file.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.assets.file.expected.json index e700ed0a11b98..fecdce9a68e42 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.assets.file.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.assets.file.expected.json @@ -31,91 +31,6 @@ ] } }, - "MyLambdaServiceRoleDefaultPolicy5BBC6F68": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:GetBucket*", - "s3:GetObject*", - "s3:List*" - ], - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "MyLambdaCodeS3BucketC82A5870" - } - ] - ] - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "MyLambdaCodeS3BucketC82A5870" - } - ] - ] - }, - "/", - { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "MyLambdaCodeS3VersionKey47762537" - } - ] - } - ] - }, - "*" - ] - ] - } - ] - ] - } - ] - } - ] - }, - "PolicyName": "MyLambdaServiceRoleDefaultPolicy5BBC6F68", - "Roles": [ - { - "Ref": "MyLambdaServiceRole4539ECB6" - } - ] - } - }, "MyLambdaCCE802FB": { "Type": "AWS::Lambda::Function", "Properties": { @@ -167,8 +82,7 @@ "Runtime": "python3.6" }, "DependsOn": [ - "MyLambdaServiceRole4539ECB6", - "MyLambdaServiceRoleDefaultPolicy5BBC6F68" + "MyLambdaServiceRole4539ECB6" ] } }, diff --git a/packages/@aws-cdk/aws-lambda/test/integ.assets.lit.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.assets.lit.expected.json index e20502ff24e91..78036f8ce9951 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.assets.lit.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.assets.lit.expected.json @@ -31,91 +31,6 @@ ] } }, - "MyLambdaServiceRoleDefaultPolicy5BBC6F68": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:GetBucket*", - "s3:GetObject*", - "s3:List*" - ], - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "MyLambdaCodeS3BucketC82A5870" - } - ] - ] - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":s3:::", - { - "Ref": "MyLambdaCodeS3BucketC82A5870" - } - ] - ] - }, - "/", - { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "MyLambdaCodeS3VersionKey47762537" - } - ] - } - ] - }, - "*" - ] - ] - } - ] - ] - } - ] - } - ] - }, - "PolicyName": "MyLambdaServiceRoleDefaultPolicy5BBC6F68", - "Roles": [ - { - "Ref": "MyLambdaServiceRole4539ECB6" - } - ] - } - }, "MyLambdaCCE802FB": { "Type": "AWS::Lambda::Function", "Properties": { @@ -167,8 +82,7 @@ "Runtime": "python3.6" }, "DependsOn": [ - "MyLambdaServiceRole4539ECB6", - "MyLambdaServiceRoleDefaultPolicy5BBC6F68" + "MyLambdaServiceRole4539ECB6" ] } }, diff --git a/packages/@aws-cdk/aws-lambda/test/integ.assets.lit.ts b/packages/@aws-cdk/aws-lambda/test/integ.assets.lit.ts index e0696c5261edd..e63168562c226 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.assets.lit.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.assets.lit.ts @@ -8,7 +8,7 @@ class TestStack extends cdk.Stack { /// !show new lambda.Function(this, 'MyLambda', { - code: lambda.Code.directory(path.join(__dirname, 'my-lambda-handler')), + code: lambda.Code.asset(path.join(__dirname, 'my-lambda-handler')), handler: 'index.main', runtime: lambda.Runtime.Python36 }); diff --git a/packages/@aws-cdk/aws-lambda/test/integ.inline.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.inline.expected.json deleted file mode 100644 index 1d3bf1a04b0c9..0000000000000 --- a/packages/@aws-cdk/aws-lambda/test/integ.inline.expected.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "Resources": { - "MyBucketF68F3FF0": { - "Type": "AWS::S3::Bucket" - }, - "MyLambdaServiceRole4539ECB6": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "sts:AssumeRole", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ] - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "MyLambdaServiceRoleDefaultPolicy5BBC6F68": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:Abort*", - "s3:DeleteObject*", - "s3:GetBucket*", - "s3:GetObject*", - "s3:List*", - "s3:PutObject*" - ], - "Resource": [ - { - "Fn::GetAtt": [ - "MyBucketF68F3FF0", - "Arn" - ] - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "MyBucketF68F3FF0", - "Arn" - ] - }, - "/*" - ] - ] - } - ] - } - ] - }, - "PolicyName": "MyLambdaServiceRoleDefaultPolicy5BBC6F68", - "Roles": [ - { - "Ref": "MyLambdaServiceRole4539ECB6" - } - ] - } - }, - "MyLambdaCCE802FB": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "exports.handler = (_event, _context, callback) => {\n // tslint:disable:no-console\n const S3 = require('aws-sdk').S3;\n const client = new S3();\n const bucketName = process.env.BUCKET_NAME;\n const req = {\n Bucket: bucketName,\n Key: 'myfile.txt',\n Body: 'Hello, world'\n };\n return client.upload(req, (err, data) => {\n if (err) {\n return callback(err);\n }\n console.log(data);\n return callback();\n });\n }" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "MyLambdaServiceRole4539ECB6", - "Arn" - ] - }, - "Runtime": "nodejs6.10", - "Environment": { - "Variables": { - "BUCKET_NAME": { - "Ref": "MyBucketF68F3FF0" - } - } - }, - "Timeout": 30 - }, - "DependsOn": [ - "MyLambdaServiceRole4539ECB6", - "MyLambdaServiceRoleDefaultPolicy5BBC6F68" - ] - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/integ.inline.ts b/packages/@aws-cdk/aws-lambda/test/integ.inline.ts deleted file mode 100644 index d2f469c0f75ed..0000000000000 --- a/packages/@aws-cdk/aws-lambda/test/integ.inline.ts +++ /dev/null @@ -1,39 +0,0 @@ -import s3 = require('@aws-cdk/aws-s3'); -import cdk = require('@aws-cdk/cdk'); -import lambda = require('../lib'); - -const app = new cdk.App(); - -const stack = new cdk.Stack(app, 'aws-cdk-lambda-2'); - -const bucket = new s3.Bucket(stack, 'MyBucket'); - -const fn = new lambda.InlineJavaScriptFunction(stack, 'MyLambda', { - environment: { - BUCKET_NAME: bucket.bucketName - }, - handler: { - fn: (_event: any, _context: any, callback: any) => { - // tslint:disable:no-console - const S3 = require('aws-sdk').S3; - const client = new S3(); - const bucketName = process.env.BUCKET_NAME; - const req = { - Bucket: bucketName, - Key: 'myfile.txt', - Body: 'Hello, world' - }; - return client.upload(req, (err: any, data: any) => { - if (err) { - return callback(err); - } - console.log(data); - return callback(); - }); - }, - } -}); - -bucket.grantReadWrite(fn.role); - -app.run(); diff --git a/packages/@aws-cdk/aws-lambda/test/test.code.ts b/packages/@aws-cdk/aws-lambda/test/test.code.ts new file mode 100644 index 0000000000000..91e361a51e5d4 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/test.code.ts @@ -0,0 +1,58 @@ +import assets = require('@aws-cdk/assets'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import path = require('path'); +import lambda = require('../lib'); + +export = { + 'lambda.Code.inline': { + 'fails if used with unsupported runtimes'(test: Test) { + test.throws(() => defineFunction(lambda.Code.inline('boom'), lambda.Runtime.Go1x), /Inline source not allowed for go1\.x/); + test.throws(() => defineFunction(lambda.Code.inline('boom'), lambda.Runtime.Java8), /Inline source not allowed for java8/); + test.done(); + }, + 'fails if larger than 4096 bytes'(test: Test) { + test.throws( + () => defineFunction(lambda.Code.inline(generateRandomString(4097)), lambda.Runtime.NodeJS610), + /Lambda source is too large, must be <= 4096 but is 4097/); + test.done(); + } + }, + 'lambda.Code.asset': { + 'determines packaging type from file type'(test: Test) { + // WHEN + const fileAsset = lambda.Code.asset(path.join(__dirname, 'handler.zip')); + const directoryAsset = lambda.Code.asset(path.join(__dirname, 'my-lambda-handler')); + + // THEN + test.deepEqual(fileAsset.packaging, assets.AssetPackaging.File); + test.deepEqual(directoryAsset.packaging, assets.AssetPackaging.ZipDirectory); + test.done(); + }, + + 'fails if a non-zip asset is used'(test: Test) { + // GIVEN + const fileAsset = lambda.Code.asset(path.join(__dirname, 'my-lambda-handler', 'index.py')); + + // THEN + test.throws(() => defineFunction(fileAsset), /Asset must be a \.zip file or a directory/); + test.done(); + } + } +}; + +function defineFunction(code: lambda.Code, runtime: lambda.Runtime = lambda.Runtime.NodeJS810) { + const stack = new cdk.Stack(); + return new lambda.Function(stack, 'Func', { + handler: 'foom', + code, runtime + }); +} + +function generateRandomString(bytes: number) { + let s = ''; + for (let i = 0; i < bytes; ++i) { + s += String.fromCharCode(Math.round(Math.random() * 256)); + } + return s; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/test.inline.ts b/packages/@aws-cdk/aws-lambda/test/test.inline.ts deleted file mode 100644 index bfe35b6ad7366..0000000000000 --- a/packages/@aws-cdk/aws-lambda/test/test.inline.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { expect } from '@aws-cdk/assert'; -import s3 = require('@aws-cdk/aws-s3'); -import cdk = require('@aws-cdk/cdk'); -import fs = require('fs'); -import { Test } from 'nodeunit'; -import path = require('path'); -import { InlineJavaScriptFunction } from '../lib'; - -export = { - 'inline node lambda allows plugging in javascript functions as handlers'(test: Test) { - - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'MyBucket'); - const lambda = new InlineJavaScriptFunction(stack, 'MyLambda', { - environment: { - BUCKET_NAME: bucket.bucketName - }, - handler: { - fn: (_event: any, _context: any, callback: any) => { - // tslint:disable:no-console - const S3 = require('aws-sdk').S3; - const client = new S3(); - const bucketName = process.env.BUCKET_NAME; - client.upload({ Bucket: bucketName, Key: 'myfile.txt', Body: 'Hello, world' }, (err: any, data: any) => { - if (err) { - return callback(err); - } - console.log(data); - return callback(); - }); - }, - } - }); - - bucket.grantReadWrite(lambda.role); - - const expected = JSON.parse(fs.readFileSync(path.join(__dirname, 'inline.expected.json')).toString()); - expect(stack).toMatch(expected); - - test.done(); - } -}; diff --git a/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.expected.json b/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.expected.json index 0be66cf9ec553..a3847b565a4cf 100644 --- a/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.expected.json +++ b/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.expected.json @@ -53,7 +53,7 @@ "Type": "AWS::Lambda::Function", "Properties": { "Code": { - "ZipFile": "exports.handler = (event, _context, callback) => {\n // tslint:disable:no-console\n console.log('====================================================');\n console.log(JSON.stringify(event, undefined, 2));\n console.log('====================================================');\n return callback(undefined, event);\n }" + "ZipFile": "exports.handler = function handler(event, _context, callback) {\n // tslint:disable:no-console\n console.log('====================================================');\n console.log(JSON.stringify(event, undefined, 2));\n console.log('====================================================');\n return callback(undefined, event);\n}" }, "Handler": "index.handler", "Role": { @@ -62,8 +62,7 @@ "Arn" ] }, - "Runtime": "nodejs6.10", - "Timeout": 30 + "Runtime": "nodejs8.10" }, "DependsOn": [ "EchoServiceRoleBE28060B" diff --git a/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.ts b/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.ts index 71649af6d6e06..9929a95046f47 100644 --- a/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.ts +++ b/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.ts @@ -8,16 +8,10 @@ class SnsToSqs extends cdk.Stack { const topic = new sns.Topic(this, 'MyTopic'); - const fction = new lambda.InlineJavaScriptFunction(this, 'Echo', { - handler: { - fn: (event, _context, callback) => { - // tslint:disable:no-console - console.log('===================================================='); - console.log(JSON.stringify(event, undefined, 2)); - console.log('===================================================='); - return callback(undefined, event); - } - } + const fction = new lambda.Function(this, 'Echo', { + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS810, + code: lambda.Code.inline(`exports.handler = ${handler.toString()}`) }); topic.subscribeLambda(fction); @@ -29,3 +23,11 @@ const app = new cdk.App(); new SnsToSqs(app, 'aws-cdk-sns-lambda'); app.run(); + +function handler(event: any, _context: any, callback: any) { + // tslint:disable:no-console + console.log('===================================================='); + console.log(JSON.stringify(event, undefined, 2)); + console.log('===================================================='); + return callback(undefined, event); +} diff --git a/packages/@aws-cdk/aws-sns/test/test.sns.ts b/packages/@aws-cdk/aws-sns/test/test.sns.ts index aa6ce2ec80bd1..4821284793e31 100644 --- a/packages/@aws-cdk/aws-sns/test/test.sns.ts +++ b/packages/@aws-cdk/aws-sns/test/test.sns.ts @@ -217,10 +217,10 @@ export = { displayName: 'displayName' }); - const fction = new lambda.InlineJavaScriptFunction(stack, 'MyFunc', { - handler: { - fn: (_event, _context, callback) => callback() - } + const fction = new lambda.Function(stack, 'MyFunc', { + runtime: lambda.Runtime.NodeJS610, + handler: 'index.handler', + code: lambda.Code.inline('exports.handler = function(e, c, cb) { return cb() }') }); topic.subscribeLambda(fction); @@ -273,7 +273,7 @@ export = { "Type": "AWS::Lambda::Function", "Properties": { "Code": { - "ZipFile": "exports.handler = (_event, _context, callback) => callback()" + "ZipFile": "exports.handler = function(e, c, cb) { return cb() }" }, "Handler": "index.handler", "Role": { @@ -282,8 +282,7 @@ export = { "Arn" ] }, - "Runtime": "nodejs6.10", - "Timeout": 30 + "Runtime": "nodejs6.10" }, "DependsOn": [ "MyFuncServiceRole54065130" @@ -352,10 +351,10 @@ export = { }); const queue = new sqs.Queue(stack, 'MyQueue'); - const func = new lambda.InlineJavaScriptFunction(stack, 'MyLambda', { - handler: { - fn: (_event, _context, callback: any) => callback() - } + const func = new lambda.Function(stack, 'MyFunc', { + runtime: lambda.Runtime.NodeJS610, + handler: 'index.handler', + code: lambda.Code.inline('exports.handler = function(e, c, cb) { return cb() }') }); topic.subscribeQueue(queue); @@ -363,135 +362,145 @@ export = { expect(stack).toMatch({ "Resources": { - "MyTopic86869434": { - "Type": "AWS::SNS::Topic", - "Properties": { - "DisplayName": "displayName", - "TopicName": "topicName" - } - }, - "MyTopicMyQueueSubscription3245B11E": { - "Type": "AWS::SNS::Subscription", - "Properties": { - "Endpoint": { - "Fn::GetAtt": [ - "MyQueueE6CA6235", - "Arn" - ] - }, - "Protocol": "sqs", - "TopicArn": { - "Ref": "MyTopic86869434" - } - } - }, - "MyTopicMyLambdaSubscription3591BC1E": { - "Type": "AWS::SNS::Subscription", - "Properties": { - "Endpoint": { - "Fn::GetAtt": [ - "MyLambdaCCE802FB", - "Arn" - ] + "MyTopic86869434": { + "Type": "AWS::SNS::Topic", + "Properties": { + "DisplayName": "displayName", + "TopicName": "topicName" + } }, - "Protocol": "lambda", - "TopicArn": { - "Ref": "MyTopic86869434" - } - } - }, - "MyQueueE6CA6235": { - "Type": "AWS::SQS::Queue" - }, - "MyQueuePolicy6BBEDDAC": { - "Type": "AWS::SQS::QueuePolicy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sqs:SendMessage", - "Condition": { - "ArnEquals": { - "aws:SourceArn": { - "Ref": "MyTopic86869434" - } - } - }, - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com" + "MyTopicMyQueueSubscription3245B11E": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "MyQueueE6CA6235", + "Arn" + ] }, - "Resource": { - "Fn::GetAtt": [ - "MyQueueE6CA6235", - "Arn" - ] + "Protocol": "sqs", + "TopicArn": { + "Ref": "MyTopic86869434" } } - ], - "Version": "2012-10-17" }, - "Queues": [ - { - "Ref": "MyQueueE6CA6235" - } - ] - } - }, - "MyLambdaServiceRole4539ECB6": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" + "MyTopicMyFuncSubscriptionEAF54A3F": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "MyFunc8A243A2C", + "Arn" + ] + }, + "Protocol": "lambda", + "TopicArn": { + "Ref": "MyTopic86869434" } } - ], - "Version": "2012-10-17" }, - "ManagedPolicyArns": [ - { "Fn::Join": ["", ["arn:", {"Ref": "AWS::Partition"}, ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]]} - ] - } - }, - "MyLambdaCCE802FB": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "exports.handler = (_event, _context, callback) => callback()" + "MyQueueE6CA6235": { + "Type": "AWS::SQS::Queue" }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "MyLambdaServiceRole4539ECB6", - "Arn" - ] + "MyQueuePolicy6BBEDDAC": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "MyQueueE6CA6235", + "Arn" + ] + }, + "Principal": { + "Service": "sns.amazonaws.com" + }, + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "MyTopic86869434" + } + } + } + } + ] + }, + "Queues": [ + { + "Ref": "MyQueueE6CA6235" + } + ] + } }, - "Runtime": "nodejs6.10", - "Timeout": 30 + "MyFuncServiceRole54065130": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } }, - "DependsOn": [ - "MyLambdaServiceRole4539ECB6" - ] - }, - "MyLambdaMyTopic96470869": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyLambdaCCE802FB" + "MyFunc8A243A2C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function(e, c, cb) { return cb() }" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFuncServiceRole54065130", + "Arn" + ] + }, + "Runtime": "nodejs6.10" + }, + "DependsOn": [ + "MyFuncServiceRole54065130" + ] }, - "Principal": "sns.amazonaws.com", - "SourceArn": { - "Ref": "MyTopic86869434" - } + "MyFuncMyTopicC77D8FAB": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunc8A243A2C" + }, + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Ref": "MyTopic86869434" + } + } } } - } }); test.done(); diff --git a/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.expected.json b/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.expected.json index 8d71bb8dc6bac..e4b82cebcf85c 100644 --- a/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.expected.json +++ b/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.expected.json @@ -110,8 +110,7 @@ "Ref": "AWS::StackName" } } - }, - "Timeout": 30 + } }, "DependsOn": [ "MyFunctionServiceRole3C357FF2", diff --git a/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts b/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts index 3144b6d32d42d..02a311f4e7d98 100644 --- a/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts +++ b/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts @@ -12,8 +12,10 @@ class TestStack extends cdk.Stack { super(parent, name); const queue = new sqs.Queue(this, 'MyQueue'); - const fn = new lambda.InlineJavaScriptFunction(this, 'MyFunction', { - handler: { fn: runtimeCode }, + const fn = new lambda.Function(this, 'MyFunction', { + code: lambda.Code.inline(`exports.handler = ${runtimeCode.toString()}`), + runtime: lambda.Runtime.NodeJS610, + handler: 'index.handler' }); // this line defines an AWS::SSM::Parameter resource with the