diff --git a/.github/workflows/test-on-push-and-pr.yml b/.github/workflows/test-on-push-and-pr.yml
index f12bf0e..5b80d23 100644
--- a/.github/workflows/test-on-push-and-pr.yml
+++ b/.github/workflows/test-on-push-and-pr.yml
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Run 'pr' target
run: make pr
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Run alpine integration tests
run: DISTRO=alpine make test-integ
@@ -27,23 +27,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Run amazonlinux integration tests
run: DISTRO=amazonlinux make test-integ
- centos:
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v2
- - name: Run centos integration tests
- run: DISTRO=centos make test-integ
-
debian:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Run debian integration tests
run: DISTRO=debian make test-integ
@@ -51,6 +43,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Run ubuntu integration tests
run: DISTRO=ubuntu make test-integ
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index bd5c9b7..9d46e4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -148,3 +148,8 @@ cython_debug/
# Test files generated
tmp*.py
+
+# dependencies
+deps/artifacts/
+deps/aws-lambda-cpp-*/
+deps/curl-*/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index dcabbf4..bb66b03 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,5 +3,5 @@ repos:
rev: 19.3b0
hooks:
- id: black
- language_version: python3.6
+ language_version: python3.9
exclude_types: ['markdown', 'ini', 'toml', 'rst']
diff --git a/Makefile b/Makefile
index ff99587..521b61c 100644
--- a/Makefile
+++ b/Makefile
@@ -41,9 +41,10 @@ dev: init test
# Verifications to run before sending a pull request
.PHONY: pr
-pr: init check-format check-security dev setup-codebuild-agent
- CODEBUILD_IMAGE_TAG=codebuild-agent DISTRO="$(DISTRO)" tests/integration/codebuild-local/test_all.sh tests/integration/codebuild
+pr: init check-format check-security dev
+codebuild: setup-codebuild-agent
+ CODEBUILD_IMAGE_TAG=codebuild-agent DISTRO="$(DISTRO)" tests/integration/codebuild-local/test_all.sh tests/integration/codebuild
.PHONY: clean
clean:
diff --git a/README.md b/README.md
index 248fde4..4a96a3f 100644
--- a/README.md
+++ b/README.md
@@ -5,12 +5,12 @@ We have open-sourced a set of software packages, Runtime Interface Clients (RIC)
base images to be Lambda compatible.
The Lambda Runtime Interface Client is a lightweight interface that allows your runtime to receive requests from and send requests to the Lambda service.
-The Lambda Python Runtime Interface Client is vended through [pip](https://pypi.org/project/awslambdaric).
+The Lambda Python Runtime Interface Client is vended through [pip](https://pypi.org/project/awslambdaric).
You can include this package in your preferred base image to make that base image Lambda compatible.
## Requirements
The Python Runtime Interface Client package currently supports Python versions:
- - 3.7.x up to and including 3.12.x
+ - 3.9.x up to and including 3.13.x
## Usage
@@ -19,7 +19,6 @@ First step is to choose the base image to be used. The supported Linux OS distri
- Amazon Linux 2
- Alpine
- - CentOS
- Debian
- Ubuntu
@@ -104,18 +103,18 @@ def handler(event, context):
### Local Testing
-To make it easy to locally test Lambda functions packaged as container images we open-sourced a lightweight web-server, Lambda Runtime Interface Emulator (RIE), which allows your function packaged as a container image to accept HTTP requests. You can install the [AWS Lambda Runtime Interface Emulator](https://github.com/aws/aws-lambda-runtime-interface-emulator) on your local machine to test your function. Then when you run the image function, you set the entrypoint to be the emulator.
+To make it easy to locally test Lambda functions packaged as container images we open-sourced a lightweight web-server, Lambda Runtime Interface Emulator (RIE), which allows your function packaged as a container image to accept HTTP requests. You can install the [AWS Lambda Runtime Interface Emulator](https://github.com/aws/aws-lambda-runtime-interface-emulator) on your local machine to test your function. Then when you run the image function, you set the entrypoint to be the emulator.
*To install the emulator and test your Lambda function*
-1) From your project directory, run the following command to download the RIE from GitHub and install it on your local machine.
+1) From your project directory, run the following command to download the RIE from GitHub and install it on your local machine.
```shell script
mkdir -p ~/.aws-lambda-rie && \
curl -Lo ~/.aws-lambda-rie/aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie && \
chmod +x ~/.aws-lambda-rie/aws-lambda-rie
```
-2) Run your Lambda image function using the docker run command.
+2) Run your Lambda image function using the docker run command.
```shell script
docker run -d -v ~/.aws-lambda-rie:/aws-lambda -p 9000:8080 \
@@ -124,9 +123,9 @@ docker run -d -v ~/.aws-lambda-rie:/aws-lambda -p 9000:8080 \
/usr/local/bin/python -m awslambdaric app.handler
```
-This runs the image as a container and starts up an endpoint locally at `http://localhost:9000/2015-03-31/functions/function/invocations`.
+This runs the image as a container and starts up an endpoint locally at `http://localhost:9000/2015-03-31/functions/function/invocations`.
-3) Post an event to the following endpoint using a curl command:
+3) Post an event to the following endpoint using a curl command:
```shell script
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'
@@ -175,4 +174,4 @@ If you discover a potential security issue in this project we ask that you notif
## License
-This project is licensed under the Apache-2.0 License.
\ No newline at end of file
+This project is licensed under the Apache-2.0 License.
diff --git a/RELEASE.CHANGELOG.md b/RELEASE.CHANGELOG.md
index 4f71031..fc45791 100644
--- a/RELEASE.CHANGELOG.md
+++ b/RELEASE.CHANGELOG.md
@@ -1,3 +1,34 @@
+### May 26, 2025
+`3.1.1`
+- Move unhandled exception warning message to init errors. ([#189](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/189))
+
+### May 21, 2025
+`3.1.0`
+- Add support for multi tenancy ([#187](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/187))
+
+### February 27, 2024
+`3.0.2`
+- Update `simplejson` to `3.20.1`([#184](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/184))
+
+### January 27, 2024
+`3.0.1`
+- Don't enforce text format on uncaught exception warning message ([#182](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/182))
+
+### November 19, 2024
+`3.0.0`
+- Drop support for deprecated python versions ([#179](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/179))
+- Add support for snapstart runtime hooks ([#176](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/176))
+
+### August 23, 2024
+`2.2.1`:
+- Patch libcurl configure.ac to work with later versions of autoconf ([#166](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/168))
+
+### August 8, 2024
+
+`2.2.0`:
+
+- Propogate error type in header when reporting init error to RAPID ([#166](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/166))
+
### July 31, 2024
`2.1.0`:
diff --git a/awslambdaric/__init__.py b/awslambdaric/__init__.py
index 9d397ae..5605903 100644
--- a/awslambdaric/__init__.py
+++ b/awslambdaric/__init__.py
@@ -2,4 +2,4 @@
Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
"""
-__version__ = "2.1.0"
+__version__ = "3.1.1"
diff --git a/awslambdaric/bootstrap.py b/awslambdaric/bootstrap.py
index 60aa216..cb8d5c3 100644
--- a/awslambdaric/bootstrap.py
+++ b/awslambdaric/bootstrap.py
@@ -31,6 +31,8 @@
_AWS_LAMBDA_LOG_LEVEL = _get_log_level_from_env_var(
os.environ.get("AWS_LAMBDA_LOG_LEVEL")
)
+AWS_LAMBDA_INITIALIZATION_TYPE = "AWS_LAMBDA_INITIALIZATION_TYPE"
+INIT_TYPE_SNAP_START = "snap-start"
def _get_handler(handler):
@@ -100,7 +102,6 @@ def replace_line_indentation(line, indent_char, new_indent_char):
if _AWS_LAMBDA_LOG_FORMAT == LogFormat.JSON:
_ERROR_FRAME_TYPE = _JSON_FRAME_TYPES[logging.ERROR]
- _WARNING_FRAME_TYPE = _JSON_FRAME_TYPES[logging.WARNING]
def log_error(error_result, log_sink):
error_result = {
@@ -116,7 +117,6 @@ def log_error(error_result, log_sink):
else:
_ERROR_FRAME_TYPE = _TEXT_FRAME_TYPES[logging.ERROR]
- _WARNING_FRAME_TYPE = _TEXT_FRAME_TYPES[logging.WARNING]
def log_error(error_result, log_sink):
error_description = "[ERROR]"
@@ -158,6 +158,7 @@ def handle_event_request(
cognito_identity_json,
invoked_function_arn,
epoch_deadline_time_in_ms,
+ tenant_id,
log_sink,
):
error_result = None
@@ -168,6 +169,7 @@ def handle_event_request(
epoch_deadline_time_in_ms,
invoke_id,
invoked_function_arn,
+ tenant_id,
)
event = lambda_runtime_client.marshaller.unmarshal_request(
event_body, content_type
@@ -199,9 +201,7 @@ def handle_event_request(
)
if error_result is not None:
- from .lambda_literals import lambda_unhandled_exception_warning_message
- log_sink.log(lambda_unhandled_exception_warning_message, _WARNING_FRAME_TYPE)
log_error(error_result, log_sink)
lambda_runtime_client.post_invocation_error(
invoke_id, to_json(error_result), to_json(xray_fault)
@@ -229,6 +229,7 @@ def create_lambda_context(
epoch_deadline_time_in_ms,
invoke_id,
invoked_function_arn,
+ tenant_id,
):
client_context = None
if client_context_json:
@@ -243,6 +244,7 @@ def create_lambda_context(
cognito_identity,
epoch_deadline_time_in_ms,
invoked_function_arn,
+ tenant_id,
)
@@ -286,6 +288,29 @@ def extract_traceback(tb):
]
+def on_init_complete(lambda_runtime_client, log_sink):
+ from . import lambda_runtime_hooks_runner
+
+ try:
+ lambda_runtime_hooks_runner.run_before_snapshot()
+ lambda_runtime_client.restore_next()
+ except:
+ error_result = build_fault_result(sys.exc_info(), None)
+ log_error(error_result, log_sink)
+ lambda_runtime_client.post_init_error(
+ error_result, FaultException.BEFORE_SNAPSHOT_ERROR
+ )
+ sys.exit(64)
+
+ try:
+ lambda_runtime_hooks_runner.run_after_restore()
+ except:
+ error_result = build_fault_result(sys.exc_info(), None)
+ log_error(error_result, log_sink)
+ lambda_runtime_client.report_restore_error(error_result)
+ sys.exit(65)
+
+
class LambdaLoggerHandler(logging.Handler):
def __init__(self, log_sink):
logging.Handler.__init__(self)
@@ -314,6 +339,7 @@ def emit(self, record):
class LambdaLoggerFilter(logging.Filter):
def filter(self, record):
record.aws_request_id = _GLOBAL_AWS_REQUEST_ID or ""
+ record.tenant_id = _GLOBAL_TENANT_ID
return True
@@ -422,6 +448,7 @@ def create_log_sink():
_GLOBAL_AWS_REQUEST_ID = None
+_GLOBAL_TENANT_ID = None
def _setup_logging(log_format, log_level, log_sink):
@@ -454,9 +481,10 @@ def run(app_root, handler, lambda_runtime_api_addr):
sys.stdout = Unbuffered(sys.stdout)
sys.stderr = Unbuffered(sys.stderr)
- use_thread_for_polling_next = (
- os.environ.get("AWS_EXECUTION_ENV") == "AWS_Lambda_python3.12"
- )
+ use_thread_for_polling_next = os.environ.get("AWS_EXECUTION_ENV") in {
+ "AWS_Lambda_python3.12",
+ "AWS_Lambda_python3.13",
+ }
with create_log_sink() as log_sink:
lambda_runtime_client = LambdaRuntimeClient(
@@ -466,7 +494,7 @@ def run(app_root, handler, lambda_runtime_api_addr):
try:
_setup_logging(_AWS_LAMBDA_LOG_FORMAT, _AWS_LAMBDA_LOG_LEVEL, log_sink)
- global _GLOBAL_AWS_REQUEST_ID
+ global _GLOBAL_AWS_REQUEST_ID, _GLOBAL_TENANT_ID
request_handler = _get_handler(handler)
except FaultException as e:
@@ -479,15 +507,22 @@ def run(app_root, handler, lambda_runtime_api_addr):
error_result = build_fault_result(sys.exc_info(), None)
if error_result is not None:
+ from .lambda_literals import lambda_unhandled_exception_warning_message
+
+ logging.warning(lambda_unhandled_exception_warning_message)
log_error(error_result, log_sink)
- lambda_runtime_client.post_init_error(to_json(error_result))
+ lambda_runtime_client.post_init_error(error_result)
sys.exit(1)
+ if os.environ.get(AWS_LAMBDA_INITIALIZATION_TYPE) == INIT_TYPE_SNAP_START:
+ on_init_complete(lambda_runtime_client, log_sink)
+
while True:
event_request = lambda_runtime_client.wait_next_invocation()
_GLOBAL_AWS_REQUEST_ID = event_request.invoke_id
+ _GLOBAL_TENANT_ID = event_request.tenant_id
update_xray_env_variable(event_request.x_amzn_trace_id)
@@ -501,5 +536,6 @@ def run(app_root, handler, lambda_runtime_api_addr):
event_request.cognito_identity,
event_request.invoked_function_arn,
event_request.deadline_time_in_ms,
+ event_request.tenant_id,
log_sink,
)
diff --git a/awslambdaric/lambda_context.py b/awslambdaric/lambda_context.py
index 1465827..e0a3363 100644
--- a/awslambdaric/lambda_context.py
+++ b/awslambdaric/lambda_context.py
@@ -16,6 +16,7 @@ def __init__(
cognito_identity,
epoch_deadline_time_in_ms,
invoked_function_arn=None,
+ tenant_id=None,
):
self.aws_request_id = invoke_id
self.log_group_name = os.environ.get("AWS_LAMBDA_LOG_GROUP_NAME")
@@ -24,6 +25,7 @@ def __init__(
self.memory_limit_in_mb = os.environ.get("AWS_LAMBDA_FUNCTION_MEMORY_SIZE")
self.function_version = os.environ.get("AWS_LAMBDA_FUNCTION_VERSION")
self.invoked_function_arn = invoked_function_arn
+ self.tenant_id = tenant_id
self.client_context = make_obj_from_dict(ClientContext, client_context)
if self.client_context is not None:
@@ -65,7 +67,8 @@ def __repr__(self):
f"function_version={self.function_version},"
f"invoked_function_arn={self.invoked_function_arn},"
f"client_context={self.client_context},"
- f"identity={self.identity}"
+ f"identity={self.identity},"
+ f"tenant_id={self.tenant_id}"
"])"
)
diff --git a/awslambdaric/lambda_runtime_client.py b/awslambdaric/lambda_runtime_client.py
index 07243fc..ba4ad92 100644
--- a/awslambdaric/lambda_runtime_client.py
+++ b/awslambdaric/lambda_runtime_client.py
@@ -5,6 +5,9 @@
import sys
from awslambdaric import __version__
from .lambda_runtime_exception import FaultException
+from .lambda_runtime_marshaller import to_json
+
+ERROR_TYPE_HEADER = "Lambda-Runtime-Function-Error-Type"
def _user_agent():
@@ -59,22 +62,57 @@ def __init__(self, lambda_runtime_address, use_thread_for_polling_next=False):
# Not defining symbol as global to avoid relying on TPE being imported unconditionally.
self.ThreadPoolExecutor = ThreadPoolExecutor
- def post_init_error(self, error_response_data):
+ def call_rapid(
+ self, http_method, endpoint, expected_http_code, payload=None, headers=None
+ ):
# These imports are heavy-weight. They implicitly trigger `import ssl, hashlib`.
# Importing them lazily to speed up critical path of a common case.
- import http
import http.client
runtime_connection = http.client.HTTPConnection(self.lambda_runtime_address)
runtime_connection.connect()
- endpoint = "/2018-06-01/runtime/init/error"
- runtime_connection.request("POST", endpoint, error_response_data)
+ if http_method == "GET":
+ runtime_connection.request(http_method, endpoint)
+ else:
+ runtime_connection.request(
+ http_method, endpoint, to_json(payload), headers=headers
+ )
+
response = runtime_connection.getresponse()
response_body = response.read()
-
- if response.code != http.HTTPStatus.ACCEPTED:
+ if response.code != expected_http_code:
raise LambdaRuntimeClientError(endpoint, response.code, response_body)
+ def post_init_error(self, error_response_data, error_type_override=None):
+ import http
+
+ endpoint = "/2018-06-01/runtime/init/error"
+ headers = {
+ ERROR_TYPE_HEADER: (
+ error_type_override
+ if error_type_override
+ else error_response_data["errorType"]
+ )
+ }
+ self.call_rapid(
+ "POST", endpoint, http.HTTPStatus.ACCEPTED, error_response_data, headers
+ )
+
+ def restore_next(self):
+ import http
+
+ endpoint = "/2018-06-01/runtime/restore/next"
+ self.call_rapid("GET", endpoint, http.HTTPStatus.OK)
+
+ def report_restore_error(self, restore_error_data):
+ import http
+
+ endpoint = "/2018-06-01/runtime/restore/error"
+ headers = {ERROR_TYPE_HEADER: FaultException.AFTER_RESTORE_ERROR}
+ self.call_rapid(
+ "POST", endpoint, http.HTTPStatus.ACCEPTED, restore_error_data, headers
+ )
+
def wait_next_invocation(self):
# Calling runtime_client.next() from a separate thread unblocks the main thread,
# which can then process signals.
@@ -99,6 +137,7 @@ def wait_next_invocation(self):
deadline_time_in_ms=headers.get("Lambda-Runtime-Deadline-Ms"),
client_context=headers.get("Lambda-Runtime-Client-Context"),
cognito_identity=headers.get("Lambda-Runtime-Cognito-Identity"),
+ tenant_id=headers.get("Lambda-Runtime-Aws-Tenant-Id"),
content_type=headers.get("Content-Type"),
event_body=response_body,
)
diff --git a/awslambdaric/lambda_runtime_exception.py b/awslambdaric/lambda_runtime_exception.py
index e09af70..3ea5b29 100644
--- a/awslambdaric/lambda_runtime_exception.py
+++ b/awslambdaric/lambda_runtime_exception.py
@@ -11,6 +11,8 @@ class FaultException(Exception):
IMPORT_MODULE_ERROR = "Runtime.ImportModuleError"
BUILT_IN_MODULE_CONFLICT = "Runtime.BuiltInModuleConflict"
MALFORMED_HANDLER_NAME = "Runtime.MalformedHandlerName"
+ BEFORE_SNAPSHOT_ERROR = "Runtime.BeforeSnapshotError"
+ AFTER_RESTORE_ERROR = "Runtime.AfterRestoreError"
LAMBDA_CONTEXT_UNMARSHAL_ERROR = "Runtime.LambdaContextUnmarshalError"
LAMBDA_RUNTIME_CLIENT_ERROR = "Runtime.LambdaRuntimeClientError"
diff --git a/awslambdaric/lambda_runtime_hooks_runner.py b/awslambdaric/lambda_runtime_hooks_runner.py
new file mode 100644
index 0000000..8aee181
--- /dev/null
+++ b/awslambdaric/lambda_runtime_hooks_runner.py
@@ -0,0 +1,18 @@
+# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+# SPDX-License-Identifier: Apache-2.0
+
+from snapshot_restore_py import get_before_snapshot, get_after_restore
+
+
+def run_before_snapshot():
+ before_snapshot_callables = get_before_snapshot()
+ while before_snapshot_callables:
+ # Using pop as before checkpoint callables are executed in the reverse order of their registration
+ func, args, kwargs = before_snapshot_callables.pop()
+ func(*args, **kwargs)
+
+
+def run_after_restore():
+ after_restore_callables = get_after_restore()
+ for func, args, kwargs in after_restore_callables:
+ func(*args, **kwargs)
diff --git a/awslambdaric/lambda_runtime_log_utils.py b/awslambdaric/lambda_runtime_log_utils.py
index 7ed9940..9ddbcfb 100644
--- a/awslambdaric/lambda_runtime_log_utils.py
+++ b/awslambdaric/lambda_runtime_log_utils.py
@@ -30,6 +30,7 @@
"processName",
"process",
"aws_request_id",
+ "tenant_id",
"_frame_type",
}
@@ -124,6 +125,9 @@ def format(self, record: logging.LogRecord) -> str:
"requestId": getattr(record, "aws_request_id", None),
"location": self.__format_location(record),
}
+ if hasattr(record, "tenant_id") and record.tenant_id is not None:
+ result["tenantId"] = record.tenant_id
+
result.update(
(key, value)
for key, value in record.__dict__.items()
diff --git a/awslambdaric/lambda_runtime_marshaller.py b/awslambdaric/lambda_runtime_marshaller.py
index 3b28313..4256066 100644
--- a/awslambdaric/lambda_runtime_marshaller.py
+++ b/awslambdaric/lambda_runtime_marshaller.py
@@ -15,7 +15,10 @@
# We also set 'ensure_ascii=False' so that the encoded json contains unicode characters instead of unicode escape sequences
class Encoder(json.JSONEncoder):
def __init__(self):
- if os.environ.get("AWS_EXECUTION_ENV") == "AWS_Lambda_python3.12":
+ if os.environ.get("AWS_EXECUTION_ENV") in {
+ "AWS_Lambda_python3.12",
+ "AWS_Lambda_python3.13",
+ }:
super().__init__(use_decimal=False, ensure_ascii=False, allow_nan=True)
else:
super().__init__(use_decimal=False, allow_nan=True)
diff --git a/awslambdaric/runtime_client.cpp b/awslambdaric/runtime_client.cpp
index 66252bf..7fb2e95 100644
--- a/awslambdaric/runtime_client.cpp
+++ b/awslambdaric/runtime_client.cpp
@@ -52,9 +52,10 @@ static PyObject *method_next(PyObject *self) {
auto client_context = response.client_context.c_str();
auto content_type = response.content_type.c_str();
auto cognito_id = response.cognito_identity.c_str();
+ auto tenant_id = response.tenant_id.c_str();
PyObject *payload_bytes = PyBytes_FromStringAndSize(payload.c_str(), payload.length());
- PyObject *result = Py_BuildValue("(O,{s:s,s:s,s:s,s:l,s:s,s:s,s:s})",
+ PyObject *result = Py_BuildValue("(O,{s:s,s:s,s:s,s:l,s:s,s:s,s:s,s:s})",
payload_bytes, //Py_BuildValue() increments reference counter
"Lambda-Runtime-Aws-Request-Id", request_id,
"Lambda-Runtime-Trace-Id", NULL_IF_EMPTY(trace_id),
@@ -62,7 +63,8 @@ static PyObject *method_next(PyObject *self) {
"Lambda-Runtime-Deadline-Ms", deadline,
"Lambda-Runtime-Client-Context", NULL_IF_EMPTY(client_context),
"Content-Type", NULL_IF_EMPTY(content_type),
- "Lambda-Runtime-Cognito-Identity", NULL_IF_EMPTY(cognito_id)
+ "Lambda-Runtime-Cognito-Identity", NULL_IF_EMPTY(cognito_id),
+ "Lambda-Runtime-Aws-Tenant-Id", NULL_IF_EMPTY(tenant_id)
);
Py_XDECREF(payload_bytes);
diff --git a/deps/aws-lambda-cpp-0.2.6.tar.gz b/deps/aws-lambda-cpp-0.2.6.tar.gz
index 26fa498..51d7f51 100644
Binary files a/deps/aws-lambda-cpp-0.2.6.tar.gz and b/deps/aws-lambda-cpp-0.2.6.tar.gz differ
diff --git a/deps/curl-7.83.1.tar.gz b/deps/curl-7.83.1.tar.gz
index b71926a..305bb4f 100644
Binary files a/deps/curl-7.83.1.tar.gz and b/deps/curl-7.83.1.tar.gz differ
diff --git a/deps/patches/aws-lambda-cpp-add-tenant-id.patch b/deps/patches/aws-lambda-cpp-add-tenant-id.patch
new file mode 100644
index 0000000..a7b7172
--- /dev/null
+++ b/deps/patches/aws-lambda-cpp-add-tenant-id.patch
@@ -0,0 +1,39 @@
+diff --git a/include/aws/lambda-runtime/runtime.h b/include/aws/lambda-runtime/runtime.h
+index 7812ff6..96be869 100644
+--- a/include/aws/lambda-runtime/runtime.h
++++ b/include/aws/lambda-runtime/runtime.h
+@@ -61,6 +61,11 @@ struct invocation_request {
+ */
+ std::string content_type;
+
++ /**
++ * The Tenant ID of the current invocation.
++ */
++ std::string tenant_id;
++
+ /**
+ * Function execution deadline counted in milliseconds since the Unix epoch.
+ */
+diff --git a/src/runtime.cpp b/src/runtime.cpp
+index e53b2b8..9763282 100644
+--- a/src/runtime.cpp
++++ b/src/runtime.cpp
+@@ -40,6 +40,7 @@ static constexpr auto CLIENT_CONTEXT_HEADER = "lambda-runtime-client-context";
+ static constexpr auto COGNITO_IDENTITY_HEADER = "lambda-runtime-cognito-identity";
+ static constexpr auto DEADLINE_MS_HEADER = "lambda-runtime-deadline-ms";
+ static constexpr auto FUNCTION_ARN_HEADER = "lambda-runtime-invoked-function-arn";
++static constexpr auto TENANT_ID_HEADER = "lambda-runtime-aws-tenant-id";
+
+ enum Endpoints {
+ INIT,
+@@ -289,6 +290,10 @@ runtime::next_outcome runtime::get_next()
+ req.function_arn = resp.get_header(FUNCTION_ARN_HEADER);
+ }
+
++ if (resp.has_header(TENANT_ID_HEADER)) {
++ req.tenant_id = resp.get_header(TENANT_ID_HEADER);
++ }
++
+ if (resp.has_header(DEADLINE_MS_HEADER)) {
+ auto const& deadline_string = resp.get_header(DEADLINE_MS_HEADER);
+ constexpr int base = 10;
diff --git a/deps/patches/libcurl-configure-template.patch b/deps/patches/libcurl-configure-template.patch
new file mode 100644
index 0000000..e26be47
--- /dev/null
+++ b/deps/patches/libcurl-configure-template.patch
@@ -0,0 +1,131 @@
+diff --git a/configure.ac b/configure.ac
+index d24daea..64aca7f 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -193,87 +193,96 @@ AS_HELP_STRING([--with-schannel],[enable Windows native SSL/TLS]),
+
+ OPT_SECURETRANSPORT=no
+ AC_ARG_WITH(secure-transport,dnl
+-AS_HELP_STRING([--with-secure-transport],[enable Apple OS native SSL/TLS]),
++AS_HELP_STRING([--with-secure-transport],[enable Apple OS native SSL/TLS]),[
+ OPT_SECURETRANSPORT=$withval
+ test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }Secure-Transport"
+-)
++])
+
+ OPT_AMISSL=no
+ AC_ARG_WITH(amissl,dnl
+-AS_HELP_STRING([--with-amissl],[enable Amiga native SSL/TLS (AmiSSL)]),
++AS_HELP_STRING([--with-amissl],[enable Amiga native SSL/TLS (AmiSSL)]),[
+ OPT_AMISSL=$withval
+- test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }AmiSSL")
++ test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }AmiSSL"
++])
++
+
+ OPT_OPENSSL=no
+ dnl Default to no CA bundle
+ ca="no"
+ AC_ARG_WITH(ssl,dnl
+ AS_HELP_STRING([--with-ssl=PATH],[old version of --with-openssl])
+-AS_HELP_STRING([--without-ssl], [build without any TLS library]),
++AS_HELP_STRING([--without-ssl], [build without any TLS library]),[
+ OPT_SSL=$withval
+ OPT_OPENSSL=$withval
+ if test X"$withval" != Xno; then
+- test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }OpenSSL")
++ test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }OpenSSL"
+ fi
++])
+
+ AC_ARG_WITH(openssl,dnl
+-AS_HELP_STRING([--with-openssl=PATH],[Where to look for OpenSSL, PATH points to the SSL installation (default: /usr/local/ssl); when possible, set the PKG_CONFIG_PATH environment variable instead of using this option]),
++AS_HELP_STRING([--with-openssl=PATH],[Where to look for OpenSSL, PATH points to the SSL installation (default: /usr/local/ssl); when possible, set the PKG_CONFIG_PATH environment variable instead of using this option]),[
+ OPT_OPENSSL=$withval
+ if test X"$withval" != Xno; then
+- test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }OpenSSL")
++ test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }OpenSSL"
+ fi
++])
+
+ OPT_GNUTLS=no
+ AC_ARG_WITH(gnutls,dnl
+-AS_HELP_STRING([--with-gnutls=PATH],[where to look for GnuTLS, PATH points to the installation root]),
++AS_HELP_STRING([--with-gnutls=PATH],[where to look for GnuTLS, PATH points to the installation root]),[
+ OPT_GNUTLS=$withval
+ if test X"$withval" != Xno; then
+- test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }GnuTLS")
++ test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }GnuTLS"
+ fi
++])
+
+ OPT_MBEDTLS=no
+ AC_ARG_WITH(mbedtls,dnl
+-AS_HELP_STRING([--with-mbedtls=PATH],[where to look for mbedTLS, PATH points to the installation root]),
++AS_HELP_STRING([--with-mbedtls=PATH],[where to look for mbedTLS, PATH points to the installation root]),[
+ OPT_MBEDTLS=$withval
+ if test X"$withval" != Xno; then
+- test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }mbedTLS")
++ test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }mbedTLS"
+ fi
++])
+
+ OPT_WOLFSSL=no
+ AC_ARG_WITH(wolfssl,dnl
+-AS_HELP_STRING([--with-wolfssl=PATH],[where to look for WolfSSL, PATH points to the installation root (default: system lib default)]),
++AS_HELP_STRING([--with-wolfssl=PATH],[where to look for WolfSSL, PATH points to the installation root (default: system lib default)]),[
+ OPT_WOLFSSL=$withval
+ if test X"$withval" != Xno; then
+- test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }wolfSSL")
++ test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }wolfSSL"
+ fi
++])
+
+ OPT_BEARSSL=no
+ AC_ARG_WITH(bearssl,dnl
+-AS_HELP_STRING([--with-bearssl=PATH],[where to look for BearSSL, PATH points to the installation root]),
++AS_HELP_STRING([--with-bearssl=PATH],[where to look for BearSSL, PATH points to the installation root]),[
+ OPT_BEARSSL=$withval
+ if test X"$withval" != Xno; then
+- test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }BearSSL")
++ test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }BearSSL"
+ fi
++])
+
+ OPT_RUSTLS=no
+ AC_ARG_WITH(rustls,dnl
+-AS_HELP_STRING([--with-rustls=PATH],[where to look for rustls, PATH points to the installation root]),
++AS_HELP_STRING([--with-rustls=PATH],[where to look for rustls, PATH points to the installation root]),[
+ OPT_RUSTLS=$withval
+ if test X"$withval" != Xno; then
+- test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }rustls")
++ test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }rustls"
+ fi
++])
+
+ OPT_NSS_AWARE=no
+ AC_ARG_WITH(nss-deprecated,dnl
+-AS_HELP_STRING([--with-nss-deprecated],[confirm you realize NSS is going away]),
++AS_HELP_STRING([--with-nss-deprecated],[confirm you realize NSS is going away]),[
+ if test X"$withval" != Xno; then
+ OPT_NSS_AWARE=$withval
+ fi
+-)
++])
+
+ OPT_NSS=no
+ AC_ARG_WITH(nss,dnl
+-AS_HELP_STRING([--with-nss=PATH],[where to look for NSS, PATH points to the installation root]),
++AS_HELP_STRING([--with-nss=PATH],[where to look for NSS, PATH points to the installation root]),[
+ OPT_NSS=$withval
+ if test X"$withval" != Xno; then
+
+@@ -283,7 +292,7 @@ AS_HELP_STRING([--with-nss=PATH],[where to look for NSS, PATH points to the inst
+
+ test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }NSS"
+ fi
+-)
++])
+
+ dnl If no TLS choice has been made, check if it was explicitly disabled or
+ dnl error out to force the user to decide.
diff --git a/requirements/base.txt b/requirements/base.txt
index 819c723..4bb251e 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -1 +1,2 @@
-simplejson>=3.18.4
+simplejson>=3.20.1
+snapshot-restore-py>=1.0.0
diff --git a/scripts/update_deps.sh b/scripts/update_deps.sh
index 4ec4ec1..4799a6f 100755
--- a/scripts/update_deps.sh
+++ b/scripts/update_deps.sh
@@ -1,6 +1,6 @@
#!/bin/bash
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-set -e
+set -x
cd deps
source versions
@@ -8,8 +8,17 @@ source versions
# Clean up old files
rm -f aws-lambda-cpp-*.tar.gz && rm -f curl-*.tar.gz
+
+LIBCURL="curl-${CURL_MAJOR_VERSION}.${CURL_MINOR_VERSION}.${CURL_PATCH_VERSION}"
+
# Grab Curl
-wget -c "https://github.com/curl/curl/releases/download/curl-${CURL_MAJOR_VERSION}_${CURL_MINOR_VERSION}_${CURL_PATCH_VERSION}/curl-${CURL_MAJOR_VERSION}.${CURL_MINOR_VERSION}.${CURL_PATCH_VERSION}.tar.gz"
+wget -c "https://github.com/curl/curl/releases/download/curl-${CURL_MAJOR_VERSION}_${CURL_MINOR_VERSION}_${CURL_PATCH_VERSION}/$LIBCURL.tar.gz" -O - | tar -xz
+(
+ cd $LIBCURL && \
+ patch -p1 < ../patches/libcurl-configure-template.patch
+)
+
+tar -czf $LIBCURL.tar.gz $LIBCURL --no-same-owner && rm -rf $LIBCURL
# Grab aws-lambda-cpp
wget -c https://github.com/awslabs/aws-lambda-cpp/archive/v$AWS_LAMBDA_CPP_RELEASE.tar.gz -O - | tar -xz
@@ -21,9 +30,10 @@ wget -c https://github.com/awslabs/aws-lambda-cpp/archive/v$AWS_LAMBDA_CPP_RELEA
patch -p1 < ../patches/aws-lambda-cpp-posting-init-errors.patch && \
patch -p1 < ../patches/aws-lambda-cpp-make-the-runtime-client-user-agent-overrideable.patch && \
patch -p1 < ../patches/aws-lambda-cpp-make-lto-optional.patch && \
- patch -p1 < ../patches/aws-lambda-cpp-add-content-type.patch
+ patch -p1 < ../patches/aws-lambda-cpp-add-content-type.patch && \
+ patch -p1 < ../patches/aws-lambda-cpp-add-tenant-id.patch
)
## Pack again and remove the folder
-tar -czvf aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE.tar.gz aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE --no-same-owner && \
+tar -czf aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE.tar.gz aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE --no-same-owner && \
rm -rf aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE
diff --git a/setup.py b/setup.py
index 2544b21..2bf28ef 100644
--- a/setup.py
+++ b/setup.py
@@ -84,17 +84,15 @@ def read_requirements(req="base.txt"):
"Intended Audience :: Developers",
"Natural Language :: English",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.6",
- "Programming Language :: Python :: 3.7",
- "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
],
- python_requires=">=3.6",
+ python_requires=">=3.9",
ext_modules=get_runtime_client_extension(),
test_suite="tests",
)
diff --git a/tests/integration/codebuild/buildspec.os.alpine.yml b/tests/integration/codebuild/buildspec.os.alpine.yml
index 8d13f9e..8b290f5 100644
--- a/tests/integration/codebuild/buildspec.os.alpine.yml
+++ b/tests/integration/codebuild/buildspec.os.alpine.yml
@@ -15,15 +15,14 @@ batch:
env:
variables:
DISTRO_VERSION:
- - "3.13"
- - "3.14"
- - "3.15"
+ - "3.19"
+ - "3.20"
RUNTIME_VERSION:
- - "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
+ - "3.13"
phases:
pre_build:
commands:
@@ -52,20 +51,16 @@ phases:
echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \
"${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
- >
- if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]];
- then
- echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login."
- else
- echo "Performing DockerHub login . . ."
- docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
- fi
+ echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json
+ service docker restart
- echo "Building image ${IMAGE_TAG}"
- >
docker build . \
-f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \
-t "${IMAGE_TAG}" \
--build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \
- --build-arg DISTRO_VERSION="${DISTRO_VERSION}"
+ --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \
+ --load
build:
commands:
- set -x
diff --git a/tests/integration/codebuild/buildspec.os.amazonlinux.1.yml b/tests/integration/codebuild/buildspec.os.amazonlinux.1.yml
deleted file mode 100644
index 91bb021..0000000
--- a/tests/integration/codebuild/buildspec.os.amazonlinux.1.yml
+++ /dev/null
@@ -1,104 +0,0 @@
-version: 0.2
-
-env:
- variables:
- OS_DISTRIBUTION: amazonlinux
- PYTHON_LOCATION: "/usr/local/bin/python3"
- TEST_NAME: "aws-lambda-python-rtc-amazonlinux-test"
-batch:
- build-matrix:
- static:
- ignore-failure: false
- env:
- privileged-mode: true
- dynamic:
- env:
- variables:
- DISTRO_VERSION:
- - "1"
- RUNTIME_VERSION:
- - "3.7"
- - "3.8"
- - "3.9"
- - "3.10"
- - "3.11"
- - "3.12"
-phases:
- pre_build:
- commands:
- - export IMAGE_TAG="python-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}"
- - echo "Extracting and including the Runtime Interface Emulator"
- - SCRATCH_DIR=".scratch"
- - mkdir "${SCRATCH_DIR}"
- - ARCHITECTURE=$(arch)
- - tar -xvf tests/integration/resources/aws-lambda-rie.tar.gz --directory "${SCRATCH_DIR}"
- - >
- cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \
- "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
- - >
- echo "COPY ${SCRATCH_DIR}/aws-lambda-rie /usr/bin/aws-lambda-rie" >> \
- "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
- - >
- if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]];
- then
- echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login."
- else
- echo "Performing DockerHub login . . ."
- docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
- fi
- - echo "Building image ${IMAGE_TAG}"
- - >
- docker build . \
- -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \
- -t "${IMAGE_TAG}" \
- --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \
- --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \
- --build-arg ARCHITECTURE="${ARCHITECTURE}"
- build:
- commands:
- - set -x
- - echo "Running Image ${IMAGE_TAG}"
- - docker network create "${TEST_NAME}-network"
- - >
- docker run \
- --detach \
- -e "PYTHON_LOCATION=${PYTHON_LOCATION}" \
- --name "${TEST_NAME}-app" \
- --network "${TEST_NAME}-network" \
- --entrypoint="" \
- "${IMAGE_TAG}" \
- sh -c '/usr/bin/aws-lambda-rie ${PYTHON_LOCATION} -m awslambdaric app.handler'
- - sleep 2
- - >
- docker run \
- --name "${TEST_NAME}-tester" \
- --env "TARGET=${TEST_NAME}-app" \
- --network "${TEST_NAME}-network" \
- --entrypoint="" \
- "${IMAGE_TAG}" \
- sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10'
- - actual="$(docker logs --tail 1 "${TEST_NAME}-tester" | xargs)"
- - expected='success'
- - |
- echo "Response: ${actual}"
- if [[ "$actual" != "$expected" ]]; then
- echo "fail! runtime: $RUNTIME - expected output $expected - got $actual"
- echo "---------Container Logs: ${TEST_NAME}-app----------"
- echo
- docker logs "${TEST_NAME}-app" || true
- echo
- echo "---------------------------------------------------"
- echo "--------Container Logs: ${TEST_NAME}-tester--------"
- echo
- docker logs "${TEST_NAME}-tester" || true
- echo
- echo "---------------------------------------------------"
- exit -1
- fi
- finally:
- - echo "Cleaning up..."
- - docker stop "${TEST_NAME}-app" || true
- - docker rm --force "${TEST_NAME}-app" || true
- - docker stop "${TEST_NAME}-tester" || true
- - docker rm --force "${TEST_NAME}-tester" || true
- - docker network rm "${TEST_NAME}-network" || true
diff --git a/tests/integration/codebuild/buildspec.os.amazonlinux.2.yml b/tests/integration/codebuild/buildspec.os.amazonlinux.2.yml
index 38f2509..05722bb 100644
--- a/tests/integration/codebuild/buildspec.os.amazonlinux.2.yml
+++ b/tests/integration/codebuild/buildspec.os.amazonlinux.2.yml
@@ -2,7 +2,7 @@ version: 0.2
env:
variables:
- OS_DISTRIBUTION: amazonlinux
+ OS_DISTRIBUTION: amazonlinux2
PYTHON_LOCATION: "/usr/local/bin/python3"
TEST_NAME: "aws-lambda-python-rtc-amazonlinux-test"
batch:
@@ -17,12 +17,9 @@ batch:
DISTRO_VERSION:
- "2"
RUNTIME_VERSION:
- - "3.7"
- - "3.8"
- "3.9"
- "3.10"
- "3.11"
- - "3.12"
phases:
pre_build:
commands:
@@ -48,13 +45,8 @@ phases:
echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \
"${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
- >
- if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]];
- then
- echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login."
- else
- echo "Performing DockerHub login . . ."
- docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
- fi
+ echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json
+ service docker restart
- echo "Building image ${IMAGE_TAG}"
- >
docker build . \
@@ -62,7 +54,8 @@ phases:
-t "${IMAGE_TAG}" \
--build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \
--build-arg DISTRO_VERSION="${DISTRO_VERSION}" \
- --build-arg ARCHITECTURE="${ARCHITECTURE}"
+ --build-arg ARCHITECTURE="${ARCHITECTURE}" \
+ --load
build:
commands:
- set -x
diff --git a/tests/integration/codebuild/buildspec.os.centos.yml b/tests/integration/codebuild/buildspec.os.amazonlinux.2023.yml
similarity index 85%
rename from tests/integration/codebuild/buildspec.os.centos.yml
rename to tests/integration/codebuild/buildspec.os.amazonlinux.2023.yml
index e6930b9..9d6d20f 100644
--- a/tests/integration/codebuild/buildspec.os.centos.yml
+++ b/tests/integration/codebuild/buildspec.os.amazonlinux.2023.yml
@@ -2,9 +2,9 @@ version: 0.2
env:
variables:
- OS_DISTRIBUTION: centos
+ OS_DISTRIBUTION: amazonlinux2023
PYTHON_LOCATION: "/usr/local/bin/python3"
- TEST_NAME: "aws-lambda-python-rtc-centos-test"
+ TEST_NAME: "aws-lambda-python-rtc-amazonlinux-test"
batch:
build-matrix:
static:
@@ -15,13 +15,10 @@ batch:
env:
variables:
DISTRO_VERSION:
- - "7"
+ - "2023"
RUNTIME_VERSION:
- - "3.8"
- - "3.9"
- - "3.10"
- - "3.11"
- "3.12"
+ - "3.13"
phases:
pre_build:
commands:
@@ -47,13 +44,8 @@ phases:
echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \
"${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
- >
- if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]];
- then
- echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login."
- else
- echo "Performing DockerHub login . . ."
- docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
- fi
+ echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json
+ service docker restart
- echo "Building image ${IMAGE_TAG}"
- >
docker build . \
@@ -61,7 +53,8 @@ phases:
-t "${IMAGE_TAG}" \
--build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \
--build-arg DISTRO_VERSION="${DISTRO_VERSION}" \
- --build-arg ARCHITECTURE="${ARCHITECTURE}"
+ --build-arg ARCHITECTURE="${ARCHITECTURE}" \
+ --load
build:
commands:
- set -x
diff --git a/tests/integration/codebuild/buildspec.os.debian.yml b/tests/integration/codebuild/buildspec.os.debian.yml
index 7365f07..44c061f 100644
--- a/tests/integration/codebuild/buildspec.os.debian.yml
+++ b/tests/integration/codebuild/buildspec.os.debian.yml
@@ -15,14 +15,14 @@ batch:
env:
variables:
DISTRO_VERSION:
- - "buster"
+ - "bookworm"
- "bullseye"
RUNTIME_VERSION:
- - "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
+ - "3.13"
phases:
pre_build:
commands:
@@ -51,20 +51,16 @@ phases:
echo "RUN apt-get update && apt-get install -y curl" >> \
"${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
- >
- if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]];
- then
- echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login."
- else
- echo "Performing DockerHub login . . ."
- docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
- fi
+ echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json
+ service docker restart
- echo "Building image ${IMAGE_TAG}"
- >
docker build . \
-f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \
-t "${IMAGE_TAG}" \
--build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \
- --build-arg DISTRO_VERSION="${DISTRO_VERSION}"
+ --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \
+ --load
build:
commands:
- set -x
diff --git a/tests/integration/codebuild/buildspec.os.ubuntu.yml b/tests/integration/codebuild/buildspec.os.ubuntu.yml
index 2e312f6..a6e556d 100644
--- a/tests/integration/codebuild/buildspec.os.ubuntu.yml
+++ b/tests/integration/codebuild/buildspec.os.ubuntu.yml
@@ -15,14 +15,14 @@ batch:
env:
variables:
DISTRO_VERSION:
- - "20.04"
- "22.04"
+ - "24.04"
RUNTIME_VERSION:
- - "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
+ - "3.13"
phases:
pre_build:
commands:
@@ -48,20 +48,16 @@ phases:
echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \
"${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
- >
- if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]];
- then
- echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login."
- else
- echo "Performing DockerHub login . . ."
- docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
- fi
+ echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json
+ service docker restart
- echo "Building image ${IMAGE_TAG}"
- >
docker build . \
-f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \
-t "${IMAGE_TAG}" \
--build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \
- --build-arg DISTRO_VERSION="${DISTRO_VERSION}"
+ --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \
+ --load
build:
commands:
- set -x
diff --git a/tests/integration/docker/Dockerfile.echo.alpine b/tests/integration/docker/Dockerfile.echo.alpine
index a53efc0..f6790fa 100644
--- a/tests/integration/docker/Dockerfile.echo.alpine
+++ b/tests/integration/docker/Dockerfile.echo.alpine
@@ -7,7 +7,8 @@ ARG DISTRO_VERSION
FROM public.ecr.aws/docker/library/python:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} AS python-alpine
# Install libstdc++
RUN apk add --no-cache \
- libstdc++
+ libstdc++ \
+ binutils
# Stage 2 - build function and dependencies
@@ -30,6 +31,7 @@ RUN mkdir -p ${RIC_BUILD_DIR}
# Copy function code and Runtime Interface Client .tgz
WORKDIR ${RIC_BUILD_DIR}
COPY . .
+RUN pip3 install setuptools
RUN make init build test && \
mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz
@@ -39,15 +41,12 @@ ARG FUNCTION_DIR="/home/app/"
RUN mkdir -p ${FUNCTION_DIR}
# Copy function code
COPY tests/integration/test-handlers/echo/* ${FUNCTION_DIR}
-# Copy Runtime Interface Client .tgz
-RUN cp ./dist/awslambdaric-test.tar.gz ${FUNCTION_DIR}/awslambdaric-test.tar.gz
# Install the function's dependencies
WORKDIR ${FUNCTION_DIR}
RUN python${RUNTIME_VERSION} -m pip install \
- awslambdaric-test.tar.gz \
- --target ${FUNCTION_DIR} && \
- rm awslambdaric-test.tar.gz
+ ${RIC_BUILD_DIR}/dist/awslambdaric-test.tar.gz \
+ --target ${FUNCTION_DIR}
# Stage 3 - final runtime interface client image
diff --git a/tests/integration/docker/Dockerfile.echo.amazonlinux b/tests/integration/docker/Dockerfile.echo.amazonlinux2
similarity index 98%
rename from tests/integration/docker/Dockerfile.echo.amazonlinux
rename to tests/integration/docker/Dockerfile.echo.amazonlinux2
index 188de01..be05aa1 100644
--- a/tests/integration/docker/Dockerfile.echo.amazonlinux
+++ b/tests/integration/docker/Dockerfile.echo.amazonlinux2
@@ -17,8 +17,10 @@ RUN yum install -y \
freetype-devel \
yum-utils \
findutils \
- openssl-devel \
wget \
+ openssl11 \
+ openssl11-devel \
+ bzip2-devel \
libffi-devel \
sqlite-devel
@@ -78,6 +80,7 @@ RUN mkdir -p ${RIC_BUILD_DIR}
# Copy function code and Runtime Interface Client .tgz
WORKDIR ${RIC_BUILD_DIR}
COPY . .
+
RUN make init build test && \
mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz
diff --git a/tests/integration/docker/Dockerfile.echo.centos b/tests/integration/docker/Dockerfile.echo.amazonlinux2023
similarity index 77%
rename from tests/integration/docker/Dockerfile.echo.centos
rename to tests/integration/docker/Dockerfile.echo.amazonlinux2023
index e2dd3d0..16bbc79 100644
--- a/tests/integration/docker/Dockerfile.echo.centos
+++ b/tests/integration/docker/Dockerfile.echo.amazonlinux2023
@@ -1,13 +1,12 @@
ARG DISTRO_VERSION
-
# Stage 1 - bundle base image + runtime interface client
# Grab a fresh copy of the image and install Python
-FROM public.ecr.aws/docker/library/centos:${DISTRO_VERSION} AS python-centos-builder
+FROM public.ecr.aws/amazonlinux/amazonlinux:${DISTRO_VERSION} AS python-amazonlinux-builder
ARG RUNTIME_VERSION
# Install apt dependencies
-RUN yum install -y \
+RUN dnf install -y \
gcc \
gcc-c++ \
tar \
@@ -18,8 +17,10 @@ RUN yum install -y \
freetype-devel \
yum-utils \
findutils \
- openssl-devel \
wget \
+ openssl \
+ openssl-devel \
+ bzip2-devel \
libffi-devel \
sqlite-devel
@@ -39,21 +40,21 @@ RUN RUNTIME_LATEST_VERSION=${RUNTIME_VERSION}.$(curl -s https://www.python.org/f
&& ln -s /usr/local/bin/python${RUNTIME_VERSION} /usr/local/bin/python${RUNTIME_LATEST_VERSION}
# Stage 2 - clean python build dependencies
-FROM public.ecr.aws/docker/library/centos:${DISTRO_VERSION} AS python-centos
-RUN yum install -y \
+FROM public.ecr.aws/amazonlinux/amazonlinux:${DISTRO_VERSION} AS python-amazonlinux
+RUN dnf install -y \
libffi-devel
# Copy the compiled python to /usr/local
-COPY --from=python-centos-builder /usr/local /usr/local
+COPY --from=python-amazonlinux-builder /usr/local /usr/local
ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
# Stage 3 - build function and dependencies
-FROM python-centos-builder AS build-image
+FROM python-amazonlinux-builder AS build-image
ARG RUNTIME_VERSION
ARG ARCHITECTURE
# Install aws-lambda-cpp build dependencies
-RUN yum install -y \
+RUN dnf install -y \
tar \
gzip \
make \
@@ -62,7 +63,8 @@ RUN yum install -y \
libtool \
libcurl-devel \
gcc-c++ \
- wget
+ wget \
+ sqlite-devel
# Install a modern CMake
RUN wget --quiet -O cmake-install https://github.com/Kitware/CMake/releases/download/v3.20.0/cmake-3.20.0-linux-${ARCHITECTURE}.sh && \
@@ -79,8 +81,19 @@ RUN mkdir -p ${RIC_BUILD_DIR}
# Copy function code and Runtime Interface Client .tgz
WORKDIR ${RIC_BUILD_DIR}
COPY . .
-RUN make init build && \
- mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz
+
+# distutils no longer available in python3.12 and later
+# https://docs.python.org/3/whatsnew/3.12.html
+# https://peps.python.org/pep-0632/
+RUN pip3 install setuptools
+RUN make init build
+
+RUN mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz
+RUN python${RUNTIME_VERSION} -m pip install \
+ ./dist/awslambdaric-test.tar.gz \
+ --target ${RIC_BUILD_DIR}
+
+RUN make test
# Include global args in this stage of the build
ARG FUNCTION_DIR="/home/app/"
@@ -93,19 +106,16 @@ RUN cp ./dist/awslambdaric-test.tar.gz ${FUNCTION_DIR}/awslambdaric-test.tar.gz
# Install the function's dependencies
WORKDIR ${FUNCTION_DIR}
-ARG ENABLE_LTO=OFF
-ENV ENABLE_LTO ${ENABLE_LTO}
RUN python${RUNTIME_VERSION} -m pip install \
- awslambdaric-test.tar.gz \
- --verbose \
- --target ${FUNCTION_DIR} && \
+ awslambdaric-test.tar.gz \
+ --target ${FUNCTION_DIR} && \
rm awslambdaric-test.tar.gz
# Stage 4 - final runtime interface client image
# Grab a fresh copy of the Python image
-FROM python-centos
-
+FROM python-amazonlinux
+RUN dnf install -y brotli
# Include global arg in this stage of the build
ARG FUNCTION_DIR="/home/app/"
# Set working directory to function root directory
diff --git a/tests/integration/docker/Dockerfile.echo.debian b/tests/integration/docker/Dockerfile.echo.debian
index 8ac660b..bf0f4fa 100644
--- a/tests/integration/docker/Dockerfile.echo.debian
+++ b/tests/integration/docker/Dockerfile.echo.debian
@@ -19,6 +19,7 @@ RUN mkdir -p ${RIC_BUILD_DIR}
# Copy function code and Runtime Interface Client .tgz
WORKDIR ${RIC_BUILD_DIR}
COPY . .
+RUN pip3 install setuptools
RUN make init build test && \
mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz
diff --git a/tests/integration/docker/Dockerfile.echo.ubuntu b/tests/integration/docker/Dockerfile.echo.ubuntu
index 692b3f2..0ce3000 100644
--- a/tests/integration/docker/Dockerfile.echo.ubuntu
+++ b/tests/integration/docker/Dockerfile.echo.ubuntu
@@ -9,40 +9,41 @@ ENV DEBIAN_FRONTEND=noninteractive
ARG RUNTIME_VERSION
# Install python and pip
-RUN apt-get update && \
- apt-get install -y \
- software-properties-common
+RUN apt-get update && apt-get install -y software-properties-common
RUN add-apt-repository ppa:deadsnakes/ppa
RUN apt-get update && \
- apt-get install -y \
- curl \
- python${RUNTIME_VERSION} \
- python${RUNTIME_VERSION}-distutils
+ apt-get install -y \
+ curl \
+ python${RUNTIME_VERSION} \
+ python3-pip \
+ python3-virtualenv
+
+# python3xx-distutils is needed for python < 3.12
+RUN if [ $(echo ${RUNTIME_VERSION} | cut -d '.' -f 2) -lt 12 ]; then \
+ apt-get install -y python${RUNTIME_VERSION}-distutils; \
+ fi
+RUN virtualenv --python /usr/bin/python${RUNTIME_VERSION} --no-setuptools /home/venv
+
-RUN ln -s /usr/bin/python${RUNTIME_VERSION} /usr/local/bin/python3
# Stage 2 - build function and dependencies
FROM python-image AS python-ubuntu-builder
ARG RUNTIME_VERSION
-RUN curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
-RUN python${RUNTIME_VERSION} get-pip.py
-
# Install aws-lambda-cpp build dependencies
-RUN apt-get update && \
- apt-get install -y \
- g++ \
- gcc \
- tar \
- gzip \
- make \
- cmake \
- autoconf \
- automake \
- libtool \
- libcurl4-openssl-dev \
- python${RUNTIME_VERSION}-dev
+RUN apt-get install -y \
+ g++ \
+ gcc \
+ tar \
+ gzip \
+ make \
+ cmake \
+ autoconf \
+ automake \
+ libtool \
+ libcurl4-openssl-dev \
+ python${RUNTIME_VERSION}-dev
# Include global args in this stage of the build
ARG RIC_BUILD_DIR="/home/build/"
@@ -51,27 +52,28 @@ RUN mkdir -p ${RIC_BUILD_DIR}
# Copy function code and Runtime Interface Client .tgz
WORKDIR ${RIC_BUILD_DIR}
COPY . .
-RUN make init build test && \
+RUN . /home/venv/bin/activate && \
+ pip install setuptools && \
+ make init build test && \
mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz
+
+
# Include global args in this stage of the build
ARG FUNCTION_DIR="/home/app/"
# Create function directory
RUN mkdir -p ${FUNCTION_DIR}
# Copy function code
COPY tests/integration/test-handlers/echo/* ${FUNCTION_DIR}
-# Copy Runtime Interface Client .tgz
-RUN cp ./dist/awslambdaric-test.tar.gz ${FUNCTION_DIR}/awslambdaric-test.tar.gz
-
# Install the function's dependencies
WORKDIR ${FUNCTION_DIR}
-RUN python${RUNTIME_VERSION} -m pip install \
- awslambdaric-test.tar.gz \
- --target ${FUNCTION_DIR} && \
- rm awslambdaric-test.tar.gz
+RUN . /home/venv/bin/activate && \
+ pip install ${RIC_BUILD_DIR}/dist/awslambdaric-test.tar.gz --target ${FUNCTION_DIR}
+
+
-# Stage 4 - final runtime interface client image
+# Stage 3 - final runtime interface client image
# Grab a fresh copy of the Python image
FROM python-image
diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py
index 7bc2ad2..33afb1c 100644
--- a/tests/test_bootstrap.py
+++ b/tests/test_bootstrap.py
@@ -14,15 +14,20 @@
import unittest
from io import StringIO
from tempfile import NamedTemporaryFile
-from unittest.mock import MagicMock, Mock, patch
+from unittest.mock import MagicMock, Mock, patch, ANY
import awslambdaric.bootstrap as bootstrap
from awslambdaric.lambda_runtime_exception import FaultException
-from awslambdaric.lambda_runtime_log_utils import LogFormat, _get_log_level_from_env_var
+from awslambdaric.lambda_runtime_log_utils import (
+ LogFormat,
+ _get_log_level_from_env_var,
+ JsonFormatter,
+)
from awslambdaric.lambda_runtime_marshaller import LambdaMarshaller
from awslambdaric.lambda_literals import (
lambda_unhandled_exception_warning_message,
)
+import snapshot_restore_py
class TestUpdateXrayEnv(unittest.TestCase):
@@ -60,6 +65,14 @@ def setUp(self):
self.event_body = '"event_body"'
self.working_directory = os.getcwd()
+ logging.getLogger().handlers.clear()
+
+ def tearDown(self) -> None:
+ logging.getLogger().handlers.clear()
+ logging.getLogger().level = logging.NOTSET
+
+ return super().tearDown()
+
@staticmethod
def dummy_handler(json_input, lambda_context):
return {"input": json_input, "aws_request_id": lambda_context.aws_request_id}
@@ -75,6 +88,7 @@ def test_handle_event_request_happy_case(self):
{},
"invoked_function_arn",
0,
+ "tenant_id",
bootstrap.StandardLogSink(),
)
self.lambda_runtime.post_invocation_result.assert_called_once_with(
@@ -98,6 +112,7 @@ def test_handle_event_request_invalid_client_context(self):
{},
"invoked_function_arn",
0,
+ "tenant_id",
bootstrap.StandardLogSink(),
)
args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -139,6 +154,7 @@ def test_handle_event_request_invalid_cognito_idenity(self):
"invalid_cognito_identity",
"invoked_function_arn",
0,
+ "tenant_id",
bootstrap.StandardLogSink(),
)
args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -181,6 +197,7 @@ def test_handle_event_request_invalid_event_body(self):
{},
"invoked_function_arn",
0,
+ "tenant_id",
bootstrap.StandardLogSink(),
)
args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -225,6 +242,7 @@ def invalid_json_response(json_input, lambda_context):
{},
"invoked_function_arn",
0,
+ "tenant_id",
bootstrap.StandardLogSink(),
)
args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -270,6 +288,7 @@ def __init__(self, message):
{},
"invoked_function_arn",
0,
+ "tenant_id",
bootstrap.StandardLogSink(),
)
args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -322,6 +341,7 @@ def __init__(self, message):
{},
"invoked_function_arn",
0,
+ "tenant_id",
bootstrap.StandardLogSink(),
)
args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -373,6 +393,7 @@ def unable_to_import_module(json_input, lambda_context):
{},
"invoked_function_arn",
0,
+ "tenant_id",
bootstrap.StandardLogSink(),
)
args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -412,6 +433,7 @@ def raise_exception_handler(json_input, lambda_context):
{},
"invoked_function_arn",
0,
+ "tenant_id",
bootstrap.StandardLogSink(),
)
args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -450,6 +472,8 @@ def raise_exception_handler(json_input, lambda_context):
),
)
+ logging.getLogger().addHandler(logging.StreamHandler(mock_stdout))
+
bootstrap.handle_event_request(
self.lambda_runtime,
raise_exception_handler,
@@ -460,14 +484,12 @@ def raise_exception_handler(json_input, lambda_context):
{},
"invoked_function_arn",
0,
+ "tenant_id",
bootstrap.StandardLogSink(),
)
# NOTE: Indentation characters are NO-BREAK SPACE (U+00A0) not SPACE (U+0020)
- error_logs = (
- lambda_unhandled_exception_warning_message
- + "[ERROR] FaultExceptionType: Fault exception msg\r"
- )
+ error_logs = "[ERROR] FaultExceptionType: Fault exception msg\r"
error_logs += "Traceback (most recent call last):\r"
error_logs += ' File "spam.py", line 3, in <module>\r'
error_logs += " spam.eggs()\r"
@@ -486,6 +508,8 @@ def raise_exception_handler(json_input, lambda_context):
"FaultExceptionType", "Fault exception msg", None
)
+ logging.getLogger().addHandler(logging.StreamHandler(mock_stdout))
+
bootstrap.handle_event_request(
self.lambda_runtime,
raise_exception_handler,
@@ -496,12 +520,10 @@ def raise_exception_handler(json_input, lambda_context):
{},
"invoked_function_arn",
0,
+ "tenant_id",
bootstrap.StandardLogSink(),
)
- error_logs = (
- lambda_unhandled_exception_warning_message
- + "[ERROR] FaultExceptionType: Fault exception msg\rTraceback (most recent call last):\n"
- )
+ error_logs = "[ERROR] FaultExceptionType: Fault exception msg\rTraceback (most recent call last):\n"
self.assertEqual(mock_stdout.getvalue(), error_logs)
@@ -515,6 +537,8 @@ def raise_exception_handler(json_input, lambda_context):
except ImportError:
raise bootstrap.FaultException("FaultExceptionType", None, None)
+ logging.getLogger().addHandler(logging.StreamHandler(mock_stdout))
+
bootstrap.handle_event_request(
self.lambda_runtime,
raise_exception_handler,
@@ -525,12 +549,10 @@ def raise_exception_handler(json_input, lambda_context):
{},
"invoked_function_arn",
0,
+ "tenant_id",
bootstrap.StandardLogSink(),
)
- error_logs = (
- lambda_unhandled_exception_warning_message
- + "[ERROR] FaultExceptionType\rTraceback (most recent call last):\n"
- )
+ error_logs = "[ERROR] FaultExceptionType\rTraceback (most recent call last):\n"
self.assertEqual(mock_stdout.getvalue(), error_logs)
@@ -544,6 +566,8 @@ def raise_exception_handler(json_input, lambda_context):
except ImportError:
raise bootstrap.FaultException(None, "Fault exception msg", None)
+ logging.getLogger().addHandler(logging.StreamHandler(mock_stdout))
+
bootstrap.handle_event_request(
self.lambda_runtime,
raise_exception_handler,
@@ -554,12 +578,10 @@ def raise_exception_handler(json_input, lambda_context):
{},
"invoked_function_arn",
0,
+ "tenant_id",
bootstrap.StandardLogSink(),
)
- error_logs = (
- lambda_unhandled_exception_warning_message
- + "[ERROR] Fault exception msg\rTraceback (most recent call last):\n"
- )
+ error_logs = "[ERROR] Fault exception msg\rTraceback (most recent call last):\n"
self.assertEqual(mock_stdout.getvalue(), error_logs)
@@ -582,6 +604,8 @@ def raise_exception_handler(json_input, lambda_context):
),
)
+ logging.getLogger().addHandler(logging.StreamHandler(mock_stdout))
+
bootstrap.handle_event_request(
self.lambda_runtime,
raise_exception_handler,
@@ -592,9 +616,10 @@ def raise_exception_handler(json_input, lambda_context):
{},
"invoked_function_arn",
0,
+ "tenant_id",
bootstrap.StandardLogSink(),
)
- error_logs = lambda_unhandled_exception_warning_message + "[ERROR]\r"
+ error_logs = "[ERROR]\r"
error_logs += "Traceback (most recent call last):\r"
error_logs += ' File "spam.py", line 3, in <module>\r'
error_logs += " spam.eggs()\r"
@@ -603,6 +628,39 @@ def raise_exception_handler(json_input, lambda_context):
self.assertEqual(mock_stdout.getvalue(), error_logs)
+ @patch("sys.stdout", new_callable=StringIO)
+ def test_handle_event_request_fault_exception_logging_in_json(self, mock_stdout):
+ def raise_exception_handler(json_input, lambda_context):
+ try:
+ import invalid_module # noqa: F401
+ except ImportError:
+ raise bootstrap.FaultException("FaultExceptionType", None, None)
+
+ logging_handler = logging.StreamHandler(mock_stdout)
+ logging_handler.setFormatter(JsonFormatter())
+ logging.getLogger().addHandler(logging_handler)
+
+ bootstrap.handle_event_request(
+ self.lambda_runtime,
+ raise_exception_handler,
+ "invoke_id",
+ self.event_body,
+ "application/json",
+ {},
+ {},
+ "invoked_function_arn",
+ 0,
+ "tenant_id",
+ bootstrap.StandardLogSink(),
+ )
+
+ stdout_value = mock_stdout.getvalue()
+
+ # this line is not in json because of the way the test runtime is bootstrapped
+ error_logs = "[ERROR] FaultExceptionType\rTraceback (most recent call last):\n"
+
+ self.assertEqual(stdout_value, error_logs)
+
class TestXrayFault(unittest.TestCase):
def test_make_xray(self):
@@ -799,6 +857,7 @@ def test_application_json(self):
cognito_identity_json=None,
invoked_function_arn="invocation-arn",
epoch_deadline_time_in_ms=1415836801003,
+ tenant_id=None,
log_sink=bootstrap.StandardLogSink(),
)
@@ -818,6 +877,7 @@ def test_binary_request_binary_response(self):
cognito_identity_json=None,
invoked_function_arn="invocation-arn",
epoch_deadline_time_in_ms=1415836801003,
+ tenant_id=None,
log_sink=bootstrap.StandardLogSink(),
)
@@ -837,6 +897,7 @@ def test_json_request_binary_response(self):
cognito_identity_json=None,
invoked_function_arn="invocation-arn",
epoch_deadline_time_in_ms=1415836801003,
+ tenant_id=None,
log_sink=bootstrap.StandardLogSink(),
)
@@ -855,6 +916,7 @@ def test_binary_with_application_json(self):
cognito_identity_json=None,
invoked_function_arn="invocation-arn",
epoch_deadline_time_in_ms=1415836801003,
+ tenant_id=None,
log_sink=bootstrap.StandardLogSink(),
)
@@ -1288,6 +1350,31 @@ def test_json_formatter(self, mock_stderr):
)
self.assertEqual(mock_stderr.getvalue(), "")
+ @patch("awslambdaric.bootstrap._GLOBAL_TENANT_ID", "test-tenant-id")
+ @patch("sys.stderr", new_callable=StringIO)
+ def test_json_formatter_with_tenant_id(self, mock_stderr):
+ logger = logging.getLogger("a.b")
+ level = logging.INFO
+ message = "Test json formatting with tenant id"
+ expected = {
+ "level": "INFO",
+ "logger": "a.b",
+ "message": message,
+ "requestId": "",
+ "tenantId": "test-tenant-id",
+ }
+
+ with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
+ logger.log(level, message)
+
+ data = json.loads(mock_stdout.getvalue())
+ data.pop("timestamp")
+ self.assertEqual(
+ data,
+ expected,
+ )
+ self.assertEqual(mock_stderr.getvalue(), "")
+
@patch("sys.stdout", new_callable=StringIO)
@patch("sys.stderr", new_callable=StringIO)
def test_exception(self, mock_stderr, mock_stdout):
@@ -1457,5 +1544,53 @@ class TestException(Exception):
mock_sys.exit.assert_called_once_with(1)
+class TestOnInitComplete(unittest.TestCase):
+ def tearDown(self):
+ # We are accessing private filed for cleaning up
+ snapshot_restore_py._before_snapshot_registry = []
+ snapshot_restore_py._after_restore_registry = []
+
+ # We are using ANY over here as the main thing we want to test is teh errorType propogation and stack trace generation
+ error_result = {
+ "errorMessage": "This is a Dummy type error",
+ "errorType": "TypeError",
+ "requestId": "",
+ "stackTrace": ANY,
+ }
+
+ def raise_type_error(self):
+ raise TypeError("This is a Dummy type error")
+
+ @patch("awslambdaric.bootstrap.LambdaRuntimeClient")
+ def test_before_snapshot_exception(self, mock_runtime_client):
+ snapshot_restore_py.register_before_snapshot(self.raise_type_error)
+
+ with self.assertRaises(SystemExit) as cm:
+ bootstrap.on_init_complete(
+ mock_runtime_client, log_sink=bootstrap.StandardLogSink()
+ )
+
+ self.assertEqual(cm.exception.code, 64)
+ mock_runtime_client.post_init_error.assert_called_once_with(
+ self.error_result,
+ FaultException.BEFORE_SNAPSHOT_ERROR,
+ )
+
+ @patch("awslambdaric.bootstrap.LambdaRuntimeClient")
+ def test_after_restore_exception(self, mock_runtime_client):
+ snapshot_restore_py.register_after_restore(self.raise_type_error)
+
+ with self.assertRaises(SystemExit) as cm:
+ bootstrap.on_init_complete(
+ mock_runtime_client, log_sink=bootstrap.StandardLogSink()
+ )
+
+ self.assertEqual(cm.exception.code, 65)
+ mock_runtime_client.restore_next.assert_called_once()
+ mock_runtime_client.report_restore_error.assert_called_once_with(
+ self.error_result
+ )
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/test_lambda_context.py b/tests/test_lambda_context.py
index 34d59da..f7959ab 100644
--- a/tests/test_lambda_context.py
+++ b/tests/test_lambda_context.py
@@ -37,6 +37,7 @@ def test_init(self):
self.assertEqual(context.memory_limit_in_mb, "1234")
self.assertEqual(context.function_version, "version1")
self.assertEqual(context.invoked_function_arn, "arn:test1")
+ self.assertEqual(context.tenant_id, None)
self.assertEqual(context.identity.cognito_identity_id, None)
self.assertEqual(context.identity.cognito_identity_pool_id, None)
self.assertEqual(context.client_context.client.installation_id, None)
@@ -74,6 +75,21 @@ def test_init_cognito(self):
self.assertEqual(context.identity.cognito_identity_id, "id1")
self.assertEqual(context.identity.cognito_identity_pool_id, "poolid1")
+ def test_init_tenant_id(self):
+ client_context = {}
+ cognito_identity = {}
+ tenant_id = "blue"
+
+ context = LambdaContext(
+ "invoke-id1",
+ client_context,
+ cognito_identity,
+ 1415836801000,
+ "arn:test",
+ tenant_id,
+ )
+ self.assertEqual(context.tenant_id, "blue")
+
def test_init_client_context(self):
client_context = {
"client": {
diff --git a/tests/test_lambda_runtime_client.py b/tests/test_lambda_runtime_client.py
index b0eae4a..fc4af65 100644
--- a/tests/test_lambda_runtime_client.py
+++ b/tests/test_lambda_runtime_client.py
@@ -14,6 +14,7 @@
LambdaRuntimeClientError,
_user_agent,
)
+from awslambdaric.lambda_runtime_marshaller import to_json
class TestInvocationRequest(unittest.TestCase):
@@ -25,6 +26,7 @@ def test_constructor(self):
deadline_time_in_ms="Lambda-Runtime-Deadline-Ms",
client_context="Lambda-Runtime-Client-Context",
cognito_identity="Lambda-Runtime-Cognito-Identity",
+ tenant_id="Lambda-Runtime-Aws-Tenant-Id",
content_type="Content-Type",
event_body="response_body",
)
@@ -36,6 +38,7 @@ def test_constructor(self):
deadline_time_in_ms="Lambda-Runtime-Deadline-Ms",
client_context="Lambda-Runtime-Client-Context",
cognito_identity="Lambda-Runtime-Cognito-Identity",
+ tenant_id="Lambda-Runtime-Aws-Tenant-Id",
content_type="Content-Type",
event_body="response_body",
)
@@ -47,6 +50,7 @@ def test_constructor(self):
deadline_time_in_ms="Lambda-Runtime-Deadline-Ms",
client_context="Lambda-Runtime-Client-Context",
cognito_identity="Lambda-Runtime-Cognito-Identity",
+ tenant_id="Lambda-Runtime-Aws-Tenant-Id",
content_type="Content-Type",
event_body="another_response_body",
)
@@ -67,6 +71,7 @@ def test_wait_next_invocation(self, mock_runtime_client):
"Lambda-Runtime-Deadline-Ms": 12,
"Lambda-Runtime-Client-Context": "client_context",
"Lambda-Runtime-Cognito-Identity": "cognito_identity",
+ "Lambda-Runtime-Aws-Tenant-Id": "tenant_id",
"Content-Type": "application/json",
}
mock_runtime_client.next.return_value = response_body, headears
@@ -81,6 +86,7 @@ def test_wait_next_invocation(self, mock_runtime_client):
self.assertEqual(event_request.deadline_time_in_ms, 12)
self.assertEqual(event_request.client_context, "client_context")
self.assertEqual(event_request.cognito_identity, "cognito_identity")
+ self.assertEqual(event_request.tenant_id, "tenant_id")
self.assertEqual(event_request.content_type, "application/json")
self.assertEqual(event_request.event_body, response_body)
@@ -96,9 +102,101 @@ def test_wait_next_invocation(self, mock_runtime_client):
self.assertEqual(event_request.deadline_time_in_ms, 12)
self.assertEqual(event_request.client_context, "client_context")
self.assertEqual(event_request.cognito_identity, "cognito_identity")
+ self.assertEqual(event_request.tenant_id, "tenant_id")
self.assertEqual(event_request.content_type, "application/json")
self.assertEqual(event_request.event_body, response_body)
+ @patch("awslambdaric.lambda_runtime_client.runtime_client")
+ def test_wait_next_invocation_without_tenant_id_header(self, mock_runtime_client):
+ response_body = b"{}"
+ headers = {
+ "Lambda-Runtime-Aws-Request-Id": "RID1234",
+ "Lambda-Runtime-Trace-Id": "TID1234",
+ "Lambda-Runtime-Invoked-Function-Arn": "FARN1234",
+ "Lambda-Runtime-Deadline-Ms": 12,
+ "Lambda-Runtime-Client-Context": "client_context",
+ "Lambda-Runtime-Cognito-Identity": "cognito_identity",
+ "Content-Type": "application/json",
+ }
+ mock_runtime_client.next.return_value = response_body, headers
+ runtime_client = LambdaRuntimeClient("localhost:1234")
+
+ event_request = runtime_client.wait_next_invocation()
+
+ self.assertIsNotNone(event_request)
+ self.assertIsNone(event_request.tenant_id)
+ self.assertEqual(event_request.event_body, response_body)
+
+ @patch("awslambdaric.lambda_runtime_client.runtime_client")
+ def test_wait_next_invocation_with_null_tenant_id_header(self, mock_runtime_client):
+ response_body = b"{}"
+ headers = {
+ "Lambda-Runtime-Aws-Request-Id": "RID1234",
+ "Lambda-Runtime-Trace-Id": "TID1234",
+ "Lambda-Runtime-Invoked-Function-Arn": "FARN1234",
+ "Lambda-Runtime-Deadline-Ms": 12,
+ "Lambda-Runtime-Client-Context": "client_context",
+ "Lambda-Runtime-Cognito-Identity": "cognito_identity",
+ "Lambda-Runtime-Aws-Tenant-Id": None,
+ "Content-Type": "application/json",
+ }
+ mock_runtime_client.next.return_value = response_body, headers
+ runtime_client = LambdaRuntimeClient("localhost:1234")
+
+ event_request = runtime_client.wait_next_invocation()
+
+ self.assertIsNotNone(event_request)
+ self.assertIsNone(event_request.tenant_id)
+ self.assertEqual(event_request.event_body, response_body)
+
+ @patch("awslambdaric.lambda_runtime_client.runtime_client")
+ def test_wait_next_invocation_with_empty_tenant_id_header(
+ self, mock_runtime_client
+ ):
+ response_body = b"{}"
+ headers = {
+ "Lambda-Runtime-Aws-Request-Id": "RID1234",
+ "Lambda-Runtime-Trace-Id": "TID1234",
+ "Lambda-Runtime-Invoked-Function-Arn": "FARN1234",
+ "Lambda-Runtime-Deadline-Ms": 12,
+ "Lambda-Runtime-Client-Context": "client_context",
+ "Lambda-Runtime-Cognito-Identity": "cognito_identity",
+ "Lambda-Runtime-Aws-Tenant-Id": "",
+ "Content-Type": "application/json",
+ }
+ mock_runtime_client.next.return_value = response_body, headers
+ runtime_client = LambdaRuntimeClient("localhost:1234")
+
+ event_request = runtime_client.wait_next_invocation()
+
+ self.assertIsNotNone(event_request)
+ self.assertEqual(event_request.tenant_id, "")
+ self.assertEqual(event_request.event_body, response_body)
+
+ error_result = {
+ "errorMessage": "Dummy message",
+ "errorType": "Runtime.DummyError",
+ "requestId": "",
+ "stackTrace": [],
+ }
+
+ headers = {"Lambda-Runtime-Function-Error-Type": error_result["errorType"]}
+
+ restore_error_result = {
+ "errorMessage": "Dummy Restore error",
+ "errorType": "Runtime.DummyRestoreError",
+ "requestId": "",
+ "stackTrace": [],
+ }
+
+ restore_error_header = {
+ "Lambda-Runtime-Function-Error-Type": "Runtime.AfterRestoreError"
+ }
+
+ before_snapshot_error_header = {
+ "Lambda-Runtime-Function-Error-Type": "Runtime.BeforeSnapshotError"
+ }
+
@patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection)
def test_post_init_error(self, MockHTTPConnection):
mock_conn = MockHTTPConnection.return_value
@@ -108,11 +206,14 @@ def test_post_init_error(self, MockHTTPConnection):
mock_response.code = http.HTTPStatus.ACCEPTED
runtime_client = LambdaRuntimeClient("localhost:1234")
- runtime_client.post_init_error("error_data")
+ runtime_client.post_init_error(self.error_result)
MockHTTPConnection.assert_called_with("localhost:1234")
mock_conn.request.assert_called_once_with(
- "POST", "/2018-06-01/runtime/init/error", "error_data"
+ "POST",
+ "/2018-06-01/runtime/init/error",
+ to_json(self.error_result),
+ headers=self.headers,
)
mock_response.read.assert_called_once()
@@ -127,7 +228,7 @@ def test_post_init_error_non_accepted_status_code(self, MockHTTPConnection):
runtime_client = LambdaRuntimeClient("localhost:1234")
with self.assertRaises(LambdaRuntimeClientError) as cm:
- runtime_client.post_init_error("error_data")
+ runtime_client.post_init_error(self.error_result)
returned_exception = cm.exception
self.assertEqual(returned_exception.endpoint, "/2018-06-01/runtime/init/error")
@@ -212,15 +313,73 @@ def test_post_invocation_error_with_too_large_xray_cause(self, mock_runtime_clie
invoke_id, error_data, ""
)
+ @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection)
+ def test_restore_next(self, MockHTTPConnection):
+ mock_conn = MockHTTPConnection.return_value
+ mock_response = MagicMock(autospec=http.client.HTTPResponse)
+ mock_conn.getresponse.return_value = mock_response
+ mock_response.read.return_value = b""
+ mock_response.code = http.HTTPStatus.OK
+
+ runtime_client = LambdaRuntimeClient("localhost:1234")
+ runtime_client.restore_next()
+
+ MockHTTPConnection.assert_called_with("localhost:1234")
+ mock_conn.request.assert_called_once_with(
+ "GET",
+ "/2018-06-01/runtime/restore/next",
+ )
+ mock_response.read.assert_called_once()
+
+ @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection)
+ def test_restore_error(self, MockHTTPConnection):
+ mock_conn = MockHTTPConnection.return_value
+ mock_response = MagicMock(autospec=http.client.HTTPResponse)
+ mock_conn.getresponse.return_value = mock_response
+ mock_response.read.return_value = b""
+ mock_response.code = http.HTTPStatus.ACCEPTED
+
+ runtime_client = LambdaRuntimeClient("localhost:1234")
+ runtime_client.report_restore_error(self.restore_error_result)
+
+ MockHTTPConnection.assert_called_with("localhost:1234")
+ mock_conn.request.assert_called_once_with(
+ "POST",
+ "/2018-06-01/runtime/restore/error",
+ to_json(self.restore_error_result),
+ headers=self.restore_error_header,
+ )
+ mock_response.read.assert_called_once()
+
+ @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection)
+ def test_init_before_snapshot_error(self, MockHTTPConnection):
+ mock_conn = MockHTTPConnection.return_value
+ mock_response = MagicMock(autospec=http.client.HTTPResponse)
+ mock_conn.getresponse.return_value = mock_response
+ mock_response.read.return_value = b""
+ mock_response.code = http.HTTPStatus.ACCEPTED
+
+ runtime_client = LambdaRuntimeClient("localhost:1234")
+ runtime_client.post_init_error(self.error_result, "Runtime.BeforeSnapshotError")
+
+ MockHTTPConnection.assert_called_with("localhost:1234")
+ mock_conn.request.assert_called_once_with(
+ "POST",
+ "/2018-06-01/runtime/init/error",
+ to_json(self.error_result),
+ headers=self.before_snapshot_error_header,
+ )
+ mock_response.read.assert_called_once()
+
def test_connection_refused(self):
with self.assertRaises(ConnectionRefusedError):
runtime_client = LambdaRuntimeClient("127.0.0.1:1")
- runtime_client.post_init_error("error")
+ runtime_client.post_init_error(self.error_result)
def test_invalid_addr(self):
with self.assertRaises(OSError):
runtime_client = LambdaRuntimeClient("::::")
- runtime_client.post_init_error("error")
+ runtime_client.post_init_error(self.error_result)
def test_lambdaric_version(self):
self.assertTrue(_user_agent().endswith(__version__))
diff --git a/tests/test_lambda_runtime_marshaller.py b/tests/test_lambda_runtime_marshaller.py
index 7cd73b4..843bcee 100644
--- a/tests/test_lambda_runtime_marshaller.py
+++ b/tests/test_lambda_runtime_marshaller.py
@@ -11,13 +11,17 @@
class TestLambdaRuntimeMarshaller(unittest.TestCase):
execution_envs = (
+ "AWS_Lambda_python3.13",
"AWS_Lambda_python3.12",
"AWS_Lambda_python3.11",
"AWS_Lambda_python3.10",
"AWS_Lambda_python3.9",
)
- envs_lambda_marshaller_ensure_ascii_false = {"AWS_Lambda_python3.12"}
+ envs_lambda_marshaller_ensure_ascii_false = {
+ "AWS_Lambda_python3.12",
+ "AWS_Lambda_python3.13",
+ }
execution_envs_lambda_marshaller_ensure_ascii_true = tuple(
set(execution_envs).difference(envs_lambda_marshaller_ensure_ascii_false)
diff --git a/tests/test_runtime_hooks.py b/tests/test_runtime_hooks.py
new file mode 100644
index 0000000..e73204f
--- /dev/null
+++ b/tests/test_runtime_hooks.py
@@ -0,0 +1,65 @@
+# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+# SPDX-License-Identifier: Apache-2.0
+
+import unittest
+from unittest.mock import patch, call
+from awslambdaric import lambda_runtime_hooks_runner
+import snapshot_restore_py
+
+
+def fun_test1():
+ print("In function ONE")
+
+
+def fun_test2():
+ print("In function TWO")
+
+
+def fun_with_args_kwargs(x, y, **kwargs):
+ print("Here are the args:", x, y)
+ print("Here are the keyword args:", kwargs)
+
+
+class TestRuntimeHooks(unittest.TestCase):
+ def tearDown(self):
+ # We are accessing private filed for cleaning up
+ snapshot_restore_py._before_snapshot_registry = []
+ snapshot_restore_py._after_restore_registry = []
+
+ @patch("builtins.print")
+ def test_before_snapshot_execution_order(self, mock_print):
+ snapshot_restore_py.register_before_snapshot(
+ fun_with_args_kwargs, 5, 7, arg1="Lambda", arg2="SnapStart"
+ )
+ snapshot_restore_py.register_before_snapshot(fun_test2)
+ snapshot_restore_py.register_before_snapshot(fun_test1)
+
+ lambda_runtime_hooks_runner.run_before_snapshot()
+
+ calls = []
+ calls.append(call("In function ONE"))
+ calls.append(call("In function TWO"))
+ calls.append(call("Here are the args:", 5, 7))
+ calls.append(
+ call("Here are the keyword args:", {"arg1": "Lambda", "arg2": "SnapStart"})
+ )
+ self.assertEqual(calls, mock_print.mock_calls)
+
+ @patch("builtins.print")
+ def test_after_restore_execution_order(self, mock_print):
+ snapshot_restore_py.register_after_restore(
+ fun_with_args_kwargs, 11, 13, arg1="Lambda", arg2="SnapStart"
+ )
+ snapshot_restore_py.register_after_restore(fun_test2)
+ snapshot_restore_py.register_after_restore(fun_test1)
+
+ lambda_runtime_hooks_runner.run_after_restore()
+
+ calls = []
+ calls.append(call("Here are the args:", 11, 13))
+ calls.append(
+ call("Here are the keyword args:", {"arg1": "Lambda", "arg2": "SnapStart"})
+ )
+ calls.append(call("In function TWO"))
+ calls.append(call("In function ONE"))
+ self.assertEqual(calls, mock_print.mock_calls)