Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lambda): singleton function: access runtime, log group and configure layers and environment #17372

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