docker-stack 2.2.4__py3-none-any.whl → 2.2.5__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 +24 -7
- docker_stack/manager_api.py +41 -2
- {docker_stack-2.2.4.dist-info → docker_stack-2.2.5.dist-info}/METADATA +1 -1
- {docker_stack-2.2.4.dist-info → docker_stack-2.2.5.dist-info}/RECORD +7 -7
- {docker_stack-2.2.4.dist-info → docker_stack-2.2.5.dist-info}/WHEEL +0 -0
- {docker_stack-2.2.4.dist-info → docker_stack-2.2.5.dist-info}/entry_points.txt +0 -0
- {docker_stack-2.2.4.dist-info → docker_stack-2.2.5.dist-info}/top_level.txt +0 -0
docker_stack/cli.py
CHANGED
|
@@ -37,7 +37,6 @@ from docker_stack.shell_auth import (
|
|
|
37
37
|
shell_session_active,
|
|
38
38
|
)
|
|
39
39
|
from docker_stack.manager_api import (
|
|
40
|
-
FEATURE_CLUSTER_CONTAINER_CLI,
|
|
41
40
|
FEATURE_STACK_DEPLOY,
|
|
42
41
|
FEATURE_STACK_QUERY,
|
|
43
42
|
ManagerApiClient,
|
|
@@ -102,15 +101,23 @@ def _write_selected_node(value: Optional[str], display_name: str) -> None:
|
|
|
102
101
|
Path(node_state).write_text(f"{display_name}\n", encoding="utf-8")
|
|
103
102
|
|
|
104
103
|
|
|
105
|
-
def
|
|
106
|
-
client
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
def _require_manager() -> ManagerApiClient:
|
|
105
|
+
"""Return a client for the Docker-Manager behind this shell, or explain why not.
|
|
106
|
+
|
|
107
|
+
Only the current manager is supported, so there is no capability negotiation here -
|
|
108
|
+
either the target is a Docker-Manager or it is a plain Docker daemon.
|
|
109
|
+
"""
|
|
110
|
+
client = discover_manager_client(strict=True)
|
|
111
|
+
if client is None or not client.is_manager_backend():
|
|
112
|
+
raise RuntimeError(
|
|
113
|
+
f"{client.manager_url if client else 'the current Docker endpoint'} is not a "
|
|
114
|
+
"Docker-Manager; node-local commands need one"
|
|
115
|
+
)
|
|
109
116
|
return client
|
|
110
117
|
|
|
111
118
|
|
|
112
119
|
def _select_node(value: str) -> str:
|
|
113
|
-
client =
|
|
120
|
+
client = _require_manager()
|
|
114
121
|
requested = value.strip()
|
|
115
122
|
if requested.lower() == "cluster":
|
|
116
123
|
_write_selected_node(None, "cluster")
|
|
@@ -133,6 +140,9 @@ def _select_node(value: str) -> str:
|
|
|
133
140
|
raise RuntimeError(f"node '{requested}' is not ready")
|
|
134
141
|
node_id = str(node.get("id") or "").strip()
|
|
135
142
|
hostname = str(node.get("hostname") or node_id).strip()
|
|
143
|
+
# Only this node has to be usable. The health of every other node - drained, down, or
|
|
144
|
+
# missing its agent - is none of this command's business.
|
|
145
|
+
client.check_node_agent(node_id or hostname)
|
|
136
146
|
_write_selected_node(node_id, hostname)
|
|
137
147
|
return hostname
|
|
138
148
|
|
|
@@ -339,6 +349,13 @@ def _print_cluster_containers(payload: Dict[str, object], *, no_trunc: bool = Fa
|
|
|
339
349
|
columns.append("SIZE")
|
|
340
350
|
columns.append("NODE")
|
|
341
351
|
_print_table(columns, rows)
|
|
352
|
+
skipped = payload.get("skipped_nodes") if isinstance(payload.get("skipped_nodes"), list) else []
|
|
353
|
+
for node in skipped:
|
|
354
|
+
if isinstance(node, dict):
|
|
355
|
+
print(
|
|
356
|
+
f"docker ps: skipped node {node.get('node_name') or node.get('node_id')}: {node.get('reason')}",
|
|
357
|
+
file=sys.stderr,
|
|
358
|
+
)
|
|
342
359
|
errors = payload.get("node_errors") if isinstance(payload.get("node_errors"), list) else []
|
|
343
360
|
for error in errors:
|
|
344
361
|
if isinstance(error, dict):
|
|
@@ -413,7 +430,7 @@ def _managed_docker(arguments: List[str]) -> int:
|
|
|
413
430
|
return _exec_docker(values)
|
|
414
431
|
try:
|
|
415
432
|
client = discover_manager_client()
|
|
416
|
-
if client is None or not client.
|
|
433
|
+
if client is None or not client.is_manager_backend():
|
|
417
434
|
return _exec_docker(values)
|
|
418
435
|
filters = []
|
|
419
436
|
all_containers = False
|
docker_stack/manager_api.py
CHANGED
|
@@ -416,6 +416,22 @@ class ManagerApiClient:
|
|
|
416
416
|
endpoint_id = self._resolve_endpoint_id()
|
|
417
417
|
return f"/api/endpoints/{endpoint_id}{normalized}"
|
|
418
418
|
|
|
419
|
+
def is_manager_backend(self) -> bool:
|
|
420
|
+
"""True when the target is a Docker-Manager stack API rather than a raw daemon."""
|
|
421
|
+
return self._detect_manager_backend()
|
|
422
|
+
|
|
423
|
+
def check_node_agent(self, selector: str) -> Dict[str, Any]:
|
|
424
|
+
"""Report whether one node can serve node-local Docker commands.
|
|
425
|
+
|
|
426
|
+
Scoped to ``selector`` on purpose: an unrelated node that is drained, down, or
|
|
427
|
+
missing its agent must never make a usable node look unusable.
|
|
428
|
+
"""
|
|
429
|
+
node = urllib.parse.quote(selector, safe="")
|
|
430
|
+
payload = self._request_json(f"/api/docker-stack/nodes/{node}/agent")
|
|
431
|
+
if not isinstance(payload, dict):
|
|
432
|
+
raise RuntimeError("Docker-Manager node agent response is invalid")
|
|
433
|
+
return payload
|
|
434
|
+
|
|
419
435
|
def supports(self, feature_name: str) -> bool:
|
|
420
436
|
features = self.detect_features()
|
|
421
437
|
if FEATURE_MESUDIP_DOCKER_ENTERPRISE in features and feature_name in {
|
|
@@ -721,7 +737,15 @@ class ManagerApiClient:
|
|
|
721
737
|
)
|
|
722
738
|
|
|
723
739
|
|
|
724
|
-
def discover_manager_client(
|
|
740
|
+
def discover_manager_client(
|
|
741
|
+
timeout_secs: int = 5, *, strict: bool = False
|
|
742
|
+
) -> Optional[ManagerApiClient]:
|
|
743
|
+
"""Build a client for the manager behind DOCKER_MANAGER_URL or the Docker context.
|
|
744
|
+
|
|
745
|
+
``strict`` raises the concrete reason instead of returning ``None``. Callers that
|
|
746
|
+
fall back to the plain Docker CLI want the quiet form; callers that are about to
|
|
747
|
+
report a failure to the user must not discard why discovery failed.
|
|
748
|
+
"""
|
|
725
749
|
try:
|
|
726
750
|
target = _manager_target_from_env()
|
|
727
751
|
if target:
|
|
@@ -729,11 +753,26 @@ def discover_manager_client(timeout_secs: int = 5) -> Optional[ManagerApiClient]
|
|
|
729
753
|
elif shutil.which("docker"):
|
|
730
754
|
_, context_target = current_docker_context_target()
|
|
731
755
|
if not context_target or not context_target.startswith(("tcp://", "http://", "https://")):
|
|
756
|
+
if strict:
|
|
757
|
+
raise RuntimeError(
|
|
758
|
+
"no Docker-Manager endpoint found: set DOCKER_MANAGER_URL, or select a "
|
|
759
|
+
f"docker context with a tcp:// endpoint (current target: {context_target or 'none'})"
|
|
760
|
+
)
|
|
732
761
|
return None
|
|
733
762
|
config = resolve_login_config(manager_target=context_target)
|
|
734
763
|
else:
|
|
764
|
+
if strict:
|
|
765
|
+
raise RuntimeError(
|
|
766
|
+
"no Docker-Manager endpoint found: DOCKER_MANAGER_URL is unset and the docker CLI is not installed"
|
|
767
|
+
)
|
|
735
768
|
return None
|
|
736
|
-
except
|
|
769
|
+
except RuntimeError:
|
|
770
|
+
if strict:
|
|
771
|
+
raise
|
|
772
|
+
return None
|
|
773
|
+
except Exception as exc:
|
|
774
|
+
if strict:
|
|
775
|
+
raise RuntimeError(f"failed resolving the Docker-Manager endpoint: {exc}") from exc
|
|
737
776
|
return None
|
|
738
777
|
|
|
739
778
|
return ManagerApiClient(
|
|
@@ -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=NlUsiSLLrP7drUP5FCj8bwdc32ZFe6d2wHP_tQMnFk0,96797
|
|
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
|
|
@@ -7,14 +7,14 @@ docker_stack/envsubst.py,sha256=gdEUpsjWJeNbbahySk56vUdQh1nns85o3LbU6SUBo3I,8337
|
|
|
7
7
|
docker_stack/envsubst_merge.py,sha256=TwOu8BducG0rWnSreJGwesNi-VghCetljcf-aWceE8M,4971
|
|
8
8
|
docker_stack/helpers.py,sha256=nJHaIS1vtFyPYclXVl3-Qd-1h55jnSy1LwdATDrF4hk,5953
|
|
9
9
|
docker_stack/login.py,sha256=jnrmrDh79Brlzj89YdZ1sGpg-ex16yb3ZN47eFpcW5g,41754
|
|
10
|
-
docker_stack/manager_api.py,sha256=
|
|
10
|
+
docker_stack/manager_api.py,sha256=ZTHcudzHuLq0dDN-qu51nJyX0hQy5PEPHvf807y3jyw,30194
|
|
11
11
|
docker_stack/markers.py,sha256=gqStnr0tNWZ1kzqvREc2jVQVQrevheShgJtL5QzSx4g,2388
|
|
12
12
|
docker_stack/merge_conf.py,sha256=Pmsabcgf3SDyaWvf2OLsBPodzEqsvOIjj9xzn_qWXH0,1917
|
|
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.5.dist-info/METADATA,sha256=4cqUzfi3pZrTNSL1oqrf9xW0RN4ijdVvSXZAbULFqwY,13821
|
|
17
|
+
docker_stack-2.2.5.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
|
18
|
+
docker_stack-2.2.5.dist-info/entry_points.txt,sha256=mpe2RwIguARsosXIUBQEN2pKU53URqZCh2S4ATwDFL4,55
|
|
19
|
+
docker_stack-2.2.5.dist-info/top_level.txt,sha256=zT6TPL54cLrt9LO_MNkhEpGGOmsoe2HV6Na5Ohy3_2c,13
|
|
20
|
+
docker_stack-2.2.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|