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")

Reply via email to