docker-stack 2.2.3__py3-none-any.whl → 2.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- docker_stack/cli.py +85 -11
- {docker_stack-2.2.3.dist-info → docker_stack-2.2.4.dist-info}/METADATA +1 -1
- {docker_stack-2.2.3.dist-info → docker_stack-2.2.4.dist-info}/RECORD +6 -6
- {docker_stack-2.2.3.dist-info → docker_stack-2.2.4.dist-info}/WHEEL +0 -0
- {docker_stack-2.2.3.dist-info → docker_stack-2.2.4.dist-info}/entry_points.txt +0 -0
- {docker_stack-2.2.3.dist-info → docker_stack-2.2.4.dist-info}/top_level.txt +0 -0
docker_stack/cli.py
CHANGED
|
@@ -350,6 +350,38 @@ def _print_cluster_containers(payload: Dict[str, object], *, no_trunc: bool = Fa
|
|
|
350
350
|
return 1 if errors else 0
|
|
351
351
|
|
|
352
352
|
|
|
353
|
+
def _exec_process(command: List[str], env: Optional[Dict[str, str]] = None) -> int:
|
|
354
|
+
"""Replace this process with ``command`` so it owns the terminal, its signals, and its exit code.
|
|
355
|
+
|
|
356
|
+
Proxying through ``subprocess.run`` keeps Python in the foreground process group: Ctrl+C is
|
|
357
|
+
delivered to the proxy as well as to the child, which surfaces as a ``KeyboardInterrupt``
|
|
358
|
+
traceback, and a signal-killed child reports a negative return code that ``sys.exit`` turns into
|
|
359
|
+
a nonsense status (``-2`` becomes ``254`` instead of ``130``). ``exec`` removes the middleman.
|
|
360
|
+
Only POSIX execs: on Windows ``exec`` detaches the child and hands control straight back, which
|
|
361
|
+
would break the caller's wait semantics.
|
|
362
|
+
|
|
363
|
+
Returns only on failure; the caller keeps its ``int`` contract for that case.
|
|
364
|
+
"""
|
|
365
|
+
if os.name != "posix":
|
|
366
|
+
return subprocess.run(command, check=False, env=env).returncode
|
|
367
|
+
# exec discards Python's buffers, so anything already printed has to reach the fd first.
|
|
368
|
+
sys.stdout.flush()
|
|
369
|
+
sys.stderr.flush()
|
|
370
|
+
try:
|
|
371
|
+
if env is None:
|
|
372
|
+
os.execvp(command[0], command)
|
|
373
|
+
else:
|
|
374
|
+
os.execvpe(command[0], command, env)
|
|
375
|
+
except OSError as exc:
|
|
376
|
+
print(f"docker-stack: cannot execute {command[0]}: {exc}", file=sys.stderr)
|
|
377
|
+
return 127
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _exec_docker(values: List[str]) -> int:
|
|
381
|
+
"""Hand the command to the real Docker CLI, replacing this process."""
|
|
382
|
+
return _exec_process(["docker", *values])
|
|
383
|
+
|
|
384
|
+
|
|
353
385
|
def _managed_docker(arguments: List[str]) -> int:
|
|
354
386
|
values = list(arguments)
|
|
355
387
|
if values and values[0] == "--":
|
|
@@ -378,11 +410,11 @@ def _managed_docker(arguments: List[str]) -> int:
|
|
|
378
410
|
value in {"-q", "--quiet", "--format"} or value.startswith("--format=")
|
|
379
411
|
for value in command
|
|
380
412
|
):
|
|
381
|
-
return
|
|
413
|
+
return _exec_docker(values)
|
|
382
414
|
try:
|
|
383
415
|
client = discover_manager_client()
|
|
384
416
|
if client is None or not client.supports(FEATURE_CLUSTER_CONTAINER_CLI):
|
|
385
|
-
return
|
|
417
|
+
return _exec_docker(values)
|
|
386
418
|
filters = []
|
|
387
419
|
all_containers = False
|
|
388
420
|
latest = False
|
|
@@ -411,7 +443,7 @@ def _managed_docker(arguments: List[str]) -> int:
|
|
|
411
443
|
elif value.startswith("--last="):
|
|
412
444
|
limit = int(value.split("=", 1)[1])
|
|
413
445
|
else:
|
|
414
|
-
return
|
|
446
|
+
return _exec_docker(values)
|
|
415
447
|
index += 1
|
|
416
448
|
payload = client.list_containers(
|
|
417
449
|
all_containers=all_containers,
|
|
@@ -1774,7 +1806,7 @@ def open_context_shell(config_dir: Path, context_name: str) -> int:
|
|
|
1774
1806
|
env["DOCKER_CONTEXT"] = context_name
|
|
1775
1807
|
env["DOCKER_MANAGER_CONTEXT_NAME"] = context_name
|
|
1776
1808
|
shell = env.get("SHELL", "").strip() or "/bin/bash"
|
|
1777
|
-
return
|
|
1809
|
+
return _exec_process([shell, "-i"], env=env)
|
|
1778
1810
|
|
|
1779
1811
|
|
|
1780
1812
|
def active_shell_config_dir(context_name: str) -> Optional[Path]:
|
|
@@ -1805,6 +1837,29 @@ def _error_output(exc: subprocess.CalledProcessError) -> str:
|
|
|
1805
1837
|
return "\n".join(value for value in values if value)
|
|
1806
1838
|
|
|
1807
1839
|
|
|
1840
|
+
_MANAGER_VERSION_PROBE_PATTERN = re.compile(r"manager request failed \(\w+ /version\)", re.IGNORECASE)
|
|
1841
|
+
|
|
1842
|
+
|
|
1843
|
+
def _humanize_manager_error(message: str) -> str:
|
|
1844
|
+
"""Render a manager JSON error body as prose instead of dumping the raw payload."""
|
|
1845
|
+
match = re.search(r"^(.*?: HTTP \d+): (\{.*\})$", message, re.DOTALL)
|
|
1846
|
+
if not match:
|
|
1847
|
+
return message
|
|
1848
|
+
try:
|
|
1849
|
+
body = json.loads(match.group(2))
|
|
1850
|
+
except json.JSONDecodeError:
|
|
1851
|
+
return message
|
|
1852
|
+
if not isinstance(body, dict):
|
|
1853
|
+
return message
|
|
1854
|
+
detail = str(body.get("message", "")).strip()
|
|
1855
|
+
if not detail:
|
|
1856
|
+
return message
|
|
1857
|
+
incident = str(body.get("incident_id") or body.get("trace_id") or "").strip()
|
|
1858
|
+
if incident:
|
|
1859
|
+
return f"{match.group(1)}: {detail} (incident {incident})"
|
|
1860
|
+
return f"{match.group(1)}: {detail}"
|
|
1861
|
+
|
|
1862
|
+
|
|
1808
1863
|
def _manager_deploy_suggestion(message: str) -> Optional[str]:
|
|
1809
1864
|
if GITHUB_WORKFLOW_RESTRICTED_MESSAGE in message:
|
|
1810
1865
|
return (
|
|
@@ -1824,10 +1879,16 @@ def _manager_deploy_suggestion(message: str) -> Optional[str]:
|
|
|
1824
1879
|
"by this stack. Either allow that external network in the Docker-Manager deployment rule, attach the service to "
|
|
1825
1880
|
"a stack-owned network, or relabel/recreate the existing network with the expected stack ownership before deploying."
|
|
1826
1881
|
)
|
|
1827
|
-
if "
|
|
1882
|
+
if "stack not found" in lowered:
|
|
1883
|
+
return (
|
|
1884
|
+
"Suggestion: the manager was reached but has no stack by that name in that namespace. "
|
|
1885
|
+
"Run 'docker-stack ls' to list stacks in the current namespace, or 'docker-stack ls -A' to search every namespace."
|
|
1886
|
+
)
|
|
1887
|
+
if _MANAGER_VERSION_PROBE_PATTERN.search(message):
|
|
1828
1888
|
return (
|
|
1829
|
-
"Suggestion:
|
|
1830
|
-
"
|
|
1889
|
+
"Suggestion: docker-stack could not read the manager's /version endpoint. Check that DOCKER_MANAGER_URL points "
|
|
1890
|
+
"at a reachable manager and that the Docker auth headers are set - in CI that means running the docker-stack "
|
|
1891
|
+
"setup action before this step."
|
|
1831
1892
|
)
|
|
1832
1893
|
if "docker-manager is configured" in lowered and "did not advertise required feature" in lowered:
|
|
1833
1894
|
return (
|
|
@@ -1841,7 +1902,7 @@ def _format_called_process_error(exc: subprocess.CalledProcessError) -> str:
|
|
|
1841
1902
|
output = _error_output(exc)
|
|
1842
1903
|
lines = [f"docker-stack: command failed with exit code {exc.returncode}: {_format_command(exc.cmd)}"]
|
|
1843
1904
|
if output:
|
|
1844
|
-
lines.append(output)
|
|
1905
|
+
lines.append(_humanize_manager_error(output))
|
|
1845
1906
|
suggestion = _manager_deploy_suggestion(output)
|
|
1846
1907
|
if suggestion:
|
|
1847
1908
|
lines.extend(["", suggestion])
|
|
@@ -1850,14 +1911,14 @@ def _format_called_process_error(exc: subprocess.CalledProcessError) -> str:
|
|
|
1850
1911
|
|
|
1851
1912
|
def _format_runtime_error(exc: RuntimeError) -> str:
|
|
1852
1913
|
message = str(exc).strip() or exc.__class__.__name__
|
|
1853
|
-
lines = [f"docker-stack: {message}"]
|
|
1914
|
+
lines = [f"docker-stack: {_humanize_manager_error(message)}"]
|
|
1854
1915
|
suggestion = _manager_deploy_suggestion(message)
|
|
1855
1916
|
if suggestion:
|
|
1856
1917
|
lines.extend(["", suggestion])
|
|
1857
1918
|
return "\n".join(lines)
|
|
1858
1919
|
|
|
1859
1920
|
|
|
1860
|
-
def
|
|
1921
|
+
def _run(args: List[str] = None):
|
|
1861
1922
|
parser = argparse.ArgumentParser(description="Deploy and manage Docker stacks.")
|
|
1862
1923
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
1863
1924
|
|
|
@@ -2202,5 +2263,18 @@ def main(args: List[str] = None):
|
|
|
2202
2263
|
sys.exit(2)
|
|
2203
2264
|
|
|
2204
2265
|
|
|
2266
|
+
def main(args: List[str] = None):
|
|
2267
|
+
"""Entry point. Ctrl+C has to look like an interrupted shell command, not a Python crash.
|
|
2268
|
+
|
|
2269
|
+
Commands that exec into another binary never reach this handler; it covers the paths that stay
|
|
2270
|
+
in Python (cluster-rendered ``ps``, login, deploy) and any HTTP call they are blocked on.
|
|
2271
|
+
"""
|
|
2272
|
+
try:
|
|
2273
|
+
return _run(args)
|
|
2274
|
+
except KeyboardInterrupt:
|
|
2275
|
+
print(file=sys.stderr)
|
|
2276
|
+
return 130
|
|
2277
|
+
|
|
2278
|
+
|
|
2205
2279
|
if __name__ == "__main__":
|
|
2206
|
-
main()
|
|
2280
|
+
sys.exit(main())
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
docker_stack/__init__.py,sha256=qgHdW8ZDBlyk6FzIf8Mld_cJD1SjOfqo2dELg4pK668,880
|
|
2
|
-
docker_stack/cli.py,sha256=
|
|
2
|
+
docker_stack/cli.py,sha256=_IWypxyKZ4reUaB4qb7WpRt24WtUVWLlmrr3ALY8riM,96017
|
|
3
3
|
docker_stack/command_runner.py,sha256=mNaUAVKtrJbji_5ARVPLKhc92avqllNKrVQj1A6S0dA,1252
|
|
4
4
|
docker_stack/compose.py,sha256=_fAVesjyW5ecid46sYYhcu02yF1vqknDSCFYflJPDOE,534
|
|
5
5
|
docker_stack/docker_objects.py,sha256=U6szytv5gKrmBYKqThSeMMXdiJ2u4STIQLkeqQ-Fn4Q,12103
|
|
@@ -13,8 +13,8 @@ docker_stack/merge_conf.py,sha256=Pmsabcgf3SDyaWvf2OLsBPodzEqsvOIjj9xzn_qWXH0,19
|
|
|
13
13
|
docker_stack/registry.py,sha256=sWC1J9JDIrcYyhei1POYCZHJ0xUCKC2pveisSHMgYsQ,9036
|
|
14
14
|
docker_stack/shell_auth.py,sha256=UUu9y4wMFKfc_j86yGcasvS_TLZ2uV4kv0T4XKTMQBQ,33424
|
|
15
15
|
docker_stack/url_parser.py,sha256=Sk8GQE0nEiwCkijp1OltP2AfgtKEmM2hybcH_rBhfiI,6824
|
|
16
|
-
docker_stack-2.2.
|
|
17
|
-
docker_stack-2.2.
|
|
18
|
-
docker_stack-2.2.
|
|
19
|
-
docker_stack-2.2.
|
|
20
|
-
docker_stack-2.2.
|
|
16
|
+
docker_stack-2.2.4.dist-info/METADATA,sha256=__Y1aknQuctP1Nuf2zhuinUnlyYDu-qGY54rUma8alM,13821
|
|
17
|
+
docker_stack-2.2.4.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
|
18
|
+
docker_stack-2.2.4.dist-info/entry_points.txt,sha256=mpe2RwIguARsosXIUBQEN2pKU53URqZCh2S4ATwDFL4,55
|
|
19
|
+
docker_stack-2.2.4.dist-info/top_level.txt,sha256=zT6TPL54cLrt9LO_MNkhEpGGOmsoe2HV6Na5Ohy3_2c,13
|
|
20
|
+
docker_stack-2.2.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|