Compare commits

...
3 Commits
Author SHA1 Message Date
devx-ci-bot d398c8e971 release: v0.9.5 [skip ci] 2026-06-24 01:37:15 +00:00
emil 39526d8e6a DEVX-26: fix: use host Docker socket with DOCKER_HOST fallback to local dockerd
Post-merge / detect-type (push) Successful in 7s
Post-merge / validate-commit-msg (push) Successful in 7s
Post-merge / configure-repo (push) Successful in 9s
Post-merge / release (push) Successful in 46s
Post-merge / vikunja (push) Successful in 10s
Post-merge / badges (push) Successful in 41s
Post-merge / sync-wiki (push) Successful in 43s
2026-06-24 01:36:21 +00:00
emil 37730e2187 DEVX-25: fix: use host Docker socket with DOCKER_HOST fallback to local dockerd
Post-merge / detect-type (push) Successful in 7s
Post-merge / validate-commit-msg (push) Successful in 7s
Post-merge / configure-repo (push) Successful in 17s
Post-merge / release (push) Successful in 41s
Post-merge / vikunja (push) Successful in 12s
Post-merge / sync-wiki (push) Successful in 48s
Post-merge / badges (push) Successful in 52s
2026-06-24 01:24:00 +00:00
6 changed files with 52 additions and 47 deletions
+1 -1
View File
@@ -1 +1 @@
DEVX-24
DEVX-26
+7
View File
@@ -2,6 +2,13 @@
All notable changes to this project will be documented in this file.
## [0.9.5] - 2026-06-24
### Bug Fixes
- Use host Docker socket with DOCKER_HOST fallback to local dockerd
- Use host Docker socket with DOCKER_HOST fallback to local dockerd
## [0.9.4] - 2026-06-24
### Bug Fixes
+1 -1
View File
@@ -1,3 +1,3 @@
"""devx — reusable development and CI/CD tools for oblachno-oss projects."""
__version__ = "0.9.4"
__version__ = "0.9.5"
+20 -13
View File
@@ -1,11 +1,13 @@
#!/usr/bin/env python3
"""Start a Docker daemon inside a CI runner container (Docker-in-Docker).
"""Ensure Docker is available for molecule tests in CI.
CI runners (e.g. ``gitea/runner-images:ubuntu-latest``) may have the host's
Docker socket mounted, but molecule needs a local Docker daemon to create
nested containers. This module starts ``dockerd`` on a separate socket
and sets ``DOCKER_HOST`` so both the CLI and Python library connect to
the local daemon.
Docker socket mounted. This module verifies Docker is accessible and
sets ``DOCKER_HOST`` explicitly so molecule's Python docker library
connects to the same socket as the Docker CLI.
If the host socket is not available, it starts a local ``dockerd``
with the vfs storage driver (requires privileged container).
Usage::
@@ -25,11 +27,11 @@ import click
from devx.i18n import _
DEFAULT_TIMEOUT = 30
DOCKER_SOCK = "/tmp/dockerd.sock" # nosec B108
DOCKER_SOCK = "/var/run/docker.sock"
def is_docker_ready() -> bool:
"""Check if the local Docker daemon is responding."""
"""Check if Docker daemon is responding on the configured socket."""
result = subprocess.run( # nosec B603 B607
["docker", "info"],
capture_output=True,
@@ -40,19 +42,24 @@ def is_docker_ready() -> bool:
def start_docker_daemon(timeout: int = DEFAULT_TIMEOUT) -> bool:
"""Start dockerd on a separate socket and wait for it to be ready.
"""Ensure Docker is ready for molecule tests.
Uses ``/tmp/dockerd.sock`` instead of the default ``/var/run/docker.sock``
to avoid conflicts with host-mounted sockets. Sets ``DOCKER_HOST`` in the
current environment so molecule's Python docker library connects to the
local daemon.
First tries the host socket. If that works, sets ``DOCKER_HOST`` and
returns immediately. If not, starts a local ``dockerd`` with vfs
storage driver (requires privileged container).
Returns ``True`` if Docker is ready, ``False`` if it failed to
start within the timeout.
"""
# Point Docker CLI and Python library to our local socket
# Point Docker CLI and Python library to the socket explicitly
os.environ["DOCKER_HOST"] = f"unix://{DOCKER_SOCK}"
# Check if host Docker is already available
if is_docker_ready():
click.echo(_("Docker daemon already running"))
return True
# Start local dockerd (requires privileged container)
click.echo(_("Starting Docker daemon..."))
log_file = tempfile.NamedTemporaryFile( # noqa: SIM115
mode="w", suffix="dockerd.log", delete=False
+7
View File
@@ -419,6 +419,13 @@
"ru": "Created release commit.",
"zh": "Created release commit."
},
"Docker daemon already running": {
"bg": "Докер демонът вече работи",
"de": "Docker-Daemon läuft bereits",
"en": "Docker daemon already running",
"ru": "Демон Docker уже работает",
"zh": "Docker 守护进程已在运行"
},
"Docker daemon failed to start": {
"bg": "Docker daemon failed to start",
"de": "Docker-Daemon konnte nicht gestartet werden",
+16 -32
View File
@@ -23,11 +23,17 @@ class TestIsDockerReady:
class TestStartDockerDaemon:
@patch("devx.molecule.start_docker.is_docker_ready", return_value=True)
def test_host_socket_available(self, mock_ready: MagicMock) -> None:
"""Should return immediately if host Docker is available."""
assert start_docker_daemon(timeout=5) is True
mock_ready.assert_called_once()
@patch("devx.molecule.start_docker.time.sleep")
@patch("devx.molecule.start_docker.is_docker_ready")
@patch("devx.molecule.start_docker.subprocess.Popen")
@patch("devx.molecule.start_docker.tempfile.NamedTemporaryFile")
def test_starts_successfully(
def test_starts_local_daemon(
self,
mock_ntf: MagicMock,
mock_popen: MagicMock,
@@ -35,7 +41,8 @@ class TestStartDockerDaemon:
mock_sleep: MagicMock,
) -> None:
mock_ntf.return_value = MagicMock()
mock_ready.side_effect = [False, True]
# Host socket not available, then local daemon starts on third check
mock_ready.side_effect = [False, False, False, True]
assert start_docker_daemon(timeout=5) is True
mock_popen.assert_called_once()
popen_args = mock_popen.call_args.args[0]
@@ -44,7 +51,7 @@ class TestStartDockerDaemon:
assert "vfs" in popen_args
assert "-H" in popen_args
assert f"unix://{DOCKER_SOCK}" in popen_args
mock_sleep.assert_called_once_with(1)
assert mock_sleep.call_count == 2
@patch("devx.molecule.start_docker.time.sleep")
@patch("devx.molecule.start_docker.is_docker_ready", return_value=False)
@@ -66,7 +73,7 @@ class TestStartDockerDaemon:
@patch("devx.molecule.start_docker.is_docker_ready")
@patch("devx.molecule.start_docker.subprocess.Popen")
@patch("devx.molecule.start_docker.tempfile.NamedTemporaryFile")
def test_ready_on_first_check(
def test_local_daemon_ready_on_first_check(
self,
mock_ntf: MagicMock,
mock_popen: MagicMock,
@@ -74,43 +81,20 @@ class TestStartDockerDaemon:
mock_sleep: MagicMock,
) -> None:
mock_ntf.return_value = MagicMock()
mock_ready.return_value = True
# Host not available, local daemon ready on first loop check
mock_ready.side_effect = [False, False, True]
assert start_docker_daemon(timeout=5) is True
mock_popen.assert_called_once()
mock_sleep.assert_not_called()
@patch("devx.molecule.start_docker.time.sleep")
@patch("devx.molecule.start_docker.is_docker_ready")
@patch("devx.molecule.start_docker.subprocess.Popen")
@patch("devx.molecule.start_docker.tempfile.NamedTemporaryFile")
def test_custom_timeout(
self,
mock_ntf: MagicMock,
mock_popen: MagicMock,
mock_ready: MagicMock,
mock_sleep: MagicMock,
) -> None:
mock_ntf.return_value = MagicMock()
mock_ready.side_effect = [False] * 9 + [True]
assert start_docker_daemon(timeout=10) is True
assert mock_sleep.call_count == 9
mock_sleep.assert_called_once_with(1)
@patch("devx.molecule.start_docker.os.environ")
@patch("devx.molecule.start_docker.time.sleep")
@patch("devx.molecule.start_docker.is_docker_ready")
@patch("devx.molecule.start_docker.subprocess.Popen")
@patch("devx.molecule.start_docker.tempfile.NamedTemporaryFile")
@patch("devx.molecule.start_docker.is_docker_ready", return_value=True)
def test_sets_docker_host(
self,
mock_ntf: MagicMock,
mock_popen: MagicMock,
mock_ready: MagicMock,
mock_sleep: MagicMock,
mock_environ: MagicMock,
) -> None:
"""DOCKER_HOST must be set so molecule connects to local daemon."""
mock_ntf.return_value = MagicMock()
mock_ready.return_value = True
"""DOCKER_HOST must be set so molecule connects to correct socket."""
start_docker_daemon(timeout=5)
mock_environ.__setitem__.assert_called_with("DOCKER_HOST", f"unix://{DOCKER_SOCK}")