11
11
# License for the specific language governing permissions and limitations
12
12
# under the License.
13
13
import functools as ft
14
+ import ipaddress
14
15
import os
15
16
import urllib
17
+ import urllib .parse
16
18
from os .path import exists
17
19
from pathlib import Path
18
20
from typing import Optional , Union
@@ -34,7 +36,7 @@ class DockerClient:
34
36
"""
35
37
36
38
def __init__ (self , ** kwargs ) -> None :
37
- docker_host = read_tc_properties (). get ( "tc.host" )
39
+ docker_host = get_docker_host ( )
38
40
39
41
if docker_host :
40
42
LOGGER .info (f"using host { docker_host } " )
@@ -57,6 +59,12 @@ def run(
57
59
remove : bool = False ,
58
60
** kwargs ,
59
61
) -> Container :
62
+ # If the user has specified a network, we'll assume the user knows best
63
+ if "network" not in kwargs and not get_docker_host ():
64
+ # Otherwise we'll try to find the docker host for dind usage.
65
+ host_network = self .find_host_network ()
66
+ if host_network :
67
+ kwargs ["network" ] = host_network
60
68
container = self .client .containers .run (
61
69
image ,
62
70
command = command ,
@@ -71,6 +79,30 @@ def run(
71
79
)
72
80
return container
73
81
82
+ def find_host_network (self ) -> Optional [str ]:
83
+ """
84
+ Try to find the docker host network.
85
+
86
+ :return: The network name if found, None if not set.
87
+ """
88
+ # If we're docker in docker running on a custom network, we need to inherit the
89
+ # network settings, so we can access the resulting container.
90
+ try :
91
+ docker_host = ipaddress .IPv4Address (self .host ())
92
+ # See if we can find the host on our networks
93
+ for network in self .client .networks .list (filters = {"type" : "custom" }):
94
+ if "IPAM" in network .attrs :
95
+ for config in network .attrs ["IPAM" ]["Config" ]:
96
+ try :
97
+ subnet = ipaddress .IPv4Network (config ["Subnet" ])
98
+ except ipaddress .AddressValueError :
99
+ continue
100
+ if docker_host in subnet :
101
+ return network .name
102
+ except ipaddress .AddressValueError :
103
+ pass
104
+ return None
105
+
74
106
def port (self , container_id : str , port : int ) -> int :
75
107
"""
76
108
Lookup the public-facing port that is NAT-ed to :code:`port`.
@@ -94,14 +126,26 @@ def bridge_ip(self, container_id: str) -> str:
94
126
Get the bridge ip address for a container.
95
127
"""
96
128
container = self .get_container (container_id )
97
- return container ["NetworkSettings" ]["Networks" ]["bridge" ]["IPAddress" ]
129
+ network_name = self .network_name (container_id )
130
+ return container ["NetworkSettings" ]["Networks" ][network_name ]["IPAddress" ]
131
+
132
+ def network_name (self , container_id : str ) -> str :
133
+ """
134
+ Get the name of the network this container runs on
135
+ """
136
+ container = self .get_container (container_id )
137
+ name = container ["HostConfig" ]["NetworkMode" ]
138
+ if name == "default" :
139
+ return "bridge"
140
+ return name
98
141
99
142
def gateway_ip (self , container_id : str ) -> str :
100
143
"""
101
144
Get the gateway ip address for a container.
102
145
"""
103
146
container = self .get_container (container_id )
104
- return container ["NetworkSettings" ]["Networks" ]["bridge" ]["Gateway" ]
147
+ network_name = self .network_name (container_id )
148
+ return container ["NetworkSettings" ]["Networks" ][network_name ]["Gateway" ]
105
149
106
150
def host (self ) -> str :
107
151
"""
@@ -145,3 +189,7 @@ def read_tc_properties() -> dict[str, str]:
145
189
tuples = [line .split ("=" ) for line in contents .readlines () if "=" in line ]
146
190
settings = {** settings , ** {item [0 ].strip (): item [1 ].strip () for item in tuples }}
147
191
return settings
192
+
193
+
194
+ def get_docker_host () -> Optional [str ]:
195
+ return read_tc_properties ().get ("tc.host" ) or os .getenv ("DOCKER_HOST" )
0 commit comments