diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 25a818e4..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 @@ -102,18 +101,18 @@ def get_container_host_ip(self) -> str: if host == "localnpipe" and system() == "Windows": 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 3c724ac3..05d5377a 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -14,6 +14,8 @@ import functools as ft import os import urllib +from os.path import exists +from pathlib import Path from typing import Optional, Union import docker @@ -23,15 +25,8 @@ 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 class DockerClient: @@ -40,7 +35,13 @@ class DockerClient: """ def __init__(self, **kwargs) -> None: - self.client = docker.from_env(**kwargs) + docker_host = read_tc_properties().get("tc.host") + + if docker_host: + LOGGER.info(f"using host {docker_host}") + self.client = docker.DockerClient(base_url=docker_host) + else: + self.client = docker.from_env(**kwargs) @ft.wraps(ContainerCollection.run) def run( @@ -123,3 +124,33 @@ def host(self) -> str: if ip_address: return ip_address return "localhost" + + +@ft.cache +def read_tc_properties() -> dict[str, str]: + """ + 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 = {} + + 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)