Skip to content
48 changes: 47 additions & 1 deletion packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as logs from '@aws-cdk/aws-logs';
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';
import { Function as LambdaFunction, FunctionProps } from './function';
import { Function as LambdaFunction, FunctionProps, EnvironmentOptions } from './function';
import { FunctionBase } from './function-base';
import { Version } from './lambda-version';
import { ILayerVersion } from './layers';
import { Permission } from './permission';
import { Runtime } from './runtime';

/**
* Properties for a newly created singleton Lambda
Expand Down Expand Up @@ -47,6 +50,12 @@ export class SingletonFunction extends FunctionBase {
public readonly functionArn: string;
public readonly role?: iam.IRole;
public readonly permissionsNode: cdk.ConstructNode;

/**
* The runtime environment for the Lambda function.
*/
public readonly runtime: Runtime;

protected readonly canCreatePermissions: boolean;
private lambdaFunction: LambdaFunction;

Expand All @@ -59,6 +68,7 @@ export class SingletonFunction extends FunctionBase {
this.functionArn = this.lambdaFunction.functionArn;
this.functionName = this.lambdaFunction.functionName;
this.role = this.lambdaFunction.role;
this.runtime = this.lambdaFunction.runtime;
this.grantPrincipal = this.lambdaFunction.grantPrincipal;

this.canCreatePermissions = true; // Doesn't matter, addPermission is overriden anyway
Expand All @@ -78,6 +88,20 @@ export class SingletonFunction extends FunctionBase {
return this.lambdaFunction.connections;
}

/**
* The LogGroup where the Lambda function's logs are made available.
*
* If either `logRetention` is set or this property is called, a CloudFormation custom resource is added to the stack that
* pre-creates the log group as part of the stack deployment, if it already doesn't exist, and sets the correct log retention
* period (never expire, by default).
*
* Further, if the log group already exists and the `logRetention` is not set, the custom resource will reset the log retention
* to never expire even if it was configured with a different value.
*/
public get logGroup(): logs.ILogGroup {
return this.lambdaFunction.logGroup;
}

/**
* Returns a `lambda.Version` which represents the current version of this
* singleton Lambda function. A new version will be created every time the
Expand All @@ -90,6 +114,28 @@ export class SingletonFunction extends FunctionBase {
return this.lambdaFunction.currentVersion;
}

/**
* Adds an environment variable to this Lambda function.
* 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, options?: EnvironmentOptions) {
return this.lambdaFunction.addEnvironment(key, value, options);
}

/**
* Adds one or more Lambda Layers to this Lambda function.
*
* @param layers the layers to be added.
*
* @throws if there are already 5 layers on this function, or the layer is incompatible with this function's runtime.
*/
public addLayers(...layers: ILayerVersion[]) {
return this.lambdaFunction.addLayers(...layers);
}

public addPermission(name: string, permission: Permission) {
return this.lambdaFunction.addPermission(name, permission);
}
Expand Down
87 changes: 87 additions & 0 deletions packages/@aws-cdk/aws-lambda/test/singleton-lambda.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import '@aws-cdk/assert-internal/jest';
import { ResourcePart } from '@aws-cdk/assert-internal';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as s3 from '@aws-cdk/aws-s3';
import * as cdk from '@aws-cdk/core';
import * as lambda from '../lib';

Expand Down Expand Up @@ -109,6 +110,57 @@ describe('singleton lambda', () => {
}, ResourcePart.CompleteDefinition);
});

test('Environment is added to Lambda, when .addEnvironment() is provided one key pair', () => {
// 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',
timeout: cdk.Duration.minutes(5),
});

// WHEN
singleton.addEnvironment('KEY', 'value');

// THEN
expect(stack).toHaveResource('AWS::Lambda::Function', {
Environment: {
Variables: {
KEY: 'value',
},
},
});
});

test('Layer is added to Lambda, when .addLayers() is provided a valid layer', () => {
// 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',
timeout: cdk.Duration.minutes(5),
});
const bucket = new s3.Bucket(stack, 'Bucket');
const layer = new lambda.LayerVersion(stack, 'myLayer', {
code: new lambda.S3Code(bucket, 'ObjectKey'),
compatibleRuntimes: [lambda.Runtime.PYTHON_2_7],
});

// WHEN
singleton.addLayers(layer);

// THEN
expect(stack).toHaveResource('AWS::Lambda::Function', {
Layers: [{
Ref: 'myLayerBA1B098A',
}],
});
});

test('grantInvoke works correctly', () => {
// GIVEN
const stack = new cdk.Stack();
Expand Down Expand Up @@ -154,6 +206,41 @@ describe('singleton lambda', () => {
.toThrow(/contains environment variables .* and is not compatible with Lambda@Edge/);
});

test('logGroup is correctly returned', () => {
// GIVEN
const stack = new cdk.Stack();

// WHEN
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',
timeout: cdk.Duration.minutes(5),
});

// THEN
expect(singleton.logGroup.logGroupName).toBeDefined();
expect(singleton.logGroup.logGroupArn).toBeDefined();
});

test('runtime is correctly returned', () => {
// GIVEN
const stack = new cdk.Stack();

// WHEN
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',
timeout: cdk.Duration.minutes(5),
});

// THEN
expect(singleton.runtime).toStrictEqual(lambda.Runtime.PYTHON_2_7);
});

test('current version of a singleton function', () => {
// GIVEN
const stack = new cdk.Stack();
Expand Down