From bac393782f63e5bbc859fe42d4b50c1e8a445a4a Mon Sep 17 00:00:00 2001 From: Kevin Wittek Date: Thu, 7 Sep 2023 16:01:50 +0000 Subject: [PATCH 1/4] WIP Add support for tc.host --- core/testcontainers/core/container.py | 24 +++++++++++------------ core/testcontainers/core/docker_client.py | 17 +++++++++++++++- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 65acf9be..b36a2e44 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -90,18 +90,18 @@ def get_container_host_ip(self) -> str: if not host: return "localhost" - # check testcontainers itself runs inside docker container - if inside_container() and not os.getenv("DOCKER_HOST"): - # If newly spawned container's gateway IP address from the docker - # "bridge" network is equal to detected host address, we should use - # container IP address, otherwise fall back to detected host - # address. Even it's inside container, we need to double check, - # because docker host might be set to docker:dind, usually in CI/CD environment - gateway_ip = self.get_docker_client().gateway_ip(self._container.id) - - if gateway_ip == host: - return self.get_docker_client().bridge_ip(self._container.id) - return gateway_ip + # # check testcontainers itself runs inside docker container + # if inside_container() and not os.getenv("DOCKER_HOST") and not host.startswith("http://"): + # # If newly spawned container's gateway IP address from the docker + # # "bridge" network is equal to detected host address, we should use + # # container IP address, otherwise fall back to detected host + # # address. Even it's inside container, we need to double check, + # # because docker host might be set to docker:dind, usually in CI/CD environment + # gateway_ip = self.get_docker_client().gateway_ip(self._container.id) + + # if gateway_ip == host: + # return self.get_docker_client().bridge_ip(self._container.id) + # return gateway_ip return host @wait_container_is_ready() diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 7d5bcf53..349a70ee 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -40,7 +40,22 @@ class DockerClient: Thin wrapper around :class:`docker.DockerClient` for a more functional interface. """ def __init__(self, **kwargs) -> None: - self.client = docker.from_env(**kwargs) + docker_host = DockerClient.read_docker_host_from_properties() + + if docker_host: + print(f"using host {docker_host}") + self.client = docker.DockerClient(base_url=docker_host) + else: + self.client = docker.from_env(**kwargs) + + def read_docker_host_from_properties(): + try: + with open(os.path.expanduser('~/.testcontainers.properties')) as f: + for line in f: + if line.startswith('tc.host'): + return line.split('=')[1].strip() + except FileNotFoundError: + return None @ft.wraps(ContainerCollection.run) def run(self, image: str, command: Union[str, List[str]] = None, From 9115f61937e9a373819b50ffc61d10bb677f7fd2 Mon Sep 17 00:00:00 2001 From: Balint Bartha Date: Thu, 29 Feb 2024 17:22:24 +0100 Subject: [PATCH 2/4] fix: small cleanup of the properties file fetching --- core/testcontainers/core/container.py | 1 - core/testcontainers/core/docker_client.py | 50 ++++++++++++++--------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 789f2df5..6ecc384b 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -1,5 +1,4 @@ import contextlib -import os from platform import system from typing import Optional diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 0773f7a7..a7b5961e 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -14,6 +14,7 @@ import functools as ft import os import urllib +from pathlib import Path from typing import Optional, Union import docker @@ -23,15 +24,9 @@ from .utils import default_gateway_ip, inside_container, setup_logger LOGGER = setup_logger(__name__) - - -def _stop_container(container: Container) -> None: - try: - container.stop() - except NotFound: - pass - except Exception as ex: - LOGGER.warning("failed to shut down container %s with image %s: %s", container.id, container.image, ex) +TC_FILE = ".testcontainers.properties" +TC_GLOBAL = Path.home() / TC_FILE +TC_LOCAL = Path.cwd() / TC_FILE class DockerClient: @@ -40,23 +35,14 @@ class DockerClient: """ def __init__(self, **kwargs) -> None: - docker_host = DockerClient.read_docker_host_from_properties() + docker_host = read_tc_properties().get("tc.host") if docker_host: - print(f"using host {docker_host}") + LOGGER.info(f"using host {docker_host}") self.client = docker.DockerClient(base_url=docker_host) else: self.client = docker.from_env(**kwargs) - def read_docker_host_from_properties(): - try: - with open(os.path.expanduser('~/.testcontainers.properties')) as f: - for line in f: - if line.startswith('tc.host'): - return line.split('=')[1].strip() - except FileNotFoundError: - return None - @ft.wraps(ContainerCollection.run) def run( self, @@ -138,3 +124,27 @@ def host(self) -> str: if ip_address: return ip_address return "localhost" + + +@ft.cache +def read_tc_properties() -> dict[str, str]: + tc_files = [item for item in [TC_GLOBAL, TC_LOCAL] if os.path.exists(item)] + if not tc_files: + return {} + settings = {} + + for file in tc_files: + tuples = [] + with open(file) as contents: + tuples = [line.split("=") for line in contents.readlines() if "=" in line] + settings = {**settings, **{item[0]: item[1] for item in tuples}} + return settings + + +def _stop_container(container: Container) -> None: + try: + container.stop() + except NotFound: + pass + except Exception as ex: + LOGGER.warning("failed to shut down container %s with image %s: %s", container.id, container.image, ex) From 631437427b096b4947f9c699acf13c053cdda53f Mon Sep 17 00:00:00 2001 From: David Ankin Date: Thu, 29 Feb 2024 21:30:24 -0700 Subject: [PATCH 3/4] from os.path import exists --- core/testcontainers/core/docker_client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index a7b5961e..10dc6602 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -14,6 +14,7 @@ import functools as ft import os import urllib +from os.path import exists from pathlib import Path from typing import Optional, Union @@ -128,7 +129,7 @@ def host(self) -> str: @ft.cache def read_tc_properties() -> dict[str, str]: - tc_files = [item for item in [TC_GLOBAL, TC_LOCAL] if os.path.exists(item)] + tc_files = [item for item in [TC_GLOBAL, TC_LOCAL] if exists(item)] if not tc_files: return {} settings = {} From 09775f95fb37f1be38af43cab269e8ccd03e4a5d Mon Sep 17 00:00:00 2001 From: Balint Bartha Date: Fri, 1 Mar 2024 17:33:38 +0100 Subject: [PATCH 4/4] fix: remove multiple sources, just leave user-wide settings --- core/testcontainers/core/docker_client.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index 10dc6602..05d5377a 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -27,7 +27,6 @@ LOGGER = setup_logger(__name__) TC_FILE = ".testcontainers.properties" TC_GLOBAL = Path.home() / TC_FILE -TC_LOCAL = Path.cwd() / TC_FILE class DockerClient: @@ -129,7 +128,13 @@ def host(self) -> str: @ft.cache def read_tc_properties() -> dict[str, str]: - tc_files = [item for item in [TC_GLOBAL, TC_LOCAL] if exists(item)] + """ + Read the .testcontainers.properties for settings. (see the Java implementation for details) + Currently we only support the ~/.testcontainers.properties but may extend to per-project variables later. + + :return: the merged properties from the sources. + """ + tc_files = [item for item in [TC_GLOBAL] if exists(item)] if not tc_files: return {} settings = {}