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
-
45
1
export const runtimeLookup : { [ key : string ] : RuntimeType } = {
46
2
dotnet6 : RuntimeType . DOTNET ,
47
3
dotnet8 : RuntimeType . DOTNET ,
@@ -65,6 +21,7 @@ export const runtimeLookup: { [key: string]: RuntimeType } = {
65
21
"python3.11" : RuntimeType . PYTHON ,
66
22
"python3.12" : RuntimeType . PYTHON ,
67
23
"python3.13" : RuntimeType . PYTHON ,
24
+ "python3.14" : RuntimeType . PYTHON ,
68
25
} ;
69
26
70
27
export const layerNameLookup : { [ key in ArchitectureType ] : { [ key : string ] : string } } = {
@@ -91,6 +48,7 @@ export const layerNameLookup: { [key in ArchitectureType]: { [key: string]: stri
91
48
"python3.11" : "Datadog-Python311" ,
92
49
"python3.12" : "Datadog-Python312" ,
93
50
"python3.13" : "Datadog-Python313" ,
51
+ "python3.14" : "Datadog-Python314" ,
94
52
} ,
95
53
[ ArchitectureType . ARM64 ] : {
96
54
dotnet6 : "dd-trace-dotnet-ARM" ,
@@ -112,240 +70,6 @@ export const layerNameLookup: { [key in ArchitectureType]: { [key: string]: stri
112
70
"python3.11" : "Datadog-Python311-ARM" ,
113
71
"python3.12" : "Datadog-Python312-ARM" ,
114
72
"python3.13" : "Datadog-Python313-ARM" ,
73
+ "python3.14" : "Datadog-Python314-ARM" ,
115
74
} ,
116
75
} ;
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
- }
0 commit comments