Skip to content

Commit 4f28b03

Browse files
committed
improve host detection
1 parent c058fd0 commit 4f28b03

File tree

3 files changed

+63
-20
lines changed

3 files changed

+63
-20
lines changed

testcontainers/core/container.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -78,30 +78,33 @@ def __del__(self):
7878
pass
7979

8080
def get_container_host_ip(self) -> str:
81-
# https://github.com/testcontainers/testcontainers-go/blob/dd76d1e39c654433a3d80429690d07abcec04424/docker.go#L644
82-
# if os env TC_HOST is set, use it
83-
host = os.environ.get('TC_HOST')
84-
if host:
85-
return host
86-
8781
# infer from docker host
88-
url = self.get_docker_client().host()
89-
90-
if 'http' in url.scheme or 'tcp' in url.scheme:
91-
return url.hostname
92-
if 'unix' in url.scheme or 'npipe' in url.scheme:
93-
# if testcontainers itself runs in docker, get the newly spawned
94-
# container's IP address from the dockder "bridge" network
95-
if inside_container():
82+
host = self.get_docker_client().host()
83+
if not host:
84+
return "localhost"
85+
86+
# check testcontainers itself runs inside docker container
87+
if inside_container():
88+
# If newly spawned container's gateway IP address from the docker
89+
# "bridge" network is equal to detected host address, we should use
90+
# container IP address, otherwise fall back to detected host
91+
# address. Even it's inside container, we need to double check,
92+
# because docker host might be set to docker:dind, usually in CI/CD environment
93+
gateway_ip = self.get_docker_client().gateway_ip(self._container.id)
94+
95+
if gateway_ip == host:
9696
return self.get_docker_client().bridge_ip(self._container.id)
97-
return "localhost"
97+
return host
9898

9999
def get_exposed_port(self, port) -> str:
100-
url = self.get_docker_client().host()
101-
if 'unix' in url.scheme or 'npipe' in url.scheme:
102-
if inside_container():
100+
mapped_port = self.get_docker_client().port(self._container.id, port)
101+
if inside_container():
102+
gateway_ip = self.get_docker_client().gateway_ip(self._container.id)
103+
host = self.get_docker_client().host()
104+
105+
if gateway_ip == host:
103106
return port
104-
return self.get_docker_client().port(self._container.id, port)
107+
return mapped_port
105108

106109
def with_command(self, command: str) -> 'DockerContainer':
107110
self._command = command

testcontainers/core/docker_client.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1111
# License for the specific language governing permissions and limitations
1212
# under the License.
13+
import os
1314
import urllib
1415
import docker
1516
from docker.models.containers import Container
17+
from testcontainers.core.utils import inside_container
18+
from testcontainers.core.utils import default_gateway_ip
1619

1720

1821
class DockerClient(object):
@@ -44,8 +47,26 @@ def bridge_ip(self, container_id):
4447
container = self.client.api.containers(filters={'id': container_id})[0]
4548
return container['NetworkSettings']['Networks']['bridge']['IPAddress']
4649

50+
def gateway_ip(self, container_id):
51+
container = self.client.api.containers(filters={'id': container_id})[0]
52+
return container['NetworkSettings']['Networks']['bridge']['Gateway']
53+
4754
def host(self):
55+
# https://github.com/testcontainers/testcontainers-go/blob/dd76d1e39c654433a3d80429690d07abcec04424/docker.go#L644
56+
# if os env TC_HOST is set, use it
57+
host = os.environ.get('TC_HOST')
58+
if host:
59+
return host
4860
try:
49-
return urllib.parse.urlparse(self.client.api.base_url)
61+
url = urllib.parse.urlparse(self.client.api.base_url)
62+
5063
except ValueError:
5164
return None
65+
if 'http' in url.scheme or 'tcp' in url.scheme:
66+
return url.hostname
67+
if 'unix' in url.scheme or 'npipe' in url.scheme:
68+
if inside_container():
69+
ip_address = default_gateway_ip()
70+
if ip_address:
71+
return ip_address
72+
return "localhost"

testcontainers/core/utils.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import sys
3+
import subprocess
34

45
LINUX = "linux"
56
MAC = "mac"
@@ -35,3 +36,21 @@ def inside_container():
3536
https://github.com/docker/docker/blob/a9fa38b1edf30b23cae3eade0be48b3d4b1de14b/daemon/initlayer/setup_unix.go#L25
3637
"""
3738
return os.path.exists('/.dockerenv')
39+
40+
41+
def default_gateway_ip():
42+
"""
43+
Returns gateway IP address of the host that testcontainer process is
44+
running on
45+
46+
https://github.com/testcontainers/testcontainers-java/blob/3ad8d80e2484864e554744a4800a81f6b7982168/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L27
47+
"""
48+
cmd = ["sh", "-c", "ip route|awk '/default/ { print $3 }'"]
49+
try:
50+
process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
51+
stderr=subprocess.PIPE)
52+
ip_address = process.communicate()[0]
53+
if ip_address and process.returncode == 0:
54+
return ip_address.decode('utf-8').strip().strip('\n')
55+
except subprocess.SubprocessError:
56+
return None

0 commit comments

Comments
 (0)