Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions modules/aws/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
:code:`testcontainers-aws` is a set of AWS containers modules that can be used to create AWS containers.

.. autoclass:: testcontainers.aws.AWSLambdaContainer
.. title:: testcontainers.aws.AWSLambdaContainer

The following environment variables are used by the AWS Lambda container:

+-------------------------------+--------------------------+------------------------------+
| Env Variable | Default | Notes |
+===============================+==========================+==============================+
| ``AWS_DEFAULT_REGION`` | ``us-west-1`` | Fetched from os environment |
+-------------------------------+--------------------------+------------------------------+
| ``AWS_ACCESS_KEY_ID`` | ``testcontainers-aws`` | Fetched from os environment |
+-------------------------------+--------------------------+------------------------------+
| ``AWS_SECRET_ACCESS_KEY`` | ``testcontainers-aws`` | Fetched from os environment |
+-------------------------------+--------------------------+------------------------------+

Each one of the environment variables is expected to be set in the host machine where the test is running.

Make sure you are using an image based on :code:`public.ecr.aws/lambda/python`

Please checkout https://docs.aws.amazon.com/lambda/latest/dg/python-image.html for more information on how to run AWS Lambda functions locally.
1 change: 1 addition & 0 deletions modules/aws/testcontainers/aws/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .aws_lambda import AWSLambdaContainer # noqa: F401
53 changes: 53 additions & 0 deletions modules/aws/testcontainers/aws/aws_lambda.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import os
from typing import Union

import httpx

from testcontainers.core.image import DockerImage
from testcontainers.generic.server import ServerContainer

RIE_PATH = "/2015-03-31/functions/function/invocations"
# AWS OS-only base images contain an Amazon Linux distribution and the runtime interface emulator (RIE) for Lambda.


class AWSLambdaContainer(ServerContainer):
"""
AWS Lambda container that is based on a custom image.

Example:

.. doctest::

>>> from testcontainers.aws import AWSLambdaContainer
>>> from testcontainers.core.waiting_utils import wait_for_logs
>>> from testcontainers.core.image import DockerImage

>>> with DockerImage(path="./modules/aws/tests/lambda_sample", tag="test-lambda:latest") as image:
... with AWSLambdaContainer(image=image, port=8080) as func:
... response = func.send_request(data={'payload': 'some data'})
... assert response.status_code == 200
... assert "Hello from AWS Lambda using Python" in response.json()
... delay = wait_for_logs(func, "START RequestId:")

:param image: Docker image to be used for the container.
:param port: Port to be exposed on the container (default: 8080).
"""

def __init__(self, image: Union[str, DockerImage], port: int = 8080) -> None:
super().__init__(port, str(image))
self.with_env("AWS_DEFAULT_REGION", os.environ.get("AWS_DEFAULT_REGION", "us-west-1"))
self.with_env("AWS_ACCESS_KEY_ID", os.environ.get("AWS_ACCESS_KEY_ID", "testcontainers-aws"))
self.with_env("AWS_SECRET_ACCESS_KEY", os.environ.get("AWS_SECRET_ACCESS_KEY", "testcontainers-aws"))

def get_api_url(self) -> str:
return self._create_connection_url() + RIE_PATH

def send_request(self, data: dict) -> httpx.Response:
"""
Send a request to the AWS Lambda function.

:param data: Data to be sent to the AWS Lambda function.
:return: Response from the AWS Lambda function.
"""
client = self.get_client()
return client.post(self.get_api_url(), json=data)
10 changes: 10 additions & 0 deletions modules/aws/tests/lambda_sample/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM public.ecr.aws/lambda/python:3.9

RUN pip install boto3

COPY lambda_function.py ${LAMBDA_TASK_ROOT}

EXPOSE 8080

# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "lambda_function.handler" ]
5 changes: 5 additions & 0 deletions modules/aws/tests/lambda_sample/lambda_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import sys


def handler(event, context):
return "Hello from AWS Lambda using Python" + sys.version + "!"
56 changes: 56 additions & 0 deletions modules/aws/tests/test_aws.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import re
import os

import pytest
from unittest.mock import patch

from testcontainers.core.image import DockerImage
from testcontainers.aws import AWSLambdaContainer
from testcontainers.aws.aws_lambda import RIE_PATH

DOCKER_FILE_PATH = "./modules/aws/tests/lambda_sample"
IMAGE_TAG = "lambda:test"


def test_aws_lambda_container():
with DockerImage(path=DOCKER_FILE_PATH, tag="test-lambda:latest") as image:
with AWSLambdaContainer(image=image, port=8080) as func:
assert func.get_container_host_ip() == "localhost"
assert func.internal_port == 8080
assert func.env["AWS_DEFAULT_REGION"] == "us-west-1"
assert func.env["AWS_ACCESS_KEY_ID"] == "testcontainers-aws"
assert func.env["AWS_SECRET_ACCESS_KEY"] == "testcontainers-aws"
assert re.match(rf"http://localhost:\d+{RIE_PATH}", func.get_api_url())
response = func.send_request(data={"payload": "test"})
assert response.status_code == 200
assert "Hello from AWS Lambda using Python" in response.json()
for log_str in ["START RequestId", "END RequestId", "REPORT RequestId"]:
assert log_str in func.get_stdout()


def test_aws_lambda_container_external_env_vars():
vars = {
"AWS_DEFAULT_REGION": "region",
"AWS_ACCESS_KEY_ID": "id",
"AWS_SECRET_ACCESS_KEY": "key",
}
with patch.dict(os.environ, vars):
with DockerImage(path=DOCKER_FILE_PATH, tag="test-lambda-env-vars:latest") as image:
with AWSLambdaContainer(image=image, port=8080) as func:
assert func.env["AWS_DEFAULT_REGION"] == "region"
assert func.env["AWS_ACCESS_KEY_ID"] == "id"
assert func.env["AWS_SECRET_ACCESS_KEY"] == "key"


def test_aws_lambda_container_no_port():
with DockerImage(path=DOCKER_FILE_PATH, tag="test-lambda-no-port:latest") as image:
with AWSLambdaContainer(image=image) as func:
response = func.send_request(data={"payload": "test"})
assert response.status_code == 200


def test_aws_lambda_container_no_path():
with pytest.raises(TypeError):
with DockerImage(path=DOCKER_FILE_PATH, tag="test-lambda-no-path:latest") as image:
with AWSLambdaContainer() as func: # noqa: F841
pass
3 changes: 2 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ classifiers = [
packages = [
{ include = "testcontainers", from = "core" },
{ include = "testcontainers", from = "modules/arangodb" },
{ include = "testcontainers", from = "modules/aws"},
{ include = "testcontainers", from = "modules/azurite" },
{ include = "testcontainers", from = "modules/cassandra" },
{ include = "testcontainers", from = "modules/chroma" },
Expand Down Expand Up @@ -116,6 +117,7 @@ trino = { version = "*", optional = true }

[tool.poetry.extras]
arangodb = ["python-arango"]
aws = ["boto3", "httpx"]
azurite = ["azure-storage-blob"]
cassandra = []
clickhouse = ["clickhouse-driver"]
Expand Down