Skip to content

Commit

Permalink
switch from init to option enablement
Browse files Browse the repository at this point in the history
  • Loading branch information
RomainMuller committed Oct 31, 2023
1 parent 31a466b commit 15f432b
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 8 deletions.
29 changes: 29 additions & 0 deletions lambda/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import (
"fmt"
"io"
"io/ioutil" // nolint:staticcheck
"os"
"reflect"
"strings"
"syscall"

"github.com/aws/aws-lambda-go/lambda/handlertrace"
)
Expand All @@ -28,6 +30,8 @@ type handlerOptions struct {
jsonResponseIndentValue string
enableSIGTERM bool
sigtermCallbacks []func()
enableExecWrapper bool
execWrapperCallbacks []func()
}

type Option func(*handlerOptions)
Expand Down Expand Up @@ -102,6 +106,28 @@ func WithEnableSIGTERM(callbacks ...func()) Option {
})
}

// WithEnableExecWrapper enables applying the value of the AWS_LAMBDA_EXEC_WRAPPER environment
// variable. If this fariable is set, the current process will be re-started, wrapped under the
// specified wrapper script. Optionally, an array of callback functions to run before restarting
// the process may be provided.
//
// Usage:
//
// lambda.StartWithOptions(
// func (event any) (any, error) {
// return event, nil
// },
// lambda.WithEnableExecWrapper(func(){
// log.Print("[AWS_LAMBDA_EXEC_WRAPPER] process is about to be re-started...")
// }),
// )
func WithEnableExecWrapper(callbacks ...func()) Option {
return Option(func(h *handlerOptions) {
h.execWrapperCallbacks = append(h.execWrapperCallbacks, callbacks...)
h.enableExecWrapper = true
})
}

// handlerTakesContext returns whether the handler takes a context.Context as its first argument.
func handlerTakesContext(handler reflect.Type) (bool, error) {
switch handler.NumIn() {
Expand Down Expand Up @@ -184,6 +210,9 @@ func newHandler(handlerFunc interface{}, options ...Option) *handlerOptions {
for _, option := range options {
option(h)
}
if h.enableExecWrapper {
execAWSLambdaExecWrapper(os.Getenv, syscall.Exec, h.execWrapperCallbacks)

Check failure on line 214 in lambda/handler.go

View workflow job for this annotation

GitHub Actions / run tests with code coverage (1.18)

undefined: execAWSLambdaExecWrapper
}
if h.enableSIGTERM {
enableSIGTERM(h.sigtermCallbacks)
}
Expand Down
15 changes: 7 additions & 8 deletions lambda/wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,29 @@ package lambda
import (
"log"
"os"
"syscall"
)

const awsLambdaExecWrapper = "AWS_LAMBDA_EXEC_WRAPPER"

func init() {
// Honor the AWS_LAMBDA_EXEC_WRAPPER configuration at startup, trying to emulate
// the behavior of managed runtimes, as this configuration is otherwise not applied
// by provided runtimes (or go1.x).
execAWSLambdaExecWrapper(os.Getenv, syscall.Exec)
}

// execAWSLambdaExecWrapper applies the AWS_LAMBDA_EXEC_WRAPPER environment variable.
// If AWS_LAMBDA_EXEC_WRAPPER is defined, replace the current process by spawning
// it with the current process' arguments (including the program name). If the call
// to syscall.Exec fails, this aborts the process with a fatal error.
func execAWSLambdaExecWrapper(
getenv func(key string) string,
sysExec func(argv0 string, argv []string, envv []string) error,
callbacks []func(),
) {
wrapper := getenv(awsLambdaExecWrapper)
if wrapper == "" {
return
}

// Execute the provided callbacks before re-starting the process...
for _, callback := range callbacks {
callback()
}

// The AWS_LAMBDA_EXEC_WRAPPER variable is blanked before replacing the process
// in order to avoid endlessly restarting the process.
env := append(os.Environ(), awsLambdaExecWrapper+"=")
Expand Down
10 changes: 10 additions & 0 deletions lambda/wrapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,32 @@ import (
)

func TestExecAwsLambdaExecWrapperNotSet(t *testing.T) {
var called bool
callback := func() { called = true }

exec, execCalled := mockExec(t, "<nope>")
execAWSLambdaExecWrapper(
mockedGetenv(t, ""),
exec,
[]func(){callback},
)
require.False(t, *execCalled)
require.False(t, called)
}

func TestExecAwsLambdaExecWrapperSet(t *testing.T) {
var called bool
callback := func() { called = true }

wrapper := "/path/to/wrapper/entry/point"
exec, execCalled := mockExec(t, wrapper)
execAWSLambdaExecWrapper(
mockedGetenv(t, wrapper),
exec,
[]func(){callback},
)
require.True(t, *execCalled)
require.True(t, called)
}

func mockExec(t *testing.T, value string) (mock func(string, []string, []string) error, called *bool) {
Expand Down

0 comments on commit 15f432b

Please sign in to comment.