Skip to content

Commit

Permalink
[Step Function] 3.2 Extract class ConfigLoader and LambdaConfigLoader
Browse files Browse the repository at this point in the history
  • Loading branch information
lym953 committed Jan 7, 2025
1 parent 46d4883 commit 92a5421
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 164 deletions.
4 changes: 2 additions & 2 deletions serverless/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { validateParameters as validateLambdaParameters, getConfig as getLambdaConfig } from "./lambda/env";
import { validateParameters as validateLambdaParameters, LambdaConfigLoader } from "./lambda/env";
import { instrumentLambdas } from "./lambda/lambda";
import { InputEvent, OutputEvent, SUCCESS, FAILURE } from "./types";
import { instrumentStateMachines } from "./step_function/step_function";
Expand All @@ -11,7 +11,7 @@ export const handler = async (event: InputEvent, _: any): Promise<OutputEvent> =

const fragment = event.fragment;

const lambdaConfig = getLambdaConfig(event);
const lambdaConfig = new LambdaConfigLoader().getConfig(event);
const errors = validateLambdaParameters(lambdaConfig);
if (errors.length > 0) {
return {
Expand Down
306 changes: 162 additions & 144 deletions serverless/src/lambda/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,168 @@ export interface Configuration {
apmFlushDeadline?: string;
}

abstract class ConfigLoader<TConfig> {
abstract readonly defaultConfiguration: TConfig;
/**
* Returns the default configuration with any values overwritten by environment variables.
*/
abstract getConfigFromEnvVars(): TConfig;

/**
* Returns the configuration.
* If DatadogServerless transform params are set, then the priority order is:
* 1. CloudFormation Macro params
* 2. Environment variables
* 3. Default configuration
* Otherwise, if CloudFormation Mappings for Datadog are set, then the priority order is:
* 1. CloudFormation Mappings params
* 2. Environment variables
* 3. Default configuration
* Otherwise, the priority order is:
* 1. Environment variables
* 2. Default configuration
*/
public getConfig(event: InputEvent): TConfig {
let config: TConfig;
// Use the parameters given for this specific transform/macro if it exists
const transformParams = event.params ?? {};
if (Object.keys(transformParams).length > 0) {
log.debug("Parsing config from CloudFormation transform/macro parameters");
config = this.getConfigFromCfnParams(transformParams);
} else {
// If not, check the Mappings section for Datadog config parameters as well
log.debug("Parsing config from CloudFormation template mappings");
config = this.getConfigFromCfnMappings(event.fragment.Mappings);
}
return config;
}

/**
* Parses the Mappings section for Datadog config parameters.
* Assumes that the parameters live under the Mappings section in this format:
*
* Mappings:
* Datadog:
* Parameters:
* addLayers: true
* ...
*/
public getConfigFromCfnMappings(mappings: any): TConfig {
if (mappings === undefined || mappings[DATADOG] === undefined) {
log.debug("No Datadog mappings found in the CloudFormation template, using the default config");
return this.getConfigFromEnvVars();
}
return this.getConfigFromCfnParams(mappings[DATADOG][PARAMETERS]);
}

/**
* Takes a set of parameters from the CloudFormation template. This could come from either
* the Mappings section of the template, or directly from the Parameters under the transform/macro
* as the 'params' property under the original InputEvent to the handler in src/index.ts
*
* Uses these parameters as the Datadog configuration, and for values that are required in the
* configuration but not provided in the parameters, uses the default values from
* the defaultConfiguration above.
*/
public getConfigFromCfnParams(params: CfnParams) {
let datadogConfig = params as Partial<TConfig> | undefined;
if (datadogConfig === undefined) {
log.debug("No Datadog config found, using the default config");
datadogConfig = {};
}
return {
...this.getConfigFromEnvVars(),
...datadogConfig,
};
}
}

export class LambdaConfigLoader extends ConfigLoader<Configuration> {
readonly defaultConfiguration: Configuration = {
addLayers: true,
addExtension: false,
exclude: [],
flushMetricsToLogs: true,
logLevel: undefined,
site: "datadoghq.com",
enableXrayTracing: false,
enableDDTracing: true,
enableDDLogs: true,
enableEnhancedMetrics: true,
captureLambdaPayload: false,
};

public getConfigFromEnvVars(): Configuration {
const config: Configuration = {
...this.defaultConfiguration,
};

if (apiKeyEnvVar in process.env) {
config.apiKey = process.env[apiKeyEnvVar];
}
if (apiKeySecretArnEnvVar in process.env) {
config.apiKeySecretArn = process.env[apiKeySecretArnEnvVar];
}
if (apiKeyKMSEnvVar in process.env) {
config.apiKMSKey = process.env[apiKeyKMSEnvVar];
}
if (siteURLEnvVar in process.env && process.env[siteURLEnvVar] !== undefined) {
// Fall back to default site for type safety
config.site = process.env[siteURLEnvVar] ?? this.defaultConfiguration.site;
}
if (logLevelEnvVar in process.env) {
config.logLevel = process.env[logLevelEnvVar];
}
if (logForwardingEnvVar in process.env) {
config.flushMetricsToLogs = process.env[logForwardingEnvVar] === "true";
}
if (enhancedMetricsEnvVar in process.env) {
config.enableEnhancedMetrics = process.env[enhancedMetricsEnvVar] === "true";
}
if (enableDDLogsEnvVar in process.env) {
config.enableDDLogs = process.env[enableDDLogsEnvVar] === "true";
}
if (captureLambdaPayloadEnvVar in process.env) {
config.captureLambdaPayload = process.env[captureLambdaPayloadEnvVar] === "true";
}
if (serviceEnvVar in process.env) {
config.service = process.env[serviceEnvVar];
}
if (envEnvVar in process.env) {
config.env = process.env[envEnvVar];
}
if (versionEnvVar in process.env) {
config.version = process.env[versionEnvVar];
}
if (tagsEnvVar in process.env) {
config.tags = process.env[tagsEnvVar];
}
if (ddColdStartTracingEnabledEnvVar in process.env) {
config.enableColdStartTracing = process.env[ddColdStartTracingEnabledEnvVar] === "true";
}
if (ddMinColdStartDurationEnvVar in process.env) {
config.minColdStartTraceDuration = process.env[ddMinColdStartDurationEnvVar];
}
if (ddColdStartTracingSkipLibsEnvVar in process.env) {
config.coldStartTraceSkipLibs = process.env[ddColdStartTracingSkipLibsEnvVar];
}
if (ddProfilingEnabledEnvVar in process.env) {
config.enableProfiling = process.env[ddProfilingEnabledEnvVar] === "true";
}
if (ddEncodeAuthorizerContextEnvVar in process.env) {
config.encodeAuthorizerContext = process.env[ddEncodeAuthorizerContextEnvVar] === "true";
}
if (ddDecodeAuthorizerContextEnvVar in process.env) {
config.decodeAuthorizerContext = process.env[ddDecodeAuthorizerContextEnvVar] === "true";
}
if (ddApmFlushDeadlineMillisecondsEnvVar in process.env) {
config.apmFlushDeadline = process.env[ddApmFlushDeadlineMillisecondsEnvVar];
}

return config;
}
}

// Same interface as Configuration above, except all parameters are optional, since user does
// not have to provide the values (in which case we will use the default configuration below).
interface CfnParams extends Partial<Configuration> {}
Expand Down Expand Up @@ -113,150 +275,6 @@ const ddEncodeAuthorizerContextEnvVar = "DD_ENCODE_AUTHORIZER_CONTEXT";
const ddDecodeAuthorizerContextEnvVar = "DD_DECODE_AUTHORIZER_CONTEXT";
const ddApmFlushDeadlineMillisecondsEnvVar = "DD_APM_FLUSH_DEADLINE_MILLISECONDS";

export const defaultConfiguration: Configuration = {
addLayers: true,
addExtension: false,
exclude: [],
flushMetricsToLogs: true,
logLevel: undefined,
site: "datadoghq.com",
enableXrayTracing: false,
enableDDTracing: true,
enableDDLogs: true,
enableEnhancedMetrics: true,
captureLambdaPayload: false,
};

/**
* Returns the default configuration with any values overwritten by environment variables.
*/
export function getConfig(event: InputEvent): Configuration {
let config: Configuration;
// Use the parameters given for this specific transform/macro if it exists
const transformParams = event.params ?? {};
if (Object.keys(transformParams).length > 0) {
log.debug("Parsing config from CloudFormation transform/macro parameters");
config = getConfigFromCfnParams(transformParams);
} else {
// If not, check the Mappings section for Datadog config parameters as well
log.debug("Parsing config from CloudFormation template mappings");
config = getConfigFromCfnMappings(event.fragment.Mappings);
}
return config;
}

/**
* Returns the default configuration with any values overwritten by environment variables.
*/
export function getConfigFromEnvVars(): Configuration {
const config: Configuration = {
...defaultConfiguration,
};

if (apiKeyEnvVar in process.env) {
config.apiKey = process.env[apiKeyEnvVar];
}
if (apiKeySecretArnEnvVar in process.env) {
config.apiKeySecretArn = process.env[apiKeySecretArnEnvVar];
}
if (apiKeyKMSEnvVar in process.env) {
config.apiKMSKey = process.env[apiKeyKMSEnvVar];
}
if (siteURLEnvVar in process.env && process.env[siteURLEnvVar] !== undefined) {
// Fall back to default site for type safety
config.site = process.env[siteURLEnvVar] ?? defaultConfiguration.site;
}
if (logLevelEnvVar in process.env) {
config.logLevel = process.env[logLevelEnvVar];
}
if (logForwardingEnvVar in process.env) {
config.flushMetricsToLogs = process.env[logForwardingEnvVar] === "true";
}
if (enhancedMetricsEnvVar in process.env) {
config.enableEnhancedMetrics = process.env[enhancedMetricsEnvVar] === "true";
}
if (enableDDLogsEnvVar in process.env) {
config.enableDDLogs = process.env[enableDDLogsEnvVar] === "true";
}
if (captureLambdaPayloadEnvVar in process.env) {
config.captureLambdaPayload = process.env[captureLambdaPayloadEnvVar] === "true";
}
if (serviceEnvVar in process.env) {
config.service = process.env[serviceEnvVar];
}
if (envEnvVar in process.env) {
config.env = process.env[envEnvVar];
}
if (versionEnvVar in process.env) {
config.version = process.env[versionEnvVar];
}
if (tagsEnvVar in process.env) {
config.tags = process.env[tagsEnvVar];
}
if (ddColdStartTracingEnabledEnvVar in process.env) {
config.enableColdStartTracing = process.env[ddColdStartTracingEnabledEnvVar] === "true";
}
if (ddMinColdStartDurationEnvVar in process.env) {
config.minColdStartTraceDuration = process.env[ddMinColdStartDurationEnvVar];
}
if (ddColdStartTracingSkipLibsEnvVar in process.env) {
config.coldStartTraceSkipLibs = process.env[ddColdStartTracingSkipLibsEnvVar];
}
if (ddProfilingEnabledEnvVar in process.env) {
config.enableProfiling = process.env[ddProfilingEnabledEnvVar] === "true";
}
if (ddEncodeAuthorizerContextEnvVar in process.env) {
config.encodeAuthorizerContext = process.env[ddEncodeAuthorizerContextEnvVar] === "true";
}
if (ddDecodeAuthorizerContextEnvVar in process.env) {
config.decodeAuthorizerContext = process.env[ddDecodeAuthorizerContextEnvVar] === "true";
}
if (ddApmFlushDeadlineMillisecondsEnvVar in process.env) {
config.apmFlushDeadline = process.env[ddApmFlushDeadlineMillisecondsEnvVar];
}

return config;
}

/**
* Parses the Mappings section for Datadog config parameters.
* Assumes that the parameters live under the Mappings section in this format:
*
* Mappings:
* Datadog:
* Parameters:
* addLayers: true
* ...
*/
export function getConfigFromCfnMappings(mappings: any): Configuration {
if (mappings === undefined || mappings[DATADOG] === undefined) {
log.debug("No Datadog mappings found in the CloudFormation template, using the default config");
return getConfigFromEnvVars();
}
return getConfigFromCfnParams(mappings[DATADOG][PARAMETERS]);
}

/**
* Takes a set of parameters from the CloudFormation template. This could come from either
* the Mappings section of the template, or directly from the Parameters under the transform/macro
* as the 'params' property under the original InputEvent to the handler in src/index.ts
*
* Uses these parameters as the Datadog configuration, and for values that are required in the
* configuration but not provided in the parameters, uses the default values from
* the defaultConfiguration above.
*/
export function getConfigFromCfnParams(params: CfnParams) {
let datadogConfig = params as Partial<Configuration> | undefined;
if (datadogConfig === undefined) {
log.debug("No Datadog config found, using the default config");
datadogConfig = {};
}
return {
...getConfigFromEnvVars(),
...datadogConfig,
};
}

export function validateParameters(config: Configuration): string[] {
log.debug("Validating parameters...");
const errors: string[] = [];
Expand Down
Loading

0 comments on commit 92a5421

Please sign in to comment.