diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3ccb66f..919a2d0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.20' + go-version: '1.24' - name: Build env: @@ -29,7 +29,7 @@ jobs: name: aws-lambda-rie path: bin/* - name: Release binaries - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: files: bin/* diff --git a/.github/workflows/check-binaries.yml b/.github/workflows/check-binaries.yml new file mode 100644 index 0000000..75fa28f --- /dev/null +++ b/.github/workflows/check-binaries.yml @@ -0,0 +1,82 @@ +name: Check binaries + +on: + workflow_dispatch: + schedule: + - cron: "0 16 * * 1-5" # min h d Mo DoW / 9am PST M-F + +jobs: + check-for-vulnerabilities: + runs-on: ubuntu-latest + outputs: + report_contents: ${{ steps.save-output.outputs.report_contents }} + steps: + - name: Setup python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: main + - name: Download latest release + uses: robinraju/release-downloader@v1.10 + with: + latest: true + fileName: 'aws-lambda-rie*' + out-file-path: "bin" + - name: Run check for vulnerabilities + id: check-binaries + run: | + make check-binaries + - if: always() && failure() # `always()` to run even if the previous step failed. Failure means that there are vulnerabilities + name: Save content of the vulnerabilities report as GitHub output + id: save-output + run: | + report_csv="$(ls -tr output.cve-bin-*.csv 2>/dev/null | tail -n1)" # last file generated + if [ -z "$report_csv" ]; then + echo "No file with vulnerabilities. Probably a failure in previous step." + else + echo "Vulnerabilities stored in $report_csv" + fi + final_report="${report_csv}.txt" + awk -F',' '{n=split($10, path, "/"); print $2,$3,$4,$5,path[n]}' "$report_csv" | column -t > "$final_report" # make the CSV nicer + echo "report_contents<> "$GITHUB_OUTPUT" + cat "$final_report" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + - if: always() && steps.save-output.outputs.report_contents + name: Build new binaries and check vulnerabilities again + id: check-new-version + run: | + mkdir ./bin2 + mv ./bin/* ./bin2 + make compile-with-docker-all + latest_version=$(strings bin/aws-lambda-rie* | grep '^go1\.' | sort | uniq) + echo "latest_version=$latest_version" >> "$GITHUB_OUTPUT" + make check-binaries + - if: always() && steps.save-output.outputs.report_contents + name: Save outputs for the check with the latest build + id: save-new-version + run: | + if [ "${{ steps.check-new-version.outcome }}" == "failure" ]; then + fixed="No" + else + fixed="Yes" + fi + echo "fixed=$fixed" >> "$GITHUB_OUTPUT" + - if: always() && steps.save-output.outputs.report_contents + name: Create GitHub Issue indicating vulnerabilities + id: create-issue + uses: dacbd/create-issue-action@main + with: + token: ${{ github.token }} + title: | + CVEs found in latest RIE release + body: | + ### CVEs found in latest RIE release + ``` + ${{ steps.save-output.outputs.report_contents }} + ``` + + #### Are these resolved by building with the latest patch version of Go (${{ steps.check-new-version.outputs.latest_version }})?: + > **${{ steps.save-new-version.outputs.fixed }}** diff --git a/.github/workflows/integ-tests.yml b/.github/workflows/integ-tests.yml new file mode 100644 index 0000000..7fddc95 --- /dev/null +++ b/.github/workflows/integ-tests.yml @@ -0,0 +1,49 @@ +name: Run Integration Tests + +on: + pull_request: + branches: + - develop + +jobs: + go-tests: + runs-on: ubuntu-latest + environment: + name: integ-tests + steps: + - uses: actions/checkout@v4 + - name: run go tests + run: make tests-with-docker + integ-tests-x86: + runs-on: ubuntu-latest + environment: + name: integ-tests + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: run integration tests + run: make integ-tests-with-docker-x86-64 + integ-tests-arm64: + runs-on: ubuntu-latest + environment: + name: integ-tests + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: run integration tests + run: make integ-tests-with-docker-arm64 + integ-tests-old: + runs-on: ubuntu-latest + environment: + name: integ-tests + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: run integration tests + run: make integ-tests-with-docker-old \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..32e878d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,41 @@ +name: Release + +on: + workflow_dispatch: + inputs: + releaseVersion: + description: "Version to use for the release." + required: true + default: "X.Y" + releaseBody: + description: "Information about the release" + required: true + default: "New release" +jobs: + Release: + environment: Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: main + - name: Set up python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Build + run: make compile-with-docker-all + - name: Run Integ Tests + run: | + make tests-with-docker + make integ-tests + - name: Release + uses: softprops/action-gh-release@v2 + with: + name: Release ${{ github.event.inputs.releaseVersion }} + tag_name: v${{ github.event.inputs.releaseVersion }} + body: ${{ github.event.inputs.releaseBody }} + files: | + bin/aws-lambda-rie + bin/aws-lambda-rie-arm64 + bin/aws-lambda-rie-x86_64 diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..e912ea1 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +# Add default reviewers for community PRs +* @dfangl @joe4dev @gregfurman @dominikschubert diff --git a/Makefile b/Makefile index 85bebb2..35a5fbb 100644 --- a/Makefile +++ b/Makefile @@ -12,20 +12,24 @@ GO_ARCH_arm64 := arm64 DESTINATION_x86_64 := bin/${BINARY_NAME}-x86_64 DESTINATION_arm64 := bin/${BINARY_NAME}-arm64 +run_in_docker = docker run --env GOPROXY=direct -v $(shell pwd):/LambdaRuntimeLocal -w /LambdaRuntimeLocal golang:1.24 $(1) + compile-with-docker-all: - make ARCH=x86_64 compile-with-docker - make ARCH=arm64 compile-with-docker + $(call run_in_docker, make compile-lambda-linux-all) compile-lambda-linux-all: make ARCH=x86_64 compile-lambda-linux make ARCH=arm64 compile-lambda-linux compile-with-docker: - docker run --rm --env GOPROXY=direct -v $(shell pwd):/LambdaRuntimeLocal -w /LambdaRuntimeLocal golang:1.20 make ARCH=${ARCH} compile-lambda-linux + $(call run_in_docker, make ARCH=${ARCH} compile-lambda-linux) compile-lambda-linux: CGO_ENABLED=0 GOOS=linux GOARCH=${GO_ARCH_${ARCH}} go build -buildvcs=false -ldflags "${RELEASE_BUILD_LINKER_FLAGS}" -gcflags="${GC_FLAGS}" -o ${DESTINATION_${ARCH}} ./cmd/localstack +tests-with-docker: + $(call run_in_docker, make tests) + tests: go test ./... @@ -33,12 +37,41 @@ integ-tests-and-compile: tests make compile-lambda-linux-all make integ-tests -integ-tests-with-docker: tests +integ-tests-with-docker: tests-with-docker make compile-with-docker-all make integ-tests - -integ-tests: + +prep-python: python3 -m venv .venv .venv/bin/pip install --upgrade pip .venv/bin/pip install requests parameterized + +exec-python-e2e-test: .venv/bin/python3 test/integration/local_lambda/test_end_to_end.py + +integ-tests: + make prep-python + docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + make TEST_ARCH=x86_64 TEST_PORT=8002 exec-python-e2e-test + make TEST_ARCH=arm64 TEST_PORT=9002 exec-python-e2e-test + make TEST_ARCH="" TEST_PORT=9052 exec-python-e2e-test + +integ-tests-with-docker-x86-64: + make ARCH=x86_64 compile-with-docker + make prep-python + make TEST_ARCH=x86_64 TEST_PORT=8002 exec-python-e2e-test + +integ-tests-with-docker-arm64: + make ARCH=arm64 compile-with-docker + make prep-python + docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + make TEST_ARCH=arm64 TEST_PORT=9002 exec-python-e2e-test + +integ-tests-with-docker-old: + make ARCH=old compile-with-docker + make prep-python + make TEST_ARCH="" TEST_PORT=9052 exec-python-e2e-test + +check-binaries: prep-python + .venv/bin/pip install cve-bin-tool + .venv/bin/python -m cve_bin_tool.cli bin/ -r go -d REDHAT,OSV,GAD,CURL --no-0-cve-report -f csv diff --git a/cmd/aws-lambda-rie/handlers.go b/cmd/aws-lambda-rie/handlers.go index 42032cf..2cca12d 100644 --- a/cmd/aws-lambda-rie/handlers.go +++ b/cmd/aws-lambda-rie/handlers.go @@ -5,6 +5,7 @@ package main import ( "bytes" + "encoding/base64" "fmt" "io/ioutil" "math" @@ -81,6 +82,13 @@ func InvokeHandler(w http.ResponseWriter, r *http.Request, sandbox Sandbox, bs i return } + rawClientContext, err := base64.StdEncoding.DecodeString(r.Header.Get("X-Amz-Client-Context")) + if err != nil { + log.Errorf("Failed to decode X-Amz-Client-Context: %s", err) + w.WriteHeader(500) + return + } + initDuration := "" inv := GetenvWithDefault("AWS_LAMBDA_FUNCTION_TIMEOUT", "300") timeoutDuration, _ := time.ParseDuration(inv + "s") @@ -114,6 +122,7 @@ func InvokeHandler(w http.ResponseWriter, r *http.Request, sandbox Sandbox, bs i TraceID: r.Header.Get("X-Amzn-Trace-Id"), LambdaSegmentID: r.Header.Get("X-Amzn-Segment-Id"), Payload: bytes.NewReader(bodyBytes), + ClientContext: string(rawClientContext), } fmt.Println("START RequestId: " + invokePayload.ID + " Version: " + functionVersion) diff --git a/cmd/localstack/awsutil.go b/cmd/localstack/awsutil.go index 7fa02c4..c7fcbc4 100644 --- a/cmd/localstack/awsutil.go +++ b/cmd/localstack/awsutil.go @@ -139,23 +139,6 @@ func resetListener(changeChannel <-chan bool, server *CustomInteropServer) { } -func RunDNSRewriter(opts *LsOpts, ctx context.Context) { - if opts.EnableDnsServer != "1" { - log.Debugln("DNS server disabled.") - return - } - dnsForwarder, err := NewDnsForwarder(opts.LocalstackIP) - if err != nil { - log.Errorln("Error creating dns forwarder.") - return - } - defer dnsForwarder.Shutdown() - dnsForwarder.Start() - - <-ctx.Done() - log.Debugln("DNS server stopped") -} - func RunHotReloadingListener(server *CustomInteropServer, targetPaths []string, ctx context.Context, fileWatcherStrategy string) { if len(targetPaths) == 1 && targetPaths[0] == "" { log.Debugln("Hot reloading disabled.") diff --git a/cmd/localstack/custom_interop.go b/cmd/localstack/custom_interop.go index 2bd3541..1941668 100644 --- a/cmd/localstack/custom_interop.go +++ b/cmd/localstack/custom_interop.go @@ -6,6 +6,7 @@ package main import ( "bytes" "encoding/json" + "errors" "fmt" "github.com/go-chi/chi" log "github.com/sirupsen/logrus" @@ -117,8 +118,8 @@ func NewCustomInteropServer(lsOpts *LsOpts, delegate interop.Server, logCollecto timeout := int(server.delegate.GetInvokeTimeout().Seconds()) isErr := false if err != nil { - switch err { - case rapidcore.ErrInvokeTimeout: + switch { + case errors.Is(err, rapidcore.ErrInvokeTimeout): log.Debugf("Got invoke timeout") isErr = true errorResponse := ErrorResponse{ @@ -137,6 +138,8 @@ func NewCustomInteropServer(lsOpts *LsOpts, delegate interop.Server, logCollecto if err != nil { log.Fatalln("unable to write to response") } + case errors.Is(err, rapidcore.ErrInvokeDoneFailed): + // we can actually just continue here, error message is sent below default: log.Fatalln(err) } diff --git a/cmd/localstack/dns.go b/cmd/localstack/dns.go deleted file mode 100644 index fc68819..0000000 --- a/cmd/localstack/dns.go +++ /dev/null @@ -1,71 +0,0 @@ -package main - -import ( - "github.com/miekg/dns" - log "github.com/sirupsen/logrus" - "net" -) - -type DNSForwarder struct { - server *dns.Server -} - -type DNSRewriteForwardHandler struct { - upstreamServer string - redirectTo string -} - -func (D DNSRewriteForwardHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { - client := dns.Client{ - Net: "udp", - } - response, _, err := client.Exchange(r, D.upstreamServer+":53") - if err != nil { - log.Errorln("Error connecting to upstream: ", err) - return - } - for _, rr := range response.Answer { - switch rr.Header().Rrtype { - case dns.TypeA: - if t, ok := rr.(*dns.A); ok { - if t.A.Equal(net.IPv4(127, 0, 0, 1)) { - log.Debugln("Redirecting answer for ", t.Header().Name, "to ", D.redirectTo) - t.A = net.ParseIP(D.redirectTo) - } - } - } - } - err = w.WriteMsg(response) - if err != nil { - log.Errorln("Error writing response: ", err) - } -} - -func NewDnsForwarder(upstreamServer string) (*DNSForwarder, error) { - forwarder := &DNSForwarder{ - server: &dns.Server{ - Net: "udp", - Handler: DNSRewriteForwardHandler{ - upstreamServer: upstreamServer, - redirectTo: upstreamServer, - }, - }, - } - return forwarder, nil -} - -func (c *DNSForwarder) Start() { - go func() { - err := c.server.ListenAndServe() - if err != nil { - log.Errorln("Error starting DNS server: ", err) - } - }() -} - -func (c *DNSForwarder) Shutdown() { - err := c.server.Shutdown() - if err != nil { - log.Errorln("Error shutting down DNS server: ", err) - } -} diff --git a/cmd/localstack/file_utils.go b/cmd/localstack/file_utils.go index ed65c70..0de9519 100644 --- a/cmd/localstack/file_utils.go +++ b/cmd/localstack/file_utils.go @@ -1,11 +1,40 @@ package main import ( + "encoding/json" + log "github.com/sirupsen/logrus" "io" "os" "path/filepath" + "strconv" ) +type Chmod struct { + Path string `json:"path"` + Mode string `json:"mode"` +} + +// AdaptFilesystemPermissions Adapts the file system permissions to the mode specified in the chmodInfoString parameter +// chmodInfoString should be a json encoded list of `Chmod` structs. +// example: '[{"path": "/opt", "mode": "0755"}]'. The mode string should be an octal representation of the targeted file mode. +func AdaptFilesystemPermissions(chmodInfoString string) error { + var chmodInfo []Chmod + err := json.Unmarshal([]byte(chmodInfoString), &chmodInfo) + if err != nil { + return err + } + for _, chmod := range chmodInfo { + mode, err := strconv.ParseInt(chmod.Mode, 0, 32) + if err != nil { + return err + } + if err := ChmodRecursively(chmod.Path, os.FileMode(mode)); err != nil { + log.Warnf("Could not change file mode recursively of directory %s: %s\n", chmod.Path, err) + } + } + return nil +} + // Inspired by https://stackoverflow.com/questions/73864379/golang-change-permission-os-chmod-and-os-chowm-recursively // but using the more efficient WalkDir API func ChmodRecursively(root string, mode os.FileMode) error { diff --git a/cmd/localstack/main.go b/cmd/localstack/main.go index e6eb026..064a174 100644 --- a/cmd/localstack/main.go +++ b/cmd/localstack/main.go @@ -5,6 +5,7 @@ package main import ( "context" log "github.com/sirupsen/logrus" + "go.amzn.com/lambda/interop" "go.amzn.com/lambda/rapidcore" "os" "runtime/debug" @@ -22,12 +23,13 @@ type LsOpts struct { CodeArchives string HotReloadingPaths []string FileWatcherStrategy string - EnableDnsServer string + ChmodPaths string LocalstackIP string InitLogLevel string EdgePort string EnableXRayTelemetry string PostInvokeWaitMS string + MaxPayloadSize string } func GetEnvOrDie(env string) string { @@ -50,14 +52,15 @@ func InitLsOpts() *LsOpts { User: GetenvWithDefault("LOCALSTACK_USER", "sbx_user1051"), InitLogLevel: GetenvWithDefault("LOCALSTACK_INIT_LOG_LEVEL", "warn"), EdgePort: GetenvWithDefault("EDGE_PORT", "4566"), + MaxPayloadSize: GetenvWithDefault("LOCALSTACK_MAX_PAYLOAD_SIZE", "6291556"), // optional or empty CodeArchives: os.Getenv("LOCALSTACK_CODE_ARCHIVES"), HotReloadingPaths: strings.Split(GetenvWithDefault("LOCALSTACK_HOT_RELOADING_PATHS", ""), ","), FileWatcherStrategy: os.Getenv("LOCALSTACK_FILE_WATCHER_STRATEGY"), - EnableDnsServer: os.Getenv("LOCALSTACK_ENABLE_DNS_SERVER"), EnableXRayTelemetry: os.Getenv("LOCALSTACK_ENABLE_XRAY_TELEMETRY"), LocalstackIP: os.Getenv("LOCALSTACK_HOSTNAME"), PostInvokeWaitMS: os.Getenv("LOCALSTACK_POST_INVOKE_WAIT_MS"), + ChmodPaths: GetenvWithDefault("LOCALSTACK_CHMOD_PATHS", "[]"), } } @@ -72,11 +75,12 @@ func UnsetLsEnvs() { "LOCALSTACK_USER", "LOCALSTACK_CODE_ARCHIVES", "LOCALSTACK_HOT_RELOADING_PATHS", - "LOCALSTACK_ENABLE_DNS_SERVER", "LOCALSTACK_ENABLE_XRAY_TELEMETRY", "LOCALSTACK_INIT_LOG_LEVEL", "LOCALSTACK_POST_INVOKE_WAIT_MS", "LOCALSTACK_FUNCTION_ACCOUNT_ID", + "LOCALSTACK_MAX_PAYLOAD_SIZE", + "LOCALSTACK_CHMOD_PATHS", // Docker container ID "HOSTNAME", @@ -128,31 +132,20 @@ func main() { log.Fatal("Invalid value for LOCALSTACK_INIT_LOG_LEVEL") } - // enable dns server - dnsServerContext, stopDnsServer := context.WithCancel(context.Background()) - go RunDNSRewriter(lsOpts, dnsServerContext) + // patch MaxPayloadSize + payloadSize, err := strconv.Atoi(lsOpts.MaxPayloadSize) + if err != nil { + log.Panicln("Please specify a number for LOCALSTACK_MAX_PAYLOAD_SIZE") + } + interop.MaxPayloadSize = payloadSize // download code archive if env variable is set if err := DownloadCodeArchives(lsOpts.CodeArchives); err != nil { log.Fatal("Failed to download code archives: " + err.Error()) } - // set file permissions of the tmp directory for better AWS parity - if err := ChmodRecursively("/tmp", 0700); err != nil { - log.Warnln("Could not change file mode recursively of directory /tmp:", err) - } - // set file permissions of the layers directory for better AWS parity - if err := ChmodRecursively("/opt", 0755); err != nil { - log.Warnln("Could not change file mode recursively of directory /opt:", err) - } - // set file permissions of the code directory if at least one layer is present for better AWS parity - // Limitation: hot reloading likely breaks file permission parity for /var/task in combination with layers - // Heuristic for detecting the presence of layers. It might fail for an empty layer or image-based Lambda. - if isDirEmpty, _ := IsDirEmpty("/opt"); !isDirEmpty { - log.Debugln("Detected layer present") - if err := ChmodRecursively("/var/task", 0755); err != nil { - log.Warnln("Could not change file mode recursively of directory /var/task:", err) - } + if err := AdaptFilesystemPermissions(lsOpts.ChmodPaths); err != nil { + log.Warnln("Could not change file mode of code directories:", err) } // parse CLI args @@ -189,8 +182,6 @@ func main() { AddShutdownFunc(func() { log.Debugln("Stopping file watcher") cancelFileWatcher() - log.Debugln("Stopping DNS server") - stopDnsServer() }). SetExtensionsFlag(true). SetInitCachingFlag(true). diff --git a/debugging/Makefile b/debugging/Makefile index 9bd3e35..fe3a68f 100644 --- a/debugging/Makefile +++ b/debugging/Makefile @@ -1,5 +1,5 @@ # Golang EOL overview: https://endoflife.date/go -DOCKER_GOLANG_IMAGE ?= golang:1.19 +DOCKER_GOLANG_IMAGE ?= golang:1.20-bullseye # On ARM hosts, use: make ARCH=arm64 build-init # Check host architecture: uname -m diff --git a/go.mod b/go.mod index 206b761..02da1b1 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,21 @@ module go.amzn.com -go 1.20 +go 1.24 require ( - github.com/aws/aws-lambda-go v1.41.0 - github.com/aws/aws-sdk-go v1.44.62 - github.com/aws/aws-xray-daemon v0.0.0-20230202010956-acaf06e9a638 + github.com/aws/aws-lambda-go v1.46.0 + github.com/aws/aws-sdk-go v1.44.298 + github.com/aws/aws-xray-daemon v0.0.0-20250212175715-5defe1b8d61b github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 github.com/fsnotify/fsnotify v1.6.0 - github.com/go-chi/chi v4.1.2+incompatible - github.com/google/uuid v1.3.0 + github.com/go-chi/chi v1.5.5 + github.com/google/uuid v1.6.0 github.com/jessevdk/go-flags v1.5.0 - github.com/miekg/dns v1.1.50 github.com/shirou/gopsutil v2.19.10+incompatible github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.8.4 - golang.org/x/sync v0.2.0 - golang.org/x/sys v0.14.0 + github.com/stretchr/testify v1.9.0 + golang.org/x/sync v0.12.0 + golang.org/x/sys v0.31.0 ) require ( @@ -25,11 +24,9 @@ require ( github.com/go-ole/go-ole v1.2.4 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/objx v0.5.0 // indirect - golang.org/x/mod v0.8.0 // indirect - golang.org/x/net v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.6.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/text v0.23.0 // indirect gopkg.in/yaml.v2 v2.2.8 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8cc90e8..3f2f234 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/aws/aws-lambda-go v1.41.0 h1:l/5fyVb6Ud9uYd411xdHZzSf2n86TakxzpvIoz7l+3Y= -github.com/aws/aws-lambda-go v1.41.0/go.mod h1:jwFe2KmMsHmffA1X2R09hH6lFzJQxzI8qK17ewzbQMM= -github.com/aws/aws-sdk-go v1.44.62 h1:N8qOPnBhl2ZCIFiqyB640Xt5CeX9D8CEVhG/Vj7jGJU= -github.com/aws/aws-sdk-go v1.44.62/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-xray-daemon v0.0.0-20230202010956-acaf06e9a638 h1:G0C87W0m2uyh3uHV24Q60JJx+AyJ3//gJjalvSizXhc= -github.com/aws/aws-xray-daemon v0.0.0-20230202010956-acaf06e9a638/go.mod h1:glwf7zqf0NzGozJscRs0/xC+CpTU4DyMN4V9eXxD2Co= +github.com/aws/aws-lambda-go v1.46.0 h1:UWVnvh2h2gecOlFhHQfIPQcD8pL/f7pVCutmFl+oXU8= +github.com/aws/aws-lambda-go v1.46.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= +github.com/aws/aws-sdk-go v1.44.298 h1:5qTxdubgV7PptZJmp/2qDwD2JL187ePL7VOxsSh1i3g= +github.com/aws/aws-sdk-go v1.44.298/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-xray-daemon v0.0.0-20250212175715-5defe1b8d61b h1:hiV1SQDGCUECdYdKRvfBmIZnoCWggTDauTintGTkIFU= +github.com/aws/aws-xray-daemon v0.0.0-20250212175715-5defe1b8d61b/go.mod h1:1tKEa2CqVzCVcMS59532MHzZP5P0hF682qCGpR/Tl1k= github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 h1:kHaBemcxl8o/pQ5VM1c8PVE1PubbNx3mjUr09OqWGCs= github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,20 +13,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= -github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= +github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= -github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -35,61 +33,49 @@ github.com/shirou/gopsutil v2.19.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMT github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= diff --git a/lambda/core/directinvoke/directinvoke.go b/lambda/core/directinvoke/directinvoke.go index 3510132..396bd39 100644 --- a/lambda/core/directinvoke/directinvoke.go +++ b/lambda/core/directinvoke/directinvoke.go @@ -1,5 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +// LOCALSTACK CHANGES 2024-02-13: casting of MaxPayloadSize package directinvoke @@ -51,7 +52,7 @@ var ResetReasonMap = map[string]fatalerror.ErrorType{ "timeout": fatalerror.SandboxTimeout, } -var MaxDirectResponseSize int64 = interop.MaxPayloadSize // this is intentionally not a constant so we can configure it via CLI +var MaxDirectResponseSize = int64(interop.MaxPayloadSize) // this is intentionally not a constant so we can configure it via CLI var ResponseBandwidthRate int64 = interop.ResponseBandwidthRate var ResponseBandwidthBurstSize int64 = interop.ResponseBandwidthBurstSize @@ -104,7 +105,7 @@ func ReceiveDirectInvoke(w http.ResponseWriter, r *http.Request, token interop.T now := metering.Monotime() - MaxDirectResponseSize = interop.MaxPayloadSize + MaxDirectResponseSize = int64(interop.MaxPayloadSize) if maxPayloadSize := r.Header.Get(MaxPayloadSizeHeader); maxPayloadSize != "" { if n, err := strconv.ParseInt(maxPayloadSize, 10, 64); err == nil && n >= -1 { MaxDirectResponseSize = n diff --git a/lambda/interop/model.go b/lambda/interop/model.go index a4bdbf4..ee7bb2a 100644 --- a/lambda/interop/model.go +++ b/lambda/interop/model.go @@ -1,5 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +// LOCALSTACK CHANGES 2024-02-13: adjust error message for ErrorResponseTooLarge to be in parity with what AWS returns; make MaxPayloadSize adjustable package interop @@ -18,10 +19,10 @@ import ( log "github.com/sirupsen/logrus" ) +var MaxPayloadSize int = 6*1024*1024 + 100 // 6 MiB + 100 bytes + // MaxPayloadSize max event body size declared as LAMBDA_EVENT_BODY_SIZE const ( - MaxPayloadSize = 6*1024*1024 + 100 // 6 MiB + 100 bytes - ResponseBandwidthRate = 2 * 1024 * 1024 // default average rate of 2 MiB/s ResponseBandwidthBurstSize = 6 * 1024 * 1024 // default burst size of 6 MiB @@ -355,7 +356,7 @@ type ErrorResponseTooLargeDI struct { // ErrorResponseTooLarge is returned when response provided by Runtime does not fit into shared memory buffer func (s *ErrorResponseTooLarge) Error() string { - return fmt.Sprintf("Response payload size (%d bytes) exceeded maximum allowed payload size (%d bytes).", s.ResponseSize, s.MaxResponseSize) + return fmt.Sprintf("Response payload size exceeded maximum allowed payload size (%d bytes).", s.MaxResponseSize) } // AsErrorResponse generates ErrorInvokeResponse from ErrorResponseTooLarge diff --git a/lambda/rapi/handler/agentnext.go b/lambda/rapi/handler/agentnext.go index 7ce76f0..ffdd61d 100644 --- a/lambda/rapi/handler/agentnext.go +++ b/lambda/rapi/handler/agentnext.go @@ -48,7 +48,7 @@ func (h *agentNextHandler) ServeHTTP(writer http.ResponseWriter, request *http.R } } else { log.Warnf("Unknown agent %s tried to call /next", agentID.String()) - rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentIdentifierUnknown, "Unknown extension"+agentID.String()) + rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentIdentifierUnknown, "Unknown extension %s", agentID.String()) return } diff --git a/lambda/rapi/handler/agentregister.go b/lambda/rapi/handler/agentregister.go index 8da9e4c..867ad9d 100644 --- a/lambda/rapi/handler/agentregister.go +++ b/lambda/rapi/handler/agentregister.go @@ -77,7 +77,7 @@ func (h *agentRegisterHandler) ServeHTTP(writer http.ResponseWriter, request *ht registerRequest, err := parseRegister(request) if err != nil { - rendering.RenderForbiddenWithTypeMsg(writer, request, errInvalidRequestFormat, err.Error()) + rendering.RenderForbiddenWithTypeMsg(writer, request, errInvalidRequestFormat, "%s", err.Error()) return } diff --git a/lambda/rapi/handler/runtimelogs.go b/lambda/rapi/handler/runtimelogs.go index 6b8a67e..4fd534e 100644 --- a/lambda/rapi/handler/runtimelogs.go +++ b/lambda/rapi/handler/runtimelogs.go @@ -30,7 +30,7 @@ func (h *runtimeLogsHandler) ServeHTTP(writer http.ResponseWriter, request *http log.Errorf("Agent Verification Error: %s", err) switch err := err.(type) { case *ErrAgentIdentifierUnknown: - rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentIdentifierUnknown, "Unknown extension "+err.agentID.String()) + rendering.RenderForbiddenWithTypeMsg(writer, request, errAgentIdentifierUnknown, "Unknown extension %s", err.agentID.String()) h.telemetrySubscription.RecordCounterMetric(telemetry.SubscribeClientErr, 1) default: rendering.RenderInternalServerError(writer, request) @@ -55,7 +55,7 @@ func (h *runtimeLogsHandler) ServeHTTP(writer http.ResponseWriter, request *http switch err { case telemetry.ErrTelemetryServiceOff: rendering.RenderForbiddenWithTypeMsg(writer, request, - h.telemetrySubscription.GetServiceClosedErrorType(), h.telemetrySubscription.GetServiceClosedErrorMessage()) + h.telemetrySubscription.GetServiceClosedErrorType(), "%s", h.telemetrySubscription.GetServiceClosedErrorMessage()) h.telemetrySubscription.RecordCounterMetric(telemetry.SubscribeClientErr, 1) default: rendering.RenderInternalServerError(writer, request) diff --git a/lambda/rapi/rendering/rendering.go b/lambda/rapi/rendering/rendering.go index 9a9d77b..08de1e3 100644 --- a/lambda/rapi/rendering/rendering.go +++ b/lambda/rapi/rendering/rendering.go @@ -1,5 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +// LOCALSTACK CHANGES 2024-02-13: casting of MaxPayloadSize package rendering @@ -174,7 +175,7 @@ func (s *InvokeRenderer) bufferInvokeRequest() error { defer s.requestMutex.Unlock() var err error = nil if s.requestBuffer.Len() == 0 { - reader := io.LimitReader(s.invoke.Payload, interop.MaxPayloadSize) + reader := io.LimitReader(s.invoke.Payload, int64(interop.MaxPayloadSize)) start := time.Now() _, err = s.requestBuffer.ReadFrom(reader) s.metrics = InvokeRendererMetrics{ diff --git a/lambda/rapid/handlers.go b/lambda/rapid/handlers.go index f379c4c..2e759e9 100644 --- a/lambda/rapid/handlers.go +++ b/lambda/rapid/handlers.go @@ -243,7 +243,7 @@ func (c *rapidContext) watchEvents(events <-chan supvmodel.Event) { if termination.Success() { err = fmt.Errorf("exit code 0") } else { - err = fmt.Errorf(termination.String()) + err = fmt.Errorf("%s", termination.String()) } appctx.StoreFirstFatalError(c.appCtx, fatalerror.AgentCrash) @@ -851,7 +851,7 @@ func handleRestore(execCtx *rapidContext, restore *interop.Restore) (interop.Res // check if there is any error stored in appctx to get the root cause error type // Runtime.ExitError is an example to such a scenario if fatalErrorFound { - err = fmt.Errorf(string(fatalErrorType)) + err = fmt.Errorf("%s", string(fatalErrorType)) } if err != nil { diff --git a/test/integration/local_lambda/test_end_to_end.py b/test/integration/local_lambda/test_end_to_end.py index c5c3e63..d564bb1 100644 --- a/test/integration/local_lambda/test_end_to_end.py +++ b/test/integration/local_lambda/test_end_to_end.py @@ -4,284 +4,262 @@ from subprocess import Popen, PIPE from unittest import TestCase, main from pathlib import Path +import base64 +import json import time - +import os import requests +from contextlib import contextmanager from parameterized import parameterized -SLEEP_TIME = 2 +SLEEP_TIME = 1.5 DEFAULT_1P_ENTRYPOINT = "/lambda-entrypoint.sh" ARCHS = ["x86_64", "arm64", ""] + class TestEndToEnd(TestCase): + ARCH = os.environ.get('TEST_ARCH', "") + PORT = os.environ.get('TEST_PORT', 8002) @classmethod def setUpClass(cls): testdata_path = Path(__file__).resolve().parents[1].joinpath("testdata") dockerfile_path = testdata_path.joinpath("Dockerfile-allinone") - cls.image_name = "aws-lambda-local:testing" cls.path_to_binary = Path().resolve().joinpath("bin") # build image - for arch in ARCHS: - image_name = cls.image_name if arch == "" else f"{cls.image_name}-{arch}" - architecture = arch if arch == "arm64" else "amd64" - build_cmd = [ - "docker", - "build", - "--platform", - f"linux/{architecture}", - "-t", - image_name, - "-f", - str(dockerfile_path), - str(testdata_path), - ] - Popen(build_cmd).communicate() + image_name_base = "aws-lambda-local:testing" + cls.image_name = image_name_base if cls.ARCH == "" else f"{image_name_base}-{cls.ARCH}" + architecture = cls.ARCH if cls.ARCH == "arm64" else "amd64" + docker_arch = cls.ARCH if cls.ARCH == "arm64" else "x86_64" + + build_cmd = [ + "docker", + "build", + "--platform", + f"linux/{architecture}", + "-t", + cls.image_name, + "-f", + str(dockerfile_path), + str(testdata_path), + "--build-arg", + f"IMAGE_ARCH={docker_arch}", + ] + Popen(build_cmd).communicate() @classmethod def tearDownClass(cls): - images_to_delete = [ - "envvarcheck", - "twoinvokes", - "arnexists", - "customname", - "timeout", - "exception", - "remaining_time_in_three_seconds", - "remaining_time_in_ten_seconds", - "remaining_time_in_default_deadline", - "pre-runtime-api", - "assert-overwritten", - "port_override" - ] - - for image in images_to_delete: - for arch in ARCHS: - arch_tag = "" if arch == "" else f"-{arch}" - cmd = f"docker rm -f {image}{arch_tag}" - Popen(cmd.split(" ")).communicate() - - for arch in ARCHS: - arch_tag = "" if arch == "" else f"-{arch}" - Popen(f"docker rmi {cls.image_name}{arch_tag}".split(" ")).communicate() - - def tagged_name(self, name, architecture): - tag = self.get_tag(architecture) - return (name + tag, "aws-lambda-rie" + tag, self.image_name + tag) - - def get_tag(self, architecture): - return "" if architecture == "" else str(f"-{architecture}") - - @parameterized.expand([("x86_64", "8000"), ("arm64", "9000"), ("", "9050")]) - def test_env_var_with_equal_sign(self, arch, port): - image, rie, image_name = self.tagged_name("envvarcheck", arch) - - cmd = f"docker run --name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_env_var_handler" - Popen(cmd.split(" ")).communicate() - - # sleep 1s to give enough time for the endpoint to be up to curl - time.sleep(SLEEP_TIME) - - r = requests.post( - f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={} - ) - self.assertEqual(b'"4=4"', r.content) - - @parameterized.expand([("x86_64", "8001"), ("arm64", "9001"), ("", "9051")]) - def test_two_invokes(self, arch, port): - image, rie, image_name = self.tagged_name("twoinvokes", arch) - - cmd = f"docker run --name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler" - - Popen(cmd.split(" ")).communicate() - - # sleep 1s to give enough time for the endpoint to be up to curl - time.sleep(SLEEP_TIME) - - r = requests.post( - f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={} - ) - self.assertEqual(b'"My lambda ran succesfully"', r.content) - - # Make sure we can invoke the function twice - r = requests.post( - f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={} - ) - self.assertEqual(b'"My lambda ran succesfully"', r.content) - - @parameterized.expand([("x86_64", "8002"), ("arm64", "9002"), ("", "9052")]) - def test_lambda_function_arn_exists(self, arch, port): - image, rie, image_name = self.tagged_name("arnexists", arch) - - cmd = f"docker run --name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_lambda_arn_in_context" - - Popen(cmd.split(" ")).communicate() - - # sleep 1s to give enough time for the endpoint to be up to curl - time.sleep(SLEEP_TIME) + Popen(f"docker rmi {cls.image_name}".split(" ")).communicate() - r = requests.post( - f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={} - ) - self.assertEqual(b'"My lambda ran succesfully"', r.content) - - @parameterized.expand([("x86_64", "8003"), ("arm64", "9003"), ("", "9053")]) - def test_lambda_function_arn_exists_with_defining_custom_name(self, arch, port): - image, rie, image_name = self.tagged_name("customname", arch) + def tagged_name(self, name): + tag = self.get_tag() + return (name + tag, "aws-lambda-rie" + tag, self.image_name) - cmd = f"docker run --name {image} --env AWS_LAMBDA_FUNCTION_NAME=MyCoolName -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_lambda_arn_in_context" + def get_tag(self): + return "" if self.ARCH == "" else str(f"-{self.ARCH}") + + def run_command(self, cmd): Popen(cmd.split(" ")).communicate() - - # sleep 1s to give enough time for the endpoint to be up to curl + + def sleep_1s(self): time.sleep(SLEEP_TIME) - r = requests.post( - f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={} + def invoke_function(self, json={}, headers={}): + return requests.post( + f"http://localhost:{self.PORT}/2015-03-31/functions/function/invocations", + json=json, + headers=headers, ) - self.assertEqual(b'"My lambda ran succesfully"', r.content) - @parameterized.expand([("x86_64", "8004"), ("arm64", "9004"), ("", "9054")]) - def test_timeout_invoke(self, arch, port): - image, rie, image_name = self.tagged_name("timeout", arch) + @contextmanager + def create_container(self, param, image): + try: + platform = "x86_64" if self.ARCH == "" else self.ARCH + cmd_full = f"docker run --platform linux/{platform} {param}" + self.run_command(cmd_full) + + # sleep 1s to give enough time for the endpoint to be up to curl + self.sleep_1s() + yield + except Exception as e: + print(f"An error occurred while executing cmd: {cmd_full}. error: {e}") + raise e + finally: + self.run_command(f"docker stop {image}") + self.run_command(f"docker rm -f {image}") + + + def test_env_var_with_equal_sign(self): + image, rie, image_name = self.tagged_name("envvarcheck") + params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_env_var_handler" + + with self.create_container(params, image): + r = self.invoke_function() + + self.assertEqual(b'"4=4"', r.content) + + + def test_two_invokes(self): + image, rie, image_name = self.tagged_name("twoinvokes") + + params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler" + + with self.create_container(params, image): + r = self.invoke_function() + + self.assertEqual(b'"My lambda ran succesfully"', r.content) + + # Make sure we can invoke the function twice + r = self.invoke_function() + + self.assertEqual(b'"My lambda ran succesfully"', r.content) - cmd = f"docker run --name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=1 -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.sleep_handler" - Popen(cmd.split(" ")).communicate() + def test_lambda_function_arn_exists(self): + image, rie, image_name = self.tagged_name("arnexists") - # sleep 1s to give enough time for the endpoint to be up to curl - time.sleep(SLEEP_TIME) + params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_lambda_arn_in_context" - r = requests.post( - f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={} - ) - self.assertEqual(b"Task timed out after 1.00 seconds", r.content) + with self.create_container(params, image): + r = self.invoke_function() + + self.assertEqual(b'"My lambda ran succesfully"', r.content) - @parameterized.expand([("x86_64", "8005"), ("arm64", "9005"), ("", "9055")]) - def test_exception_returned(self, arch, port): - image, rie, image_name = self.tagged_name("exception", arch) - cmd = f"docker run --name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.exception_handler" + def test_lambda_function_arn_exists_with_defining_custom_name(self): + image, rie, image_name = self.tagged_name("customname") - Popen(cmd.split(" ")).communicate() + params = f"--name {image} --env AWS_LAMBDA_FUNCTION_NAME=MyCoolName -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_lambda_arn_in_context" + + with self.create_container(params, image): + r = self.invoke_function() + + self.assertEqual(b'"My lambda ran succesfully"', r.content) - # sleep 1s to give enough time for the endpoint to be up to curl - time.sleep(SLEEP_TIME) - r = requests.post( - f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={} - ) - self.assertEqual( - b'{"errorMessage": "Raising an exception", "errorType": "Exception", "stackTrace": [" File \\"/var/task/main.py\\", line 13, in exception_handler\\n raise Exception(\\"Raising an exception\\")\\n"]}', - r.content, - ) + def test_timeout_invoke(self): + image, rie, image_name = self.tagged_name("timeout") - @parameterized.expand([("x86_64", "8006"), ("arm64", "9006"), ("", "9056")]) - def test_context_get_remaining_time_in_three_seconds(self, arch, port): - image, rie, image_name = self.tagged_name("remaining_time_in_three_seconds", arch) + params = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=1 -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.sleep_handler" - cmd = f"docker run --name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=3 -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler" + with self.create_container(params, image): + r = self.invoke_function() + + self.assertEqual(b"Task timed out after 1.00 seconds", r.content) - Popen(cmd.split(' ')).communicate() - # sleep 1s to give enough time for the endpoint to be up to curl - time.sleep(SLEEP_TIME) + def test_exception_returned(self): + image, rie, image_name = self.tagged_name("exception") - r = requests.post( - f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={} - ) + params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.exception_handler" - # Execution time is not decided, 1.0s ~ 3.0s is a good estimation - self.assertLess(int(r.content), 3000) - self.assertGreater(int(r.content), 1000) + with self.create_container(params, image): + r = self.invoke_function() + + # Except the 3 fields assrted below, there's another field `request_id` included start from python3.12 runtime. + # We should ignore asserting the field `request_id` for it is in a UUID like format and changes everytime + result = r.json() + self.assertEqual(result["errorMessage"], "Raising an exception") + self.assertEqual(result["errorType"], "Exception") + self.assertEqual(result["stackTrace"], [" File \"/var/task/main.py\", line 13, in exception_handler\n raise Exception(\"Raising an exception\")\n"]) - @parameterized.expand([("x86_64", "8007"), ("arm64", "9007"), ("", "9057")]) - def test_context_get_remaining_time_in_ten_seconds(self, arch, port): - image, rie, image_name = self.tagged_name("remaining_time_in_ten_seconds", arch) - cmd = f"docker run --name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=10 -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler" + def test_context_get_remaining_time_in_three_seconds(self): + image, rie, image_name = self.tagged_name("remaining_time_in_three_seconds") - Popen(cmd.split(' ')).communicate() + params = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=3 -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler" - # sleep 1s to give enough time for the endpoint to be up to curl - time.sleep(SLEEP_TIME) + with self.create_container(params, image): + r = self.invoke_function() + + # Execution time is not decided, but it should be around 2.0s + self.assertAround(int(r.content), 2000) - r = requests.post( - f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={} - ) - # Execution time is not decided, 8.0s ~ 10.0s is a good estimation - self.assertLess(int(r.content), 10000) - self.assertGreater(int(r.content), 8000) + def test_context_get_remaining_time_in_ten_seconds(self): + image, rie, image_name = self.tagged_name("remaining_time_in_ten_seconds") - @parameterized.expand([("x86_64", "8008"), ("arm64", "9008"), ("", "9058")]) - def test_context_get_remaining_time_in_default_deadline(self, arch, port): - image, rie, image_name = self.tagged_name("remaining_time_in_default_deadline", arch) + params = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=10 -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler" - cmd = f"docker run --name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler" + with self.create_container(params, image): + r = self.invoke_function() + + # Execution time is not decided, but it should be around 9.0s + self.assertAround(int(r.content), 9000) - Popen(cmd.split(' ')).communicate() - # sleep 1s to give enough time for the endpoint to be up to curl - time.sleep(SLEEP_TIME) + def test_context_get_remaining_time_in_default_deadline(self): + image, rie, image_name = self.tagged_name("remaining_time_in_default_deadline") - r = requests.post( - f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={} - ) + params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler" - # Executation time is not decided, 298.0s ~ 300.0s is a good estimation - self.assertLess(int(r.content), 300000) - self.assertGreater(int(r.content), 298000) + with self.create_container(params, image): + r = self.invoke_function() - @parameterized.expand([("x86_64", "8009"), ("arm64", "9009"), ("", "9059")]) - def test_invoke_with_pre_runtime_api_runtime(self, arch, port): - image, rie, image_name = self.tagged_name("pre-runtime-api", arch) + # Execution time is not decided, but it should be around 299.0s + self.assertAround(int(r.content), 299000) - cmd = f"docker run --name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler" - Popen(cmd.split(" ")).communicate() + def test_invoke_with_pre_runtime_api_runtime(self): + image, rie, image_name = self.tagged_name("pre-runtime-api") - # sleep 1s to give enough time for the endpoint to be up to curl - time.sleep(SLEEP_TIME) + params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler" - r = requests.post( - f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={} - ) - self.assertEqual(b'"My lambda ran succesfully"', r.content) + with self.create_container(params, image): + r = self.invoke_function() + + self.assertEqual(b'"My lambda ran succesfully"', r.content) - @parameterized.expand([("x86_64", "8010"), ("arm64", "9010"), ("", "9060")]) - def test_function_name_is_overriden(self, arch, port): - image, rie, image_name = self.tagged_name("assert-overwritten", arch) - cmd = f"docker run --name {image} -d --env AWS_LAMBDA_FUNCTION_NAME=MyCoolName -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_env_var_is_overwritten" + def test_function_name_is_overriden(self): + image, rie, image_name = self.tagged_name("assert-overwritten") - Popen(cmd.split(" ")).communicate() + params = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_NAME=MyCoolName -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_env_var_is_overwritten" - # sleep 1s to give enough time for the endpoint to be up to curl - time.sleep(SLEEP_TIME) + with self.create_container(params, image): + r = self.invoke_function() + + self.assertEqual(b'"My lambda ran succesfully"', r.content) - r = requests.post( - f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={} - ) - self.assertEqual(b'"My lambda ran succesfully"', r.content) - @parameterized.expand([("x86_64", "8011"), ("arm64", "9011"), ("", "9061")]) - def test_port_override(self, arch, port): - image, rie, image_name = self.tagged_name("port_override", arch) + def test_port_override(self): + image, rie, image_name = self.tagged_name("port_override") # Use port 8081 inside the container instead of 8080 - cmd = f"docker run --name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8081 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler --runtime-interface-emulator-address 0.0.0.0:8081" - - Popen(cmd.split(" ")).communicate() - - # sleep 1s to give enough time for the endpoint to be up to curl - time.sleep(SLEEP_TIME) - - r = requests.post( - f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={} - ) - self.assertEqual(b'"My lambda ran succesfully"', r.content) - + params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8081 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler --runtime-interface-emulator-address 0.0.0.0:8081" + + with self.create_container(params, image): + r = self.invoke_function() + + self.assertEqual(b'"My lambda ran succesfully"', r.content) + + + def test_custom_client_context(self): + image, rie, image_name = self.tagged_name("custom_client_context") + + params = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.custom_client_context_handler" + + with self.create_container(params, image): + r = self.invoke_function(headers={ + "X-Amz-Client-Context": base64.b64encode(json.dumps({ + "custom": { + "foo": "bar", + "baz": 123, + } + }).encode('utf8')).decode('utf8'), + }) + content = json.loads(r.content) + self.assertEqual("bar", content["foo"]) + self.assertEqual(123, content["baz"]) + + def assertAround(self, number, target): + # Emulating arm64 on x86 causes the invoke to take longer + delay_arm64 = 500 + actual_target = target if self.ARCH != 'arm64' else target - delay_arm64 + + self.assertLess(number, actual_target + 1000) + self.assertGreater(number, actual_target - 1000) if __name__ == "__main__": main() diff --git a/test/integration/testdata/Dockerfile-allinone b/test/integration/testdata/Dockerfile-allinone index b804e5c..1d28406 100644 --- a/test/integration/testdata/Dockerfile-allinone +++ b/test/integration/testdata/Dockerfile-allinone @@ -1,4 +1,5 @@ -FROM public.ecr.aws/lambda/python:3.8 +ARG IMAGE_ARCH +FROM public.ecr.aws/lambda/python:3.12-$IMAGE_ARCH WORKDIR /var/task COPY ./ ./ diff --git a/test/integration/testdata/main.py b/test/integration/testdata/main.py index b6b527d..9757be8 100644 --- a/test/integration/testdata/main.py +++ b/test/integration/testdata/main.py @@ -41,3 +41,7 @@ def check_remaining_time_handler(event, context): # Wait 1s to see if the remaining time changes time.sleep(1) return context.get_remaining_time_in_millis() + + +def custom_client_context_handler(event, context): + return context.client_context.custom