From 9b5ba2bef6bea595271bf1386f3151aa17d21e9c Mon Sep 17 00:00:00 2001 From: Duncan Harvey Date: Mon, 8 Jan 2024 15:01:47 -0500 Subject: [PATCH 1/5] refactors layer name lookup --- serverless/src/layer.ts | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/serverless/src/layer.ts b/serverless/src/layer.ts index 38a350a..6d2ecac 100644 --- a/serverless/src/layer.ts +++ b/serverless/src/layer.ts @@ -53,16 +53,13 @@ export const runtimeLookup: { [key: string]: RuntimeType } = { "python3.12": RuntimeType.PYTHON, }; -function runtimeToLayerName(runtime: string, architecture: string): string { - const nodeLookup: { [key: string]: string } = { +export const layerNameLookup: { [key in ArchitectureType]: { [key: string]: string } } = { + [ArchitectureType.x86_64]: { "nodejs12.x": "Datadog-Node12-x", "nodejs14.x": "Datadog-Node14-x", "nodejs16.x": "Datadog-Node16-x", "nodejs18.x": "Datadog-Node18-x", "nodejs20.x": "Datadog-Node20-x", - }; - - const pythonLookup: { [key: string]: string } = { "python2.7": "Datadog-Python27", "python3.6": "Datadog-Python36", "python3.7": "Datadog-Python37", @@ -71,25 +68,19 @@ function runtimeToLayerName(runtime: string, architecture: string): string { "python3.10": "Datadog-Python310", "python3.11": "Datadog-Python311", "python3.12": "Datadog-Python312", - }; - - const pythonArmLookup: { [key: string]: string } = { + }, + [ArchitectureType.ARM64]: { + "nodejs12.x": "Datadog-Node12-x", + "nodejs14.x": "Datadog-Node14-x", + "nodejs16.x": "Datadog-Node16-x", + "nodejs18.x": "Datadog-Node18-x", + "nodejs20.x": "Datadog-Node20-x", "python3.8": "Datadog-Python38-ARM", "python3.9": "Datadog-Python39-ARM", "python3.10": "Datadog-Python310-ARM", "python3.11": "Datadog-Python311-ARM", "python3.12": "Datadog-Python312-ARM", - }; - - if (runtimeLookup[runtime] === RuntimeType.NODE) { - return nodeLookup[runtime]; } - - if (runtimeLookup[runtime] === RuntimeType.PYTHON && architectureLookup[architecture] === ArchitectureType.ARM64) { - return pythonArmLookup[runtime]; - } - - return pythonLookup[runtime]; } /** @@ -237,7 +228,7 @@ export function getNewLayers(layerArn: string, currentLayers: LambdaLayersProper } export function getLambdaLibraryLayerArn(region: string, version: number, runtime: string, architecture: string) { - const layerName = runtimeToLayerName(runtime, architecture); + const layerName = layerNameLookup[architectureLookup[architecture]][runtime]; const isGovCloud = region === "us-gov-east-1" || region === "us-gov-west-1"; // if this is a GovCloud region, use the GovCloud lambda layer From 2d94fb83ba6964674cddae811ab0c56e76c0e71d Mon Sep 17 00:00:00 2001 From: Duncan Harvey Date: Mon, 8 Jan 2024 15:17:22 -0500 Subject: [PATCH 2/5] adds dotnet and java support --- serverless/src/env.ts | 4 + serverless/src/index.ts | 2 + serverless/src/layer.ts | 48 +++++++++++- serverless/test/layer.spec.ts | 140 +++++++++++++++++++++++++++++++++- 4 files changed, 188 insertions(+), 6 deletions(-) diff --git a/serverless/src/env.ts b/serverless/src/env.ts index 2540529..eef6a29 100644 --- a/serverless/src/env.ts +++ b/serverless/src/env.ts @@ -9,6 +9,10 @@ export interface Configuration { pythonLayerVersion?: number; // Node.js Lambda layer version nodeLayerVersion?: number; + // .Net Lambda Layer version + dotnetLayerVersion?: number; + // Java Lambda Layer version + javaLayerVersion?: number; // Datadog Lambda Extension layer version extensionLayerVersion?: number; // Datadog API Key, only necessary when using metrics without log forwarding diff --git a/serverless/src/index.ts b/serverless/src/index.ts index 5eaeae6..a50e09d 100644 --- a/serverless/src/index.ts +++ b/serverless/src/index.ts @@ -103,6 +103,8 @@ export const handler = async (event: InputEvent, _: any) => { lambdas, config.pythonLayerVersion, config.nodeLayerVersion, + config.dotnetLayerVersion, + config.javaLayerVersion, config.extensionLayerVersion, ); if (errors.length > 0) { diff --git a/serverless/src/layer.ts b/serverless/src/layer.ts index 6d2ecac..e6b0722 100644 --- a/serverless/src/layer.ts +++ b/serverless/src/layer.ts @@ -6,6 +6,8 @@ export const DD_ACCOUNT_ID = "464622532012"; export const DD_GOV_ACCOUNT_ID = "002406178527"; export enum RuntimeType { + DOTNET, + JAVA, NODE, PYTHON, UNSUPPORTED, @@ -38,6 +40,12 @@ const architectureToExtensionLayerName: { [key: string]: string } = { }; export const runtimeLookup: { [key: string]: RuntimeType } = { + dotnet6: RuntimeType.DOTNET, + java11: RuntimeType.JAVA, + java17: RuntimeType.JAVA, + java21: RuntimeType.JAVA, + java8: RuntimeType.JAVA, + "java8.al2": RuntimeType.JAVA, "nodejs12.x": RuntimeType.NODE, "nodejs14.x": RuntimeType.NODE, "nodejs16.x": RuntimeType.NODE, @@ -55,6 +63,12 @@ export const runtimeLookup: { [key: string]: RuntimeType } = { export const layerNameLookup: { [key in ArchitectureType]: { [key: string]: string } } = { [ArchitectureType.x86_64]: { + dotnet6: "dd-trace-dotnet", + java11: "dd-trace-java", + java17: "dd-trace-java", + java21: "dd-trace-java", + java8: "dd-trace-java", + "java8.al2": "dd-trace-java", "nodejs12.x": "Datadog-Node12-x", "nodejs14.x": "Datadog-Node14-x", "nodejs16.x": "Datadog-Node16-x", @@ -70,6 +84,12 @@ export const layerNameLookup: { [key in ArchitectureType]: { [key: string]: stri "python3.12": "Datadog-Python312", }, [ArchitectureType.ARM64]: { + dotnet6: "dd-trace-dotnet-ARM", + java11: "dd-trace-java", + java17: "dd-trace-java", + java21: "dd-trace-java", + java8: "dd-trace-java", + "java8.al2": "dd-trace-java", "nodejs12.x": "Datadog-Node12-x", "nodejs14.x": "Datadog-Node14-x", "nodejs16.x": "Datadog-Node16-x", @@ -80,8 +100,8 @@ export const layerNameLookup: { [key in ArchitectureType]: { [key: string]: stri "python3.10": "Datadog-Python310-ARM", "python3.11": "Datadog-Python311-ARM", "python3.12": "Datadog-Python312-ARM", - } -} + }, +}; /** * Parse through the Resources section of the provided CloudFormation template to find all lambda @@ -142,6 +162,8 @@ export function applyLayers( lambdas: LambdaFunction[], pythonLayerVersion?: number, nodeLayerVersion?: number, + dotnetLayerVersion?: number, + javaLayerVersion?: number, extensionLayerVersion?: number, ) { const errors: string[] = []; @@ -176,6 +198,28 @@ export function applyLayers( addLayer(lambdaLibraryLayerArn, lambda); } + if (lambda.runtimeType === RuntimeType.DOTNET) { + if (dotnetLayerVersion === undefined) { + errors.push(getMissingLayerVersionErrorMsg(lambda.key, ".Net", "dotnet")); + return; + } + + log.debug(`Setting .NET Lambda layer for ${lambda.key}`); + lambdaLibraryLayerArn = getLambdaLibraryLayerArn(region, dotnetLayerVersion, lambda.runtime, lambda.architecture); + addLayer(lambdaLibraryLayerArn, lambda); + } + + if (lambda.runtimeType === RuntimeType.JAVA) { + if (javaLayerVersion === undefined) { + errors.push(getMissingLayerVersionErrorMsg(lambda.key, "Java", "java")); + return; + } + + log.debug(`Setting Java Lambda layer for ${lambda.key}`); + lambdaLibraryLayerArn = getLambdaLibraryLayerArn(region, javaLayerVersion, lambda.runtime, lambda.architecture); + addLayer(lambdaLibraryLayerArn, lambda); + } + if (extensionLayerVersion !== undefined) { log.debug(`Setting Lambda Extension layer for ${lambda.key}`); lambdaExtensionLayerArn = getExtensionLayerArn(region, extensionLayerVersion, lambda.architecture); diff --git a/serverless/test/layer.spec.ts b/serverless/test/layer.spec.ts index e29442b..5c79999 100644 --- a/serverless/test/layer.spec.ts +++ b/serverless/test/layer.spec.ts @@ -50,6 +50,12 @@ function mockLambdaFunction( describe("findLambdas", () => { it("finds lambdas and correctly assigns runtime types", () => { const resources = { + Dotnet6Function: mockFunctionResource("dotnet6", ["x86_64"]), + Java11Function: mockFunctionResource("java11", ["x86_64"]), + Java17Function: mockFunctionResource("java17", ["x86_64"]), + Java21Function: mockFunctionResource("java21", ["x86_64"]), + Java8Function: mockFunctionResource("java8", ["x86_64"]), + Java8al2Function: mockFunctionResource("java8.al2", ["x86_64"]), Node12Function: mockFunctionResource("nodejs12.x", ["x86_64"]), Node14Function: mockFunctionResource("nodejs14.x", ["x86_64"]), Node16Function: mockFunctionResource("nodejs16.x", ["x86_64"]), @@ -69,6 +75,12 @@ describe("findLambdas", () => { const lambdas = findLambdas(resources, { ValueRef: "nodejs14.x" }); expect(lambdas).toEqual([ + mockLambdaFunction("Dotnet6Function", "dotnet6", RuntimeType.DOTNET, "x86_64", ArchitectureType.x86_64), + mockLambdaFunction("Java11Function", "java11", RuntimeType.JAVA, "x86_64", ArchitectureType.x86_64), + mockLambdaFunction("Java17Function", "java17", RuntimeType.JAVA, "x86_64", ArchitectureType.x86_64), + mockLambdaFunction("Java21Function", "java21", RuntimeType.JAVA, "x86_64", ArchitectureType.x86_64), + mockLambdaFunction("Java8Function", "java8", RuntimeType.JAVA, "x86_64", ArchitectureType.x86_64), + mockLambdaFunction("Java8al2Function", "java8.al2", RuntimeType.JAVA, "x86_64", ArchitectureType.x86_64), mockLambdaFunction("Node12Function", "nodejs12.x", RuntimeType.NODE, "x86_64", ArchitectureType.x86_64), mockLambdaFunction("Node14Function", "nodejs14.x", RuntimeType.NODE, "x86_64", ArchitectureType.x86_64), mockLambdaFunction("Node16Function", "nodejs16.x", RuntimeType.NODE, "x86_64", ArchitectureType.x86_64), @@ -156,7 +168,15 @@ describe("applyLayers", () => { const region = "us-east-1"; const nodeLayerVersion = 25; const extensionLayerVersion = 6; - const errors = applyLayers(region, [lambda], undefined, nodeLayerVersion, extensionLayerVersion); + const errors = applyLayers( + region, + [lambda], + undefined, + nodeLayerVersion, + undefined, + undefined, + extensionLayerVersion, + ); expect(errors.length).toEqual(0); expect(lambda.properties.Layers).toEqual([ @@ -170,7 +190,15 @@ describe("applyLayers", () => { const region = "us-east-1"; const nodeLayerVersion = 25; const extensionLayerVersion = 6; - const errors = applyLayers(region, [lambda], undefined, nodeLayerVersion, extensionLayerVersion); + const errors = applyLayers( + region, + [lambda], + undefined, + nodeLayerVersion, + undefined, + undefined, + extensionLayerVersion, + ); expect(errors.length).toEqual(0); expect(lambda.properties.Layers).toEqual([ @@ -184,7 +212,15 @@ describe("applyLayers", () => { const region = "us-east-1"; const pythonLayerVersion = 25; const extensionLayerVersion = 6; - const errors = applyLayers(region, [lambda], pythonLayerVersion, undefined, extensionLayerVersion); + const errors = applyLayers( + region, + [lambda], + pythonLayerVersion, + undefined, + undefined, + undefined, + extensionLayerVersion, + ); expect(errors.length).toEqual(0); expect(lambda.properties.Layers).toEqual([ @@ -198,7 +234,15 @@ describe("applyLayers", () => { const region = "us-east-1"; const pythonLayerVersion = 25; const extensionLayerVersion = 6; - const errors = applyLayers(region, [lambda], pythonLayerVersion, undefined, extensionLayerVersion); + const errors = applyLayers( + region, + [lambda], + pythonLayerVersion, + undefined, + undefined, + undefined, + extensionLayerVersion, + ); expect(errors.length).toEqual(0); expect(lambda.properties.Layers).toEqual([ @@ -206,6 +250,94 @@ describe("applyLayers", () => { `arn:aws:lambda:${region}:${DD_ACCOUNT_ID}:layer:Datadog-Extension-ARM:${extensionLayerVersion}`, ]); }); + + it("applies the dotnet and extension lambda layers", () => { + const lambda = mockLambdaFunction("FunctionKey", "dotnet6", RuntimeType.DOTNET, "x86_64"); + const region = "us-east-1"; + const dotnetLayerVersion = 14; + const extensionLayerVersion = 6; + const errors = applyLayers( + region, + [lambda], + undefined, + undefined, + dotnetLayerVersion, + undefined, + extensionLayerVersion, + ); + + expect(errors.length).toEqual(0); + expect(lambda.properties.Layers).toEqual([ + `arn:aws:lambda:${region}:${DD_ACCOUNT_ID}:layer:dd-trace-dotnet:${dotnetLayerVersion}`, + `arn:aws:lambda:${region}:${DD_ACCOUNT_ID}:layer:Datadog-Extension:${extensionLayerVersion}`, + ]); + }); + + it("applies the dotnet and extension lambda layers for arm", () => { + const lambda = mockLambdaFunction("FunctionKey", "dotnet6", RuntimeType.DOTNET, "arm64", ArchitectureType.ARM64); + const region = "us-east-1"; + const dotnetLayerVersion = 14; + const extensionLayerVersion = 6; + const errors = applyLayers( + region, + [lambda], + undefined, + undefined, + dotnetLayerVersion, + undefined, + extensionLayerVersion, + ); + + expect(errors.length).toEqual(0); + expect(lambda.properties.Layers).toEqual([ + `arn:aws:lambda:${region}:${DD_ACCOUNT_ID}:layer:dd-trace-dotnet-ARM:${dotnetLayerVersion}`, + `arn:aws:lambda:${region}:${DD_ACCOUNT_ID}:layer:Datadog-Extension-ARM:${extensionLayerVersion}`, + ]); + }); + + it("applies the java and extension lambda layers", () => { + const lambda = mockLambdaFunction("FunctionKey", "java21", RuntimeType.JAVA, "x86_64"); + const region = "us-east-1"; + const javaLayerVersion = 12; + const extensionLayerVersion = 6; + const errors = applyLayers( + region, + [lambda], + undefined, + undefined, + undefined, + javaLayerVersion, + extensionLayerVersion, + ); + + expect(errors.length).toEqual(0); + expect(lambda.properties.Layers).toEqual([ + `arn:aws:lambda:${region}:${DD_ACCOUNT_ID}:layer:dd-trace-java:${javaLayerVersion}`, + `arn:aws:lambda:${region}:${DD_ACCOUNT_ID}:layer:Datadog-Extension:${extensionLayerVersion}`, + ]); + }); + + it("applies the java and extension lambda layers for arm", () => { + const lambda = mockLambdaFunction("FunctionKey", "java21", RuntimeType.JAVA, "arm64", ArchitectureType.ARM64); + const region = "us-east-1"; + const javaLayerVersion = 12; + const extensionLayerVersion = 6; + const errors = applyLayers( + region, + [lambda], + undefined, + undefined, + undefined, + javaLayerVersion, + extensionLayerVersion, + ); + + expect(errors.length).toEqual(0); + expect(lambda.properties.Layers).toEqual([ + `arn:aws:lambda:${region}:${DD_ACCOUNT_ID}:layer:dd-trace-java:${javaLayerVersion}`, + `arn:aws:lambda:${region}:${DD_ACCOUNT_ID}:layer:Datadog-Extension-ARM:${extensionLayerVersion}`, + ]); + }); }); describe("getNewLayers", () => { From a4efeb67629d8d95fb0f328b19284d15887ff6c9 Mon Sep 17 00:00:00 2001 From: Duncan Harvey Date: Wed, 10 Jan 2024 17:28:37 -0500 Subject: [PATCH 3/5] configures lambda exec wrapper env var for java and dotnet --- serverless/src/redirect.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/serverless/src/redirect.ts b/serverless/src/redirect.ts index 9d388d3..e7d0d21 100644 --- a/serverless/src/redirect.ts +++ b/serverless/src/redirect.ts @@ -1,5 +1,7 @@ import { LambdaFunction, RuntimeType } from "./layer"; +export const AWS_LAMBDA_EXEC_WRAPPER_ENV_VAR = "AWS_LAMBDA_EXEC_WRAPPER"; +export const AWS_LAMBDA_EXEC_WRAPPER = "/opt/datadog_wrapper"; export const DD_HANDLER_ENV_VAR = "DD_LAMBDA_HANDLER"; export const PYTHON_HANDLER = "datadog_lambda.handler.handler"; export const JS_HANDLER_WITH_LAYERS = "/opt/nodejs/node_modules/datadog-lambda-js/handler.handler"; @@ -13,6 +15,11 @@ export const JS_HANDLER = "node_modules/datadog-lambda-js/dist/handler.handler"; */ export function redirectHandlers(lambdas: LambdaFunction[], addLayers: boolean) { lambdas.forEach((lambda) => { + if (lambda.runtimeType == RuntimeType.DOTNET || lambda.runtimeType == RuntimeType.JAVA) { + setEnvDatadogWrapper(lambda); + return; + } + setEnvDatadogHandler(lambda); const handler = getDDHandler(lambda.runtimeType, addLayers); if (handler === undefined) { @@ -44,3 +51,13 @@ function setEnvDatadogHandler(lambda: LambdaFunction) { environment.Variables = environmentVariables; lambda.properties.Environment = environment; } + +function setEnvDatadogWrapper(lambda: LambdaFunction) { + const environment = lambda.properties.Environment ?? {}; + const environmentVariables = environment.Variables ?? {}; + + environmentVariables[AWS_LAMBDA_EXEC_WRAPPER_ENV_VAR] = AWS_LAMBDA_EXEC_WRAPPER; + + environment.Variables = environmentVariables; + lambda.properties.Environment = environment; +} From 44a1d3d8125cbf9628f7909eeb00118ab61cefe0 Mon Sep 17 00:00:00 2001 From: Duncan Harvey Date: Wed, 10 Jan 2024 17:39:09 -0500 Subject: [PATCH 4/5] add dotnet and java layers to readme --- serverless/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/serverless/README.md b/serverless/README.md index 9d7b8fb..4c5f323 100644 --- a/serverless/README.md +++ b/serverless/README.md @@ -119,6 +119,8 @@ To further configure your plugin, use the following custom parameters: | `addLayers` | Whether to add the Lambda Layers or expect the user to bring their own. Defaults to true. When true, the Lambda Library version variables are also required. When false, you must include the Datadog Lambda library in your functions' deployment packages. | | `pythonLayerVersion` | Version of the Python Lambda layer to install, such as "21". Required if you are deploying at least one Lambda function written in Python and `addLayers` is true. Find the latest version number from [https://github.com/DataDog/datadog-lambda-python/releases][5]. | | `nodeLayerVersion` | Version of the Node.js Lambda layer to install, such as "29". Required if you are deploying at least one Lambda function written in Node.js and `addLayers` is true. Find the latest version number from [https://github.com/DataDog/datadog-lambda-js/releases][6]. | +| `dotnetLayerVersion` | Version of the .NET Lambda layer to install, such as "14". Required if you are deploying at least one Lambda function written in .NET and `addLayers` is true. Find the latest version number from [https://github.com/DataDog/dd-trace-dotnet-aws-lambda-layer/releases][9]. +| `javaLayerVersion` | Version of the Java Lambda layer to install, such as "12". Required if you are deploying at least one Lambda function written in Java and `addLayers` is true. Find the latest version number from [https://github.com/DataDog/datadog-lambda-java/releases][10]. | `extensionLayerVersion` | Version of the Datadog Lambda Extension layer to install, such as "5". When `extensionLayerVersion` is set, `apiKey` (or if encrypted, `apiKMSKey` or `apiKeySecretArn`) needs to be set as well. While using `extensionLayerVersion` do not set `forwarderArn`. Learn more about the Lambda extension [here][8]. | | `forwarderArn` | When set, the plugin will automatically subscribe the functions' log groups to the Datadog Forwarder. Alternatively, you can define the log subscription using the [AWS::Logs::SubscriptionFilter][7] resource. **Note**: The 'FunctionName' property must be defined for functions that are deployed for the first time because the macro needs the function name to create the log groups and subscription filters. 'FunctionName' must NOT contain any CloudFormation functions, such as `!Sub`. | | `stackName` | The name of the CloudFormation stack being deployed. Only required when a `forwarderArn` is provided and Lambda functions are dynamically named (when the `FunctionName` property isn't provided for a Lambda). For more information on how to add this parameter for SAM and CDK, see the examples below. | @@ -216,3 +218,5 @@ For product feedback and questions, join the `#serverless` channel in the [Datad [6]: https://github.com/DataDog/datadog-lambda-js/releases [7]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-subscriptionfilter.html [8]: https://docs.datadoghq.com/serverless/datadog_lambda_library/extension/ +[9]: https://github.com/DataDog/dd-trace-dotnet-aws-lambda-layer/releases +[10]: https://github.com/DataDog/datadog-lambda-java/releases From 1b5327f62bd67e10cb91dad7fa354a966abbecb2 Mon Sep 17 00:00:00 2001 From: Duncan Harvey Date: Thu, 11 Jan 2024 09:46:52 -0500 Subject: [PATCH 5/5] minor updates to readme --- serverless/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/serverless/README.md b/serverless/README.md index 4c5f323..ebdfbf6 100644 --- a/serverless/README.md +++ b/serverless/README.md @@ -12,7 +12,7 @@ Datadog recommends the Serverless CloudFormation macro for customers using AWS S The macro automatically configures ingestion of metrics, traces, and logs from your serverless applications by: -- Installing and configuring the Datadog Lambda Library and Lambda Extension for your [Python][1] and [Node.js][2] Lambda functions. +- Installing and configuring the Datadog Lambda Library and Lambda Extension for your [Python][1], [Node.js][2], [.NET][9], and [Java][10] Lambda functions. - Enabling the collection of enhanced Lambda metrics and custom metrics from your Lambda functions. - Managing subscriptions from the Datadog Forwarder to your Lambda function log groups, if desired. @@ -50,7 +50,7 @@ Transform: Parameters: stackName: !Ref "AWS::StackName" apiKey: "" - pythonLayerVersion: "" # Use nodeLayerVersion for Node.js + pythonLayerVersion: "" # Use appropriate parameter for other runtimes extensionLayerVersion: "" service: "" # Optional env: "" # Optional @@ -88,7 +88,7 @@ Resources: - SetFunctionName - Ref: FunctionName - Ref: AWS::NoValue - Description: Processes a CloudFormation template to install Datadog Lambda layers for Python and Node.js Lambda functions. + Description: Processes a CloudFormation template to install Datadog Lambda layers for Lambda functions. Handler: src/index.handler ... Environment: @@ -149,7 +149,7 @@ To further configure your plugin, use the following custom parameters: ## How it works -This macro modifies your CloudFormation template to install the Datadog Lambda Library by attaching the Lambda Layers for [Node.js][2] and [Python][1] to your functions. It redirects to a replacement handler that initializes the Lambda Library without any required code changes. +This macro modifies your CloudFormation template to install the Datadog Lambda Library by attaching the Lambda Layers for [Node.js][2], [Python][1], [.NET][9], and [Java][10] to your functions. It redirects to a replacement handler that initializes the Lambda Library without any required code changes. ## Troubleshooting