From 953faeb7d7e1f2d9f2f2405b60fe7f46a351fa69 Mon Sep 17 00:00:00 2001 From: Ronald Bergmann Date: Fri, 21 Jun 2024 23:30:34 +0200 Subject: [PATCH 1/2] feat(core): add buildargs to DockerImage --- core/testcontainers/core/image.py | 9 +++++++-- core/tests/test_core.py | 19 +++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/core/testcontainers/core/image.py b/core/testcontainers/core/image.py index 4004e9e44..317857ebd 100644 --- a/core/testcontainers/core/image.py +++ b/core/testcontainers/core/image.py @@ -1,5 +1,5 @@ from os import PathLike -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING, Optional, Union, Dict from typing_extensions import Self @@ -25,6 +25,7 @@ class DockerImage: :param tag: Tag for the image to be built (default: None) :param path: Path to the Dockerfile to build the image + :param build_args: Dict of build args; equivalent to CLI's --build-arg """ def __init__( @@ -33,6 +34,7 @@ def __init__( docker_client_kw: Optional[dict] = None, tag: Optional[str] = None, clean_up: bool = True, + build_args: Dict[str, str] = None, **kwargs, ) -> None: self.tag = tag @@ -42,11 +44,14 @@ def __init__( self._kwargs = kwargs self._image = None self._logs = None + self._build_args = build_args def build(self, **kwargs) -> Self: logger.info(f"Building image from {self.path}") docker_client = self.get_docker_client() - self._image, self._logs = docker_client.build(path=str(self.path), tag=self.tag, **kwargs) + self._image, self._logs = docker_client.build( + path=str(self.path), tag=self.tag, buildargs=self._build_args, **kwargs + ) logger.info(f"Built image {self.short_id} with tag {self.tag}") return self diff --git a/core/tests/test_core.py b/core/tests/test_core.py index efac8262e..b79c1b5b7 100644 --- a/core/tests/test_core.py +++ b/core/tests/test_core.py @@ -2,7 +2,7 @@ import tempfile import random -from typing import Optional +from typing import Optional, Tuple from testcontainers.core.container import DockerContainer from testcontainers.core.image import DockerImage @@ -40,27 +40,34 @@ def test_can_get_logs(): @pytest.mark.parametrize("test_cleanup", [True, False]) @pytest.mark.parametrize("test_image_tag", [None, "test-image:latest"]) -def test_docker_image(test_image_tag: Optional[str], test_cleanup: bool, check_for_image): +@pytest.mark.parametrize("test_build_arg", [None, ("buildargkey", "buildargval")]) +def test_docker_image(test_image_tag: Optional[str], test_cleanup: bool, test_build_arg: Optional[Tuple[str, str]], check_for_image): with tempfile.TemporaryDirectory() as temp_directory: # It's important to use a random string to avoid image caching random_string = "Hello from Docker Image! " + str(random.randint(0, 1000)) + build_arg_name = test_build_arg[0] + build_arg_value = test_build_arg[0] with open(f"{temp_directory}/Dockerfile", "w") as f: f.write( f""" FROM alpine:latest - CMD echo "{random_string}" + ARG {build_arg_name} + ENV {build_arg_name}=${build_arg_name} + CMD echo "{random_string} ${build_arg_name}" """ ) - with DockerImage(path=temp_directory, tag=test_image_tag, clean_up=test_cleanup) as image: + with DockerImage( + path=temp_directory, tag=test_image_tag, clean_up=test_cleanup, build_args={build_arg_name: build_arg_value} + ) as image: image_short_id = image.short_id assert image.tag is test_image_tag, f"Expected {test_image_tag}, got {image.tag}" assert image.short_id is not None, "Short ID should not be None" logs = image.get_logs() assert isinstance(logs, list), "Logs should be a list" assert logs[0] == {"stream": "Step 1/2 : FROM alpine:latest"} - assert logs[3] == {"stream": f'Step 2/2 : CMD echo "{random_string}"'} + assert logs[3] == {"stream": f'Step 2/2 : CMD echo "{random_string} {build_arg_value}"'} with DockerContainer(str(image)) as container: assert container._container.image.short_id.endswith(image_short_id), "Image ID mismatch" - assert container.get_logs() == ((random_string + "\n").encode(), b""), "Container logs mismatch" + assert container.get_logs() == ((f"{random_string} {build_arg_value}\n").encode(), b""), "Container logs mismatch" check_for_image(image_short_id, test_cleanup) From 3e2cd5ac1cd660495c62476837635db1d9b36952 Mon Sep 17 00:00:00 2001 From: David Ankin Date: Sun, 23 Jun 2024 15:22:13 -0400 Subject: [PATCH 2/2] tests pass --- core/tests/test_core.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/tests/test_core.py b/core/tests/test_core.py index b79c1b5b7..34126f4c3 100644 --- a/core/tests/test_core.py +++ b/core/tests/test_core.py @@ -45,8 +45,8 @@ def test_docker_image(test_image_tag: Optional[str], test_cleanup: bool, test_bu with tempfile.TemporaryDirectory() as temp_directory: # It's important to use a random string to avoid image caching random_string = "Hello from Docker Image! " + str(random.randint(0, 1000)) - build_arg_name = test_build_arg[0] - build_arg_value = test_build_arg[0] + build_arg_name = test_build_arg[0] if test_build_arg else "MISSING" + build_arg_value = test_build_arg[1] if test_build_arg else "MISSING" with open(f"{temp_directory}/Dockerfile", "w") as f: f.write( f""" @@ -64,8 +64,10 @@ def test_docker_image(test_image_tag: Optional[str], test_cleanup: bool, test_bu assert image.short_id is not None, "Short ID should not be None" logs = image.get_logs() assert isinstance(logs, list), "Logs should be a list" - assert logs[0] == {"stream": "Step 1/2 : FROM alpine:latest"} - assert logs[3] == {"stream": f'Step 2/2 : CMD echo "{random_string} {build_arg_value}"'} + assert any("Step 1" in log.get("stream", "") for log in logs) + assert any("FROM alpine:latest" in log.get("stream", "") for log in logs) + assert any("Step 2" in log.get("stream", "") for log in logs) + # assert any(f'CMD echo "{random_string} {build_arg_value}"' in log.get("stream", "") for log in logs) with DockerContainer(str(image)) as container: assert container._container.image.short_id.endswith(image_short_id), "Image ID mismatch" assert container.get_logs() == ((f"{random_string} {build_arg_value}\n").encode(), b""), "Container logs mismatch"