Coverage for frappe_manager / docker / docker_exceptions.py: 47%
30 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-07-02 18:13 +0530
« prev ^ index » next coverage.py v7.13.5, created at 2026-07-02 18:13 +0530
1from frappe_manager.docker.subprocess_output import SubprocessOutput
4class DockerException(Exception):
5 """Exception raised when Docker command execution fails."""
7 def __init__(
8 self,
9 command_launched: list[str],
10 output: SubprocessOutput,
11 ):
12 self.docker_command: list[str] = command_launched
13 self.output = output
15 command_launched_str = " ".join(command_launched)
17 error_msg = (
18 f"The docker command executed was `{command_launched_str}`.\n"
19 f"It returned with code {self.output.exit_code}\n"
20 )
22 if self.output.stdout:
23 stdout_output = "\n".join(self.output.stdout)
24 error_msg += f"The content of stdout is \n{'--' * 10}\n'{stdout_output}'\n"
25 else:
26 error_msg += "The content of stdout can be found above the stacktrace (it wasn't captured).\n"
28 if self.output.stderr:
29 stderr_output = "\n".join(self.output.stderr)
30 error_msg += f"The content of stderr is \n{'--' * 10}\n'{stderr_output}'\n"
31 else:
32 error_msg += "The content of stderr can be found above the stacktrace (it wasn't captured)."
34 super().__init__(error_msg)
37def is_port_conflict_error(exception: DockerException) -> tuple[bool, list[int]]:
38 """
39 Detect if DockerException was caused by port binding conflict.
41 Returns:
42 Tuple of (is_conflict, list_of_conflicting_ports)
44 Examples:
45 >>> exc = DockerException(["compose", "up"], output_with_port_error)
46 >>> is_conflict, ports = is_port_conflict_error(exc)
47 >>> is_conflict
48 True
49 >>> ports
50 [80, 443]
51 """
52 import re
54 stderr_text = "\n".join(exception.output.stderr) if exception.output.stderr else ""
56 if not ("port is already allocated" in stderr_text or "address already in use" in stderr_text):
57 return False, []
59 ports = []
60 port_patterns = [
61 r"Bind for (?:0\.0\.0\.0|:::):(\d+)",
62 r"port (\d+) is already allocated",
63 r"address already in use.*:(\d+)",
64 ]
66 for pattern in port_patterns:
67 matches = re.findall(pattern, stderr_text)
68 for match in matches:
69 port = int(match)
70 if port not in ports:
71 ports.append(port)
73 return len(ports) > 0, sorted(ports)