Skip to content

Commit 2db8e6d

Browse files
kiviewtotallyzenalexanderankin
authoredMar 1, 2024··
feat(core)!: add support for tc.host and de-prioritise docker:dind (#388)
## What does this PR do? Adds support for reading the `tc.host` property from the `~/.testcontainers.properties` file. This config will have the highest priority for configuring the Docker client. ## Why is it important This brings [Testcontainers Desktop](https://testcontainers.com/desktop/) support to testcontainers-python and aligns this language with the `TestcontainersHostStrategy` found it testcontainers-java. ## Misc I wasn't able to get a working testcontainers-python development environment set up on my M2 MacBook. This seems to be related to some kind of Cython 3.0.0 issue, that I don't fully understand and trying to fix it breaks the installation of further dependencies (like pymssql). So I was only able to test it in an Ubuntu Codespaces environment (also required some weird Cython workarounds). ## Breaking Changes - To prioritise `tc.host` this PR prioritised having that over true `docker:dind` use cases - This means we stopped trying to automatically infer the container host IP when running inside a `docker:dind` container - When using `-v /var/run/docker.sock:/var/run/docker.sock` you should be unaffected since your containers run on the original host --------- Co-authored-by: Bálint Bartha <39852431+totallyzen@users.noreply.github.com> Co-authored-by: David Ankin <daveankin@gmail.com>
1 parent 9e240d0 commit 2db8e6d

File tree

2 files changed

+53
-23
lines changed

2 files changed

+53
-23
lines changed
 

‎core/testcontainers/core/container.py

+12-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import contextlib
2-
import os
32
from platform import system
43
from typing import Optional
54

@@ -102,18 +101,18 @@ def get_container_host_ip(self) -> str:
102101
if host == "localnpipe" and system() == "Windows":
103102
return "localhost"
104103

105-
# check testcontainers itself runs inside docker container
106-
if inside_container() and not os.getenv("DOCKER_HOST"):
107-
# If newly spawned container's gateway IP address from the docker
108-
# "bridge" network is equal to detected host address, we should use
109-
# container IP address, otherwise fall back to detected host
110-
# address. Even it's inside container, we need to double check,
111-
# because docker host might be set to docker:dind, usually in CI/CD environment
112-
gateway_ip = self.get_docker_client().gateway_ip(self._container.id)
113-
114-
if gateway_ip == host:
115-
return self.get_docker_client().bridge_ip(self._container.id)
116-
return gateway_ip
104+
# # check testcontainers itself runs inside docker container
105+
# if inside_container() and not os.getenv("DOCKER_HOST") and not host.startswith("http://"):
Has conversations. Original line has conversations.
106+
# # If newly spawned container's gateway IP address from the docker
107+
# # "bridge" network is equal to detected host address, we should use
108+
# # container IP address, otherwise fall back to detected host
109+
# # address. Even it's inside container, we need to double check,
110+
# # because docker host might be set to docker:dind, usually in CI/CD environment
111+
# gateway_ip = self.get_docker_client().gateway_ip(self._container.id)
112+
113+
# if gateway_ip == host:
114+
# return self.get_docker_client().bridge_ip(self._container.id)
115+
# return gateway_ip
117116
return host
118117

119118
@wait_container_is_ready()

‎core/testcontainers/core/docker_client.py

+41-10
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import functools as ft
1515
import os
1616
import urllib
17+
from os.path import exists
18+
from pathlib import Path
1719
from typing import Optional, Union
1820

1921
import docker
@@ -23,15 +25,8 @@
2325
from .utils import default_gateway_ip, inside_container, setup_logger
2426

2527
LOGGER = setup_logger(__name__)
26-
27-
28-
def _stop_container(container: Container) -> None:
29-
try:
30-
container.stop()
31-
except NotFound:
32-
pass
33-
except Exception as ex:
34-
LOGGER.warning("failed to shut down container %s with image %s: %s", container.id, container.image, ex)
28+
TC_FILE = ".testcontainers.properties"
29+
TC_GLOBAL = Path.home() / TC_FILE
3530

3631

3732
class DockerClient:
@@ -40,7 +35,13 @@ class DockerClient:
4035
"""
4136

4237
def __init__(self, **kwargs) -> None:
43-
self.client = docker.from_env(**kwargs)
38+
docker_host = read_tc_properties().get("tc.host")
39+
40+
if docker_host:
41+
LOGGER.info(f"using host {docker_host}")
42+
self.client = docker.DockerClient(base_url=docker_host)
43+
else:
44+
self.client = docker.from_env(**kwargs)
4445

4546
@ft.wraps(ContainerCollection.run)
4647
def run(
@@ -123,3 +124,33 @@ def host(self) -> str:
123124
if ip_address:
124125
return ip_address
125126
return "localhost"
127+
128+
129+
@ft.cache
130+
def read_tc_properties() -> dict[str, str]:
131+
"""
132+
Read the .testcontainers.properties for settings. (see the Java implementation for details)
133+
Currently we only support the ~/.testcontainers.properties but may extend to per-project variables later.
134+
135+
:return: the merged properties from the sources.
136+
"""
137+
tc_files = [item for item in [TC_GLOBAL] if exists(item)]
138+
if not tc_files:
139+
return {}
140+
settings = {}
141+
142+
for file in tc_files:
143+
tuples = []
144+
with open(file) as contents:
145+
tuples = [line.split("=") for line in contents.readlines() if "=" in line]
146+
settings = {**settings, **{item[0]: item[1] for item in tuples}}
147+
return settings
148+
149+
150+
def _stop_container(container: Container) -> None:
151+
try:
152+
container.stop()
153+
except NotFound:
154+
pass
155+
except Exception as ex:
156+
LOGGER.warning("failed to shut down container %s with image %s: %s", container.id, container.image, ex)

0 commit comments

Comments
 (0)
Please sign in to comment.