From dfe2c5c665a5662972098415e713d73e742e93cd Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 19 Aug 2020 16:15:14 +0200 Subject: [PATCH] fix(lambda-nodejs): NodejsFunction construct incompatible with lambda@edge (#9562) Check version and function compatibility when a Lambda is used for Lambda@Edge. Environment variables can be marked as "removable" when used for Lambda@Edge. Closes #9328 Closes #9453 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/private/cache-behavior.ts | 13 +-- .../aws-cloudfront/lib/web_distribution.ts | 2 +- .../aws-cloudfront/test/distribution.test.ts | 53 +++++++++++ .../test/web_distribution.test.ts | 91 ++++++++++++++++--- .../aws-lambda-nodejs/lib/function.ts | 2 +- .../@aws-cdk/aws-lambda/lib/function-base.ts | 13 +++ packages/@aws-cdk/aws-lambda/lib/function.ts | 77 ++++++++++++---- .../@aws-cdk/aws-lambda/lib/lambda-version.ts | 43 ++++++++- .../aws-lambda/lib/singleton-lambda.ts | 7 ++ .../aws-lambda/test/test.lambda-version.ts | 57 ++++++++++++ .../@aws-cdk/aws-lambda/test/test.lambda.ts | 42 ++++++++- .../aws-lambda/test/test.singleton-lambda.ts | 19 ++++ 12 files changed, 376 insertions(+), 43 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts index 6461d0fd54b0a..18b4d75aac2c7 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts @@ -48,15 +48,10 @@ export class CacheBehavior { smoothStreaming: this.props.smoothStreaming, viewerProtocolPolicy: this.props.viewerProtocolPolicy ?? ViewerProtocolPolicy.ALLOW_ALL, lambdaFunctionAssociations: this.props.edgeLambdas - ? this.props.edgeLambdas.map(edgeLambda => { - if (edgeLambda.functionVersion.version === '$LATEST') { - throw new Error('$LATEST function version cannot be used for Lambda@Edge'); - } - return { - lambdaFunctionArn: edgeLambda.functionVersion.functionArn, - eventType: edgeLambda.eventType.toString(), - }; - }) + ? this.props.edgeLambdas.map(edgeLambda => ({ + lambdaFunctionArn: edgeLambda.functionVersion.edgeArn, + eventType: edgeLambda.eventType.toString(), + })) : undefined, }; } diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts index bf2bb2b3b5041..4a4ebcede0b9b 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts @@ -896,7 +896,7 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu lambdaFunctionAssociations: input.lambdaFunctionAssociations .map(fna => ({ eventType: fna.eventType, - lambdaFunctionArn: fna.lambdaFunction && fna.lambdaFunction.functionArn, + lambdaFunctionArn: fna.lambdaFunction && fna.lambdaFunction.edgeArn, })), }); diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts index 2f7d19b6c1f7e..ff629ade249c4 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts @@ -538,6 +538,59 @@ describe('with Lambda@Edge functions', () => { }); }).toThrow(/\$LATEST function version cannot be used for Lambda@Edge/); }); + + test('with removable env vars', () => { + const envLambdaFunction = new lambda.Function(stack, 'EnvFunction', { + runtime: lambda.Runtime.NODEJS, + code: lambda.Code.fromInline('whateverwithenv'), + handler: 'index.handler', + }); + envLambdaFunction.addEnvironment('KEY', 'value', { removeInEdge: true }); + + new Distribution(stack, 'MyDist', { + defaultBehavior: { + origin, + edgeLambdas: [ + { + functionVersion: envLambdaFunction.currentVersion, + eventType: LambdaEdgeEventType.ORIGIN_REQUEST, + }, + ], + }, + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Environment: ABSENT, + Code: { + ZipFile: 'whateverwithenv', + }, + }); + }); + + test('with incompatible env vars', () => { + const envLambdaFunction = new lambda.Function(stack, 'EnvFunction', { + runtime: lambda.Runtime.NODEJS, + code: lambda.Code.fromInline('whateverwithenv'), + handler: 'index.handler', + environment: { + KEY: 'value', + }, + }); + + new Distribution(stack, 'MyDist', { + defaultBehavior: { + origin, + edgeLambdas: [ + { + functionVersion: envLambdaFunction.currentVersion, + eventType: LambdaEdgeEventType.ORIGIN_REQUEST, + }, + ], + }, + }); + + expect(() => app.synth()).toThrow(/KEY/); + }); }); test('price class is included if provided', () => { 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 a52ea50929a3d..e91c4e2f53192 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts @@ -1,4 +1,4 @@ -import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; +import { ABSENT, expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import * as certificatemanager from '@aws-cdk/aws-certificatemanager'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; @@ -426,8 +426,7 @@ nodeunitShim({ const stack = new cdk.Stack(); const sourceBucket = new s3.Bucket(stack, 'Bucket'); - const lambdaFunction = new lambda.SingletonFunction(stack, 'Lambda', { - uuid: 'xxxx-xxxx-xxxx-xxxx', + const lambdaFunction = new lambda.Function(stack, 'Lambda', { code: lambda.Code.inline('foo'), handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, @@ -444,7 +443,7 @@ nodeunitShim({ isDefaultBehavior: true, lambdaFunctionAssociations: [{ eventType: LambdaEdgeEventType.ORIGIN_REQUEST, - lambdaFunction: lambdaFunction.latestVersion, + lambdaFunction: lambdaFunction.addVersion('1'), }], }, ], @@ -459,13 +458,7 @@ nodeunitShim({ { 'EventType': 'origin-request', 'LambdaFunctionARN': { - 'Fn::Join': [ - '', - [ - { 'Fn::GetAtt': ['SingletonLambdaxxxxxxxxxxxxxxxx69D4268A', 'Arn'] }, - ':$LATEST', - ], - ], + 'Ref': 'LambdaVersion1BB7548E1', }, }, ], @@ -476,6 +469,82 @@ nodeunitShim({ test.done(); }, + 'associate a lambda with removable env vars'(test: Test) { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + const sourceBucket = new s3.Bucket(stack, 'Bucket'); + + const lambdaFunction = new lambda.Function(stack, 'Lambda', { + code: lambda.Code.inline('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + }); + lambdaFunction.addEnvironment('KEY', 'value', { removeInEdge: true }); + + new CloudFrontWebDistribution(stack, 'AnAmazingWebsiteProbably', { + originConfigs: [ + { + s3OriginSource: { + s3BucketSource: sourceBucket, + }, + behaviors: [ + { + isDefaultBehavior: true, + lambdaFunctionAssociations: [{ + eventType: LambdaEdgeEventType.ORIGIN_REQUEST, + lambdaFunction: lambdaFunction.addVersion('1'), + }], + }, + ], + }, + ], + }); + + expect(stack).to(haveResource('AWS::Lambda::Function', { + Environment: ABSENT, + })); + + test.done(); + }, + + 'throws when associating a lambda with incompatible env vars'(test: Test) { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + const sourceBucket = new s3.Bucket(stack, 'Bucket'); + + const lambdaFunction = new lambda.Function(stack, 'Lambda', { + code: lambda.Code.inline('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + environment: { + KEY: 'value', + }, + }); + + new CloudFrontWebDistribution(stack, 'AnAmazingWebsiteProbably', { + originConfigs: [ + { + s3OriginSource: { + s3BucketSource: sourceBucket, + }, + behaviors: [ + { + isDefaultBehavior: true, + lambdaFunctionAssociations: [{ + eventType: LambdaEdgeEventType.ORIGIN_REQUEST, + lambdaFunction: lambdaFunction.addVersion('1'), + }], + }, + ], + }, + ], + }); + + test.throws(() => app.synth(), /KEY/); + + test.done(); + }, + 'distribution has a defaultChild'(test: Test) { const stack = new cdk.Stack(); const sourceBucket = new s3.Bucket(stack, 'Bucket'); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts index 589c376c4f948..e768cf1afd43a 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts @@ -85,7 +85,7 @@ export class NodejsFunction extends lambda.Function { // Enable connection reuse for aws-sdk if (props.awsSdkConnectionReuse ?? true) { - this.addEnvironment('AWS_NODEJS_CONNECTION_REUSE_ENABLED', '1'); + this.addEnvironment('AWS_NODEJS_CONNECTION_REUSE_ENABLED', '1', { removeInEdge: true }); } } finally { // We can only restore after the code has been bound to the function diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index 39ad7d665fcb7..41a7a3307cf52 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -318,6 +318,15 @@ export abstract class FunctionBase extends Resource implements IFunction { }); } + /** + * Checks whether this function is compatible for Lambda@Edge. + * + * @internal + */ + public _checkEdgeCompatibility(): void { + return; + } + /** * Returns the construct tree node that corresponds to the lambda function. * For use internally for constructs, when the tree is set up in non-standard ways. Ex: SingletonFunction. @@ -417,4 +426,8 @@ class LatestVersion extends FunctionBase implements IVersion { public addAlias(aliasName: string, options: AliasOptions = {}) { return addAlias(this, this, aliasName, options); } + + public get edgeArn(): never { + throw new Error('$LATEST function version cannot be used for Lambda@Edge'); + } } diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index a69a51b950e2d..04150a2d86144 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -515,7 +515,7 @@ export class Function extends FunctionBase { /** * Environment variables for this function */ - private readonly environment: { [key: string]: string }; + private environment: { [key: string]: EnvironmentConfig } = {}; private readonly currentVersionOptions?: VersionOptions; private _currentVersion?: Version; @@ -558,7 +558,7 @@ export class Function extends FunctionBase { const code = props.code.bind(this); verifyCodeConfig(code, props.runtime); - let profilingGroupEnvironmentVariables = {}; + let profilingGroupEnvironmentVariables: { [key: string]: string } = {}; if (props.profilingGroup && props.profiling !== false) { this.validateProfilingEnvironmentVariables(props); props.profilingGroup.grantPublish(this.role); @@ -582,7 +582,10 @@ export class Function extends FunctionBase { }; } - this.environment = { ...profilingGroupEnvironmentVariables, ...(props.environment || {}) }; + const env = { ...profilingGroupEnvironmentVariables, ...props.environment }; + for (const [key, value] of Object.entries(env)) { + this.addEnvironment(key, value); + } this.deadLetterQueue = this.buildDeadLetterQueue(props); @@ -675,9 +678,10 @@ export class Function extends FunctionBase { * If this is a ref to a Lambda function, this operation results in a no-op. * @param key The environment variable key. * @param value The environment variable's value. + * @param options Environment variable options. */ - public addEnvironment(key: string, value: string): this { - this.environment[key] = value; + public addEnvironment(key: string, value: string, options?: EnvironmentOptions): this { + this.environment[key] = { value, ...options }; return this; } @@ -764,27 +768,43 @@ export class Function extends FunctionBase { return this._logGroup; } + /** @internal */ + public _checkEdgeCompatibility(): void { + // Check env vars + const envEntries = Object.entries(this.environment); + for (const [key, config] of envEntries) { + if (config.removeInEdge) { + delete this.environment[key]; + this.node.addInfo(`Removed ${key} environment variable for Lambda@Edge compatibility`); + } + } + const envKeys = Object.keys(this.environment); + if (envKeys.length !== 0) { + throw new Error(`The function ${this.node.path} contains environment variables [${envKeys}] and is not compatible with Lambda@Edge. \ +Environment variables can be marked for removal when used in Lambda@Edge by setting the \'removeInEdge\' property in the \'addEnvironment()\' API.`); + } + + return; + } + private renderEnvironment() { if (!this.environment || Object.keys(this.environment).length === 0) { return undefined; } - // for backwards compatibility we do not sort environment variables in case - // _currentVersion is not defined. otherwise, this would have invalidated + const variables: { [key: string]: string } = {}; + // Sort environment so the hash of the function used to create + // `currentVersion` is not affected by key order (this is how lambda does + // it). For backwards compatibility we do not sort environment variables in case + // _currentVersion is not defined. Otherwise, this would have invalidated // the template, and for example, may cause unneeded updates for nested // stacks. - if (!this._currentVersion) { - return { - variables: this.environment, - }; - } + const keys = this._currentVersion + ? Object.keys(this.environment).sort() + : Object.keys(this.environment); - // sort environment so the hash of the function used to create - // `currentVersion` is not affected by key order (this is how lambda does - // it). - const variables: { [key: string]: string } = {}; - for (const key of Object.keys(this.environment).sort()) { - variables[key] = this.environment[key]; + for (const key of keys) { + variables[key] = this.environment[key].value; } return { variables }; @@ -905,6 +925,27 @@ export class Function extends FunctionBase { } } +/** + * Environment variables options + */ +export interface EnvironmentOptions { + /** + * When used in Lambda@Edge via edgeArn() API, these environment + * variables will be removed. If not set, an error will be thrown. + * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-requirements-limits.html#lambda-requirements-lambda-function-configuration + * + * @default false - using the function in Lambda@Edge will throw + */ + readonly removeInEdge?: boolean +} + +/** + * Configuration for an environment variable + */ +interface EnvironmentConfig extends EnvironmentOptions { + readonly value: string; +} + /** * Given an opaque (token) ARN, returns a CloudFormation expression that extracts the function * name from the ARN. diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts index d2c322a5326c2..17791a8d2ff7e 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts @@ -1,9 +1,9 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; -import { Construct, Fn, RemovalPolicy } from '@aws-cdk/core'; +import { Construct, Fn, Lazy, RemovalPolicy } from '@aws-cdk/core'; import { Alias, AliasOptions } from './alias'; import { EventInvokeConfigOptions } from './event-invoke-config'; import { Function } from './function'; -import { IFunction, QualifiedFunctionBase } from './function-base'; +import { FunctionBase, IFunction, QualifiedFunctionBase } from './function-base'; import { CfnVersion } from './lambda.generated'; import { addAlias } from './util'; @@ -19,6 +19,11 @@ export interface IVersion extends IFunction { */ readonly lambda: IFunction; + /** + * The ARN of the version for Lambda@Edge. + */ + readonly edgeArn: string; + /** * Defines an alias for this version. * @param aliasName The name of the alias @@ -128,6 +133,13 @@ export class Version extends QualifiedFunctionBase implements IVersion { public addAlias(name: string, opts: AliasOptions = { }): Alias { return addAlias(this, this, name, opts); } + + public get edgeArn(): string { + if (version === '$LATEST') { + throw new Error('$LATEST function version cannot be used for Lambda@Edge'); + } + return this.functionArn; + } } return new Import(scope, id); } @@ -147,6 +159,13 @@ export class Version extends QualifiedFunctionBase implements IVersion { public addAlias(name: string, opts: AliasOptions = { }): Alias { return addAlias(this, this, name, opts); } + + public get edgeArn(): string { + if (attrs.version === '$LATEST') { + throw new Error('$LATEST function version cannot be used for Lambda@Edge'); + } + return this.functionArn; + } } return new Import(scope, id); } @@ -223,6 +242,26 @@ export class Version extends QualifiedFunctionBase implements IVersion { return addAlias(this, this, aliasName, options); } + public get edgeArn(): string { + // Validate first that this version can be used for Lambda@Edge + if (this.version === '$LATEST') { + throw new Error('$LATEST function version cannot be used for Lambda@Edge'); + } + + // Check compatibility at synthesis. It could be that the version was associated + // with a CloudFront distribution first and made incompatible afterwards. + return Lazy.stringValue({ + produce: () => { + // Validate that the underlying function can be used for Lambda@Edge + if (this.lambda instanceof FunctionBase) { + this.lambda._checkEdgeCompatibility(); + } + + return this.functionArn; + }, + }); + } + /** * Validate that the provisionedConcurrentExecutions makes sense * diff --git a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts index f8515dc84e841..5cb9a84f88613 100644 --- a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts @@ -81,6 +81,13 @@ export class SingletonFunction extends FunctionBase { down.node.addDependency(this.lambdaFunction); } + /** @internal */ + public _checkEdgeCompatibility() { + if (this.lambdaFunction instanceof FunctionBase) { + return this.lambdaFunction._checkEdgeCompatibility(); + } + } + /** * Returns the construct tree node that corresponds to the lambda function. * @internal diff --git a/packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts b/packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts index b7f283164b715..8c9c4a45fd8e3 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.lambda-version.ts @@ -142,4 +142,61 @@ export = { })); test.done(); }, + + 'edgeArn'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'Fn', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.fromInline('foo'), + }); + const version = fn.addVersion('1'); + + // THEN + test.deepEqual(stack.resolve(version.edgeArn), { Ref: 'FnVersion1C3F5F93D' }); + + test.done(); + }, + + 'edgeArn throws with $LATEST'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const version = lambda.Version.fromVersionArn(stack, 'Version', 'arn:aws:lambda:region:account-id:function:function-name:$LATEST'); + + // THEN + test.throws(() => version.edgeArn, /\$LATEST function version cannot be used for Lambda@Edge/); + + test.done(); + }, + + 'edgeArn throws at synthesis if underlying function is not edge compatible'(test: Test) { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + const fn = new lambda.Function(stack, 'Fn', { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.fromInline('foo'), + }); + const version = fn.addVersion('1'); + + // WHEN + new lambda.Function(stack, 'OtherFn', { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.fromInline('foo'), + environment: { + EDGE_ARN: version.edgeArn, // Consume edgeArn + }, + }); + // make fn incompatible for Lambda@Edge after consuming edgeArn + fn.addEnvironment('KEY1', 'value1'); + fn.addEnvironment('KEY2', 'value2'); + + // THEN + test.throws(() => app.synth(), /KEY1,KEY2/); + + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts index 8658c0be4ec13..651b5b192203c 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts @@ -1,5 +1,5 @@ import * as path from 'path'; -import { expect, haveResource, MatchStyle, ResourcePart } from '@aws-cdk/assert'; +import { ABSENT, expect, haveResource, MatchStyle, ResourcePart } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; @@ -1525,6 +1525,46 @@ export = { test.done(); }, + + 'check edge compatibility with env vars that can be removed'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'fn', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_12_X, + }); + fn.addEnvironment('KEY', 'value', { removeInEdge: true }); + + // WHEN + fn._checkEdgeCompatibility(); + + // THEN + expect(stack).to(haveResource('AWS::Lambda::Function', { + Environment: ABSENT, + })); + + test.done(); + }, + + 'check edge compatibility with env vars that cannot be removed'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'fn', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_12_X, + environment: { + KEY: 'value', + }, + }); + fn.addEnvironment('OTHER_KEY', 'other_value', { removeInEdge: true }); + + // THEN + test.throws(() => fn._checkEdgeCompatibility(), /The function Default\/fn contains environment variables \[KEY\] and is not compatible with Lambda@Edge/); + + test.done(); + }, }; function newTestLambda(scope: cdk.Construct) { diff --git a/packages/@aws-cdk/aws-lambda/test/test.singleton-lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.singleton-lambda.ts index eac8f2adb825e..6df79c0a278eb 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.singleton-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.singleton-lambda.ts @@ -141,4 +141,23 @@ export = { }]); test.done(); }, + + 'check edge compatibility'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const singleton = new lambda.SingletonFunction(stack, 'Singleton', { + uuid: '84c0de93-353f-4217-9b0b-45b6c993251a', + code: new lambda.InlineCode('def hello(): pass'), + runtime: lambda.Runtime.PYTHON_2_7, + handler: 'index.hello', + environment: { + KEY: 'value', + }, + }); + + // THEN + test.throws(() => singleton._checkEdgeCompatibility(), /The function Default\/SingletonLambda84c0de93353f42179b0b45b6c993251a contains environment variables \[KEY\] and is not compatible with Lambda@Edge/); + + test.done(); + }, };