1010
1111from testcontainers .core .config import testcontainers_config as c , ConnectionMode
1212from testcontainers .core .container import DockerContainer
13- from testcontainers .core .docker_client import DockerClient
13+ from testcontainers .core .docker_client import DockerClient , is_ssh_docker_host
1414from testcontainers .core .auth import parse_docker_auth_config
1515from testcontainers .core .image import DockerImage
1616from testcontainers .core import utils
2020from docker .models .networks import Network
2121
2222
23+ def _expected_from_env_kwargs (** kwargs : Any ) -> dict [str , Any ]:
24+ """Build the kwargs we expect ``docker.from_env`` to be called with.
25+
26+ When DOCKER_HOST is SSH-based, ``use_ssh_client=True`` is added automatically.
27+ """
28+ if is_ssh_docker_host ():
29+ kwargs .setdefault ("use_ssh_client" , True )
30+ return kwargs
31+
32+
2333def test_docker_client_from_env ():
2434 test_kwargs = {"test_kw" : "test_value" }
2535 mock_docker = MagicMock (spec = docker )
2636 with patch ("testcontainers.core.docker_client.docker" , mock_docker ):
2737 DockerClient (** test_kwargs )
2838
29- mock_docker .from_env .assert_called_with (** test_kwargs )
39+ mock_docker .from_env .assert_called_with (** _expected_from_env_kwargs ( ** test_kwargs ) )
3040
3141
3242def test_docker_client_login_no_login ():
@@ -111,7 +121,7 @@ def test_container_docker_client_kw():
111121 with patch ("testcontainers.core.docker_client.docker" , mock_docker ):
112122 DockerContainer (image = "" , docker_client_kw = test_kwargs )
113123
114- mock_docker .from_env .assert_called_with (** test_kwargs )
124+ mock_docker .from_env .assert_called_with (** _expected_from_env_kwargs ( ** test_kwargs ) )
115125
116126
117127def test_image_docker_client_kw ():
@@ -120,7 +130,7 @@ def test_image_docker_client_kw():
120130 with patch ("testcontainers.core.docker_client.docker" , mock_docker ):
121131 DockerImage (name = "" , path = "" , docker_client_kw = test_kwargs )
122132
123- mock_docker .from_env .assert_called_with (** test_kwargs )
133+ mock_docker .from_env .assert_called_with (** _expected_from_env_kwargs ( ** test_kwargs ) )
124134
125135
126136def test_host_prefer_host_override (monkeypatch : pytest .MonkeyPatch ) -> None :
@@ -139,6 +149,8 @@ def test_host_prefer_host_override(monkeypatch: pytest.MonkeyPatch) -> None:
139149 ],
140150)
141151def test_host (monkeypatch : pytest .MonkeyPatch , base_url : str , expected : str ) -> None :
152+ if is_ssh_docker_host ():
153+ pytest .skip ("base_url parsing is not exercised under SSH (host() returns SSH hostname)" )
142154 client = DockerClient ()
143155 monkeypatch .setattr (client .client .api , "base_url" , base_url )
144156 monkeypatch .setattr (c , "tc_host_override" , None )
@@ -270,6 +282,8 @@ def test_run_uses_found_network(monkeypatch: pytest.MonkeyPatch) -> None:
270282 """
271283 If a host network is found, use it
272284 """
285+ if is_ssh_docker_host ():
286+ pytest .skip ("Host network discovery is skipped when DOCKER_HOST is set" )
273287
274288 client = DockerClient ()
275289
@@ -293,3 +307,51 @@ def __init__(self) -> None:
293307 assert client .run ("test" ) == "CONTAINER"
294308
295309 assert fake_client .containers .calls [0 ]["network" ] == "new_bridge_network"
310+
311+
312+ @pytest .mark .parametrize (
313+ "docker_host, expected" ,
314+ [
315+ pytest .param ("ssh://user@192.168.1.42" , "ssh://user@192.168.1.42" , id = "no_path" ),
316+ pytest .param ("ssh://user@host/" , "ssh://user@host" , id = "trailing_slash" ),
317+ pytest .param ("ssh://user@host/some/path" , "ssh://user@host" , id = "strips_path" ),
318+ pytest .param ("tcp://localhost:2375" , "tcp://localhost:2375" , id = "tcp_unchanged" ),
319+ pytest .param ("unix:///var/run/docker.sock" , "unix:///var/run/docker.sock" , id = "unix_unchanged" ),
320+ ],
321+ )
322+ def test_sanitize_docker_host (docker_host : str , expected : str ) -> None :
323+ from testcontainers .core .docker_client import _sanitize_docker_host
324+
325+ assert _sanitize_docker_host (docker_host ) == expected
326+
327+
328+ @pytest .mark .parametrize (
329+ "docker_host, expected_hostname" ,
330+ [
331+ pytest .param ("ssh://user@192.168.1.42" , "192.168.1.42" , id = "ssh_ip" ),
332+ pytest .param ("ssh://user@myhost.example.com" , "myhost.example.com" , id = "ssh_fqdn" ),
333+ pytest .param ("tcp://localhost:2375" , None , id = "tcp_returns_none" ),
334+ pytest .param (None , None , id = "unset_returns_none" ),
335+ ],
336+ )
337+ def test_get_docker_host_hostname (monkeypatch : pytest .MonkeyPatch , docker_host : str , expected_hostname ) -> None :
338+ from testcontainers .core .docker_client import get_docker_host_hostname
339+
340+ monkeypatch .setattr (c , "tc_properties_get_tc_host" , lambda : None )
341+ if docker_host :
342+ monkeypatch .setenv ("DOCKER_HOST" , docker_host )
343+ else :
344+ monkeypatch .delenv ("DOCKER_HOST" , raising = False )
345+ assert get_docker_host_hostname () == expected_hostname
346+
347+
348+ def test_ssh_docker_host (monkeypatch : pytest .MonkeyPatch ) -> None :
349+ """Verify SSH DOCKER_HOST sets use_ssh_client and host() returns the remote hostname."""
350+ monkeypatch .setenv ("DOCKER_HOST" , "ssh://user@10.0.0.1" )
351+ monkeypatch .setattr (c , "tc_properties_get_tc_host" , lambda : None )
352+ monkeypatch .setattr (c , "tc_host_override" , None )
353+ mock_docker = MagicMock (spec = docker )
354+ with patch ("testcontainers.core.docker_client.docker" , mock_docker ):
355+ client = DockerClient ()
356+ mock_docker .from_env .assert_called_once_with (use_ssh_client = True )
357+ assert client .host () == "10.0.0.1"
0 commit comments