@@ -4,28 +4,41 @@ package main
4
4
5
5
import (
6
6
"context"
7
+ "fmt"
8
+ "net"
7
9
"os"
10
+ "os/signal"
8
11
"runtime/debug"
9
12
"strconv"
10
13
"strings"
14
+ "syscall"
11
15
16
+ "github.com/aws/aws-sdk-go-v2/config"
17
+ "github.com/localstack/lambda-runtime-init/internal/aws/lambda"
12
18
"github.com/localstack/lambda-runtime-init/internal/aws/xray"
13
19
"github.com/localstack/lambda-runtime-init/internal/bootstrap"
20
+ "github.com/localstack/lambda-runtime-init/internal/events"
14
21
"github.com/localstack/lambda-runtime-init/internal/hotreloading"
22
+ "github.com/localstack/lambda-runtime-init/internal/localstack"
15
23
"github.com/localstack/lambda-runtime-init/internal/logging"
24
+ "github.com/localstack/lambda-runtime-init/internal/sandbox"
25
+
16
26
"github.com/localstack/lambda-runtime-init/internal/server"
27
+
28
+ "github.com/localstack/lambda-runtime-init/internal/supervisor"
17
29
"github.com/localstack/lambda-runtime-init/internal/tracing"
18
30
"github.com/localstack/lambda-runtime-init/internal/utils"
19
31
log "github.com/sirupsen/logrus"
20
32
"go.amzn.com/lambda/core/directinvoke"
33
+ "go.amzn.com/lambda/interop"
21
34
"go.amzn.com/lambda/rapidcore"
22
35
)
23
36
24
- func InitLsOpts () * server. LsOpts {
25
- return & server. LsOpts {
37
+ func InitLsOpts () * localstack. Config {
38
+ return & localstack. Config {
26
39
// required
27
- RuntimeEndpoint : utils .GetEnvOrDie ("LOCALSTACK_RUNTIME_ENDPOINT" ),
28
- RuntimeId : utils .GetEnvOrDie ("LOCALSTACK_RUNTIME_ID" ),
40
+ RuntimeEndpoint : utils .MustGetEnv ("LOCALSTACK_RUNTIME_ENDPOINT" ),
41
+ RuntimeId : utils .MustGetEnv ("LOCALSTACK_RUNTIME_ID" ),
29
42
AccountId : utils .GetEnvWithDefault ("LOCALSTACK_FUNCTION_ACCOUNT_ID" , "000000000000" ),
30
43
// optional with default
31
44
InteropPort : utils .GetEnvWithDefault ("LOCALSTACK_INTEROP_PORT" , "9563" ),
@@ -45,6 +58,19 @@ func InitLsOpts() *server.LsOpts {
45
58
}
46
59
}
47
60
61
+ func InitFunctionConfig () lambda.FunctionConfig {
62
+ return lambda.FunctionConfig {
63
+ FunctionName : utils .GetEnvWithDefault ("AWS_LAMBDA_FUNCTION_NAME" , "test_function" ),
64
+ FunctionVersion : utils .GetEnvWithDefault ("AWS_LAMBDA_FUNCTION_VERSION" , "$LATEST" ),
65
+ FunctionTimeoutSec : utils .GetEnvWithDefault ("AWS_LAMBDA_FUNCTION_TIMEOUT" , "30" ),
66
+ InitializationType : utils .GetEnvWithDefault ("AWS_LAMBDA_INITIALIZATION_TYPE" , "on-demand" ),
67
+ LogGroupName : utils .GetEnvWithDefault ("AWS_LAMBDA_LOG_GROUP_NAME" , "/aws/lambda/Functions" ),
68
+ LogStreamName : utils .GetEnvWithDefault ("AWS_LAMBDA_LOG_STREAM_NAME" , "$LATEST" ),
69
+ FunctionMemorySizeMb : utils .GetEnvWithDefault ("AWS_LAMBDA_FUNCTION_MEMORY_SIZE" , "128" ),
70
+ FunctionHandler : utils .GetEnvWithDefault ("AWS_LAMBDA_FUNCTION_HANDLER" , os .Getenv ("_HANDLER" )),
71
+ }
72
+ }
73
+
48
74
// UnsetLsEnvs unsets environment variables specific to LocalStack to achieve better runtime parity with AWS
49
75
func UnsetLsEnvs () {
50
76
unsetList := [... ]string {
@@ -75,42 +101,54 @@ func UnsetLsEnvs() {
75
101
}
76
102
}
77
103
104
+ func setupUserPermissions (user string ) {
105
+ logger := utils .UserLogger ()
106
+ // Switch to non-root user and drop root privileges
107
+ if utils .IsRootUser () && user != "" && user != "root" {
108
+ uid := 993
109
+ gid := 990
110
+ utils .AddUser (user , uid , gid )
111
+ if err := os .Chown ("/tmp" , uid , gid ); err != nil {
112
+ log .WithError (err ).Warn ("Could not change owner of directory /tmp." )
113
+ }
114
+ logger .Debug ("Process running as root user." )
115
+ err := utils .DropPrivileges (user )
116
+ if err != nil {
117
+ log .WithError (err ).Warn ("Could not drop root privileges." )
118
+ } else {
119
+ logger .Debug ("Process running as non-root user." )
120
+ }
121
+ }
122
+ }
123
+
78
124
func main () {
79
125
// we're setting this to the same value as in the official RIE
80
126
debug .SetGCPercent (33 )
81
127
82
128
// configuration parsing
83
129
lsOpts := InitLsOpts ()
130
+ functionConf := InitFunctionConfig ()
131
+ awsEnvConf , _ := config .NewEnvConfig ()
132
+ awsEnvConf .Credentials .AccountID = lsOpts .AccountId
133
+
84
134
UnsetLsEnvs ()
85
135
86
136
// set up logging following the Logrus logging levels: https://github.com/sirupsen/logrus#level-logging
87
137
log .SetReportCaller (true )
88
138
// https://docs.aws.amazon.com/xray/latest/devguide/xray-daemon-configuration.html
89
- xRayLogLevel := "info"
90
- switch lsOpts .InitLogLevel {
91
- case "trace" :
139
+
140
+ logLevel , err := log .ParseLevel (lsOpts .InitLogLevel )
141
+ if err != nil {
142
+ log .Fatal ("Invalid value for LOCALSTACK_INIT_LOG_LEVEL" )
143
+ }
144
+ log .SetLevel (logLevel )
145
+
146
+ xRayLogLevel := lsOpts .InitLogLevel
147
+ switch logLevel {
148
+ case log .TraceLevel :
92
149
log .SetFormatter (& log.JSONFormatter {})
93
- log .SetLevel (log .TraceLevel )
94
- xRayLogLevel = "debug"
95
- case "debug" :
96
- log .SetLevel (log .DebugLevel )
97
- xRayLogLevel = "debug"
98
- case "info" :
99
- log .SetLevel (log .InfoLevel )
100
- case "warn" :
101
- log .SetLevel (log .WarnLevel )
102
- xRayLogLevel = "warn"
103
- case "error" :
104
- log .SetLevel (log .ErrorLevel )
105
- xRayLogLevel = "error"
106
- case "fatal" :
107
- log .SetLevel (log .FatalLevel )
150
+ case log .ErrorLevel , log .FatalLevel , log .PanicLevel :
108
151
xRayLogLevel = "error"
109
- case "panic" :
110
- log .SetLevel (log .PanicLevel )
111
- xRayLogLevel = "error"
112
- default :
113
- log .Fatal ("Invalid value for LOCALSTACK_INIT_LOG_LEVEL" )
114
152
}
115
153
116
154
// patch MaxPayloadSize
@@ -119,6 +157,10 @@ func main() {
119
157
log .Panicln ("Please specify a number for LOCALSTACK_MAX_PAYLOAD_SIZE" )
120
158
}
121
159
directinvoke .MaxDirectResponseSize = int64 (payloadSize )
160
+ if directinvoke .MaxDirectResponseSize > interop .MaxPayloadSize {
161
+ log .Infof ("Large response size detected (%d bytes), forcing streaming mode" , directinvoke .MaxDirectResponseSize )
162
+ directinvoke .InvokeResponseMode = interop .InvokeResponseModeStreaming
163
+ }
122
164
123
165
// download code archive if env variable is set
124
166
if err := utils .DownloadCodeArchives (lsOpts .CodeArchives ); err != nil {
@@ -133,97 +175,119 @@ func main() {
133
175
bootstrap , handler := bootstrap .GetBootstrap (os .Args )
134
176
135
177
// Switch to non-root user and drop root privileges
136
- if utils .IsRootUser () && lsOpts .User != "" && lsOpts .User != "root" {
137
- uid := 993
138
- gid := 990
139
- utils .AddUser (lsOpts .User , uid , gid )
140
- if err := os .Chown ("/tmp" , uid , gid ); err != nil {
141
- log .Warnln ("Could not change owner of directory /tmp:" , err )
142
- }
143
- utils .UserLogger ().Debugln ("Process running as root user." )
144
- err := utils .DropPrivileges (lsOpts .User )
145
- if err != nil {
146
- log .Warnln ("Could not drop root privileges." , err )
147
- } else {
148
- utils .UserLogger ().Debugln ("Process running as non-root user." )
149
- }
150
- }
178
+ setupUserPermissions (lsOpts .User )
151
179
152
- // file watcher for hot-reloading
153
- fileWatcherContext , cancelFileWatcher := context .WithCancel (context .Background ())
180
+ ctx , stop := signal .NotifyContext (context .Background (), syscall .SIGINT , syscall .SIGTERM )
181
+ defer stop ()
182
+
183
+ // LocalStack client used for sending callbacks
184
+ lsClient := localstack .NewLocalStackClient (lsOpts .RuntimeEndpoint , lsOpts .RuntimeId )
185
+
186
+ // Services required for Sandbox environment
187
+ interopServer := server .NewInteropServer (lsClient )
154
188
155
189
logCollector := logging .NewLogCollector ()
156
190
localStackLogsEgressApi := logging .NewLocalStackLogsEgressAPI (logCollector )
157
191
tracer := tracing .NewLocalStackTracer ()
192
+ lsEventsAPI := events .NewLocalStackEventsAPI (lsClient )
193
+ localStackSupv := supervisor .NewLocalStackSupervisor (ctx , lsEventsAPI )
158
194
159
195
// build sandbox
160
- sandbox := rapidcore .
196
+ builder := rapidcore .
161
197
NewSandboxBuilder ().
162
- //SetTracer(tracer).
163
- AddShutdownFunc (func () {
164
- log .Debugln ("Stopping file watcher" )
165
- cancelFileWatcher ()
166
- }).
167
- SetExtensionsFlag (true ).
168
- SetInitCachingFlag (true ).
198
+ SetTracer (tracer ).
199
+ SetEventsAPI (lsEventsAPI ).
200
+ SetHandler (handler ).
201
+ SetInteropServer (interopServer ).
169
202
SetLogsEgressAPI (localStackLogsEgressApi ).
170
- SetTracer (tracer )
203
+ SetRuntimeAPIAddress ("127.0.0.1:9000" ).
204
+ SetSupervisor (localStackSupv ).
205
+ SetRuntimeFsRootPath ("/" )
206
+
207
+ builder .AddShutdownFunc (func () {
208
+ interopServer .Close ()
209
+ })
210
+
211
+ // Start daemons
212
+
213
+ // file watcher for hot-reloading
214
+ fileWatcherContext , cancelFileWatcher := context .WithCancel (ctx )
215
+ builder .AddShutdownFunc (func () {
216
+ cancelFileWatcher ()
217
+ })
218
+
219
+ go hotreloading .RunHotReloadingListener (interopServer , lsOpts .HotReloadingPaths , fileWatcherContext , lsOpts .FileWatcherStrategy )
171
220
172
- // xray daemon
173
- endpoint := "http://" + lsOpts .LocalstackIP + ":" + lsOpts .EdgePort
221
+ // Start xray daemon
222
+ endpoint := "http://" + net . JoinHostPort ( lsOpts .LocalstackIP , lsOpts .EdgePort )
174
223
xrayConfig := xray .NewConfig (endpoint , xRayLogLevel )
175
224
d := xray .NewDaemon (xrayConfig , lsOpts .EnableXRayTelemetry == "1" )
176
- sandbox .AddShutdownFunc (func () {
225
+ builder .AddShutdownFunc (func () {
177
226
log .Debugln ("Shutting down xray daemon" )
178
227
d .Stop ()
179
228
log .Debugln ("Flushing segments in xray daemon" )
180
229
d .Close ()
181
230
})
182
- d .Run () // async
231
+ d .Run () // served async
183
232
184
- defaultInterop := sandbox .DefaultInteropServer ()
185
- interopServer := server .NewCustomInteropServer (lsOpts , defaultInterop , logCollector )
186
- sandbox .SetInteropServer (interopServer )
187
- if len (handler ) > 0 {
188
- sandbox .SetHandler (handler )
189
- }
190
- exitChan := make (chan struct {})
191
- sandbox .AddShutdownFunc (func () {
192
- exitChan <- struct {}{}
193
- })
233
+ // Populate our interop server
234
+ sandboxCtx , internalStateFn := builder .Create ()
194
235
195
- // initialize all flows and start runtime API
196
- sandboxContext , internalStateFn := sandbox .Create ()
197
- // Populate our custom interop server
198
- interopServer .SetSandboxContext (sandboxContext )
236
+ interopServer .SetSandboxContext (sandboxCtx )
199
237
interopServer .SetInternalStateGetter (internalStateFn )
200
238
201
- // get timeout
202
- invokeTimeoutEnv := utils .GetEnvOrDie ("AWS_LAMBDA_FUNCTION_TIMEOUT" ) // TODO: collect all AWS_* env parsing
203
- invokeTimeoutSeconds , err := strconv .Atoi (invokeTimeoutEnv )
204
- if err != nil {
205
- log .Fatalln (err )
206
- }
207
- go hotreloading .RunHotReloadingListener (interopServer , lsOpts .HotReloadingPaths , fileWatcherContext , lsOpts .FileWatcherStrategy )
239
+ // TODO(gregfurman): The default interops server has a shutdown function added in the builder.
240
+ // This calls sandbox.Reset(), where if sandbox is not set, this will panic.
241
+ // So instead, just set the sandbox to a noop version.
242
+ builder .DefaultInteropServer ().SetSandboxContext (sandbox .NewNoopSandboxContext ())
243
+
244
+ // Create the LocalStack service
245
+ localStackService := server .NewLocalStackService (
246
+ interopServer , logCollector , lsClient , localStackSupv , xrayConfig .Endpoint , lsOpts , functionConf , awsEnvConf ,
247
+ )
248
+ defer localStackService .Close ()
208
249
209
250
// start runtime init. It is important to start `InitHandler` synchronously because we need to ensure the
210
251
// notification channels and status fields are properly initialized before `AwaitInitialized`
211
252
log .Debugln ("Starting runtime init." )
212
- server .InitHandler (sandbox .LambdaInvokeAPI (), utils .GetEnvOrDie ("AWS_LAMBDA_FUNCTION_VERSION" ), int64 (invokeTimeoutSeconds ), bootstrap , lsOpts .AccountId ) // TODO: replace this with a custom init
253
+ if err := localStackService .Initialize (bootstrap ); err != nil {
254
+ log .WithError (err ).Warnf ("Failed to initialize runtime. Initialization will be retried in the next invoke." )
255
+ }
256
+
257
+ invokeServer := server .NewServer (lsOpts .InteropPort , localStackService )
258
+ defer invokeServer .Close ()
259
+
260
+ serverErr := make (chan error , 1 )
261
+ go func () {
262
+ listener , err := net .Listen ("tcp" , fmt .Sprintf (":%s" , lsOpts .InteropPort ))
263
+ if err != nil {
264
+ log .Fatalf ("failed to start LocalStack Lambda Runtime Interface server: %s" , err )
265
+ }
266
+ go func () { serverErr <- invokeServer .Serve (listener ); close (serverErr ) }()
267
+ log .Debugf ("LocalStack API gateway listening on %s" , listener .Addr ().String ())
268
+ }()
213
269
214
270
log .Debugln ("Awaiting initialization of runtime init." )
215
271
if err := interopServer .AwaitInitialized (); err != nil {
216
272
// Error cases: ErrInitDoneFailed or ErrInitResetReceived
217
273
log .Errorln ("Runtime init failed to initialize: " + err .Error () + ". Exiting." )
218
274
// NOTE: Sending the error status to LocalStack is handled beforehand in the custom_interop.go through the
219
275
// callback SendInitErrorResponse because it contains the correct error response payload.
220
- return
276
+ // return
277
+ } else {
278
+ log .Debugln ("Completed initialization of runtime init. Sending status ready to LocalStack." )
279
+ if err := localStackService .SendStatus (localstack .Ready , []byte {}); err != nil {
280
+ log .Fatalln ("Failed to send status ready to LocalStack " + err .Error () + ". Exiting." )
281
+ }
221
282
}
222
283
223
- log .Debugln ("Completed initialization of runtime init. Sending status ready to LocalStack." )
224
- if err := interopServer .SendStatus (server .Ready , []byte {}); err != nil {
225
- log .Fatalln ("Failed to send status ready to LocalStack " + err .Error () + ". Exiting." )
284
+ // Block until context is cancelled OR the server errors out
285
+ select {
286
+ case <- ctx .Done ():
287
+ log .Info ("Shutdown signal received." )
288
+ case <- serverErr :
289
+ if err != nil {
290
+ log .Errorf ("Server error: %v" , err )
291
+ }
226
292
}
227
-
228
- <- exitChan
229
293
}
0 commit comments