Skip to content

Commit f83cff1

Browse files
committed
feat: Add python3.14
1 parent af458c6 commit f83cff1

File tree

2 files changed

+5
-279
lines changed

2 files changed

+5
-279
lines changed

serverless/src/lambda/layer.ts

Lines changed: 3 additions & 279 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,3 @@
1-
import { Resources, Parameters, CFN_IF_FUNCTION_STRING } from "../common/types";
2-
import { FunctionProperties } from "./types";
3-
import { LambdaLayersProperty } from "./types";
4-
import { TaggableResource } from "../common/tags";
5-
import log from "loglevel";
6-
7-
const LAMBDA_FUNCTION_RESOURCE_TYPE = "AWS::Lambda::Function";
8-
export const DD_ACCOUNT_ID = "464622532012";
9-
export const DD_GOV_ACCOUNT_ID = "002406178527";
10-
11-
export enum RuntimeType {
12-
DOTNET,
13-
JAVA,
14-
NODE,
15-
PYTHON,
16-
UNSUPPORTED,
17-
}
18-
19-
export enum ArchitectureType {
20-
ARM64,
21-
x86_64,
22-
}
23-
24-
// Self defined interface that only applies to the macro - the FunctionProperties interface
25-
// defined in index.ts matches the CloudFormation AWS::Lambda::Function Properties interface.
26-
export interface LambdaFunction extends TaggableResource {
27-
properties: FunctionProperties;
28-
key: string;
29-
runtimeType: RuntimeType;
30-
runtime: string;
31-
architectureType: ArchitectureType;
32-
architecture: string;
33-
}
34-
35-
const architectureLookup: { [key: string]: ArchitectureType } = {
36-
x86_64: ArchitectureType.x86_64,
37-
arm64: ArchitectureType.ARM64,
38-
};
39-
40-
const architectureToExtensionLayerName: { [key: string]: string } = {
41-
x86_64: "Datadog-Extension",
42-
arm64: "Datadog-Extension-ARM",
43-
};
44-
451
export const runtimeLookup: { [key: string]: RuntimeType } = {
462
dotnet6: RuntimeType.DOTNET,
473
dotnet8: RuntimeType.DOTNET,
@@ -65,6 +21,7 @@ export const runtimeLookup: { [key: string]: RuntimeType } = {
6521
"python3.11": RuntimeType.PYTHON,
6622
"python3.12": RuntimeType.PYTHON,
6723
"python3.13": RuntimeType.PYTHON,
24+
"python3.14": RuntimeType.PYTHON,
6825
};
6926

7027
export const layerNameLookup: { [key in ArchitectureType]: { [key: string]: string } } = {
@@ -91,6 +48,7 @@ export const layerNameLookup: { [key in ArchitectureType]: { [key: string]: stri
9148
"python3.11": "Datadog-Python311",
9249
"python3.12": "Datadog-Python312",
9350
"python3.13": "Datadog-Python313",
51+
"python3.14": "Datadog-Python314",
9452
},
9553
[ArchitectureType.ARM64]: {
9654
dotnet6: "dd-trace-dotnet-ARM",
@@ -112,240 +70,6 @@ export const layerNameLookup: { [key in ArchitectureType]: { [key: string]: stri
11270
"python3.11": "Datadog-Python311-ARM",
11371
"python3.12": "Datadog-Python312-ARM",
11472
"python3.13": "Datadog-Python313-ARM",
73+
"python3.14": "Datadog-Python314-ARM",
11574
},
11675
};
117-
118-
/**
119-
* Parse through the Resources section of the provided CloudFormation template to find all lambda
120-
* function resources. Several modifications will be made later on to these resources, and
121-
* storing them with a clearly defined interface will make it easier to implement changes.
122-
*
123-
* Also assigns a general runtime type to the output lambdas. This helps to determine which lambda
124-
* layers to add & which handler to redirect to later on in the macro.
125-
*/
126-
export function findLambdas(resources: Resources, templateParameterValues: Parameters): LambdaFunction[] {
127-
return Object.entries(resources)
128-
.map(([key, resource]) => {
129-
if (resource.Type !== LAMBDA_FUNCTION_RESOURCE_TYPE) {
130-
log.debug(`Resource ${key} is not a Lambda function, skipping...`);
131-
return;
132-
}
133-
134-
const properties: FunctionProperties = resource.Properties;
135-
136-
if (properties.PackageType == "Image") {
137-
log.debug(`Resource ${key} is an image based Lambda function, skipping...`);
138-
return;
139-
}
140-
141-
const runtime = useOrRef(properties.Runtime, templateParameterValues);
142-
const architecture = useOrRef(properties.Architectures?.[0], templateParameterValues) ?? "x86_64";
143-
144-
let runtimeType = RuntimeType.UNSUPPORTED;
145-
let architectureType = ArchitectureType.x86_64;
146-
147-
if (runtime !== undefined && runtime in runtimeLookup) {
148-
runtimeType = runtimeLookup[runtime];
149-
}
150-
if (architecture !== undefined && architecture in architectureLookup) {
151-
architectureType = architectureLookup[architecture];
152-
}
153-
154-
return {
155-
properties,
156-
key,
157-
runtimeType,
158-
runtime,
159-
architecture,
160-
architectureType,
161-
} as LambdaFunction;
162-
})
163-
.filter((lambda) => lambda !== undefined) as LambdaFunction[];
164-
}
165-
166-
function useOrRef(value: undefined | string | { Ref: any }, templateParameterValues: Parameters): undefined | string {
167-
if (!value) return undefined;
168-
if (typeof value === "string") return value;
169-
return templateParameterValues[value.Ref] ?? value;
170-
}
171-
172-
/**
173-
* Apply the provided Lambda layer that corresponds to each Lambda's runtime.
174-
* If a Lambda layer for a given runtime is required but not provided, store an error message with
175-
* that Lambda function's logical id. Return all errors, so that customer can see if they are
176-
* missing more than one required Lambda layer.
177-
*/
178-
export function applyLayers(
179-
region: string,
180-
lambdas: LambdaFunction[],
181-
pythonLayerVersion?: number,
182-
nodeLayerVersion?: number,
183-
dotnetLayerVersion?: number,
184-
javaLayerVersion?: number,
185-
): string[] {
186-
const errors: string[] = [];
187-
lambdas.forEach((lambda) => {
188-
if (lambda.runtimeType === RuntimeType.UNSUPPORTED) {
189-
log.debug(`No Lambda layer available for runtime: ${lambda.runtime}`);
190-
return;
191-
}
192-
193-
let lambdaLibraryLayerArn;
194-
195-
if (lambda.runtimeType === RuntimeType.PYTHON) {
196-
if (pythonLayerVersion === undefined) {
197-
errors.push(getMissingLayerVersionErrorMsg(lambda.key, "Python", "python"));
198-
return;
199-
}
200-
201-
log.debug(`Setting Python Lambda layer for ${lambda.key}`);
202-
lambdaLibraryLayerArn = getLambdaLibraryLayerArn(region, pythonLayerVersion, lambda.runtime, lambda.architecture);
203-
addLayer(lambdaLibraryLayerArn, lambda);
204-
}
205-
206-
if (lambda.runtimeType === RuntimeType.NODE) {
207-
if (nodeLayerVersion === undefined) {
208-
errors.push(getMissingLayerVersionErrorMsg(lambda.key, "Node.js", "node"));
209-
return;
210-
}
211-
212-
log.debug(`Setting Node Lambda layer for ${lambda.key}`);
213-
lambdaLibraryLayerArn = getLambdaLibraryLayerArn(region, nodeLayerVersion, lambda.runtime, lambda.architecture);
214-
addLayer(lambdaLibraryLayerArn, lambda);
215-
}
216-
217-
if (lambda.runtimeType === RuntimeType.DOTNET) {
218-
if (dotnetLayerVersion === undefined) {
219-
errors.push(getMissingLayerVersionErrorMsg(lambda.key, ".Net", "dotnet"));
220-
return;
221-
}
222-
223-
log.debug(`Setting .NET Lambda layer for ${lambda.key}`);
224-
lambdaLibraryLayerArn = getLambdaLibraryLayerArn(region, dotnetLayerVersion, lambda.runtime, lambda.architecture);
225-
addLayer(lambdaLibraryLayerArn, lambda);
226-
}
227-
228-
if (lambda.runtimeType === RuntimeType.JAVA) {
229-
if (javaLayerVersion === undefined) {
230-
errors.push(getMissingLayerVersionErrorMsg(lambda.key, "Java", "java"));
231-
return;
232-
}
233-
234-
log.debug(`Setting Java Lambda layer for ${lambda.key}`);
235-
lambdaLibraryLayerArn = getLambdaLibraryLayerArn(region, javaLayerVersion, lambda.runtime, lambda.architecture);
236-
addLayer(lambdaLibraryLayerArn, lambda);
237-
}
238-
});
239-
return errors;
240-
}
241-
242-
export function applyExtensionLayer(
243-
region: string,
244-
lambdas: LambdaFunction[],
245-
extensionLayerVersion?: number,
246-
): string[] {
247-
const errors: string[] = [];
248-
lambdas.forEach((lambda) => {
249-
let lambdaExtensionLayerArn;
250-
251-
if (extensionLayerVersion === undefined) {
252-
errors.push(getMissingExtensionLayerVersionErrorMsg(lambda.key));
253-
return;
254-
}
255-
256-
log.debug(`Setting Lambda Extension layer for ${lambda.key}`);
257-
lambdaExtensionLayerArn = getExtensionLayerArn(region, extensionLayerVersion, lambda.architecture);
258-
addLayer(lambdaExtensionLayerArn, lambda);
259-
});
260-
return errors;
261-
}
262-
263-
function addLayer(layerArn: string, lambda: LambdaFunction): void {
264-
if (layerArn === undefined) {
265-
return;
266-
}
267-
268-
const currentLayers = lambda.properties.Layers ?? [];
269-
const newLayers = getNewLayers(layerArn, currentLayers);
270-
lambda.properties.Layers = newLayers;
271-
}
272-
273-
// Return the layers arr or object with layerArn added
274-
export function getNewLayers(layerArn: string, currentLayers: LambdaLayersProperty): LambdaLayersProperty {
275-
if (Array.isArray(currentLayers)) {
276-
if (currentLayers.includes(layerArn)) {
277-
// Don't change layers if the layerArn is already present
278-
return currentLayers;
279-
}
280-
return [...currentLayers, layerArn];
281-
}
282-
283-
// CFN Fn::If conditional values are arrays with three items:
284-
// 1. condition, 2. output if condition is true, 3. output if false
285-
const conditionalValues = currentLayers[CFN_IF_FUNCTION_STRING];
286-
287-
// If this is not an if statement, log a warning and do not add layer
288-
if (conditionalValues === undefined) {
289-
console.warn("Unrecognized object in Layers definition. Cannot " + `add layer ${layerArn}`);
290-
return currentLayers;
291-
}
292-
293-
if (conditionalValues.length !== 3) {
294-
console.warn("Conditional in Layers definition does not have 3 items. Cannot " + `add layer ${layerArn}`);
295-
return currentLayers;
296-
}
297-
const [conditionalName, layersIfTrue, layersIfFalse] = conditionalValues;
298-
299-
const newLayersIfTrue = getNewLayers(layerArn, layersIfTrue);
300-
const newLayersIfFalse = getNewLayers(layerArn, layersIfFalse);
301-
302-
return { [CFN_IF_FUNCTION_STRING]: [conditionalName, newLayersIfTrue, newLayersIfFalse] };
303-
}
304-
305-
export function getLambdaLibraryLayerArn(
306-
region: string,
307-
version: number,
308-
runtime: string,
309-
architecture: string,
310-
): string {
311-
const layerName = layerNameLookup[architectureLookup[architecture]][runtime];
312-
const isGovCloud = region === "us-gov-east-1" || region === "us-gov-west-1";
313-
314-
// if this is a GovCloud region, use the GovCloud lambda layer
315-
if (isGovCloud) {
316-
log.debug("GovCloud region detected, using GovCloud Lambda layer");
317-
return `arn:aws-us-gov:lambda:${region}:${DD_GOV_ACCOUNT_ID}:layer:${layerName}:${version}`;
318-
}
319-
return `arn:aws:lambda:${region}:${DD_ACCOUNT_ID}:layer:${layerName}:${version}`;
320-
}
321-
322-
export function getExtensionLayerArn(region: string, version: number, architecture: string): string {
323-
const layerName = architectureToExtensionLayerName[architecture];
324-
325-
const isGovCloud = region === "us-gov-east-1" || region === "us-gov-west-1";
326-
327-
// if this is a GovCloud region, use the GovCloud lambda layer
328-
if (isGovCloud) {
329-
log.debug("GovCloud region detected, using GovCloud Lambda layer");
330-
return `arn:aws-us-gov:lambda:${region}:${DD_GOV_ACCOUNT_ID}:layer:${layerName}:${version}`;
331-
}
332-
return `arn:aws:lambda:${region}:${DD_ACCOUNT_ID}:layer:${layerName}:${version}`;
333-
}
334-
335-
export function getMissingLayerVersionErrorMsg(
336-
functionKey: string,
337-
formalRuntime: string,
338-
paramRuntime: string,
339-
): string {
340-
return (
341-
`Resource ${functionKey} has a ${formalRuntime} runtime, but no ${formalRuntime} Lambda Library version was provided. ` +
342-
`Please add the '${paramRuntime}LayerVersion' parameter for the Datadog serverless macro.`
343-
);
344-
}
345-
346-
export function getMissingExtensionLayerVersionErrorMsg(functionKey: string): string {
347-
return (
348-
`Resource ${functionKey} has been configured to apply the extension laybe but no extension version was provided. ` +
349-
`Please add the 'extensionLayerVersion' parameter for the Datadog serverless macro`
350-
);
351-
}

serverless/test/lambda/layer.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ describe("findLambdas", () => {
7474
Python311Function: mockFunctionResource("python3.11", ["x86_64"]),
7575
Python312Function: mockFunctionResource("python3.12", ["x86_64"]),
7676
Python313Function: mockFunctionResource("python3.13", ["x86_64"]),
77+
Python314Function: mockFunctionResource("python3.14", ["x86_64"]),
7778
GoFunction: mockFunctionResource("go1.10", ["x86_64"]),
7879
RefFunction: mockFunctionResource({ Ref: "ValueRef" }, ["arm64"]),
7980
};
@@ -102,6 +103,7 @@ describe("findLambdas", () => {
102103
mockLambdaFunction("Python311Function", "python3.11", RuntimeType.PYTHON, "x86_64", ArchitectureType.x86_64),
103104
mockLambdaFunction("Python312Function", "python3.12", RuntimeType.PYTHON, "x86_64", ArchitectureType.x86_64),
104105
mockLambdaFunction("Python313Function", "python3.13", RuntimeType.PYTHON, "x86_64", ArchitectureType.x86_64),
106+
mockLambdaFunction("Python314Function", "python3.14", RuntimeType.PYTHON, "x86_64", ArchitectureType.x86_64),
105107
mockLambdaFunction("GoFunction", "go1.10", RuntimeType.UNSUPPORTED, "x86_64", ArchitectureType.x86_64),
106108
mockLambdaFunction("RefFunction", "nodejs14.x", RuntimeType.NODE, "arm64", ArchitectureType.ARM64, {
107109
Ref: "ValueRef",

0 commit comments

Comments
 (0)