@@ -7,8 +7,10 @@ import {
7
7
ApiGatewayV2Client ,
8
8
GetApisCommand ,
9
9
} from "@aws-sdk/client-apigatewayv2" ;
10
+ import pLimit from "p-limit" ;
10
11
import { acquireLock , releaseLock } from "../lib/locks" ;
11
12
import Logger from "../lib/logger" ;
13
+ import { put } from "../lib/database" ;
12
14
13
15
const supportedRuntimes = [ "nodejs16.x" , "nodejs18.x" , "nodejs20.x" ] ;
14
16
const lambdaExecWrapper = "/opt/nodejs/tracer_wrapper" ;
@@ -51,6 +53,48 @@ const getApiEndpoint = async () => {
51
53
return item . ApiEndpoint ?. replace ( "https://" , "" ) ;
52
54
} ;
53
55
56
+ const updateLambda = async ( lambda , arnBase , edgeEndpoint ) => {
57
+ const updateFunctionConfigurationCommand =
58
+ new UpdateFunctionConfigurationCommand ( {
59
+ FunctionName : lambda . FunctionName ,
60
+ Layers : [
61
+ ...( lambda . Layers || [ ] )
62
+ . map ( ( layer ) => layer . Arn )
63
+ . filter ( ( arn ) => ! arn . startsWith ( arnBase ) ) ,
64
+ process . env . LAMBDA_LAYER_ARN ,
65
+ ] ,
66
+ Environment : {
67
+ ...( lambda . Environment || { } ) ,
68
+ Variables : {
69
+ ...( lambda . Environment ?. Variables || { } ) ,
70
+ AUTO_TRACE_HOST : edgeEndpoint ,
71
+ AWS_LAMBDA_EXEC_WRAPPER : lambdaExecWrapper ,
72
+ } ,
73
+ } ,
74
+ } ) ;
75
+
76
+ const res = await new LambdaClient ( ) . send ( updateFunctionConfigurationCommand ) ;
77
+ logger . info ( res ) ;
78
+ } ;
79
+
80
+ const saveFunctionInfo = async ( lambda , traceStatus ) => {
81
+ await put (
82
+ {
83
+ pk : `function#${ process . env . AWS_REGION } #${ lambda . FunctionName } ` ,
84
+ sk : `function#${ process . env . AWS_REGION } ` ,
85
+ name : lambda . FunctionName ,
86
+ type : "function" ,
87
+ runtime : lambda . Runtime ,
88
+ region : process . env . AWS_REGION ,
89
+ arn : lambda . FunctionArn ,
90
+ memoryAllocated : lambda . MemorySize ,
91
+ timeout : lambda . Timeout ,
92
+ traceStatus,
93
+ } ,
94
+ true ,
95
+ ) ;
96
+ } ;
97
+
54
98
export const autoTrace = async ( ) => {
55
99
// Check if we know our Lambda Layer ARN
56
100
const arn = process . env . LAMBDA_LAYER_ARN ;
@@ -71,69 +115,70 @@ export const autoTrace = async () => {
71
115
72
116
// List all the lambda functions in the AWS account
73
117
const lambdas = await getAccountLambdas ( ) ;
74
-
75
- // Find all lambdas that need a layer added
76
- let lambdasWithoutLayer = lambdas . filter ( ( lambda ) => {
77
- const layers = lambda . Layers || [ ] ;
78
- const envVars = lambda . Environment ?. Variables || { } ;
79
-
80
- const isTraceStack = envVars . LAMBDA_LAYER_ARN === arn ;
81
- const isUpdating = lambda . LastUpdateStatus === "InProgress" ;
82
- const hasDisableEnvVar = envVars . AUTO_TRACE_EXCLUDE ;
83
- const hasOtherWrapper =
84
- envVars . AWS_LAMBDA_EXEC_WRAPPER &&
85
- envVars . AWS_LAMBDA_EXEC_WRAPPER !== lambdaExecWrapper ;
86
- const hasSupportedRuntime = supportedRuntimes . includes ( lambda . Runtime ) ;
87
- const hasLayer = layers . find ( ( { Arn } ) => Arn . startsWith ( arnBase ) ) ;
88
- const hasUpdate = layers . find (
89
- ( { Arn } ) => Arn . startsWith ( arnBase ) && Arn !== arn ,
90
- ) ;
91
-
92
- return (
93
- ( ! hasLayer || hasUpdate ) &&
94
- ! hasDisableEnvVar &&
95
- ! hasOtherWrapper &&
96
- ! isUpdating &&
97
- ! isTraceStack &&
98
- hasSupportedRuntime
99
- ) ;
100
- } ) ;
101
-
102
- logger . info ( `Found ${ lambdasWithoutLayer . length } lambdas to update` ) ;
103
-
104
- for ( const lambda of lambdasWithoutLayer ) {
105
- try {
106
- const updateFunctionConfigurationCommand =
107
- new UpdateFunctionConfigurationCommand ( {
108
- FunctionName : lambda . FunctionName ,
109
- Layers : [
110
- ...( lambda . Layers || [ ] )
111
- . map ( ( layer ) => layer . Arn )
112
- . filter ( ( arn ) => ! arn . startsWith ( arnBase ) ) ,
113
- process . env . LAMBDA_LAYER_ARN ,
114
- ] ,
115
- Environment : {
116
- ...( lambda . Environment || { } ) ,
117
- Variables : {
118
- ...( lambda . Environment ?. Variables || { } ) ,
119
- AUTO_TRACE_HOST : edgeEndpoint ,
120
- AWS_LAMBDA_EXEC_WRAPPER : lambdaExecWrapper ,
121
- } ,
122
- } ,
123
- } ) ;
124
-
125
- const res = await new LambdaClient ( ) . send (
126
- updateFunctionConfigurationCommand ,
127
- ) ;
128
- logger . info ( res ) ;
129
-
130
- logger . info ( `✓ Updated ${ lambda . FunctionName } ` ) ;
131
- // TODO: save function info in DynamoDB
132
- } catch ( e ) {
133
- logger . warn ( `✗ Failed to update ${ lambda . FunctionName } ` ) ;
134
- logger . warn ( e ) ;
135
- }
136
- }
118
+ logger . info ( `Found ${ lambdas . length } lambdas in the account` ) ;
119
+
120
+ // Update qualifying lambdas
121
+ const limit = pLimit ( 4 ) ;
122
+ await Promise . all (
123
+ lambdas . map ( ( lambda ) =>
124
+ limit ( async ( ) => {
125
+ const layers = lambda . Layers || [ ] ;
126
+ const envVars = lambda . Environment ?. Variables || { } ;
127
+
128
+ const isTraceStack = envVars . LAMBDA_LAYER_ARN === arn ;
129
+ const isUpdating = lambda . LastUpdateStatus === "InProgress" ;
130
+ const hasDisableEnvVar = envVars . AUTO_TRACE_EXCLUDE ;
131
+ const hasOtherWrapper =
132
+ envVars . AWS_LAMBDA_EXEC_WRAPPER &&
133
+ envVars . AWS_LAMBDA_EXEC_WRAPPER !== lambdaExecWrapper ;
134
+ const hasSupportedRuntime = supportedRuntimes . includes ( lambda . Runtime ) ;
135
+ const hasLayer = layers . find ( ( { Arn } ) => Arn . startsWith ( arnBase ) ) ;
136
+ const hasUpdate = layers . find (
137
+ ( { Arn } ) => Arn . startsWith ( arnBase ) && Arn !== arn ,
138
+ ) ;
139
+
140
+ if ( hasLayer && ! hasUpdate ) {
141
+ logger . info ( `- ${ lambda . FunctionName } already has the latest layer` ) ;
142
+ await saveFunctionInfo ( lambda , "enabled" ) ;
143
+ return ;
144
+ }
145
+ if ( hasDisableEnvVar ) {
146
+ logger . info ( `- ${ lambda . FunctionName } has AUTO_TRACE_EXCLUDE` ) ;
147
+ await saveFunctionInfo ( lambda , "excluded" ) ;
148
+ return ;
149
+ }
150
+ if ( hasOtherWrapper ) {
151
+ logger . info ( `- ${ lambda . FunctionName } has a custom wrapper` ) ;
152
+ await saveFunctionInfo ( lambda , "custom_wrapper" ) ;
153
+ return ;
154
+ }
155
+ if ( isUpdating ) {
156
+ logger . info ( `- ${ lambda . FunctionName } is already updating` ) ;
157
+ await saveFunctionInfo ( lambda , "update_in_progress" ) ;
158
+ return ;
159
+ }
160
+ if ( isTraceStack ) {
161
+ logger . info ( `- ${ lambda . FunctionName } is part of TraceStack` ) ;
162
+ return ;
163
+ }
164
+ if ( ! hasSupportedRuntime ) {
165
+ logger . info ( `- ${ lambda . FunctionName } has an unsupported runtime` ) ;
166
+ await saveFunctionInfo ( lambda , "unsupported_runtime" ) ;
167
+ return ;
168
+ }
169
+
170
+ try {
171
+ await updateLambda ( lambda , arnBase , edgeEndpoint ) ;
172
+
173
+ logger . info ( `✓ Updated ${ lambda . FunctionName } ` ) ;
174
+ await saveFunctionInfo ( lambda , "enabled" ) ;
175
+ } catch ( e ) {
176
+ logger . warn ( `✗ Failed to update ${ lambda . FunctionName } ` , e ) ;
177
+ await saveFunctionInfo ( lambda , "error" ) ;
178
+ }
179
+ } ) ,
180
+ ) ,
181
+ ) ;
137
182
138
183
await releaseLock ( "auto-trace" ) ;
139
184
} ;
0 commit comments