From 2919dafd18e28c4aee8dfbd916e1f194083b3a80 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 13 Nov 2020 15:29:40 +0000 Subject: [PATCH 01/19] add cfnspec update --- .../490_Lambda_Containers_patch.json | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 packages/@aws-cdk/cfnspec/spec-source/490_Lambda_Containers_patch.json diff --git a/packages/@aws-cdk/cfnspec/spec-source/490_Lambda_Containers_patch.json b/packages/@aws-cdk/cfnspec/spec-source/490_Lambda_Containers_patch.json new file mode 100644 index 0000000000000..781f3114b2b4b --- /dev/null +++ b/packages/@aws-cdk/cfnspec/spec-source/490_Lambda_Containers_patch.json @@ -0,0 +1,89 @@ +{ + "PropertyTypes": { + "patch": { + "description": "Properties for Lambda Function Container Image", + "operations": [ + { + "op": "add", + "path": "/AWS::Lambda::Function.ImageConfig", + "value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-imageconfig.html", + "Properties": { + "Command": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-imageconfig.html#cfn-lambda-function-imageconfig-command", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "EntryPoint": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-imageconfig.html#cfn-lambda-function-imageconfig-entrypoint", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "WorkingDirectory": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-imageconfig.html#cfn-lambda-function-imageconfig-workingdirectory", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + } + }, + { + "op": "add", + "path": "/AWS::Lambda::Function.Code/Properties/ImageUri", + "value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-imageuri", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + ] + } + }, + "ResourceTypes": { + "AWS::Lambda::Function": { + "patch": { + "description": "updates to Lambda Function to support Container Image", + "operations": [ + { + "op": "replace", + "path": "/Properties/Handler/Required", + "value": false + }, + { + "op": "replace", + "path": "/Properties/Runtime/Required", + "value": false + }, + { + "op": "add", + "path": "/Properties/ImageConfig", + "value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-imageconfig", + "Required": false, + "Type": "ImageConfig", + "UpdateType": "Mutable" + } + }, + { + "op": "add", + "path": "/Properties/PackageType", + "value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-packagetype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + ] + } + } + } +} \ No newline at end of file From a0ac96ac610efefa6dbbfe752e0319c7b3adb64b Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 18 Nov 2020 17:16:53 +0000 Subject: [PATCH 02/19] hash changes due to spec updates --- .../aws-lambda/test/function-hash.test.ts | 15 ++++++--------- .../test/integ.current-version.expected.json | 6 +++--- .../aws-lambda/test/lambda-version.test.ts | 4 ++-- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts b/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts index 4f80c886e88d2..2c2699493b29b 100644 --- a/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts @@ -37,7 +37,7 @@ describe('function hash', () => { }); expect(calculateFunctionHash(fn1)).toEqual(calculateFunctionHash(fn2)); - expect(calculateFunctionHash(fn1)).toEqual('aea5463dba236007afe91d2832b3c836'); + expect(calculateFunctionHash(fn1)).toEqual('d0cf052aead13ed6112b2e8a2f728908'); }); }); @@ -49,8 +49,8 @@ describe('function hash', () => { handler: 'index.handler', }); - expect(calculateFunctionHash(fn1)).not.toEqual('aea5463dba236007afe91d2832b3c836'); - expect(calculateFunctionHash(fn1)).toEqual('979b4a14c6f174c745cdbcd1036cf844'); + expect(calculateFunctionHash(fn1)).not.toEqual('d0cf052aead13ed6112b2e8a2f728908'); + expect(calculateFunctionHash(fn1)).toEqual('14b543c4b1a9e5425d92ef93557eb5bd'); }); test('environment variables impact hash', () => { @@ -74,8 +74,7 @@ describe('function hash', () => { }, }); - expect(calculateFunctionHash(fn1)).toEqual('d1bc824ac5022b7d62d8b12dbae6580c'); - expect(calculateFunctionHash(fn2)).toEqual('3b683d05465012b0aa9c4ff53b32f014'); + expect(calculateFunctionHash(fn1)).not.toEqual(calculateFunctionHash(fn2)); }); test('runtime impacts hash', () => { @@ -99,8 +98,7 @@ describe('function hash', () => { }, }); - expect(calculateFunctionHash(fn1)).toEqual('d1bc824ac5022b7d62d8b12dbae6580c'); - expect(calculateFunctionHash(fn2)).toEqual('0f168f0772463e8e547bb3800937e54d'); + expect(calculateFunctionHash(fn1)).not.toEqual(calculateFunctionHash(fn2)); }); test('inline code change impacts the hash', () => { @@ -118,8 +116,7 @@ describe('function hash', () => { handler: 'index.handler', }); - expect(calculateFunctionHash(fn1)).toEqual('ebf2e871fc6a3062e8bdcc5ebe16db3f'); - expect(calculateFunctionHash(fn2)).toEqual('ffedf6424a18a594a513129dc97bf53c'); + expect(calculateFunctionHash(fn1)).not.toEqual(calculateFunctionHash(fn2)); }); describe('impact of env variables order on hash', () => { diff --git a/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json index bf269c8f6d555..e5ec0cb98a373 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json @@ -85,7 +85,7 @@ "MyLambdaServiceRole4539ECB6" ] }, - "MyLambdaCurrentVersionE7A382CC721de083c6b4b6360a9c534b79eb610e": { + "MyLambdaCurrentVersionE7A382CC22cdb4104d8a97928c23315ce885c22d": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -103,7 +103,7 @@ }, "Qualifier": { "Fn::GetAtt": [ - "MyLambdaCurrentVersionE7A382CC721de083c6b4b6360a9c534b79eb610e", + "MyLambdaCurrentVersionE7A382CC22cdb4104d8a97928c23315ce885c22d", "Version" ] }, @@ -118,7 +118,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaCurrentVersionE7A382CC721de083c6b4b6360a9c534b79eb610e", + "MyLambdaCurrentVersionE7A382CC22cdb4104d8a97928c23315ce885c22d", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/test/lambda-version.test.ts b/packages/@aws-cdk/aws-lambda/test/lambda-version.test.ts index 20b040a135a7b..f38ab3cd99d59 100644 --- a/packages/@aws-cdk/aws-lambda/test/lambda-version.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/lambda-version.test.ts @@ -123,7 +123,7 @@ describe('lambda version', () => { }, FunctionVersion: { 'Fn::GetAtt': [ - 'FnCurrentVersion17A89ABBab5c765f3c55e4e61583b51b00a95742', + 'FnCurrentVersion17A89ABB734f8396c6bf253af280ea9c5b22ae59', 'Version', ], }, @@ -142,7 +142,7 @@ describe('lambda version', () => { const version = fn.currentVersion; // THEN - expect(stack.resolve(version.edgeArn)).toEqual({ Ref: 'FnCurrentVersion17A89ABB19ed45993ff69fd011ae9fd4ab6e2005' }); + expect(stack.resolve(version.edgeArn)).toEqual({ Ref: 'FnCurrentVersion17A89ABB80486b870700fd98d45df0d591d19e82' }); }); test('edgeArn throws with $LATEST', () => { From e54577dad11d61f55e5caf64c50ee5faed2a95db Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Mon, 16 Nov 2020 15:17:16 +0000 Subject: [PATCH 03/19] basic functionality and some tests --- packages/@aws-cdk/aws-lambda/lib/code.ts | 96 ++++++++++++++++++- packages/@aws-cdk/aws-lambda/lib/function.ts | 58 ++++++++--- packages/@aws-cdk/aws-lambda/package.json | 14 +-- .../test/docker-lambda-handler/Dockerfile | 8 ++ .../@aws-cdk/aws-lambda/test/function.test.ts | 92 ++++++++++++++++++ 5 files changed, 241 insertions(+), 27 deletions(-) create mode 100644 packages/@aws-cdk/aws-lambda/test/docker-lambda-handler/Dockerfile diff --git a/packages/@aws-cdk/aws-lambda/lib/code.ts b/packages/@aws-cdk/aws-lambda/lib/code.ts index 263e67f415bd4..1dfd0c0845c78 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code.ts @@ -1,10 +1,16 @@ +import * as ecr from '@aws-cdk/aws-ecr'; +import * as ecr_assets from '@aws-cdk/aws-ecr-assets'; +import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import * as s3_assets from '@aws-cdk/aws-s3-assets'; import * as cdk from '@aws-cdk/core'; +/** + * Represents the Lambda Handler Code. + */ export abstract class Code { /** - * @returns `LambdaS3Code` associated with the specified S3 object. + * Lambda handler code as an S3 object. * @param bucket The S3 bucket * @param key The object key * @param objectVersion Optional S3 object version @@ -14,6 +20,7 @@ export abstract class Code { } /** + * Lambda handler code as an S3 object. * @deprecated use `fromBucket` */ public static bucket(bucket: s3.IBucket, key: string, objectVersion?: string): S3Code { @@ -21,6 +28,7 @@ export abstract class Code { } /** + * Inline code for Lambda handler * @returns `LambdaInlineCode` with inline code. * @param code The actual handler code (limited to 4KiB) */ @@ -29,6 +37,7 @@ export abstract class Code { } /** + * Inline code for Lambda handler * @deprecated use `fromInline` */ public static inline(code: string): InlineCode { @@ -45,6 +54,7 @@ export abstract class Code { } /** + * Loads the function code from a local disk path. * @deprecated use `fromAsset` */ public static asset(path: string): AssetCode { @@ -62,12 +72,27 @@ export abstract class Code { } /** + * Creates a new Lambda source defined using CloudFormation parameters. * @deprecated use `fromCfnParameters` */ public static cfnParameters(props?: CfnParametersCodeProps): CfnParametersCode { return this.fromCfnParameters(props); } + /** + * Use an existing ECR image as the Lambda code. + */ + public static fromEcrRepository(repository: ecr.IRepository, tag: string = 'latest') { + return new EcrImage(repository, tag); + } + + /** + * Create an ECR image from the specified asset and bind it as the Lambda code. + */ + public static fromAssetImage(directory: string, props: AssetImageProps = {}) { + return new AssetImage(directory, props); + } + /** * Determines whether this Code is inline code or not. * @@ -95,16 +120,27 @@ export abstract class Code { } } +/** + * Result of binding `Code` into a `Function`. + */ export interface CodeConfig { /** - * The location of the code in S3 (mutually exclusive with `inlineCode`). + * The location of the code in S3 (mutually exclusive with `inlineCode` and `imageName`). + * @default - code is not an s3 location */ readonly s3Location?: s3.Location; /** - * Inline code (mutually exclusive with `s3Location`). + * Inline code (mutually exclusive with `s3Location` and `imageName`). + * @default - code is not an inline code */ readonly inlineCode?: string; + + /** + * URI to the Docker image (mutually exclusive with `s3Location` and `inlineCode`). + * @default - code is not an ECR container image + */ + readonly imageUri?: string; } /** @@ -313,3 +349,57 @@ export class CfnParametersCode extends Code { } } } + +/** + * Represents a Docker image in ECR that can be bound as Lambda Code. + */ +export class EcrImage extends Code { + public readonly isInline: boolean = false; + + constructor(private readonly repository: ecr.IRepository, private readonly tag: string) { + super(); + } + + public bind(_: cdk.Construct): CodeConfig { + this.repository.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['ecr:BatchGetImage', 'ecr:GetDownloadUrlForLayer'], + principals: [new iam.ServicePrincipal('lambda.amazonaws.com')], + })); + return { + imageUri: this.repository.repositoryUriForTag(this.tag), + }; + } +} + +/** + * Properties to initialize a new AssetImage + */ +export interface AssetImageProps extends ecr_assets.DockerImageAssetOptions { +} + +/** + * Represents an ECR image that will be constructed from the specified asset and can be bound as Lambda code. + */ +export class AssetImage extends Code { + public readonly isInline: boolean = false; + + constructor(private readonly directory: string, private readonly props: AssetImageProps) { + super(); + } + + public bind(scope: cdk.Construct): CodeConfig { + const asset = new ecr_assets.DockerImageAsset(scope, 'AssetImage', { + directory: this.directory, + ...this.props, + }); + + asset.repository.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['ecr:BatchGetImage', 'ecr:GetDownloadUrlForLayer'], + principals: [new iam.ServicePrincipal('lambda.amazonaws.com')], + })); + + return { + imageUri: asset.imageUri, + }; + } +} diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index d2000fd32d342..6e09559981987 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -288,8 +288,10 @@ export interface FunctionProps extends FunctionOptions { * 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 - not needed when using `Code.fromEcrImage()` or `Code.fromAssetImage()`. Mandatory for all other usages. */ - readonly runtime: Runtime; + readonly runtime?: Runtime; /** * The source code of your Lambda function. You can point to a file in an @@ -307,8 +309,10 @@ export interface FunctionProps extends FunctionOptions { * NOTE: If you specify your source code as inline text by specifying the * ZipFile property within the Code property, specify index.function_name as * the handler. + * + * @default - not needed when using `Code.fromEcrImage()` or `Code.fromAssetImage()`. Mandatory for all other usages. */ - readonly handler: string; + readonly handler?: string; } /** @@ -490,11 +494,6 @@ export class Function extends FunctionBase { */ public readonly role?: iam.IRole; - /** - * The runtime configured for this lambda. - */ - public readonly runtime: Runtime; - /** * The principal this Lambda Function is running as */ @@ -512,6 +511,7 @@ export class Function extends FunctionBase { private readonly layers: ILayerVersion[] = []; private _logGroup?: logs.ILogGroup; + private _runtime?: Runtime; /** * Environment variables for this function @@ -557,7 +557,7 @@ export class Function extends FunctionBase { } const code = props.code.bind(this); - verifyCodeConfig(code, props.runtime); + verifyCodeConfig(code, props); let profilingGroupEnvironmentVariables: { [key: string]: string } = {}; if (props.profilingGroup && props.profiling !== false) { @@ -598,11 +598,12 @@ export class Function extends FunctionBase { s3Key: code.s3Location && code.s3Location.objectKey, s3ObjectVersion: code.s3Location && code.s3Location.objectVersion, zipFile: code.inlineCode, + imageUri: code.imageUri, }, layers: Lazy.list({ produce: () => this.layers.map(layer => layer.layerVersionArn) }, { omitEmpty: true }), handler: props.handler, timeout: props.timeout && props.timeout.toSeconds(), - runtime: props.runtime.name, + runtime: props.runtime?.name, role: this.role.roleArn, // Uncached because calling '_checkEdgeCompatibility', which gets called in the resolve of another // Token, actually *modifies* the 'environment' map. @@ -624,7 +625,7 @@ export class Function extends FunctionBase { sep: ':', }); - this.runtime = props.runtime; + this._runtime = props.runtime; if (props.layers) { this.addLayers(...props.layers); @@ -676,6 +677,17 @@ export class Function extends FunctionBase { } } + /** + * The runtime configured for this lambda. + * Throws an error if no runtime is configured. + */ + public get runtime(): Runtime { + if (!this._runtime) { + throw new Error(); + } + return this._runtime; + } + /** * Adds an environment variable to this Lambda function. * If this is a ref to a Lambda function, this operation results in a no-op. @@ -966,14 +978,32 @@ function extractNameFromArn(arn: string) { return Fn.select(6, Fn.split(':', arn)); } -export function verifyCodeConfig(code: CodeConfig, runtime: Runtime) { +export function verifyCodeConfig(code: CodeConfig, props: FunctionProps) { // mutually exclusive - if ((!code.inlineCode && !code.s3Location) || (code.inlineCode && code.s3Location)) { + const codeType = [code.inlineCode, code.s3Location, code.imageUri]; + const atleastOne = codeType.reduce((agg, type) => { + if (type !== undefined) { + if (agg) { + throw new Error('lambda.Code must specify one of "inlineCode" or "s3Location" but not both'); + } + return true; + } + return agg || false; + }, false); + if (!atleastOne) { throw new Error('lambda.Code must specify one of "inlineCode" or "s3Location" but not both'); } + if ((code.inlineCode || code.s3Location) && props.handler === undefined) { + throw new Error('handler must be specified when using non-image asset for Lambda function'); + } + + if ((code.inlineCode || code.s3Location) && props.runtime === undefined) { + throw new Error('runtime must be specified when using non-image asset for Lambda function'); + } + // if this is inline code, check that the runtime supports - if (code.inlineCode && !runtime.supportsInlineCode) { - throw new Error(`Inline source not allowed for ${runtime.name}`); + if (code.inlineCode && !props.runtime!.supportsInlineCode) { + throw new Error(`Inline source not allowed for ${props.runtime!.name}`); } } diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index 63319fe238f8f..475f864d4c7b4 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -91,6 +91,8 @@ "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-codeguruprofiler": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-ecr": "0.0.0", + "@aws-cdk/aws-ecr-assets": "0.0.0", "@aws-cdk/aws-efs": "0.0.0", "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", @@ -108,6 +110,8 @@ "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-codeguruprofiler": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-ecr": "0.0.0", + "@aws-cdk/aws-ecr-assets": "0.0.0", "@aws-cdk/aws-efs": "0.0.0", "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", @@ -137,13 +141,6 @@ "docs-public-apis:@aws-cdk/aws-lambda.RuntimeFamily.NODEJS", "docs-public-apis:@aws-cdk/aws-lambda.Alias.lambda", "docs-public-apis:@aws-cdk/aws-lambda.Alias.fromAliasAttributes", - "docs-public-apis:@aws-cdk/aws-lambda.Code", - "docs-public-apis:@aws-cdk/aws-lambda.Code.asset", - "docs-public-apis:@aws-cdk/aws-lambda.Code.bucket", - "docs-public-apis:@aws-cdk/aws-lambda.Code.cfnParameters", - "docs-public-apis:@aws-cdk/aws-lambda.Code.fromBucket", - "docs-public-apis:@aws-cdk/aws-lambda.Code.fromInline", - "docs-public-apis:@aws-cdk/aws-lambda.Code.inline", "docs-public-apis:@aws-cdk/aws-lambda.Function.fromFunctionArn", "docs-public-apis:@aws-cdk/aws-lambda.FunctionBase", "docs-public-apis:@aws-cdk/aws-lambda.QualifiedFunctionBase", @@ -154,9 +151,6 @@ "docs-public-apis:@aws-cdk/aws-lambda.AliasAttributes", "docs-public-apis:@aws-cdk/aws-lambda.AliasAttributes.aliasName", "docs-public-apis:@aws-cdk/aws-lambda.AliasAttributes.aliasVersion", - "docs-public-apis:@aws-cdk/aws-lambda.CodeConfig", - "props-default-doc:@aws-cdk/aws-lambda.CodeConfig.inlineCode", - "props-default-doc:@aws-cdk/aws-lambda.CodeConfig.s3Location", "docs-public-apis:@aws-cdk/aws-lambda.EventSourceMappingOptions", "props-default-doc:@aws-cdk/aws-lambda.FunctionAttributes.role", "props-default-doc:@aws-cdk/aws-lambda.FunctionAttributes.securityGroup", diff --git a/packages/@aws-cdk/aws-lambda/test/docker-lambda-handler/Dockerfile b/packages/@aws-cdk/aws-lambda/test/docker-lambda-handler/Dockerfile new file mode 100644 index 0000000000000..18064bbe78ba1 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/docker-lambda-handler/Dockerfile @@ -0,0 +1,8 @@ +FROM 628053151772.dkr.ecr.sa-east-1.amazonaws.com/awslambda/nodejs12.x-runtime-internal:beta +ARG FUNCTION_DIR="/var/task" +# Create function directory +RUN mkdir -p ${FUNCTION_DIR} +# Copy handler function and package.json +COPY app.js ${FUNCTION_DIR} +# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile) +CMD [ "app.handler" ] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index 038167a460423..246ed1bf7e42c 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -1814,6 +1814,98 @@ describe('function', () => { }); }); }); + + describe('code config', () => { + class MyCode extends lambda.Code { + public readonly isInline: boolean; + constructor(private readonly config: lambda.CodeConfig) { + super(); + this.isInline = 'inlineCode' in config; + } + + public bind(_scope: constructs.Construct): lambda.CodeConfig { + return this.config; + } + } + + test('only one of inline, s3 or imageConfig are allowed', () => { + const stack = new cdk.Stack(); + + expect(() => new lambda.Function(stack, 'Fn1', { + code: new MyCode({}), + })).toThrow(/lambda.Code must specify one of/); + + expect(() => new lambda.Function(stack, 'Fn2', { + code: new MyCode({ inlineCode: 'foo', imageUri: 'bar' }), + })).toThrow(/lambda.Code must specify one of/); + + expect(() => new lambda.Function(stack, 'Fn3', { + code: new MyCode({ imageUri: 'baz', s3Location: { bucketName: 's3foo', objectKey: 's3bar' } }), + })).toThrow(/lambda.Code must specify one of/); + + expect(() => new lambda.Function(stack, 'Fn4', { + code: new MyCode({ inlineCode: 'baz', s3Location: { bucketName: 's3foo', objectKey: 's3bar' } }), + })).toThrow(/lambda.Code must specify one of/); + }); + + test('handler must be specified when non-container asset is specified', () => { + const stack = new cdk.Stack(); + + expect(() => new lambda.Function(stack, 'Fn1', { + code: lambda.Code.fromInline('foo'), + })).toThrow(/handler must be specified/); + + expect(() => new lambda.Function(stack, 'Fn2', { + code: lambda.Code.fromAsset('test/my-lambda-handler'), + })).toThrow(/handler must be specified/); + }); + + test('handler can be omitted for container assets', () => { + const stack = new cdk.Stack(); + + expect(() => new lambda.Function(stack, 'Fn', { + code: lambda.Code.fromAssetImage('test/docker-lambda-handler'), + })).not.toThrow(); + }); + + test('runtime must be specified when non-container asset is specified', () => { + const stack = new cdk.Stack(); + + expect(() => new lambda.Function(stack, 'Fn1', { + code: lambda.Code.fromInline('foo'), + handler: 'index.handler', + })).toThrow(/runtime must be specified/); + + expect(() => new lambda.Function(stack, 'Fn2', { + code: lambda.Code.fromAsset('test/my-lambda-handler'), + handler: 'index.handler', + })).toThrow(/runtime must be specified/); + }); + + test('runtime can be ommitted for container assets', () => { + const stack = new cdk.Stack(); + + expect(() => new lambda.Function(stack, 'Fn', { + code: lambda.Code.fromAssetImage('test/docker-lambda-handler'), + })).not.toThrow(); + }); + + test('imageUri is correctly configured', () => { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'Fn1', { + code: new MyCode({ + imageUri: 'ecr image uri', + }), + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Code: { + ImageUri: 'ecr image uri', + }, + }); + }); + }); }); function newTestLambda(scope: constructs.Construct) { From 4bb9fb6e145f4f325a56363eec26b7dc4c1f325a Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 18 Nov 2020 15:52:58 +0000 Subject: [PATCH 04/19] support cmd and entrypoint --- packages/@aws-cdk/aws-lambda/lib/code.ts | 92 +++++++++++++++++-- packages/@aws-cdk/aws-lambda/lib/function.ts | 13 ++- .../@aws-cdk/aws-lambda/test/function.test.ts | 38 +++++++- 3 files changed, 131 insertions(+), 12 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/code.ts b/packages/@aws-cdk/aws-lambda/lib/code.ts index 1dfd0c0845c78..249da47184245 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code.ts @@ -82,8 +82,8 @@ export abstract class Code { /** * Use an existing ECR image as the Lambda code. */ - public static fromEcrRepository(repository: ecr.IRepository, tag: string = 'latest') { - return new EcrImage(repository, tag); + public static fromEcrRepository(repository: ecr.IRepository, props?: EcrImageProps) { + return new EcrImage(repository, props); } /** @@ -140,7 +140,34 @@ export interface CodeConfig { * URI to the Docker image (mutually exclusive with `s3Location` and `inlineCode`). * @default - code is not an ECR container image */ - readonly imageUri?: string; + readonly image?: ImageConfig; +} + +/** + * Result of the bind when an ECR image is used. + */ +export interface ImageConfig { + /** + * URI to the Docker image (mutually exclusive with `s3Location` and `inlineCode`). + */ + readonly imageUri: string; + + /** + * Specify or override the CMD on the specified Docker image or Dockerfile. + * This needs to be in the 'exec form', viz., `[ 'executable', 'param1', 'param2' ]`. + * @see https://docs.docker.com/engine/reference/builder/#cmd + * @default - use the CMD specified in the docker image or Dockerfile. + */ + readonly cmd?: string[]; + + /** + * Specify or override the ENTRYPOINT on the specified Docker image or Dockerfile. + * An ENTRYPOINT allows you to configure a container that will run as an executable. + * This needs to be in the 'exec form', viz., `[ 'executable', 'param1', 'param2' ]`. + * @see https://docs.docker.com/engine/reference/builder/#entrypoint + * @default - use the ENTRYPOINT in the docker image or Dockerfile. + */ + readonly entrypoint?: string[]; } /** @@ -350,13 +377,42 @@ export class CfnParametersCode extends Code { } } +/** + * Properties to initialize a new EcrImage + */ +export interface EcrImageProps { + /** + * Specify or override the CMD on the specified Docker image or Dockerfile. + * This needs to be in the 'exec form', viz., `[ 'executable', 'param1', 'param2' ]`. + * @see https://docs.docker.com/engine/reference/builder/#cmd + * @default - use the CMD specified in the docker image or Dockerfile. + */ + readonly cmd?: string[]; + + /** + * Specify or override the ENTRYPOINT on the specified Docker image or Dockerfile. + * An ENTRYPOINT allows you to configure a container that will run as an executable. + * This needs to be in the 'exec form', viz., `[ 'executable', 'param1', 'param2' ]`. + * @see https://docs.docker.com/engine/reference/builder/#entrypoint + * @default - use the ENTRYPOINT in the docker image or Dockerfile. + */ + readonly entrypoint?: string[]; + + /** + * The image tag to use when pulling the image from ECR + * @see https://docs.aws.amazon.com/AmazonECR/latest/userguide/ecr-using-tags.html + * @default 'latest' + */ + readonly tag?: string; +} + /** * Represents a Docker image in ECR that can be bound as Lambda Code. */ export class EcrImage extends Code { public readonly isInline: boolean = false; - constructor(private readonly repository: ecr.IRepository, private readonly tag: string) { + constructor(private readonly repository: ecr.IRepository, private readonly props: EcrImageProps = {}) { super(); } @@ -366,7 +422,11 @@ export class EcrImage extends Code { principals: [new iam.ServicePrincipal('lambda.amazonaws.com')], })); return { - imageUri: this.repository.repositoryUriForTag(this.tag), + image: { + imageUri: this.repository.repositoryUriForTag(this.props?.tag ?? 'latest'), + cmd: this.props.cmd, + entrypoint: this.props.entrypoint, + }, }; } } @@ -375,6 +435,22 @@ export class EcrImage extends Code { * Properties to initialize a new AssetImage */ export interface AssetImageProps extends ecr_assets.DockerImageAssetOptions { + /** + * Specify or override the CMD on the specified Docker image or Dockerfile. + * This needs to be in the 'exec form', viz., `[ 'executable', 'param1', 'param2' ]`. + * @see https://docs.docker.com/engine/reference/builder/#cmd + * @default - use the CMD specified in the docker image or Dockerfile. + */ + readonly cmd?: string[]; + + /** + * Specify or override the ENTRYPOINT on the specified Docker image or Dockerfile. + * An ENTRYPOINT allows you to configure a container that will run as an executable. + * This needs to be in the 'exec form', viz., `[ 'executable', 'param1', 'param2' ]`. + * @see https://docs.docker.com/engine/reference/builder/#entrypoint + * @default - use the ENTRYPOINT in the docker image or Dockerfile. + */ + readonly entrypoint?: string[]; } /** @@ -399,7 +475,11 @@ export class AssetImage extends Code { })); return { - imageUri: asset.imageUri, + image: { + imageUri: asset.imageUri, + entrypoint: this.props.entrypoint, + cmd: this.props.cmd, + }, }; } } diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 6e09559981987..9495c33e78c6d 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -598,7 +598,7 @@ export class Function extends FunctionBase { s3Key: code.s3Location && code.s3Location.objectKey, s3ObjectVersion: code.s3Location && code.s3Location.objectVersion, zipFile: code.inlineCode, - imageUri: code.imageUri, + imageUri: code.image?.imageUri, }, layers: Lazy.list({ produce: () => this.layers.map(layer => layer.layerVersionArn) }, { omitEmpty: true }), handler: props.handler, @@ -613,6 +613,10 @@ export class Function extends FunctionBase { deadLetterConfig: this.buildDeadLetterConfig(this.deadLetterQueue), tracingConfig: this.buildTracingConfig(props), reservedConcurrentExecutions: props.reservedConcurrentExecutions, + imageConfig: undefinedIfNoKeys({ + command: code.image?.cmd, + entryPoint: code.image?.entrypoint, + }), }); resource.node.addDependency(this.role); @@ -980,7 +984,7 @@ function extractNameFromArn(arn: string) { export function verifyCodeConfig(code: CodeConfig, props: FunctionProps) { // mutually exclusive - const codeType = [code.inlineCode, code.s3Location, code.imageUri]; + const codeType = [code.inlineCode, code.s3Location, code.image]; const atleastOne = codeType.reduce((agg, type) => { if (type !== undefined) { if (agg) { @@ -1007,3 +1011,8 @@ export function verifyCodeConfig(code: CodeConfig, props: FunctionProps) { throw new Error(`Inline source not allowed for ${props.runtime!.name}`); } } + +function undefinedIfNoKeys(struct: A): A | undefined { + const allUndefined = Object.values(struct).every(val => val === undefined); + return allUndefined ? undefined : struct; +} diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index 246ed1bf7e42c..bd66d5dab785c 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -1836,11 +1836,17 @@ describe('function', () => { })).toThrow(/lambda.Code must specify one of/); expect(() => new lambda.Function(stack, 'Fn2', { - code: new MyCode({ inlineCode: 'foo', imageUri: 'bar' }), + code: new MyCode({ + inlineCode: 'foo', + image: { imageUri: 'bar' }, + }), })).toThrow(/lambda.Code must specify one of/); expect(() => new lambda.Function(stack, 'Fn3', { - code: new MyCode({ imageUri: 'baz', s3Location: { bucketName: 's3foo', objectKey: 's3bar' } }), + code: new MyCode({ + image: { imageUri: 'baz' }, + s3Location: { bucketName: 's3foo', objectKey: 's3bar' }, + }), })).toThrow(/lambda.Code must specify one of/); expect(() => new lambda.Function(stack, 'Fn4', { @@ -1882,7 +1888,7 @@ describe('function', () => { })).toThrow(/runtime must be specified/); }); - test('runtime can be ommitted for container assets', () => { + test('runtime can be omitted for container assets', () => { const stack = new cdk.Stack(); expect(() => new lambda.Function(stack, 'Fn', { @@ -1895,7 +1901,9 @@ describe('function', () => { new lambda.Function(stack, 'Fn1', { code: new MyCode({ - imageUri: 'ecr image uri', + image: { + imageUri: 'ecr image uri', + }, }), }); @@ -1903,6 +1911,28 @@ describe('function', () => { Code: { ImageUri: 'ecr image uri', }, + ImageConfig: ABSENT, + }); + }); + + test('imageConfig is correctly configured', () => { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'Fn1', { + code: new MyCode({ + image: { + imageUri: 'ecr image uri', + cmd: ['cmd', 'param1'], + entrypoint: ['entrypoint', 'param2'], + }, + }), + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + ImageConfig: { + Command: ['cmd', 'param1'], + EntryPoint: ['entrypoint', 'param2'], + }, }); }); }); From ded2120fff3ec333284a43ed63616b9e1ede770d Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 18 Nov 2020 16:17:25 +0000 Subject: [PATCH 05/19] integ test --- .../test/docker-lambda-handler/app.ts | 9 +++++ .../aws-lambda/test/integ.lambda.docker.ts | 34 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 packages/@aws-cdk/aws-lambda/test/docker-lambda-handler/app.ts create mode 100644 packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts diff --git a/packages/@aws-cdk/aws-lambda/test/docker-lambda-handler/app.ts b/packages/@aws-cdk/aws-lambda/test/docker-lambda-handler/app.ts new file mode 100644 index 0000000000000..eb31c33c616f4 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/docker-lambda-handler/app.ts @@ -0,0 +1,9 @@ +/* eslint-disable no-console */ + +exports.handler = async (event: any) => { + console.log('hello world'); + console.log(`event ${event}`); + return { + statusCode: 200, + }; +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts b/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts new file mode 100644 index 0000000000000..225d84b941947 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts @@ -0,0 +1,34 @@ +import * as path from 'path'; +import { App, Aspects, IAspect, Stack } from '@aws-cdk/core'; +import { IConstruct } from 'constructs'; +import { CfnFunction, Code, Function, Runtime } from '../lib'; + +class TestStack extends Stack { + constructor(scope: App, id: string) { + super(scope, id, { + env: { region: 'sa-east-1' }, // the feature is available only in sa-east-1 during private beta. Remove after launch. + }); + + new Function(this, 'MyLambda', { + code: Code.fromAssetImage(path.join(__dirname, 'docker-lambda-handler')), + handler: 'app.handler', + runtime: Runtime.NODEJS_12_X, + }); + } +} + +class PrivateResourceAspect implements IAspect { + visit(construct: IConstruct): void { + if (construct instanceof CfnFunction) { + (construct as any).cfnResourceType = 'AWSLambdaBeta::Lambda::Function'; + } + } +} + +const app = new App(); +const stack = new TestStack(app, 'lambda-ecr-docker'); + +// the feature is available as an CFN private resource during private beta. Remove after launch. +Aspects.of(stack).add(new PrivateResourceAspect()); + +app.synth(); \ No newline at end of file From fdbf4fc8af4fd7d10a7243f7b14891e9980a81bf Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 19 Nov 2020 16:59:47 +0000 Subject: [PATCH 06/19] PR feedback --- packages/@aws-cdk/aws-lambda/lib/code.ts | 60 ++++---- .../@aws-cdk/aws-lambda/test/code.test.ts | 129 +++++++++++++++++- .../@aws-cdk/aws-lambda/test/function.test.ts | 4 +- .../aws-lambda/test/integ.lambda.docker.ts | 2 +- 4 files changed, 164 insertions(+), 31 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/code.ts b/packages/@aws-cdk/aws-lambda/lib/code.ts index 249da47184245..6013db0b6214c 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code.ts @@ -20,7 +20,7 @@ export abstract class Code { } /** - * Lambda handler code as an S3 object. + * DEPRECATED * @deprecated use `fromBucket` */ public static bucket(bucket: s3.IBucket, key: string, objectVersion?: string): S3Code { @@ -37,7 +37,7 @@ export abstract class Code { } /** - * Inline code for Lambda handler + * DEPRECATED * @deprecated use `fromInline` */ public static inline(code: string): InlineCode { @@ -54,7 +54,7 @@ export abstract class Code { } /** - * Loads the function code from a local disk path. + * DEPRECATED * @deprecated use `fromAsset` */ public static asset(path: string): AssetCode { @@ -81,16 +81,20 @@ export abstract class Code { /** * Use an existing ECR image as the Lambda code. + * @param repository the ECR repository that the image is in + * @param props properties to further configure the selected image */ - public static fromEcrRepository(repository: ecr.IRepository, props?: EcrImageProps) { - return new EcrImage(repository, props); + public static fromEcr(repository: ecr.IRepository, props?: EcrImageCodeProps) { + return new EcrImageCode(repository, props); } /** * Create an ECR image from the specified asset and bind it as the Lambda code. + * @param directory the directory from which the asset must be created + * @param props properties to further configure the selected image */ - public static fromAssetImage(directory: string, props: AssetImageProps = {}) { - return new AssetImage(directory, props); + public static fromImageAsset(directory: string, props: AssetImageCodeProps = {}) { + return new AssetImageCode(directory, props); } /** @@ -125,30 +129,30 @@ export abstract class Code { */ export interface CodeConfig { /** - * The location of the code in S3 (mutually exclusive with `inlineCode` and `imageName`). + * The location of the code in S3 (mutually exclusive with `inlineCode` and `image`). * @default - code is not an s3 location */ readonly s3Location?: s3.Location; /** - * Inline code (mutually exclusive with `s3Location` and `imageName`). + * Inline code (mutually exclusive with `s3Location` and `image`). * @default - code is not an inline code */ readonly inlineCode?: string; /** - * URI to the Docker image (mutually exclusive with `s3Location` and `inlineCode`). + * Docker image configuration (mutually exclusive with `s3Location` and `inlineCode`). * @default - code is not an ECR container image */ - readonly image?: ImageConfig; + readonly image?: CodeImageConfig; } /** * Result of the bind when an ECR image is used. */ -export interface ImageConfig { +export interface CodeImageConfig { /** - * URI to the Docker image (mutually exclusive with `s3Location` and `inlineCode`). + * URI to the Docker image. */ readonly imageUri: string; @@ -380,7 +384,7 @@ export class CfnParametersCode extends Code { /** * Properties to initialize a new EcrImage */ -export interface EcrImageProps { +export interface EcrImageCodeProps { /** * Specify or override the CMD on the specified Docker image or Dockerfile. * This needs to be in the 'exec form', viz., `[ 'executable', 'param1', 'param2' ]`. @@ -409,18 +413,16 @@ export interface EcrImageProps { /** * Represents a Docker image in ECR that can be bound as Lambda Code. */ -export class EcrImage extends Code { +export class EcrImageCode extends Code { public readonly isInline: boolean = false; - constructor(private readonly repository: ecr.IRepository, private readonly props: EcrImageProps = {}) { + constructor(private readonly repository: ecr.IRepository, private readonly props: EcrImageCodeProps = {}) { super(); } public bind(_: cdk.Construct): CodeConfig { - this.repository.addToResourcePolicy(new iam.PolicyStatement({ - actions: ['ecr:BatchGetImage', 'ecr:GetDownloadUrlForLayer'], - principals: [new iam.ServicePrincipal('lambda.amazonaws.com')], - })); + grantLambdaToEcr(this.repository); + return { image: { imageUri: this.repository.repositoryUriForTag(this.props?.tag ?? 'latest'), @@ -434,7 +436,7 @@ export class EcrImage extends Code { /** * Properties to initialize a new AssetImage */ -export interface AssetImageProps extends ecr_assets.DockerImageAssetOptions { +export interface AssetImageCodeProps extends ecr_assets.DockerImageAssetOptions { /** * Specify or override the CMD on the specified Docker image or Dockerfile. * This needs to be in the 'exec form', viz., `[ 'executable', 'param1', 'param2' ]`. @@ -456,10 +458,10 @@ export interface AssetImageProps extends ecr_assets.DockerImageAssetOptions { /** * Represents an ECR image that will be constructed from the specified asset and can be bound as Lambda code. */ -export class AssetImage extends Code { +export class AssetImageCode extends Code { public readonly isInline: boolean = false; - constructor(private readonly directory: string, private readonly props: AssetImageProps) { + constructor(private readonly directory: string, private readonly props: AssetImageCodeProps) { super(); } @@ -469,10 +471,7 @@ export class AssetImage extends Code { ...this.props, }); - asset.repository.addToResourcePolicy(new iam.PolicyStatement({ - actions: ['ecr:BatchGetImage', 'ecr:GetDownloadUrlForLayer'], - principals: [new iam.ServicePrincipal('lambda.amazonaws.com')], - })); + grantLambdaToEcr(asset.repository); return { image: { @@ -483,3 +482,10 @@ export class AssetImage extends Code { }; } } + +function grantLambdaToEcr(repository: ecr.IRepository) { + repository.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['ecr:BatchGetImage', 'ecr:GetDownloadUrlForLayer'], + principals: [new iam.ServicePrincipal('lambda.amazonaws.com')], + })); +} diff --git a/packages/@aws-cdk/aws-lambda/test/code.test.ts b/packages/@aws-cdk/aws-lambda/test/code.test.ts index b00cf74562c3e..6fb13e7eac0fe 100644 --- a/packages/@aws-cdk/aws-lambda/test/code.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/code.test.ts @@ -1,6 +1,7 @@ import '@aws-cdk/assert/jest'; import * as path from 'path'; -import { ResourcePart } from '@aws-cdk/assert'; +import { ABSENT, ResourcePart } from '@aws-cdk/assert'; +import * as ecr from '@aws-cdk/aws-ecr'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import * as lambda from '../lib'; @@ -169,6 +170,132 @@ describe('code', () => { expect(overrides['ObjectKeyParam']).toEqual('SomeObjectKey'); }); }); + + describe('lambda.Code.fromEcr', () => { + test('repository uri is correctly identified', () => { + // given + const stack = new cdk.Stack(); + const repo = new ecr.Repository(stack, 'Repo'); + + // when + new lambda.Function(stack, 'Fn', { + code: lambda.Code.fromEcr(repo), + }); + + // then + expect(stack).toHaveResource('AWS::Lambda::Function', { + Code: { + ImageUri: stack.resolve(repo.repositoryUriForTag('latest')), + }, + ImageConfig: ABSENT, + }); + }); + + test('props are correctly resolved', () => { + // given + const stack = new cdk.Stack(); + const repo = new ecr.Repository(stack, 'Repo'); + + // when + new lambda.Function(stack, 'Fn', { + code: lambda.Code.fromEcr(repo, { + cmd: ['cmd', 'param1'], + entrypoint: ['entrypoint', 'param2'], + tag: 'mytag', + }), + }); + + // then + expect(stack).toHaveResource('AWS::Lambda::Function', { + Code: { + ImageUri: stack.resolve(repo.repositoryUriForTag('mytag')), + }, + ImageConfig: { + Command: ['cmd', 'param1'], + EntryPoint: ['entrypoint', 'param2'], + }, + }); + }); + + test('permission grants', () => { + // given + const stack = new cdk.Stack(); + const repo = new ecr.Repository(stack, 'Repo'); + + // when + new lambda.Function(stack, 'Fn', { + code: lambda.Code.fromEcr(repo), + }); + + // then + expect(stack).toHaveResourceLike('AWS::ECR::Repository', { + RepositoryPolicyText: { + Statement: [ + { + Action: [ + 'ecr:BatchGetImage', + 'ecr:GetDownloadUrlForLayer', + ], + Effect: 'Allow', + Principal: { + Service: 'lambda.amazonaws.com', + }, + }, + ], + }, + }); + }); + }); + + describe('lambda.Code.fromImageAsset', () => { + test('repository uri is correctly identified', () => { + // given + const stack = new cdk.Stack(); + + // when + new lambda.Function(stack, 'Fn', { + code: lambda.Code.fromImageAsset(path.join(__dirname, 'docker-lambda-handler')), + }); + + // then + expect(stack).toHaveResource('AWS::Lambda::Function', { + Code: { + ImageUri: { + 'Fn::Join': ['', [ + { Ref: 'AWS::AccountId' }, + '.dkr.ecr.', + { Ref: 'AWS::Region' }, + '.', + { Ref: 'AWS::URLSuffix' }, + '/aws-cdk/assets:97d0c2189f553d878ffdeaacd2867d499e63c515db303993a3e33df197a31506', + ]], + }, + }, + ImageConfig: ABSENT, + }); + }); + + test('props are correctly resolved', () => { + // given + const stack = new cdk.Stack(); + + // when + new lambda.Function(stack, 'Fn', { + code: lambda.Code.fromImageAsset(path.join(__dirname, 'docker-lambda-handler'), { + cmd: ['cmd', 'param1'], + entrypoint: ['entrypoint', 'param2'], + }), + }); + + // then + expect(stack).toHaveResource('AWS::Lambda::Function', { + ImageConfig: { + Command: ['cmd', 'param1'], + EntryPoint: ['entrypoint', 'param2'], + }, + }); + }); + }); }); function defineFunction(code: lambda.Code, runtime: lambda.Runtime = lambda.Runtime.NODEJS_10_X) { diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index bd66d5dab785c..e65878438f0b7 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -1870,7 +1870,7 @@ describe('function', () => { const stack = new cdk.Stack(); expect(() => new lambda.Function(stack, 'Fn', { - code: lambda.Code.fromAssetImage('test/docker-lambda-handler'), + code: lambda.Code.fromImageAsset('test/docker-lambda-handler'), })).not.toThrow(); }); @@ -1892,7 +1892,7 @@ describe('function', () => { const stack = new cdk.Stack(); expect(() => new lambda.Function(stack, 'Fn', { - code: lambda.Code.fromAssetImage('test/docker-lambda-handler'), + code: lambda.Code.fromImageAsset('test/docker-lambda-handler'), })).not.toThrow(); }); diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts b/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts index 225d84b941947..4f99b0186bd08 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts @@ -10,7 +10,7 @@ class TestStack extends Stack { }); new Function(this, 'MyLambda', { - code: Code.fromAssetImage(path.join(__dirname, 'docker-lambda-handler')), + code: Code.fromImageAsset(path.join(__dirname, 'docker-lambda-handler')), handler: 'app.handler', runtime: Runtime.NODEJS_12_X, }); From 1b1d54bac8a3061360700d8a43a1463357dd1e2f Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 19 Nov 2020 17:09:07 +0000 Subject: [PATCH 07/19] use grantPull --- packages/@aws-cdk/aws-lambda/lib/code.ts | 11 ++--------- packages/@aws-cdk/aws-lambda/test/code.test.ts | 3 ++- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/code.ts b/packages/@aws-cdk/aws-lambda/lib/code.ts index 6013db0b6214c..f91bf951db033 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code.ts @@ -421,7 +421,7 @@ export class EcrImageCode extends Code { } public bind(_: cdk.Construct): CodeConfig { - grantLambdaToEcr(this.repository); + this.repository.grantPull(new iam.ServicePrincipal('lambda.amazonaws.com')); return { image: { @@ -471,7 +471,7 @@ export class AssetImageCode extends Code { ...this.props, }); - grantLambdaToEcr(asset.repository); + asset.repository.grantPull(new iam.ServicePrincipal('lambda.amazonaws.com')); return { image: { @@ -482,10 +482,3 @@ export class AssetImageCode extends Code { }; } } - -function grantLambdaToEcr(repository: ecr.IRepository) { - repository.addToResourcePolicy(new iam.PolicyStatement({ - actions: ['ecr:BatchGetImage', 'ecr:GetDownloadUrlForLayer'], - principals: [new iam.ServicePrincipal('lambda.amazonaws.com')], - })); -} diff --git a/packages/@aws-cdk/aws-lambda/test/code.test.ts b/packages/@aws-cdk/aws-lambda/test/code.test.ts index 6fb13e7eac0fe..d30b167c1208a 100644 --- a/packages/@aws-cdk/aws-lambda/test/code.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/code.test.ts @@ -233,8 +233,9 @@ describe('code', () => { Statement: [ { Action: [ - 'ecr:BatchGetImage', + 'ecr:BatchCheckLayerAvailability', 'ecr:GetDownloadUrlForLayer', + 'ecr:BatchGetImage', ], Effect: 'Allow', Principal: { From fd65c6763c271f5edc11544a1758c878b954d1c9 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 19 Nov 2020 17:16:07 +0000 Subject: [PATCH 08/19] more comments --- packages/@aws-cdk/aws-lambda/lib/code.ts | 3 +-- packages/@aws-cdk/aws-lambda/lib/function.ts | 16 ++++------------ .../@aws-cdk/aws-lambda/test/function.test.ts | 8 ++++---- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/code.ts b/packages/@aws-cdk/aws-lambda/lib/code.ts index f91bf951db033..d6534f067c76d 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code.ts @@ -403,8 +403,7 @@ export interface EcrImageCodeProps { readonly entrypoint?: string[]; /** - * The image tag to use when pulling the image from ECR - * @see https://docs.aws.amazon.com/AmazonECR/latest/userguide/ecr-using-tags.html + * The image tag to use when pulling the image from ECR. * @default 'latest' */ readonly tag?: string; diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 9495c33e78c6d..860252141b9b0 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -687,7 +687,7 @@ export class Function extends FunctionBase { */ public get runtime(): Runtime { if (!this._runtime) { - throw new Error(); + throw new Error('no runtime defined for this lambda function'); } return this._runtime; } @@ -985,17 +985,9 @@ function extractNameFromArn(arn: string) { export function verifyCodeConfig(code: CodeConfig, props: FunctionProps) { // mutually exclusive const codeType = [code.inlineCode, code.s3Location, code.image]; - const atleastOne = codeType.reduce((agg, type) => { - if (type !== undefined) { - if (agg) { - throw new Error('lambda.Code must specify one of "inlineCode" or "s3Location" but not both'); - } - return true; - } - return agg || false; - }, false); - if (!atleastOne) { - throw new Error('lambda.Code must specify one of "inlineCode" or "s3Location" but not both'); + + if (codeType.filter(x => !!x).length !== 1) { + throw new Error('lambda.Code must specify exactly one of: "inlineCode", "s3Location", or "image"'); } if ((code.inlineCode || code.s3Location) && props.handler === undefined) { diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index e65878438f0b7..514a3cf26533b 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -1833,25 +1833,25 @@ describe('function', () => { expect(() => new lambda.Function(stack, 'Fn1', { code: new MyCode({}), - })).toThrow(/lambda.Code must specify one of/); + })).toThrow(/lambda.Code must specify exactly one of/); expect(() => new lambda.Function(stack, 'Fn2', { code: new MyCode({ inlineCode: 'foo', image: { imageUri: 'bar' }, }), - })).toThrow(/lambda.Code must specify one of/); + })).toThrow(/lambda.Code must specify exactly one of/); expect(() => new lambda.Function(stack, 'Fn3', { code: new MyCode({ image: { imageUri: 'baz' }, s3Location: { bucketName: 's3foo', objectKey: 's3bar' }, }), - })).toThrow(/lambda.Code must specify one of/); + })).toThrow(/lambda.Code must specify exactly one of/); expect(() => new lambda.Function(stack, 'Fn4', { code: new MyCode({ inlineCode: 'baz', s3Location: { bucketName: 's3foo', objectKey: 's3bar' } }), - })).toThrow(/lambda.Code must specify one of/); + })).toThrow(/lambda.Code must specify exactly one of/); }); test('handler must be specified when non-container asset is specified', () => { From 4b4247dd265eff764ed57b145f4afae927b4a248 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 20 Nov 2020 11:25:28 +0000 Subject: [PATCH 09/19] more feedback addressed --- packages/@aws-cdk/aws-lambda/README.md | 21 ++++++ packages/@aws-cdk/aws-lambda/lib/function.ts | 29 +++----- packages/@aws-cdk/aws-lambda/lib/handler.ts | 16 +++++ .../@aws-cdk/aws-lambda/lib/image-function.ts | 71 +++++++++++++++++++ packages/@aws-cdk/aws-lambda/lib/index.ts | 2 + packages/@aws-cdk/aws-lambda/lib/runtime.ts | 5 ++ 6 files changed, 126 insertions(+), 18 deletions(-) create mode 100644 packages/@aws-cdk/aws-lambda/lib/handler.ts create mode 100644 packages/@aws-cdk/aws-lambda/lib/image-function.ts diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index 142508626a591..e66885d367abf 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -50,6 +50,27 @@ to our CDK project directory. This is especially important when we want to share this construct through a library. Different programming languages will have different techniques for bundling resources into libraries. +### Docker Images + +Lambda functions allow specifying their handlers within docker images. The docker +image can be an image from ECR or a local asset that the CDK will package and load +into ECR. + +The following creates an 'ECRFunction' that uses an existing Docker image and a +'AssetFunction' that uses a local folder with a Dockerfile in it. + +```ts +const repo = new ecr.Repository(...); + +new lambda.DockerImageFunction(this, 'ECRFunction', { + code: DockerImageCode.fromEcr(repo), +}); + +new lambda.DockerImageFunction(this, 'AssetFunction', { + code: DockerImageCode.fromImageAsset(path.join(__dirname, 'docker-handler')), +}); +``` + ### Execution Role Lambda functions assume an IAM role during execution. In CDK by default, Lambda diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 860252141b9b0..15ae84a273a87 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -289,9 +289,9 @@ export interface FunctionProps extends FunctionOptions { * For valid values, see the Runtime property in the AWS Lambda Developer * Guide. * - * @default - not needed when using `Code.fromEcrImage()` or `Code.fromAssetImage()`. Mandatory for all other usages. + * Use `Runtime.FROM_IMAGE` when using `Code.fromEcr()` or `Code.fromAssetImage()`. */ - readonly runtime?: Runtime; + readonly runtime: Runtime; /** * The source code of your Lambda function. You can point to a file in an @@ -306,13 +306,13 @@ export interface FunctionProps extends FunctionOptions { * namespaces and other qualifiers, depending on the runtime. * For more information, see https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-features.html#gettingstarted-features-programmingmodel. * + * Use `Handler.FROM_IMAGE` when defining a function from a Docker image. + * * NOTE: If you specify your source code as inline text by specifying the * ZipFile property within the Code property, specify index.function_name as * the handler. - * - * @default - not needed when using `Code.fromEcrImage()` or `Code.fromAssetImage()`. Mandatory for all other usages. */ - readonly handler?: string; + readonly handler: string; } /** @@ -494,6 +494,11 @@ export class Function extends FunctionBase { */ public readonly role?: iam.IRole; + /** + * The runtime configured for this lambda. + */ + public readonly runtime: Runtime; + /** * The principal this Lambda Function is running as */ @@ -511,7 +516,6 @@ export class Function extends FunctionBase { private readonly layers: ILayerVersion[] = []; private _logGroup?: logs.ILogGroup; - private _runtime?: Runtime; /** * Environment variables for this function @@ -629,7 +633,7 @@ export class Function extends FunctionBase { sep: ':', }); - this._runtime = props.runtime; + this.runtime = props.runtime; if (props.layers) { this.addLayers(...props.layers); @@ -681,17 +685,6 @@ export class Function extends FunctionBase { } } - /** - * The runtime configured for this lambda. - * Throws an error if no runtime is configured. - */ - public get runtime(): Runtime { - if (!this._runtime) { - throw new Error('no runtime defined for this lambda function'); - } - return this._runtime; - } - /** * Adds an environment variable to this Lambda function. * If this is a ref to a Lambda function, this operation results in a no-op. diff --git a/packages/@aws-cdk/aws-lambda/lib/handler.ts b/packages/@aws-cdk/aws-lambda/lib/handler.ts new file mode 100644 index 0000000000000..54a76f660ace4 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/lib/handler.ts @@ -0,0 +1,16 @@ +/** + * Lambda function handler + */ +export class Handler { + /** + * A special handler when the function handler is part of a Docker image. + */ + public static FROM_IMAGE = 'FROM_IMAGE'; + + /** + * Your own handler string + */ + public static custom(handler: string) { + return handler; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/lib/image-function.ts b/packages/@aws-cdk/aws-lambda/lib/image-function.ts new file mode 100644 index 0000000000000..e053b1daf0d73 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/lib/image-function.ts @@ -0,0 +1,71 @@ +import * as ecr from '@aws-cdk/aws-ecr'; +import { Construct } from 'constructs'; +import { AssetImageCode, AssetImageCodeProps, EcrImageCode, EcrImageCodeProps, Code } from './code'; +import { Function, FunctionOptions } from './function'; +import { Handler } from './handler'; +import { Runtime } from './runtime'; + +/** + * Properties to configure a new DockerImageFunction construct. + */ +export interface DockerImageFunctionProps extends FunctionOptions { + /** + * The source code of your Lambda function. You can point to a file in an + * Amazon Simple Storage Service (Amazon S3) bucket or specify your source + * code as inline text. + */ + readonly code: DockerImageCode; +} + +/** + * Code property for the DockerImageFunction construct + */ +export abstract class DockerImageCode { + /** + * Use an existing ECR image as the Lambda code. + * @param repository the ECR repository that the image is in + * @param props properties to further configure the selected image + * @experimental + */ + public static fromEcr(repository: ecr.IRepository, props?: EcrImageCodeProps): DockerImageCode { + return { + bind() { + return new EcrImageCode(repository, props); + }, + }; + } + + /** + * Create an ECR image from the specified asset and bind it as the Lambda code. + * @param directory the directory from which the asset must be created + * @param props properties to further configure the selected image + * @experimental + */ + public static fromImageAsset(directory: string, props: AssetImageCodeProps = {}): DockerImageCode { + return { + bind() { + return new AssetImageCode(directory, props); + }, + }; + } + + /** + * Produce a `Code` instance from this `DockerImageCode`. + * @internal + */ + public abstract bind(): Code; +} + +/** + * Create a lambda function where the handler is a docker image + */ +export class DockerImageFunction extends Function { + constructor(scope: Construct, id: string, props: DockerImageFunctionProps) { + super(scope, id, { + ...props, + handler: Handler.FROM_IMAGE, + runtime: Runtime.FROM_IMAGE, + code: props.code.bind(), + }); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/lib/index.ts b/packages/@aws-cdk/aws-lambda/lib/index.ts index 3581a40cdf535..1ba17427c5210 100644 --- a/packages/@aws-cdk/aws-lambda/lib/index.ts +++ b/packages/@aws-cdk/aws-lambda/lib/index.ts @@ -2,6 +2,8 @@ export * from './alias'; export * from './dlq'; export * from './function-base'; export * from './function'; +export * from './handler'; +export * from './image-function'; export * from './layers'; export * from './permission'; export * from './runtime'; diff --git a/packages/@aws-cdk/aws-lambda/lib/runtime.ts b/packages/@aws-cdk/aws-lambda/lib/runtime.ts index 56ffbecd19b3b..1930641f45f04 100644 --- a/packages/@aws-cdk/aws-lambda/lib/runtime.ts +++ b/packages/@aws-cdk/aws-lambda/lib/runtime.ts @@ -162,6 +162,11 @@ export class Runtime { */ public static readonly PROVIDED_AL2 = new Runtime('provided.al2', RuntimeFamily.OTHER); + /** + * A special runtime entry to be used when function is using a docker image. + */ + public static readonly FROM_IMAGE = new Runtime('FROM_IMAGE'); + /** * The name of this runtime, as expected by the Lambda resource. */ From 9a0c787a830bd39a625ed53292973462c5099d4f Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 20 Nov 2020 15:10:34 +0000 Subject: [PATCH 10/19] fix up --- packages/@aws-cdk/aws-lambda/lib/function.ts | 9 ++-- packages/@aws-cdk/aws-lambda/lib/handler.ts | 2 +- .../@aws-cdk/aws-lambda/lib/image-function.ts | 8 +-- .../@aws-cdk/aws-lambda/test/code.test.ts | 10 ++++ .../@aws-cdk/aws-lambda/test/function.test.ts | 54 ++++++++++--------- 5 files changed, 48 insertions(+), 35 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 15ae84a273a87..85d766f5bd910 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -12,6 +12,7 @@ import { IEventSource } from './event-source'; import { FileSystem } from './filesystem'; import { FunctionAttributes, FunctionBase, IFunction } from './function-base'; import { calculateFunctionHash, trimFromStart } from './function-hash'; +import { Handler } from './handler'; import { Version, VersionOptions } from './lambda-version'; import { CfnFunction } from './lambda.generated'; import { ILayerVersion } from './layers'; @@ -983,12 +984,12 @@ export function verifyCodeConfig(code: CodeConfig, props: FunctionProps) { throw new Error('lambda.Code must specify exactly one of: "inlineCode", "s3Location", or "image"'); } - if ((code.inlineCode || code.s3Location) && props.handler === undefined) { - throw new Error('handler must be specified when using non-image asset for Lambda function'); + if (code.image && props.handler !== Handler.FROM_IMAGE) { + throw new Error('handler must be set to `Handler.FROM_IMAGE` when using image asset for Lambda function'); } - if ((code.inlineCode || code.s3Location) && props.runtime === undefined) { - throw new Error('runtime must be specified when using non-image asset for Lambda function'); + if (code.image && props.runtime !== Runtime.FROM_IMAGE) { + throw new Error('runtime must be set to `Runtime.FROM_IMAGE` when using image asset for Lambda function'); } // if this is inline code, check that the runtime supports diff --git a/packages/@aws-cdk/aws-lambda/lib/handler.ts b/packages/@aws-cdk/aws-lambda/lib/handler.ts index 54a76f660ace4..5c672df57430e 100644 --- a/packages/@aws-cdk/aws-lambda/lib/handler.ts +++ b/packages/@aws-cdk/aws-lambda/lib/handler.ts @@ -5,7 +5,7 @@ export class Handler { /** * A special handler when the function handler is part of a Docker image. */ - public static FROM_IMAGE = 'FROM_IMAGE'; + public static readonly FROM_IMAGE = 'FROM_IMAGE'; /** * Your own handler string diff --git a/packages/@aws-cdk/aws-lambda/lib/image-function.ts b/packages/@aws-cdk/aws-lambda/lib/image-function.ts index e053b1daf0d73..b53b6216894bf 100644 --- a/packages/@aws-cdk/aws-lambda/lib/image-function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/image-function.ts @@ -29,7 +29,7 @@ export abstract class DockerImageCode { */ public static fromEcr(repository: ecr.IRepository, props?: EcrImageCodeProps): DockerImageCode { return { - bind() { + _bind() { return new EcrImageCode(repository, props); }, }; @@ -43,7 +43,7 @@ export abstract class DockerImageCode { */ public static fromImageAsset(directory: string, props: AssetImageCodeProps = {}): DockerImageCode { return { - bind() { + _bind() { return new AssetImageCode(directory, props); }, }; @@ -53,7 +53,7 @@ export abstract class DockerImageCode { * Produce a `Code` instance from this `DockerImageCode`. * @internal */ - public abstract bind(): Code; + public abstract _bind(): Code; } /** @@ -65,7 +65,7 @@ export class DockerImageFunction extends Function { ...props, handler: Handler.FROM_IMAGE, runtime: Runtime.FROM_IMAGE, - code: props.code.bind(), + code: props.code._bind(), }); } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/code.test.ts b/packages/@aws-cdk/aws-lambda/test/code.test.ts index d30b167c1208a..719f33d720222 100644 --- a/packages/@aws-cdk/aws-lambda/test/code.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/code.test.ts @@ -180,6 +180,8 @@ describe('code', () => { // when new lambda.Function(stack, 'Fn', { code: lambda.Code.fromEcr(repo), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, }); // then @@ -203,6 +205,8 @@ describe('code', () => { entrypoint: ['entrypoint', 'param2'], tag: 'mytag', }), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, }); // then @@ -225,6 +229,8 @@ describe('code', () => { // when new lambda.Function(stack, 'Fn', { code: lambda.Code.fromEcr(repo), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, }); // then @@ -256,6 +262,8 @@ describe('code', () => { // when new lambda.Function(stack, 'Fn', { code: lambda.Code.fromImageAsset(path.join(__dirname, 'docker-lambda-handler')), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, }); // then @@ -286,6 +294,8 @@ describe('code', () => { cmd: ['cmd', 'param1'], entrypoint: ['entrypoint', 'param2'], }), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, }); // then diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index 514a3cf26533b..0d30816fceb97 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -1833,6 +1833,8 @@ describe('function', () => { expect(() => new lambda.Function(stack, 'Fn1', { code: new MyCode({}), + handler: 'index.handler', + runtime: lambda.Runtime.GO_1_X, })).toThrow(/lambda.Code must specify exactly one of/); expect(() => new lambda.Function(stack, 'Fn2', { @@ -1840,6 +1842,8 @@ describe('function', () => { inlineCode: 'foo', image: { imageUri: 'bar' }, }), + handler: 'index.handler', + runtime: lambda.Runtime.GO_1_X, })).toThrow(/lambda.Code must specify exactly one of/); expect(() => new lambda.Function(stack, 'Fn3', { @@ -1847,53 +1851,47 @@ describe('function', () => { image: { imageUri: 'baz' }, s3Location: { bucketName: 's3foo', objectKey: 's3bar' }, }), + handler: 'index.handler', + runtime: lambda.Runtime.GO_1_X, })).toThrow(/lambda.Code must specify exactly one of/); expect(() => new lambda.Function(stack, 'Fn4', { code: new MyCode({ inlineCode: 'baz', s3Location: { bucketName: 's3foo', objectKey: 's3bar' } }), + handler: 'index.handler', + runtime: lambda.Runtime.GO_1_X, })).toThrow(/lambda.Code must specify exactly one of/); }); - test('handler must be specified when non-container asset is specified', () => { + test('handler must be FROM_IMAGE when image asset is specified', () => { const stack = new cdk.Stack(); expect(() => new lambda.Function(stack, 'Fn1', { - code: lambda.Code.fromInline('foo'), - })).toThrow(/handler must be specified/); - - expect(() => new lambda.Function(stack, 'Fn2', { - code: lambda.Code.fromAsset('test/my-lambda-handler'), - })).toThrow(/handler must be specified/); - }); - - test('handler can be omitted for container assets', () => { - const stack = new cdk.Stack(); - - expect(() => new lambda.Function(stack, 'Fn', { code: lambda.Code.fromImageAsset('test/docker-lambda-handler'), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, })).not.toThrow(); - }); - - test('runtime must be specified when non-container asset is specified', () => { - const stack = new cdk.Stack(); - - expect(() => new lambda.Function(stack, 'Fn1', { - code: lambda.Code.fromInline('foo'), - handler: 'index.handler', - })).toThrow(/runtime must be specified/); expect(() => new lambda.Function(stack, 'Fn2', { - code: lambda.Code.fromAsset('test/my-lambda-handler'), + code: lambda.Code.fromImageAsset('test/docker-lambda-handler'), handler: 'index.handler', - })).toThrow(/runtime must be specified/); + runtime: lambda.Runtime.FROM_IMAGE, + })).toThrow(/handler must be set.*FROM_IMAGE/); }); - test('runtime can be omitted for container assets', () => { + test('runtime must be FROM_IMAGE when image asset is specified', () => { const stack = new cdk.Stack(); - expect(() => new lambda.Function(stack, 'Fn', { + expect(() => new lambda.Function(stack, 'Fn1', { code: lambda.Code.fromImageAsset('test/docker-lambda-handler'), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, })).not.toThrow(); + + expect(() => new lambda.Function(stack, 'Fn2', { + code: lambda.Code.fromImageAsset('test/docker-lambda-handler'), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.GO_1_X, + })).toThrow(/runtime must be set.*FROM_IMAGE/); }); test('imageUri is correctly configured', () => { @@ -1905,6 +1903,8 @@ describe('function', () => { imageUri: 'ecr image uri', }, }), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, }); expect(stack).toHaveResource('AWS::Lambda::Function', { @@ -1926,6 +1926,8 @@ describe('function', () => { entrypoint: ['entrypoint', 'param2'], }, }), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, }); expect(stack).toHaveResource('AWS::Lambda::Function', { From 907e4bbb2e10c2aa229e7f2e0402bdd0a9a50b8b Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 20 Nov 2020 15:23:21 +0000 Subject: [PATCH 11/19] more fix ups --- packages/@aws-cdk/aws-lambda/lib/function.ts | 4 ++-- .../@aws-cdk/aws-lambda/test/function.test.ts | 15 +++++++++++++++ .../aws-lambda/test/integ.lambda.docker.ts | 8 +++----- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 85d766f5bd910..4cb5bbadea83f 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -606,9 +606,9 @@ export class Function extends FunctionBase { imageUri: code.image?.imageUri, }, layers: Lazy.list({ produce: () => this.layers.map(layer => layer.layerVersionArn) }, { omitEmpty: true }), - handler: props.handler, + handler: props.handler === Handler.FROM_IMAGE ? undefined : props.handler, timeout: props.timeout && props.timeout.toSeconds(), - runtime: props.runtime?.name, + runtime: props.runtime === Runtime.FROM_IMAGE ? undefined : props.runtime?.name, role: this.role.roleArn, // Uncached because calling '_checkEdgeCompatibility', which gets called in the resolve of another // Token, actually *modifies* the 'environment' map. diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index 0d30816fceb97..625cd88fee604 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -759,6 +759,21 @@ describe('function', () => { }, ResourcePart.CompleteDefinition); }); + test('runtime and handler set to FROM_IMAGE are set to undefined in CloudFormation', () => { + const stack = new cdk.Stack(); + + new lambda.Function(stack, 'MyLambda', { + code: lambda.Code.fromImageAsset(path.join(__dirname, 'docker-lambda-handler')), + handler: lambda.Handler.FROM_IMAGE, + runtime: lambda.Runtime.FROM_IMAGE, + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Runtime: ABSENT, + Handler: ABSENT, + }); + }); + describe('grantInvoke', () => { test('adds iam:InvokeFunction', () => { diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts b/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts index 4f99b0186bd08..65ee50b43ab98 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import { App, Aspects, IAspect, Stack } from '@aws-cdk/core'; import { IConstruct } from 'constructs'; -import { CfnFunction, Code, Function, Runtime } from '../lib'; +import { CfnFunction, DockerImageCode, DockerImageFunction } from '../lib'; class TestStack extends Stack { constructor(scope: App, id: string) { @@ -9,10 +9,8 @@ class TestStack extends Stack { env: { region: 'sa-east-1' }, // the feature is available only in sa-east-1 during private beta. Remove after launch. }); - new Function(this, 'MyLambda', { - code: Code.fromImageAsset(path.join(__dirname, 'docker-lambda-handler')), - handler: 'app.handler', - runtime: Runtime.NODEJS_12_X, + new DockerImageFunction(this, 'MyLambda', { + code: DockerImageCode.fromImageAsset(path.join(__dirname, 'docker-lambda-handler')), }); } } From 728284c2f333e5ac9eca93e70f771963b455c106 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 20 Nov 2020 15:50:58 +0000 Subject: [PATCH 12/19] more PR feedback --- packages/@aws-cdk/aws-lambda/README.md | 6 +- packages/@aws-cdk/aws-lambda/lib/code.ts | 4 +- packages/@aws-cdk/aws-lambda/lib/function.ts | 11 +-- .../test/docker-lambda-handler/app.ts | 2 +- .../@aws-cdk/aws-lambda/test/function.test.ts | 5 +- .../test/integ.lambda.docker.expected.json | 67 +++++++++++++++++++ 6 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.expected.json diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index e66885d367abf..0efa06c8ed9f4 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -56,11 +56,11 @@ Lambda functions allow specifying their handlers within docker images. The docke image can be an image from ECR or a local asset that the CDK will package and load into ECR. -The following creates an 'ECRFunction' that uses an existing Docker image and a -'AssetFunction' that uses a local folder with a Dockerfile in it. +The following 'ECRFunction' construct uses an existing Docker image and the +'AssetFunction' construct uses a local folder with a Dockerfile in it. ```ts -const repo = new ecr.Repository(...); +const repo = new ecr.Repository(this, 'Repository'); new lambda.DockerImageFunction(this, 'ECRFunction', { code: DockerImageCode.fromEcr(repo), diff --git a/packages/@aws-cdk/aws-lambda/lib/code.ts b/packages/@aws-cdk/aws-lambda/lib/code.ts index d6534f067c76d..ea07ba63eed49 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code.ts @@ -136,7 +136,7 @@ export interface CodeConfig { /** * Inline code (mutually exclusive with `s3Location` and `image`). - * @default - code is not an inline code + * @default - code is not inline code */ readonly inlineCode?: string; @@ -382,7 +382,7 @@ export class CfnParametersCode extends Code { } /** - * Properties to initialize a new EcrImage + * Properties to initialize a new EcrImageCode */ export interface EcrImageCodeProps { /** diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 4cb5bbadea83f..2358d24f4ab7a 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -290,7 +290,7 @@ export interface FunctionProps extends FunctionOptions { * For valid values, see the Runtime property in the AWS Lambda Developer * Guide. * - * Use `Runtime.FROM_IMAGE` when using `Code.fromEcr()` or `Code.fromAssetImage()`. + * Use `Runtime.FROM_IMAGE` when when defining a function from a Docker image. */ readonly runtime: Runtime; @@ -608,6 +608,7 @@ export class Function extends FunctionBase { layers: Lazy.list({ produce: () => this.layers.map(layer => layer.layerVersionArn) }, { omitEmpty: true }), handler: props.handler === Handler.FROM_IMAGE ? undefined : props.handler, timeout: props.timeout && props.timeout.toSeconds(), + packageType: props.runtime === Runtime.FROM_IMAGE ? 'Image' : undefined, runtime: props.runtime === Runtime.FROM_IMAGE ? undefined : props.runtime?.name, role: this.role.roleArn, // Uncached because calling '_checkEdgeCompatibility', which gets called in the resolve of another @@ -984,12 +985,12 @@ export function verifyCodeConfig(code: CodeConfig, props: FunctionProps) { throw new Error('lambda.Code must specify exactly one of: "inlineCode", "s3Location", or "image"'); } - if (code.image && props.handler !== Handler.FROM_IMAGE) { - throw new Error('handler must be set to `Handler.FROM_IMAGE` when using image asset for Lambda function'); + if (!!code.image === (props.handler !== Handler.FROM_IMAGE)) { + throw new Error('handler must be `Handler.FROM_IMAGE` when using image asset for Lambda function'); } - if (code.image && props.runtime !== Runtime.FROM_IMAGE) { - throw new Error('runtime must be set to `Runtime.FROM_IMAGE` when using image asset for Lambda function'); + if (!!code.image === (props.runtime !== Runtime.FROM_IMAGE)) { + throw new Error('runtime must be `Runtime.FROM_IMAGE` when using image asset for Lambda function'); } // if this is inline code, check that the runtime supports diff --git a/packages/@aws-cdk/aws-lambda/test/docker-lambda-handler/app.ts b/packages/@aws-cdk/aws-lambda/test/docker-lambda-handler/app.ts index eb31c33c616f4..99155b53d5bf7 100644 --- a/packages/@aws-cdk/aws-lambda/test/docker-lambda-handler/app.ts +++ b/packages/@aws-cdk/aws-lambda/test/docker-lambda-handler/app.ts @@ -2,7 +2,7 @@ exports.handler = async (event: any) => { console.log('hello world'); - console.log(`event ${event}`); + console.log(`event ${JSON.stringify(event)}`); return { statusCode: 200, }; diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index 625cd88fee604..4d3f01db48d37 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -771,6 +771,7 @@ describe('function', () => { expect(stack).toHaveResource('AWS::Lambda::Function', { Runtime: ABSENT, Handler: ABSENT, + PackageType: 'Image', }); }); @@ -1890,7 +1891,7 @@ describe('function', () => { code: lambda.Code.fromImageAsset('test/docker-lambda-handler'), handler: 'index.handler', runtime: lambda.Runtime.FROM_IMAGE, - })).toThrow(/handler must be set.*FROM_IMAGE/); + })).toThrow(/handler must be.*FROM_IMAGE/); }); test('runtime must be FROM_IMAGE when image asset is specified', () => { @@ -1906,7 +1907,7 @@ describe('function', () => { code: lambda.Code.fromImageAsset('test/docker-lambda-handler'), handler: lambda.Handler.FROM_IMAGE, runtime: lambda.Runtime.GO_1_X, - })).toThrow(/runtime must be set.*FROM_IMAGE/); + })).toThrow(/runtime must be.*FROM_IMAGE/); }); test('imageUri is correctly configured', () => { diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.expected.json new file mode 100644 index 0000000000000..4f68c37615d18 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.expected.json @@ -0,0 +1,67 @@ +{ + "Resources": { + "MyLambdaServiceRole4539ECB6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "MyLambdaCCE802FB": { + "Type": "AWSLambdaBeta::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::AccountId" + }, + ".dkr.ecr.sa-east-1.", + { + "Ref": "AWS::URLSuffix" + }, + "/aws-cdk/assets:e8a944aeb0a08ba4811503d9c138e514b112dadca84daa5b4608e4a0fb80a0c9" + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "MyLambdaServiceRole4539ECB6", + "Arn" + ] + }, + "PackageType": "Image" + }, + "DependsOn": [ + "MyLambdaServiceRole4539ECB6" + ] + } + } +} \ No newline at end of file From c537733d3f5dcb2acdcc64afe913b6eb89f67935 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 20 Nov 2020 16:07:40 +0000 Subject: [PATCH 13/19] update README --- packages/@aws-cdk/aws-lambda/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index 0efa06c8ed9f4..2c73ec2e06b58 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -60,6 +60,7 @@ The following 'ECRFunction' construct uses an existing Docker image and the 'AssetFunction' construct uses a local folder with a Dockerfile in it. ```ts +import * as ecr from '@aws-cdk/aws-ecr'; const repo = new ecr.Repository(this, 'Repository'); new lambda.DockerImageFunction(this, 'ECRFunction', { From 123dd93a1e23d3a9e78eacd228f46e1522df0730 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Mon, 23 Nov 2020 14:57:37 +0000 Subject: [PATCH 14/19] PR feedback --- packages/@aws-cdk/aws-lambda/README.md | 18 +++++++++++------- packages/@aws-cdk/aws-lambda/lib/code.ts | 6 +++--- packages/@aws-cdk/aws-lambda/test/code.test.ts | 10 +++++----- .../@aws-cdk/aws-lambda/test/function.test.ts | 10 +++++----- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index 2c73ec2e06b58..ea1d256c976bc 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -56,19 +56,23 @@ Lambda functions allow specifying their handlers within docker images. The docke image can be an image from ECR or a local asset that the CDK will package and load into ECR. -The following 'ECRFunction' construct uses an existing Docker image and the -'AssetFunction' construct uses a local folder with a Dockerfile in it. +The following `DockerImageFunction` construct uses a local folder with a +Dockerfile as the asset that will be used as the function handler. + +```ts +new lambda.DockerImageFunction(this, 'AssetFunction', { + code: DockerImageCode.fromAssetImage(path.join(__dirname, 'docker-handler')), +}); +``` + +You can also specify an image that already exists in ECR as the function handler. ```ts import * as ecr from '@aws-cdk/aws-ecr'; const repo = new ecr.Repository(this, 'Repository'); new lambda.DockerImageFunction(this, 'ECRFunction', { - code: DockerImageCode.fromEcr(repo), -}); - -new lambda.DockerImageFunction(this, 'AssetFunction', { - code: DockerImageCode.fromImageAsset(path.join(__dirname, 'docker-handler')), + code: DockerImageCode.fromEcrImage(repo), }); ``` diff --git a/packages/@aws-cdk/aws-lambda/lib/code.ts b/packages/@aws-cdk/aws-lambda/lib/code.ts index ea07ba63eed49..fef200a9a6c9c 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code.ts @@ -72,7 +72,7 @@ export abstract class Code { } /** - * Creates a new Lambda source defined using CloudFormation parameters. + * DEPRECATED * @deprecated use `fromCfnParameters` */ public static cfnParameters(props?: CfnParametersCodeProps): CfnParametersCode { @@ -84,7 +84,7 @@ export abstract class Code { * @param repository the ECR repository that the image is in * @param props properties to further configure the selected image */ - public static fromEcr(repository: ecr.IRepository, props?: EcrImageCodeProps) { + public static fromEcrImage(repository: ecr.IRepository, props?: EcrImageCodeProps) { return new EcrImageCode(repository, props); } @@ -93,7 +93,7 @@ export abstract class Code { * @param directory the directory from which the asset must be created * @param props properties to further configure the selected image */ - public static fromImageAsset(directory: string, props: AssetImageCodeProps = {}) { + public static fromAssetImage(directory: string, props: AssetImageCodeProps = {}) { return new AssetImageCode(directory, props); } diff --git a/packages/@aws-cdk/aws-lambda/test/code.test.ts b/packages/@aws-cdk/aws-lambda/test/code.test.ts index 719f33d720222..ea8b0c5c6067e 100644 --- a/packages/@aws-cdk/aws-lambda/test/code.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/code.test.ts @@ -179,7 +179,7 @@ describe('code', () => { // when new lambda.Function(stack, 'Fn', { - code: lambda.Code.fromEcr(repo), + code: lambda.Code.fromEcrImage(repo), handler: lambda.Handler.FROM_IMAGE, runtime: lambda.Runtime.FROM_IMAGE, }); @@ -200,7 +200,7 @@ describe('code', () => { // when new lambda.Function(stack, 'Fn', { - code: lambda.Code.fromEcr(repo, { + code: lambda.Code.fromEcrImage(repo, { cmd: ['cmd', 'param1'], entrypoint: ['entrypoint', 'param2'], tag: 'mytag', @@ -228,7 +228,7 @@ describe('code', () => { // when new lambda.Function(stack, 'Fn', { - code: lambda.Code.fromEcr(repo), + code: lambda.Code.fromEcrImage(repo), handler: lambda.Handler.FROM_IMAGE, runtime: lambda.Runtime.FROM_IMAGE, }); @@ -261,7 +261,7 @@ describe('code', () => { // when new lambda.Function(stack, 'Fn', { - code: lambda.Code.fromImageAsset(path.join(__dirname, 'docker-lambda-handler')), + code: lambda.Code.fromAssetImage(path.join(__dirname, 'docker-lambda-handler')), handler: lambda.Handler.FROM_IMAGE, runtime: lambda.Runtime.FROM_IMAGE, }); @@ -290,7 +290,7 @@ describe('code', () => { // when new lambda.Function(stack, 'Fn', { - code: lambda.Code.fromImageAsset(path.join(__dirname, 'docker-lambda-handler'), { + code: lambda.Code.fromAssetImage(path.join(__dirname, 'docker-lambda-handler'), { cmd: ['cmd', 'param1'], entrypoint: ['entrypoint', 'param2'], }), diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index 4d3f01db48d37..3a10ebab4247b 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -763,7 +763,7 @@ describe('function', () => { const stack = new cdk.Stack(); new lambda.Function(stack, 'MyLambda', { - code: lambda.Code.fromImageAsset(path.join(__dirname, 'docker-lambda-handler')), + code: lambda.Code.fromAssetImage(path.join(__dirname, 'docker-lambda-handler')), handler: lambda.Handler.FROM_IMAGE, runtime: lambda.Runtime.FROM_IMAGE, }); @@ -1882,13 +1882,13 @@ describe('function', () => { const stack = new cdk.Stack(); expect(() => new lambda.Function(stack, 'Fn1', { - code: lambda.Code.fromImageAsset('test/docker-lambda-handler'), + code: lambda.Code.fromAssetImage('test/docker-lambda-handler'), handler: lambda.Handler.FROM_IMAGE, runtime: lambda.Runtime.FROM_IMAGE, })).not.toThrow(); expect(() => new lambda.Function(stack, 'Fn2', { - code: lambda.Code.fromImageAsset('test/docker-lambda-handler'), + code: lambda.Code.fromAssetImage('test/docker-lambda-handler'), handler: 'index.handler', runtime: lambda.Runtime.FROM_IMAGE, })).toThrow(/handler must be.*FROM_IMAGE/); @@ -1898,13 +1898,13 @@ describe('function', () => { const stack = new cdk.Stack(); expect(() => new lambda.Function(stack, 'Fn1', { - code: lambda.Code.fromImageAsset('test/docker-lambda-handler'), + code: lambda.Code.fromAssetImage('test/docker-lambda-handler'), handler: lambda.Handler.FROM_IMAGE, runtime: lambda.Runtime.FROM_IMAGE, })).not.toThrow(); expect(() => new lambda.Function(stack, 'Fn2', { - code: lambda.Code.fromImageAsset('test/docker-lambda-handler'), + code: lambda.Code.fromAssetImage('test/docker-lambda-handler'), handler: lambda.Handler.FROM_IMAGE, runtime: lambda.Runtime.GO_1_X, })).toThrow(/runtime must be.*FROM_IMAGE/); From 9d5c8b4a7e8517744adc5eecbbc2aefac545b4d4 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Tue, 24 Nov 2020 11:44:22 +0000 Subject: [PATCH 15/19] PR feedback --- packages/@aws-cdk/aws-lambda/lib/function.ts | 2 +- packages/@aws-cdk/aws-lambda/lib/handler.ts | 7 +------ packages/@aws-cdk/aws-lambda/test/code.test.ts | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 2358d24f4ab7a..6103ef700506d 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -994,7 +994,7 @@ export function verifyCodeConfig(code: CodeConfig, props: FunctionProps) { } // if this is inline code, check that the runtime supports - if (code.inlineCode && !props.runtime!.supportsInlineCode) { + if (code.inlineCode && !props.runtime.supportsInlineCode) { throw new Error(`Inline source not allowed for ${props.runtime!.name}`); } } diff --git a/packages/@aws-cdk/aws-lambda/lib/handler.ts b/packages/@aws-cdk/aws-lambda/lib/handler.ts index 5c672df57430e..7288345f5fad5 100644 --- a/packages/@aws-cdk/aws-lambda/lib/handler.ts +++ b/packages/@aws-cdk/aws-lambda/lib/handler.ts @@ -7,10 +7,5 @@ export class Handler { */ public static readonly FROM_IMAGE = 'FROM_IMAGE'; - /** - * Your own handler string - */ - public static custom(handler: string) { - return handler; - } + private constructor() {} } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/code.test.ts b/packages/@aws-cdk/aws-lambda/test/code.test.ts index ea8b0c5c6067e..a822ba698697e 100644 --- a/packages/@aws-cdk/aws-lambda/test/code.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/code.test.ts @@ -276,7 +276,7 @@ describe('code', () => { { Ref: 'AWS::Region' }, '.', { Ref: 'AWS::URLSuffix' }, - '/aws-cdk/assets:97d0c2189f553d878ffdeaacd2867d499e63c515db303993a3e33df197a31506', + '/aws-cdk/assets:0874c7dfd254e95f5181cc7fa643e4abf010f68e5717e373b6e635b49a115b2b', ]], }, }, From 87c4aa7cc699d67aa08af4fa77781fd0d6370249 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 25 Nov 2020 10:32:14 +0000 Subject: [PATCH 16/19] adjust cloudformation test --- .../test/integ.nested-stack.expected.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.expected.json b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.expected.json index 2e467682966ba..8626fa9281ca5 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.expected.json +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.expected.json @@ -158,7 +158,7 @@ }, "/", { - "Ref": "AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aS3Bucket1DDC9C52" + "Ref": "AssetParameters91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4S3Bucket06F505B9" }, "/", { @@ -168,7 +168,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aS3VersionKey2B4F31C1" + "Ref": "AssetParameters91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4S3VersionKey06BEDE93" } ] } @@ -181,7 +181,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aS3VersionKey2B4F31C1" + "Ref": "AssetParameters91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4S3VersionKey06BEDE93" } ] } @@ -254,17 +254,17 @@ } }, "Parameters": { - "AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aS3Bucket1DDC9C52": { + "AssetParameters91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4S3Bucket06F505B9": { "Type": "String", - "Description": "S3 bucket for asset \"4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1a\"" + "Description": "S3 bucket for asset \"91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4\"" }, - "AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aS3VersionKey2B4F31C1": { + "AssetParameters91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4S3VersionKey06BEDE93": { "Type": "String", - "Description": "S3 key for asset version \"4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1a\"" + "Description": "S3 key for asset version \"91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4\"" }, - "AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aArtifactHash3AA59378": { + "AssetParameters91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4ArtifactHash407EE1C2": { "Type": "String", - "Description": "Artifact hash for asset \"4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1a\"" + "Description": "Artifact hash for asset \"91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4\"" }, "AssetParameters0d0404717d8867c09534f2cf382e8e24531ff64a968afa2efd7f071ad65a22dfS3BucketB322F951": { "Type": "String", From af4af1ef83f1d907c040ecec9d6dbdbd360c31e6 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 25 Nov 2020 11:00:06 +0000 Subject: [PATCH 17/19] fix cloudfront --- packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts | 6 +++--- .../test/integ.distribution-lambda.expected.json | 4 ++-- .../@aws-cdk/aws-cloudfront/test/web_distribution.test.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts index fe0e218e1d407..767723c5187b9 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts @@ -491,7 +491,7 @@ describe('with Lambda@Edge functions', () => { EventType: 'origin-request', IncludeBody: true, LambdaFunctionARN: { - Ref: 'FunctionCurrentVersion4E2B2261477a5ae8059bbaa7813f752292c0f65e', + Ref: 'FunctionCurrentVersion4E2B226158cf48714e97f91a64eeb1e484843d60', }, }, ], @@ -561,7 +561,7 @@ describe('with Lambda@Edge functions', () => { { EventType: 'viewer-request', LambdaFunctionARN: { - Ref: 'FunctionCurrentVersion4E2B2261477a5ae8059bbaa7813f752292c0f65e', + Ref: 'FunctionCurrentVersion4E2B226158cf48714e97f91a64eeb1e484843d60', }, }, ], @@ -667,7 +667,7 @@ describe('with Lambda@Edge functions', () => { { EventType: 'origin-request', LambdaFunctionARN: { - Ref: 'SingletonLambdasingletonforcloudfrontCurrentVersion0078406348a0962a52448a200cd0dbc0e22edb2a', + Ref: 'SingletonLambdasingletonforcloudfrontCurrentVersion007840636913e8978712eb4cf22c20e35d541bd5', }, }, ], diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json index fde47299f760e..14cb844dcd31e 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json @@ -57,7 +57,7 @@ "LambdaServiceRoleA8ED4D3B" ] }, - "LambdaCurrentVersionDF706F6A97fb843e9bd06fcd2bb15eeace80e13e": { + "LambdaCurrentVersionDF706F6Ad17005953cc06239800336982aeb19d9": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -76,7 +76,7 @@ { "EventType": "origin-request", "LambdaFunctionARN": { - "Ref": "LambdaCurrentVersionDF706F6A97fb843e9bd06fcd2bb15eeace80e13e" + "Ref": "LambdaCurrentVersionDF706F6Ad17005953cc06239800336982aeb19d9" } } ], diff --git a/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts index ea2fbf3f22295..242f8ef4fa662 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts @@ -460,7 +460,7 @@ nodeunitShim({ 'EventType': 'origin-request', 'IncludeBody': true, 'LambdaFunctionARN': { - 'Ref': 'LambdaCurrentVersionDF706F6A97fb843e9bd06fcd2bb15eeace80e13e', + 'Ref': 'LambdaCurrentVersionDF706F6Ad17005953cc06239800336982aeb19d9', }, }, ], From eb75ce5249cc4456b9417814976d92d990c4c881 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Thu, 26 Nov 2020 11:28:55 +0200 Subject: [PATCH 18/19] revert function hash changes The original CFN patch updated `Runtime` and `Handler` fields (changed optionality). Because of how json-patch works and the fact that currently our lambda function hash calculation is order-sensitive, it caused invalidation of all lambda hashes. To avoid that, we use a marker value to signal that the value should be removed by an escape hatch. This should be reverted once the new CFN spec is published. --- .../test/integ.nested-stack.expected.json | 18 +++++++++--------- .../aws-cloudfront/test/distribution.test.ts | 6 +++--- .../integ.distribution-lambda.expected.json | 4 ++-- .../test/web_distribution.test.ts | 2 +- packages/@aws-cdk/aws-lambda/lib/function.ts | 15 +++++++++++++-- .../aws-lambda/test/function-hash.test.ts | 15 +++++++++------ .../test/integ.current-version.expected.json | 6 +++--- .../aws-lambda/test/lambda-version.test.ts | 4 ++-- .../490_Lambda_Containers_patch.json | 10 ---------- 9 files changed, 42 insertions(+), 38 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.expected.json b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.expected.json index 8626fa9281ca5..2e467682966ba 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.expected.json +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.expected.json @@ -158,7 +158,7 @@ }, "/", { - "Ref": "AssetParameters91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4S3Bucket06F505B9" + "Ref": "AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aS3Bucket1DDC9C52" }, "/", { @@ -168,7 +168,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4S3VersionKey06BEDE93" + "Ref": "AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aS3VersionKey2B4F31C1" } ] } @@ -181,7 +181,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4S3VersionKey06BEDE93" + "Ref": "AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aS3VersionKey2B4F31C1" } ] } @@ -254,17 +254,17 @@ } }, "Parameters": { - "AssetParameters91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4S3Bucket06F505B9": { + "AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aS3Bucket1DDC9C52": { "Type": "String", - "Description": "S3 bucket for asset \"91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4\"" + "Description": "S3 bucket for asset \"4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1a\"" }, - "AssetParameters91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4S3VersionKey06BEDE93": { + "AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aS3VersionKey2B4F31C1": { "Type": "String", - "Description": "S3 key for asset version \"91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4\"" + "Description": "S3 key for asset version \"4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1a\"" }, - "AssetParameters91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4ArtifactHash407EE1C2": { + "AssetParameters4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1aArtifactHash3AA59378": { "Type": "String", - "Description": "Artifact hash for asset \"91169429f2b5b85501c7b1b9d7beeb80c9bb6f4891f4e600fcaf65a8817ce0f4\"" + "Description": "Artifact hash for asset \"4ed2bec8961a74942e0627883abee066300275e2c2b03fe650d313898fe68f1a\"" }, "AssetParameters0d0404717d8867c09534f2cf382e8e24531ff64a968afa2efd7f071ad65a22dfS3BucketB322F951": { "Type": "String", diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts index 767723c5187b9..fe0e218e1d407 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts @@ -491,7 +491,7 @@ describe('with Lambda@Edge functions', () => { EventType: 'origin-request', IncludeBody: true, LambdaFunctionARN: { - Ref: 'FunctionCurrentVersion4E2B226158cf48714e97f91a64eeb1e484843d60', + Ref: 'FunctionCurrentVersion4E2B2261477a5ae8059bbaa7813f752292c0f65e', }, }, ], @@ -561,7 +561,7 @@ describe('with Lambda@Edge functions', () => { { EventType: 'viewer-request', LambdaFunctionARN: { - Ref: 'FunctionCurrentVersion4E2B226158cf48714e97f91a64eeb1e484843d60', + Ref: 'FunctionCurrentVersion4E2B2261477a5ae8059bbaa7813f752292c0f65e', }, }, ], @@ -667,7 +667,7 @@ describe('with Lambda@Edge functions', () => { { EventType: 'origin-request', LambdaFunctionARN: { - Ref: 'SingletonLambdasingletonforcloudfrontCurrentVersion007840636913e8978712eb4cf22c20e35d541bd5', + Ref: 'SingletonLambdasingletonforcloudfrontCurrentVersion0078406348a0962a52448a200cd0dbc0e22edb2a', }, }, ], diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json index 14cb844dcd31e..fde47299f760e 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json @@ -57,7 +57,7 @@ "LambdaServiceRoleA8ED4D3B" ] }, - "LambdaCurrentVersionDF706F6Ad17005953cc06239800336982aeb19d9": { + "LambdaCurrentVersionDF706F6A97fb843e9bd06fcd2bb15eeace80e13e": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -76,7 +76,7 @@ { "EventType": "origin-request", "LambdaFunctionARN": { - "Ref": "LambdaCurrentVersionDF706F6Ad17005953cc06239800336982aeb19d9" + "Ref": "LambdaCurrentVersionDF706F6A97fb843e9bd06fcd2bb15eeace80e13e" } } ], diff --git a/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts index 242f8ef4fa662..ea2fbf3f22295 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts @@ -460,7 +460,7 @@ nodeunitShim({ 'EventType': 'origin-request', 'IncludeBody': true, 'LambdaFunctionARN': { - 'Ref': 'LambdaCurrentVersionDF706F6Ad17005953cc06239800336982aeb19d9', + 'Ref': 'LambdaCurrentVersionDF706F6A97fb843e9bd06fcd2bb15eeace80e13e', }, }, ], diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 6103ef700506d..d69a76c0639a5 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -595,6 +595,8 @@ export class Function extends FunctionBase { this.deadLetterQueue = this.buildDeadLetterQueue(props); + const UNDEFINED_MARKER = '$$$undefined'; + const resource: CfnFunction = new CfnFunction(this, 'Resource', { functionName: this.physicalName, description: props.description, @@ -606,10 +608,10 @@ export class Function extends FunctionBase { imageUri: code.image?.imageUri, }, layers: Lazy.list({ produce: () => this.layers.map(layer => layer.layerVersionArn) }, { omitEmpty: true }), - handler: props.handler === Handler.FROM_IMAGE ? undefined : props.handler, + handler: props.handler === Handler.FROM_IMAGE ? UNDEFINED_MARKER : props.handler, timeout: props.timeout && props.timeout.toSeconds(), packageType: props.runtime === Runtime.FROM_IMAGE ? 'Image' : undefined, - runtime: props.runtime === Runtime.FROM_IMAGE ? undefined : props.runtime?.name, + runtime: props.runtime === Runtime.FROM_IMAGE ? UNDEFINED_MARKER : props.runtime?.name, role: this.role.roleArn, // Uncached because calling '_checkEdgeCompatibility', which gets called in the resolve of another // Token, actually *modifies* the 'environment' map. @@ -625,6 +627,15 @@ export class Function extends FunctionBase { }), }); + // since patching the CFN spec to make Runtime and Handler optional causes a + // change in the order of the JSON keys, which results in a change of + // function hash (and invalidation of all lambda functions everywhere), we + // are using a marker to indicate this fields needs to be erased using an + // escape hatch. this should be fixed once the new spec is published and a + // patch is no longer needed. + if (resource.runtime === UNDEFINED_MARKER) { resource.addPropertyOverride('Runtime', undefined); } + if (resource.handler === UNDEFINED_MARKER) { resource.addPropertyOverride('Handler', undefined); } + resource.node.addDependency(this.role); this.functionName = this.getResourceNameAttribute(resource.ref); diff --git a/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts b/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts index 2c2699493b29b..4f80c886e88d2 100644 --- a/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function-hash.test.ts @@ -37,7 +37,7 @@ describe('function hash', () => { }); expect(calculateFunctionHash(fn1)).toEqual(calculateFunctionHash(fn2)); - expect(calculateFunctionHash(fn1)).toEqual('d0cf052aead13ed6112b2e8a2f728908'); + expect(calculateFunctionHash(fn1)).toEqual('aea5463dba236007afe91d2832b3c836'); }); }); @@ -49,8 +49,8 @@ describe('function hash', () => { handler: 'index.handler', }); - expect(calculateFunctionHash(fn1)).not.toEqual('d0cf052aead13ed6112b2e8a2f728908'); - expect(calculateFunctionHash(fn1)).toEqual('14b543c4b1a9e5425d92ef93557eb5bd'); + expect(calculateFunctionHash(fn1)).not.toEqual('aea5463dba236007afe91d2832b3c836'); + expect(calculateFunctionHash(fn1)).toEqual('979b4a14c6f174c745cdbcd1036cf844'); }); test('environment variables impact hash', () => { @@ -74,7 +74,8 @@ describe('function hash', () => { }, }); - expect(calculateFunctionHash(fn1)).not.toEqual(calculateFunctionHash(fn2)); + expect(calculateFunctionHash(fn1)).toEqual('d1bc824ac5022b7d62d8b12dbae6580c'); + expect(calculateFunctionHash(fn2)).toEqual('3b683d05465012b0aa9c4ff53b32f014'); }); test('runtime impacts hash', () => { @@ -98,7 +99,8 @@ describe('function hash', () => { }, }); - expect(calculateFunctionHash(fn1)).not.toEqual(calculateFunctionHash(fn2)); + expect(calculateFunctionHash(fn1)).toEqual('d1bc824ac5022b7d62d8b12dbae6580c'); + expect(calculateFunctionHash(fn2)).toEqual('0f168f0772463e8e547bb3800937e54d'); }); test('inline code change impacts the hash', () => { @@ -116,7 +118,8 @@ describe('function hash', () => { handler: 'index.handler', }); - expect(calculateFunctionHash(fn1)).not.toEqual(calculateFunctionHash(fn2)); + expect(calculateFunctionHash(fn1)).toEqual('ebf2e871fc6a3062e8bdcc5ebe16db3f'); + expect(calculateFunctionHash(fn2)).toEqual('ffedf6424a18a594a513129dc97bf53c'); }); describe('impact of env variables order on hash', () => { diff --git a/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json index e5ec0cb98a373..bf269c8f6d555 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.current-version.expected.json @@ -85,7 +85,7 @@ "MyLambdaServiceRole4539ECB6" ] }, - "MyLambdaCurrentVersionE7A382CC22cdb4104d8a97928c23315ce885c22d": { + "MyLambdaCurrentVersionE7A382CC721de083c6b4b6360a9c534b79eb610e": { "Type": "AWS::Lambda::Version", "Properties": { "FunctionName": { @@ -103,7 +103,7 @@ }, "Qualifier": { "Fn::GetAtt": [ - "MyLambdaCurrentVersionE7A382CC22cdb4104d8a97928c23315ce885c22d", + "MyLambdaCurrentVersionE7A382CC721de083c6b4b6360a9c534b79eb610e", "Version" ] }, @@ -118,7 +118,7 @@ }, "FunctionVersion": { "Fn::GetAtt": [ - "MyLambdaCurrentVersionE7A382CC22cdb4104d8a97928c23315ce885c22d", + "MyLambdaCurrentVersionE7A382CC721de083c6b4b6360a9c534b79eb610e", "Version" ] }, diff --git a/packages/@aws-cdk/aws-lambda/test/lambda-version.test.ts b/packages/@aws-cdk/aws-lambda/test/lambda-version.test.ts index f38ab3cd99d59..20b040a135a7b 100644 --- a/packages/@aws-cdk/aws-lambda/test/lambda-version.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/lambda-version.test.ts @@ -123,7 +123,7 @@ describe('lambda version', () => { }, FunctionVersion: { 'Fn::GetAtt': [ - 'FnCurrentVersion17A89ABB734f8396c6bf253af280ea9c5b22ae59', + 'FnCurrentVersion17A89ABBab5c765f3c55e4e61583b51b00a95742', 'Version', ], }, @@ -142,7 +142,7 @@ describe('lambda version', () => { const version = fn.currentVersion; // THEN - expect(stack.resolve(version.edgeArn)).toEqual({ Ref: 'FnCurrentVersion17A89ABB80486b870700fd98d45df0d591d19e82' }); + expect(stack.resolve(version.edgeArn)).toEqual({ Ref: 'FnCurrentVersion17A89ABB19ed45993ff69fd011ae9fd4ab6e2005' }); }); test('edgeArn throws with $LATEST', () => { diff --git a/packages/@aws-cdk/cfnspec/spec-source/490_Lambda_Containers_patch.json b/packages/@aws-cdk/cfnspec/spec-source/490_Lambda_Containers_patch.json index 781f3114b2b4b..cc53bd1139ad5 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/490_Lambda_Containers_patch.json +++ b/packages/@aws-cdk/cfnspec/spec-source/490_Lambda_Containers_patch.json @@ -52,16 +52,6 @@ "patch": { "description": "updates to Lambda Function to support Container Image", "operations": [ - { - "op": "replace", - "path": "/Properties/Handler/Required", - "value": false - }, - { - "op": "replace", - "path": "/Properties/Runtime/Required", - "value": false - }, { "op": "add", "path": "/Properties/ImageConfig", From e55f3cc5cf68fd5d79553934b3bb4bce333945be Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Tue, 1 Dec 2020 16:15:20 +0000 Subject: [PATCH 19/19] remove launch constraints --- .../test/integ.lambda.docker.expected.json | 8 +++++-- .../aws-lambda/test/integ.lambda.docker.ts | 22 ++++--------------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.expected.json index 4f68c37615d18..bfa1d27910000 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.expected.json @@ -32,7 +32,7 @@ } }, "MyLambdaCCE802FB": { - "Type": "AWSLambdaBeta::Lambda::Function", + "Type": "AWS::Lambda::Function", "Properties": { "Code": { "ImageUri": { @@ -42,7 +42,11 @@ { "Ref": "AWS::AccountId" }, - ".dkr.ecr.sa-east-1.", + ".dkr.ecr.", + { + "Ref": "AWS::Region" + }, + ".", { "Ref": "AWS::URLSuffix" }, diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts b/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts index 65ee50b43ab98..3870d0cadf3b5 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.docker.ts @@ -1,13 +1,10 @@ import * as path from 'path'; -import { App, Aspects, IAspect, Stack } from '@aws-cdk/core'; -import { IConstruct } from 'constructs'; -import { CfnFunction, DockerImageCode, DockerImageFunction } from '../lib'; +import { App, Stack } from '@aws-cdk/core'; +import { DockerImageCode, DockerImageFunction } from '../lib'; class TestStack extends Stack { constructor(scope: App, id: string) { - super(scope, id, { - env: { region: 'sa-east-1' }, // the feature is available only in sa-east-1 during private beta. Remove after launch. - }); + super(scope, id); new DockerImageFunction(this, 'MyLambda', { code: DockerImageCode.fromImageAsset(path.join(__dirname, 'docker-lambda-handler')), @@ -15,18 +12,7 @@ class TestStack extends Stack { } } -class PrivateResourceAspect implements IAspect { - visit(construct: IConstruct): void { - if (construct instanceof CfnFunction) { - (construct as any).cfnResourceType = 'AWSLambdaBeta::Lambda::Function'; - } - } -} - const app = new App(); -const stack = new TestStack(app, 'lambda-ecr-docker'); - -// the feature is available as an CFN private resource during private beta. Remove after launch. -Aspects.of(stack).add(new PrivateResourceAspect()); +new TestStack(app, 'lambda-ecr-docker'); app.synth(); \ No newline at end of file