diff --git a/.github/workflows/integ-tests.yml b/.github/workflows/integ-tests.yml index cb2f9dc..7fddc95 100644 --- a/.github/workflows/integ-tests.yml +++ b/.github/workflows/integ-tests.yml @@ -6,16 +6,44 @@ on: - develop jobs: - integ-tests: + go-tests: runs-on: ubuntu-latest environment: - name: prod + 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: allows us to build arm64 images - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - name: run integration tests - run: make integ-tests-with-docker \ No newline at end of file + 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/Makefile b/Makefile index 1916dae..f7a714e 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ DESTINATION_old:= bin/${BINARY_NAME} 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.22 $(1) + compile-with-docker-all: - make ARCH=x86_64 compile-with-docker - make ARCH=arm64 compile-with-docker - make ARCH=old compile-with-docker + $(call run_in_docker, make compile-lambda-linux-all) compile-lambda-linux-all: make ARCH=x86_64 compile-lambda-linux @@ -21,11 +21,14 @@ compile-lambda-linux-all: make ARCH=old compile-lambda-linux compile-with-docker: - docker run --env GOPROXY=direct -v $(shell pwd):/LambdaRuntimeLocal -w /LambdaRuntimeLocal golang:1.21 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}" -o ${DESTINATION_${ARCH}} ./cmd/aws-lambda-rie +tests-with-docker: + $(call run_in_docker, make tests) + tests: go test ./... @@ -33,12 +36,38 @@ 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 + \ No newline at end of file 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/go.mod b/go.mod index d48dc60..4ee45d7 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module go.amzn.com -go 1.21 +go 1.22 require ( github.com/aws/aws-lambda-go v1.46.0 diff --git a/test/integration/local_lambda/test_end_to_end.py b/test/integration/local_lambda/test_end_to_end.py index fd7f735..8e34b77 100644 --- a/test/integration/local_lambda/test_end_to_end.py +++ b/test/integration/local_lambda/test_end_to_end.py @@ -4,228 +4,257 @@ 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() + Popen(f"docker rmi {cls.image_name}".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 tagged_name(self, name): + tag = self.get_tag() + return (name + tag, "aws-lambda-rie" + tag, self.image_name) - def get_tag(self, architecture): - return "" if architecture == "" else str(f"-{architecture}") + def get_tag(self): + return "" if self.ARCH == "" else str(f"-{self.ARCH}") def run_command(self, cmd): Popen(cmd.split(" ")).communicate() def sleep_1s(self): time.sleep(SLEEP_TIME) - - def invoke_function(self, port): + + def invoke_function(self, json={}, headers={}): return requests.post( - f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={} + f"http://localhost:{self.PORT}/2015-03-31/functions/function/invocations", + json=json, + headers=headers, ) + + @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) - def create_container_and_invoke_function(self, cmd, port): - self.run_command(cmd) - - # sleep 1s to give enough time for the endpoint to be up to curl - self.sleep_1s() + # 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}") - return self.invoke_function(port) - @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" + 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" - r = self.create_container_and_invoke_function(cmd, port) + with self.create_container(params, image): + r = self.invoke_function() - self.assertEqual(b'"4=4"', r.content) + 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" + def test_two_invokes(self): + image, rie, image_name = self.tagged_name("twoinvokes") - r = self.create_container_and_invoke_function(cmd, port) - - 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}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler" - # Make sure we can invoke the function twice - r = self.invoke_function(port) + with self.create_container(params, image): + r = self.invoke_function() - self.assertEqual(b'"My lambda ran succesfully"', r.content) + 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) + - @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) + def test_lambda_function_arn_exists(self): + image, rie, image_name = self.tagged_name("arnexists") - 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" + 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 = self.create_container_and_invoke_function(cmd, port) + with self.create_container(params, image): + r = self.invoke_function() - self.assertEqual(b'"My lambda ran succesfully"', r.content) + 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) - 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 test_lambda_function_arn_exists_with_defining_custom_name(self): + image, rie, image_name = self.tagged_name("customname") + + 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" - r = self.create_container_and_invoke_function(cmd, port) + with self.create_container(params, image): + r = self.invoke_function() - self.assertEqual(b'"My lambda ran succesfully"', r.content) + 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) + def test_timeout_invoke(self): + image, rie, image_name = self.tagged_name("timeout") - 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" + 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" - r = self.create_container_and_invoke_function(cmd, port) + with self.create_container(params, image): + r = self.invoke_function() - self.assertEqual(b"Task timed out after 1.00 seconds", r.content) + self.assertEqual(b"Task timed out after 1.00 seconds", 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_exception_returned(self): + image, rie, image_name = self.tagged_name("exception") + + 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" + + 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"]) - r = self.create_container_and_invoke_function(cmd, port) - - 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, - ) - @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) + def test_context_get_remaining_time_in_three_seconds(self): + image, rie, image_name = self.tagged_name("remaining_time_in_three_seconds") - 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" + 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" - r = self.create_container_and_invoke_function(cmd, port) + with self.create_container(params, image): + r = self.invoke_function() - # 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) + # 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) - @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_ten_seconds(self): + image, rie, image_name = self.tagged_name("remaining_time_in_ten_seconds") - r = self.create_container_and_invoke_function(cmd, port) + 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" + + with self.create_container(params, image): + r = self.invoke_function() - # 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) + # 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) + - @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) + def test_context_get_remaining_time_in_default_deadline(self): + image, rie, image_name = self.tagged_name("remaining_time_in_default_deadline") - 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" + 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" - r = self.create_container_and_invoke_function(cmd, port) + with self.create_container(params, image): + r = self.invoke_function() - # 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) + # 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) - @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) - 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" + def test_invoke_with_pre_runtime_api_runtime(self): + image, rie, image_name = self.tagged_name("pre-runtime-api") - r = self.create_container_and_invoke_function(cmd, port) + 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) + 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) + def test_function_name_is_overriden(self): + image, rie, image_name = self.tagged_name("assert-overwritten") - 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" + 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" - r = self.create_container_and_invoke_function(cmd, port) + with self.create_container(params, image): + r = self.invoke_function() - self.assertEqual(b'"My lambda ran succesfully"', r.content) + 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" + 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" - r = self.create_container_and_invoke_function(cmd, port) + with self.create_container(params, image): + r = self.invoke_function() - self.assertEqual(b'"My lambda ran succesfully"', r.content) + 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"]) if __name__ == "__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