Skip to content

Commit

Permalink
Add Support for Java and .NET (#117)
Browse files Browse the repository at this point in the history
* refactors layer name lookup

* adds dotnet and java support

* configures lambda exec wrapper env var for java and dotnet

* add dotnet and java layers to readme

* minor updates to readme
  • Loading branch information
duncanpharvey authored Jan 12, 2024
1 parent bcd9de9 commit bb978bb
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 29 deletions.
12 changes: 8 additions & 4 deletions serverless/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -50,7 +50,7 @@ Transform:
Parameters:
stackName: !Ref "AWS::StackName"
apiKey: "<DATADOG_API_KEY>"
pythonLayerVersion: "<LAYER_VERSION>" # Use nodeLayerVersion for Node.js
pythonLayerVersion: "<LAYER_VERSION>" # Use appropriate parameter for other runtimes
extensionLayerVersion: "<LAYER_VERSION>"
service: "<SERVICE>" # Optional
env: "<ENV>" # Optional
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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. |
Expand Down Expand Up @@ -147,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

Expand Down Expand Up @@ -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
4 changes: 4 additions & 0 deletions serverless/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions serverless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
77 changes: 56 additions & 21 deletions serverless/src/layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -53,16 +61,19 @@ 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]: {
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",
"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",
Expand All @@ -71,26 +82,26 @@ 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]: {
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",
"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];
}
},
};

/**
* Parse through the Resources section of the provided CloudFormation template to find all lambda
Expand Down Expand Up @@ -151,6 +162,8 @@ export function applyLayers(
lambdas: LambdaFunction[],
pythonLayerVersion?: number,
nodeLayerVersion?: number,
dotnetLayerVersion?: number,
javaLayerVersion?: number,
extensionLayerVersion?: number,
) {
const errors: string[] = [];
Expand Down Expand Up @@ -185,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);
Expand Down Expand Up @@ -237,7 +272,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
Expand Down
17 changes: 17 additions & 0 deletions serverless/src/redirect.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}
Loading

0 comments on commit bb978bb

Please sign in to comment.