Skip to content

Commit 1766dc2

Browse files
committed
feat: apply $AWS_LAMBDA_EXEC_WRAPPER if set
Managed AWS Lambda runtimes wrap the function's runtime entry using a script specified by the `AWS_LAMBDA_EXEC_WRAPPER` environment variable, however this is not performed by provided runtimes (`provided`, `provided.al2`, `go1.x`). This adds an `init` function that re-startes the current process after wrapping it in the wrapper to emulate the behavior of other lambda runtimes, although this will cause initialization of some other go packages to be run twice, which will have an impact on cold start times. See also: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-modify.html#runtime-wrapper Fixes aws#523
1 parent 771b391 commit 1766dc2

File tree

2 files changed

+102
-0
lines changed

2 files changed

+102
-0
lines changed

lambda/wrapper.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2016-present Datadog, Inc.
5+
6+
// Specify the noexecwrapper build tag to remove the wrapper tampoline from
7+
// this library if it is undesirable.
8+
//go:build unix && !noexecwrapper
9+
10+
package lambda
11+
12+
import (
13+
"log"
14+
"os"
15+
"syscall"
16+
)
17+
18+
const awsLambdaExecWrapper = "AWS_LAMBDA_EXEC_WRAPPER"
19+
20+
func init() {
21+
// Honor the AWS_LAMBDA_EXEC_WRAPPER configuration at startup, trying to emulate
22+
// the behavior of managed runtimes, as this configuration is otherwise not applied
23+
// by provided runtimes (or go1.x).
24+
execAwsLambdaExecWrapper(os.Getenv, syscall.Exec)
25+
}
26+
27+
// If AWS_LAMBDA_EXEC_WRAPPER is defined, replace the current process by spawning
28+
// it with the current process' arguments (including the program name). If the call
29+
// to syscall.Exec fails, this aborts the process with a fatal error.
30+
func execAwsLambdaExecWrapper(
31+
getenv func(key string) string,
32+
sysExec func(argv0 string, argv []string, envv []string) error,
33+
) {
34+
wrapper := getenv(awsLambdaExecWrapper)
35+
if wrapper == "" {
36+
return
37+
}
38+
39+
// The AWS_LAMBDA_EXEC_WRAPPER variable is blanked before replacing the process
40+
// in order to avoid endlessly restarting the process.
41+
env := append(os.Environ(), awsLambdaExecWrapper+"=")
42+
if err := sysExec(wrapper, append([]string{wrapper}, os.Args...), env); err != nil {
43+
log.Fatalf("failed to sysExec() %s=%s: %v", awsLambdaExecWrapper, wrapper, err)
44+
}
45+
}

lambda/wrapper_test.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2016-present Datadog, Inc.
5+
6+
//go:build unix && !noexecwrapper
7+
8+
package lambda
9+
10+
import (
11+
"os"
12+
"testing"
13+
14+
"github.com/stretchr/testify/require"
15+
)
16+
17+
func TestExecAwsLambdaExecWrapperNotSet(t *testing.T) {
18+
exec, execCalled := mockExec(t, "<nope>")
19+
execAwsLambdaExecWrapper(
20+
mockedGetenv(t, ""),
21+
exec,
22+
)
23+
require.False(t, *execCalled)
24+
}
25+
26+
func TestExecAwsLambdaExecWrapperSet(t *testing.T) {
27+
wrapper := "/path/to/wrapper/entry/point"
28+
exec, execCalled := mockExec(t, wrapper)
29+
execAwsLambdaExecWrapper(
30+
mockedGetenv(t, wrapper),
31+
exec,
32+
)
33+
require.True(t, *execCalled)
34+
}
35+
36+
func mockExec(t *testing.T, value string) (mock func(string, []string, []string) error, called *bool) {
37+
mock = func(argv0 string, argv []string, envv []string) error {
38+
*called = true
39+
require.Equal(t, value, argv0)
40+
require.Equal(t, append([]string{value}, os.Args...), argv)
41+
require.Equal(t, awsLambdaExecWrapper+"=", envv[len(envv)-1])
42+
return nil
43+
}
44+
called = ptrTo(false)
45+
return
46+
}
47+
48+
func mockedGetenv(t *testing.T, value string) func(string) string {
49+
return func(key string) string {
50+
require.Equal(t, awsLambdaExecWrapper, key)
51+
return value
52+
}
53+
}
54+
55+
func ptrTo[T any](val T) *T {
56+
return &val
57+
}

0 commit comments

Comments
 (0)