diff --git a/src/devx/molecule/start_docker.py b/src/devx/molecule/start_docker.py index c541671..289d4c1 100644 --- a/src/devx/molecule/start_docker.py +++ b/src/devx/molecule/start_docker.py @@ -210,6 +210,22 @@ def start_docker_daemon(timeout: int = DEFAULT_TIMEOUT) -> bool: if free_bytes == 0 and sock == HOST_DOCKER_SOCK: click.echo("Host rootless Docker root dir not accessible from container, using it") return True + # If free_bytes is 0 and there are no dockerd processes inside the + # container, the socket is the host's Docker (mounted from outside). + # The data root is on the host filesystem and has plenty of space. + if free_bytes == 0 and sock == DOCKER_SOCK: + has_inner_dockerd = False + with contextlib.suppress(Exception): + pgrep_result = subprocess.run( # nosec B603 B607 + ["pgrep", "-f", "dockerd"], + capture_output=True, + text=True, + timeout=5, + ) + has_inner_dockerd = pgrep_result.returncode == 0 + if not has_inner_dockerd: + click.echo("No inner dockerd found, socket is host Docker (data root on host), using it") + return True click.echo(f" Insufficient space ({free_gb:.1f} GB), trying next...") # No socket with sufficient space found. diff --git a/tests/unit/test_start_docker.py b/tests/unit/test_start_docker.py index 44419a1..4da4437 100644 --- a/tests/unit/test_start_docker.py +++ b/tests/unit/test_start_docker.py @@ -205,8 +205,10 @@ class TestStartDockerDaemon: mock_ntf.return_value = MagicMock(name="/tmp/dockerd.log") # is_docker_ready: first check (inner dockerd) True, then local daemon checks mock_ready.side_effect = [True, False, False, False, False, True] - # subprocess.run calls: pgrep(before), pkill x3, pgrep(after), docker info, rm + # subprocess.run: pgrep(socket check, finds dockerd), pgrep(before kill), + # pkill x3, pgrep(after kill), docker info(dead), rm mock_run.side_effect = [ + MagicMock(returncode=0, stdout="123 /usr/bin/dockerd\n"), # socket check pgrep _pgrep_empty(), MagicMock(), MagicMock(), @@ -218,6 +220,27 @@ class TestStartDockerDaemon: assert start_docker_daemon(timeout=5) is True mock_popen.assert_called_once() + @patch("devx.molecule.start_docker._diagnose_socket") + @patch("devx.molecule.start_docker._get_docker_free_bytes", return_value=0) + @patch("devx.molecule.start_docker.is_docker_ready", return_value=True) + @patch("devx.molecule.start_docker.os.path.exists", side_effect=_exists_map({DOCKER_SOCK})) + @patch("devx.molecule.start_docker.glob.glob", return_value=[]) + @patch("devx.molecule.start_docker.subprocess.run") + def test_no_inner_dockerd_trusts_host_socket( + self, + mock_run: MagicMock, + mock_glob: MagicMock, + mock_exists: MagicMock, + mock_ready: MagicMock, + mock_free: MagicMock, + mock_diag: MagicMock, + ) -> None: + """Should trust /var/run/docker.sock with free=0 when no inner dockerd exists.""" + # pgrep finds no dockerd processes (returncode=1) + mock_run.return_value = MagicMock(returncode=1, stdout="") + assert start_docker_daemon(timeout=5) is True + assert os.environ.get("DOCKER_HOST") == f"unix://{DOCKER_SOCK}" + @patch("devx.molecule.start_docker._diagnose_socket") @patch("devx.molecule.start_docker._get_docker_free_bytes") @patch("devx.molecule.start_docker.is_docker_ready")