This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-3-test by this push:
new 22a2b6161c3 [v3-3-test] Stop Breeze from hanging on unresponsive
Docker (#72439) (#72664)
22a2b6161c3 is described below
commit 22a2b6161c3702d28091b416b541af11a8937aa9
Author: Jarek Potiuk <[email protected]>
AuthorDate: Mon Sep 7 23:08:53 2026 +0200
[v3-3-test] Stop Breeze from hanging on unresponsive Docker (#72439)
(#72664)
Docker CLI can wait indefinitely when the daemon or configured endpoint
does not respond, leaving Breeze commands without useful feedback.
(cherry picked from commit ed90dbb161ab45716540470d60dd49a50cd72e26)
Co-authored-by: Andrew Chang <[email protected]>
---
.../airflow_breeze/utils/docker_command_utils.py | 43 ++++++++++------
dev/breeze/tests/test_docker_command_utils.py | 58 ++++++++++++++++++++++
2 files changed, 86 insertions(+), 15 deletions(-)
diff --git a/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
b/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
index 013f7a8e265..a1f00c574e9 100644
--- a/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
+++ b/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
@@ -119,6 +119,8 @@ VOLUMES_FOR_SELECTED_MOUNTS = [
("task-sdk", "/opt/airflow/task-sdk"),
]
+DOCKER_INFO_TIMEOUT = 30
+
def check_docker_resources(airflow_image_name: str) -> RunCommandResult:
"""
@@ -144,6 +146,30 @@ def check_docker_resources(airflow_image_name: str) ->
RunCommandResult:
)
+def _run_docker_info_or_exit(command: list[str]) -> RunCommandResult:
+ try:
+ return run_command(
+ command,
+ no_output_dump_on_exception=True,
+ capture_output=True,
+ text=True,
+ check=False,
+ timeout=DOCKER_INFO_TIMEOUT,
+ )
+ except subprocess.TimeoutExpired:
+ console_print(
+ f"[error]Docker did not respond within {DOCKER_INFO_TIMEOUT}
seconds.[/]\n"
+ "[warning]Please make sure Docker is running and responsive.[/]"
+ )
+ sys.exit(1)
+ except FileNotFoundError:
+ console_print(
+ "[error]Docker executable was not found.[/]\n"
+ "[warning]Please install Docker and ensure `docker` is available
on PATH.[/]"
+ )
+ sys.exit(1)
+
+
def check_docker_permission_denied() -> bool:
"""
Checks if we have permission to write to docker socket. By default, on
Linux you need to add your user
@@ -154,14 +180,7 @@ def check_docker_permission_denied() -> bool:
:return: True if permission is denied
"""
permission_denied = False
- docker_permission_command = ["docker", "info"]
- command_result = run_command(
- docker_permission_command,
- no_output_dump_on_exception=True,
- capture_output=True,
- text=True,
- check=False,
- )
+ command_result = _run_docker_info_or_exit(["docker", "info"])
if command_result.returncode != 0:
permission_denied = True
if command_result.stdout and "Got permission denied while trying to
connect" in command_result.stdout:
@@ -182,13 +201,7 @@ def check_docker_is_running():
Checks if docker is running. Suppressed Dockers stdout and stderr output.
"""
- response = run_command(
- ["docker", "info"],
- no_output_dump_on_exception=True,
- text=True,
- capture_output=True,
- check=False,
- )
+ response = _run_docker_info_or_exit(["docker", "info"])
if response.returncode != 0:
console_print(
"[error]Docker is not running.[/]\n[warning]Please make sure
Docker is installed and running.[/]"
diff --git a/dev/breeze/tests/test_docker_command_utils.py
b/dev/breeze/tests/test_docker_command_utils.py
index d433ea7bb14..555cf897b3c 100644
--- a/dev/breeze/tests/test_docker_command_utils.py
+++ b/dev/breeze/tests/test_docker_command_utils.py
@@ -17,6 +17,7 @@
from __future__ import annotations
import json
+import subprocess
from unittest import mock
from unittest.mock import call
@@ -33,6 +34,8 @@ from airflow_breeze.utils.docker_command_utils import (
autodetect_docker_context,
bring_all_compose_projects_down,
check_docker_compose_version,
+ check_docker_is_running,
+ check_docker_permission_denied,
check_docker_version,
discover_running_compose_projects,
enter_shell,
@@ -43,6 +46,61 @@ from airflow_breeze.utils.docker_command_utils import (
)
[email protected](
+ ("exception", "expected_message"),
+ [
+ pytest.param(
+ subprocess.TimeoutExpired(["docker", "info"], 30),
+ "[error]Docker did not respond within 30 seconds.[/]\n"
+ "[warning]Please make sure Docker is running and responsive.[/]",
+ id="timeout",
+ ),
+ pytest.param(
+ FileNotFoundError(2, "No such file or directory", "docker"),
+ "[error]Docker executable was not found.[/]\n"
+ "[warning]Please install Docker and ensure `docker` is available
on PATH.[/]",
+ id="missing-executable",
+ ),
+ ],
+)
[email protected]("airflow_breeze.utils.docker_command_utils.console_print")
[email protected]("airflow_breeze.utils.docker_command_utils.run_command")
+def test_check_docker_is_running_reports_unavailable_docker(
+ mock_run_command, mock_console_print, exception, expected_message
+):
+ mock_run_command.side_effect = exception
+
+ with pytest.raises(SystemExit) as error:
+ check_docker_is_running()
+
+ assert error.value.code == 1
+ mock_run_command.assert_called_once_with(
+ ["docker", "info"],
+ no_output_dump_on_exception=True,
+ text=True,
+ capture_output=True,
+ check=False,
+ timeout=30,
+ )
+ mock_console_print.assert_called_once_with(expected_message)
+
+
[email protected]("airflow_breeze.utils.docker_command_utils.run_command")
+def
test_check_docker_permission_denied_uses_bounded_info_probe(mock_run_command):
+ mock_run_command.return_value.returncode = 0
+
+ assert check_docker_permission_denied() is False
+
+ mock_run_command.assert_called_once_with(
+ ["docker", "info"],
+ no_output_dump_on_exception=True,
+ capture_output=True,
+ text=True,
+ check=False,
+ timeout=30,
+ )
+
+
@mock.patch("airflow_breeze.utils.docker_command_utils.check_docker_permission_denied")
@mock.patch("airflow_breeze.utils.docker_command_utils.run_command")
@mock.patch("airflow_breeze.utils.docker_command_utils.console_print")