docker-stack 2.3.1__py3-none-any.whl → 2.3.3__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 +31 -33
- docker_stack/envsubst.py +69 -21
- docker_stack/login.py +2 -9
- docker_stack/manager_api.py +6 -20
- docker_stack/shell_auth.py +6 -10
- {docker_stack-2.3.1.dist-info → docker_stack-2.3.3.dist-info}/METADATA +1 -1
- {docker_stack-2.3.1.dist-info → docker_stack-2.3.3.dist-info}/RECORD +10 -10
- {docker_stack-2.3.1.dist-info → docker_stack-2.3.3.dist-info}/WHEEL +0 -0
- {docker_stack-2.3.1.dist-info → docker_stack-2.3.3.dist-info}/entry_points.txt +0 -0
- {docker_stack-2.3.1.dist-info → docker_stack-2.3.3.dist-info}/top_level.txt +0 -0
docker_stack/cli.py
CHANGED
|
@@ -48,7 +48,16 @@ from docker_stack.manager_api import (
|
|
|
48
48
|
format_deploy_error_body,
|
|
49
49
|
)
|
|
50
50
|
from docker_stack.registry import DockerRegistry
|
|
51
|
-
from .envsubst import
|
|
51
|
+
from .envsubst import (
|
|
52
|
+
ENV_DEFAULT_OPERATORS,
|
|
53
|
+
ENV_VAR_PATTERN,
|
|
54
|
+
LineCheckResult,
|
|
55
|
+
SubstitutionError,
|
|
56
|
+
envsubst,
|
|
57
|
+
envsubst_load_file,
|
|
58
|
+
ref_parts,
|
|
59
|
+
resolve_env_ref,
|
|
60
|
+
)
|
|
52
61
|
|
|
53
62
|
DOCKER_SHELL_ENDPOINT_ENV_VARS = (
|
|
54
63
|
"DOCKER_HOST",
|
|
@@ -115,8 +124,7 @@ def _require_manager() -> ManagerApiClient:
|
|
|
115
124
|
client = discover_manager_client(strict=True)
|
|
116
125
|
if client is None or not client.is_manager_backend():
|
|
117
126
|
raise RuntimeError(
|
|
118
|
-
f"{client.manager_url if client else 'the current Docker endpoint'} is not a "
|
|
119
|
-
"Docker-Manager; node-local commands need one"
|
|
127
|
+
f"{client.manager_url if client else 'the current Docker endpoint'} is not a " "Docker-Manager; node-local commands need one"
|
|
120
128
|
)
|
|
121
129
|
return client
|
|
122
130
|
|
|
@@ -130,10 +138,7 @@ def _select_node(value: str) -> str:
|
|
|
130
138
|
nodes = client.list_nodes().get("nodes", [])
|
|
131
139
|
exact = [node for node in nodes if str(node.get("id") or "") == requested]
|
|
132
140
|
candidates = exact or [
|
|
133
|
-
node
|
|
134
|
-
for node in nodes
|
|
135
|
-
if str(node.get("id") or "").startswith(requested)
|
|
136
|
-
or str(node.get("hostname") or "") == requested
|
|
141
|
+
node for node in nodes if str(node.get("id") or "").startswith(requested) or str(node.get("hostname") or "") == requested
|
|
137
142
|
]
|
|
138
143
|
if not candidates:
|
|
139
144
|
raise RuntimeError(f"node '{requested}' is not visible or does not exist")
|
|
@@ -424,14 +429,8 @@ def _managed_docker(arguments: List[str]) -> int:
|
|
|
424
429
|
or (value.startswith("-c") and value != "-c")
|
|
425
430
|
for value in global_options
|
|
426
431
|
)
|
|
427
|
-
is_ps = bool(command) and (
|
|
428
|
-
|
|
429
|
-
or (len(command) >= 2 and command[0] == "container" and command[1] in {"ls", "list"})
|
|
430
|
-
)
|
|
431
|
-
if explicit_target or not is_ps or any(
|
|
432
|
-
value in {"-q", "--quiet", "--format"} or value.startswith("--format=")
|
|
433
|
-
for value in command
|
|
434
|
-
):
|
|
432
|
+
is_ps = bool(command) and (command[0] == "ps" or (len(command) >= 2 and command[0] == "container" and command[1] in {"ls", "list"}))
|
|
433
|
+
if explicit_target or not is_ps or any(value in {"-q", "--quiet", "--format"} or value.startswith("--format=") for value in command):
|
|
435
434
|
return _exec_docker(values)
|
|
436
435
|
try:
|
|
437
436
|
client = discover_manager_client()
|
|
@@ -517,8 +516,7 @@ def _add_listing_namespace_arguments(parser: argparse.ArgumentParser) -> None:
|
|
|
517
516
|
def _prompt_for_manager_target(context_name: str) -> str:
|
|
518
517
|
if not sys.stdin.isatty():
|
|
519
518
|
raise RuntimeError(
|
|
520
|
-
f"Unknown shell context '{context_name}'. Run "
|
|
521
|
-
f"'docker-stack shell --context {context_name} <manager-url>' to create it."
|
|
519
|
+
f"Unknown shell context '{context_name}'. Run " f"'docker-stack shell --context {context_name} <manager-url>' to create it."
|
|
522
520
|
)
|
|
523
521
|
print(f"Docker context '{context_name}' does not exist yet.")
|
|
524
522
|
print("Enter the Docker-Manager URL to create it (for example https://manager.example.com:2376).")
|
|
@@ -571,7 +569,9 @@ class EnvFileResolutionError(Exception):
|
|
|
571
569
|
return "\n".join(formatted_lines)
|
|
572
570
|
|
|
573
571
|
|
|
574
|
-
|
|
572
|
+
# The compose interpolation grammar is defined once in envsubst.py and
|
|
573
|
+
# imported above; see the note there for why a second copy is a deploy bug.
|
|
574
|
+
_ref_name_operator_operand = ref_parts
|
|
575
575
|
|
|
576
576
|
|
|
577
577
|
def _strip_matching_quotes(value: str) -> Tuple[str, int]:
|
|
@@ -614,8 +614,8 @@ def _parse_env_file(env_file: str) -> Tuple[List[EnvFileEntry], List[str]]:
|
|
|
614
614
|
def _extract_refs(value: str) -> List[Tuple[str, int]]:
|
|
615
615
|
refs = []
|
|
616
616
|
for match in ENV_VAR_PATTERN.finditer(value):
|
|
617
|
-
var
|
|
618
|
-
start = match.start(1) if match.group(1) is not None else match.start(
|
|
617
|
+
var, _operator, _operand = _ref_name_operator_operand(match)
|
|
618
|
+
start = match.start(1) if match.group(1) is not None else match.start(4)
|
|
619
619
|
refs.append((var, start))
|
|
620
620
|
return refs
|
|
621
621
|
|
|
@@ -703,9 +703,9 @@ def load_env_file(env_file: str, base_env: Optional[Dict[str, str]] = None, max_
|
|
|
703
703
|
return _resolve_env_entries(entries, env_file, base_env=base_env, max_cycles=max_cycles)
|
|
704
704
|
|
|
705
705
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
_ENV_REF_PATTERN =
|
|
706
|
+
# One pattern, not a second copy: a reference form recognised here but not by
|
|
707
|
+
# the `.env` collector expands locally while its variable is never shipped.
|
|
708
|
+
_ENV_REF_PATTERN = ENV_VAR_PATTERN
|
|
709
709
|
_ESCAPED_DOLLAR = "$_ESCAPED_DOLLAR_"
|
|
710
710
|
|
|
711
711
|
|
|
@@ -721,13 +721,13 @@ def _substitute_selected_vars(text: str, names: Set[str], replacements: Dict[str
|
|
|
721
721
|
guarded = text.replace("$$", _ESCAPED_DOLLAR)
|
|
722
722
|
|
|
723
723
|
def replace(match: "re.Match[str]") -> str:
|
|
724
|
-
var
|
|
724
|
+
var, operator, operand = ref_parts(match)
|
|
725
725
|
if var not in names:
|
|
726
726
|
return match.group(0)
|
|
727
|
-
value = os.environ
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
if value
|
|
727
|
+
value = resolve_env_ref(var, operator, operand, os.environ)
|
|
728
|
+
# Unresolvable: left standing so the manager reports it against the
|
|
729
|
+
# stack, rather than silently deploying an empty value.
|
|
730
|
+
if value is None:
|
|
731
731
|
return match.group(0)
|
|
732
732
|
for old, new in replacements.items():
|
|
733
733
|
value = value.replace(old, new)
|
|
@@ -907,8 +907,8 @@ class DockerStack:
|
|
|
907
907
|
@staticmethod
|
|
908
908
|
def _iter_env_refs(value: str):
|
|
909
909
|
for match in ENV_VAR_PATTERN.finditer(value):
|
|
910
|
-
name
|
|
911
|
-
default =
|
|
910
|
+
name, operator, operand = _ref_name_operator_operand(match)
|
|
911
|
+
default = operand if operator in ENV_DEFAULT_OPERATORS else None
|
|
912
912
|
yield name, default
|
|
913
913
|
|
|
914
914
|
@staticmethod
|
|
@@ -1573,9 +1573,7 @@ class DockerStack:
|
|
|
1573
1573
|
|
|
1574
1574
|
manager_deploy = self._manager_client_for_feature(FEATURE_STACK_DEPLOY)
|
|
1575
1575
|
if manager_deploy and tag:
|
|
1576
|
-
raise RuntimeError(
|
|
1577
|
-
"Docker-Manager stack deploy does not expose deployment tags; refusing to silently ignore --tag"
|
|
1578
|
-
)
|
|
1576
|
+
raise RuntimeError("Docker-Manager stack deploy does not expose deployment tags; refusing to silently ignore --tag")
|
|
1579
1577
|
manager_options = {}
|
|
1580
1578
|
if with_registry_auth:
|
|
1581
1579
|
manager_options["with_registry_auth"] = True
|
docker_stack/envsubst.py
CHANGED
|
@@ -8,7 +8,9 @@ DESCRIPTION
|
|
|
8
8
|
|
|
9
9
|
supported syntax:
|
|
10
10
|
normal - ${VARIABLE1} or $VARIABLE1
|
|
11
|
-
with default - ${VARIABLE1:-somevalue}
|
|
11
|
+
with default - ${VARIABLE1:-somevalue} or ${VARIABLE1-somevalue}
|
|
12
|
+
required - ${VARIABLE1:?message} or ${VARIABLE1?message}
|
|
13
|
+
alternate - ${VARIABLE1:+somevalue} or ${VARIABLE1+somevalue}
|
|
12
14
|
"""
|
|
13
15
|
|
|
14
16
|
from dataclasses import dataclass
|
|
@@ -18,6 +20,62 @@ import sys
|
|
|
18
20
|
from typing import Dict, List, Literal, Optional
|
|
19
21
|
|
|
20
22
|
|
|
23
|
+
# The compose interpolation grammar, defined once.
|
|
24
|
+
#
|
|
25
|
+
# Docker-Manager resolves these same forms server-side, and the CLI leaves
|
|
26
|
+
# references standing for it to resolve, so three places have to agree on what
|
|
27
|
+
# a reference is: this substituter, the stack `.env` collector, and local
|
|
28
|
+
# expansion of secret-sourced variables. A form one of them fails to recognise
|
|
29
|
+
# is collected by nobody and the deploy dies with `missing template variable`
|
|
30
|
+
# naming a variable that was set the whole time.
|
|
31
|
+
#
|
|
32
|
+
# The name is restricted to `[A-Za-z0-9_]` on purpose. A looser class swallows
|
|
33
|
+
# the operator: `${VAR-default}` under `[^}:\s]+` parses as a variable
|
|
34
|
+
# literally named `VAR-default`, which is never set.
|
|
35
|
+
#
|
|
36
|
+
# Groups: 1 name, 2 operator, 3 operand, 4 bare `$NAME`. Colon forms lead the
|
|
37
|
+
# alternation so `:-` is never read as a bare `-`.
|
|
38
|
+
ENV_REF_RE = r"\$\{([A-Za-z0-9_]+)(?:(:-|:\?|:\+|-|\?|\+)(.*?))?\}|\$([a-zA-Z_][a-zA-Z0-9_]*)"
|
|
39
|
+
|
|
40
|
+
ENV_VAR_PATTERN = re.compile(ENV_REF_RE)
|
|
41
|
+
|
|
42
|
+
# Operators whose operand stands in for the variable's own value. `:?`/`?` take
|
|
43
|
+
# an error message and `:+`/`+` an alternate used only when the variable IS set,
|
|
44
|
+
# so treating either as a default ships a value the author never wrote.
|
|
45
|
+
ENV_DEFAULT_OPERATORS = frozenset({":-", "-"})
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def ref_parts(match: "re.Match[str]"):
|
|
49
|
+
"""Splits an ENV_VAR_PATTERN match into (name, operator, operand)."""
|
|
50
|
+
if match.group(1) is not None:
|
|
51
|
+
return match.group(1), match.group(2), match.group(3)
|
|
52
|
+
return match.group(4), None, None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def resolve_env_ref(name: str, operator: Optional[str], operand: Optional[str], env) -> Optional[str]:
|
|
56
|
+
"""Resolves one reference, mirroring the manager's `substitute_template`.
|
|
57
|
+
|
|
58
|
+
Returns None when the reference cannot be resolved, which the caller
|
|
59
|
+
reports rather than substituting an empty value.
|
|
60
|
+
"""
|
|
61
|
+
existing = env.get(name, None)
|
|
62
|
+
is_set = existing is not None
|
|
63
|
+
is_nonempty = bool(existing)
|
|
64
|
+
if operator == ":-":
|
|
65
|
+
return existing if is_nonempty else operand
|
|
66
|
+
if operator == "-":
|
|
67
|
+
return existing if is_set else operand
|
|
68
|
+
if operator == ":+":
|
|
69
|
+
return operand if is_nonempty else ""
|
|
70
|
+
if operator == "+":
|
|
71
|
+
return operand if is_set else ""
|
|
72
|
+
if operator == ":?":
|
|
73
|
+
return existing if is_nonempty else None
|
|
74
|
+
if operator == "?":
|
|
75
|
+
return existing if is_set else None
|
|
76
|
+
return existing if is_nonempty else None
|
|
77
|
+
|
|
78
|
+
|
|
21
79
|
@dataclass
|
|
22
80
|
class LineCheckResult:
|
|
23
81
|
line_no: int
|
|
@@ -102,8 +160,10 @@ class SubstitutionError(Exception):
|
|
|
102
160
|
def envsubst(template_str, env=os.environ, replacements: Dict[str, str] = None, on_error: Literal["exit", "throw"] = "exit"):
|
|
103
161
|
"""Substitute environment variables in the template string, supporting default values."""
|
|
104
162
|
|
|
105
|
-
#
|
|
106
|
-
|
|
163
|
+
# The shared grammar, with the escaped-dollar placeholder in front. That
|
|
164
|
+
# alternative captures nothing, so the group numbers stay as ENV_REF_RE
|
|
165
|
+
# defines them.
|
|
166
|
+
pattern = re.compile(r"\$\_ESCAPED_DOLLAR_|" + ENV_REF_RE)
|
|
107
167
|
|
|
108
168
|
# Handle escaped dollars
|
|
109
169
|
template_str = template_str.replace("$$", "$_ESCAPED_DOLLAR_")
|
|
@@ -120,24 +180,12 @@ def envsubst(template_str, env=os.environ, replacements: Dict[str, str] = None,
|
|
|
120
180
|
def replacer(match: re.Match[str]):
|
|
121
181
|
if match.group(0) == "$_ESCAPED_DOLLAR_":
|
|
122
182
|
return "$$"
|
|
123
|
-
|
|
124
|
-
if match.group(1) is not None
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if result in (None, "") and has_default:
|
|
130
|
-
result = default_value
|
|
131
|
-
if result in (None, "") and not has_default:
|
|
132
|
-
line_errors_raw.append((var, match.start(1))) # Use match.start(1) for ${VAR}
|
|
133
|
-
return match.group(0) # Keep original if variable not found
|
|
134
|
-
# Group 3 for $VAR
|
|
135
|
-
else:
|
|
136
|
-
var = match.group(3)
|
|
137
|
-
result = env.get(var, None)
|
|
138
|
-
if result in (None, ""):
|
|
139
|
-
line_errors_raw.append((var, match.start(3))) # Use match.start(3) for $VAR
|
|
140
|
-
return match.group(0) # Keep original if variable not found
|
|
183
|
+
var, operator, operand = ref_parts(match)
|
|
184
|
+
start = match.start(1) if match.group(1) is not None else match.start(4)
|
|
185
|
+
result = resolve_env_ref(var, operator, operand, env)
|
|
186
|
+
if result is None:
|
|
187
|
+
line_errors_raw.append((var, start))
|
|
188
|
+
return match.group(0) # Keep original if variable not found
|
|
141
189
|
|
|
142
190
|
if replacements:
|
|
143
191
|
for old, new in replacements.items():
|
docker_stack/login.py
CHANGED
|
@@ -466,12 +466,7 @@ def resolve_login_config(
|
|
|
466
466
|
inferred_context_name = manager_target
|
|
467
467
|
if isolated_context_target:
|
|
468
468
|
resolved_docker_config_dir = isolated_config_dir
|
|
469
|
-
endpoint_context_name = (
|
|
470
|
-
context_name
|
|
471
|
-
or os.getenv("DOCKER_MANAGER_CONTEXT_NAME")
|
|
472
|
-
or inferred_context_name
|
|
473
|
-
or current_context_name
|
|
474
|
-
)
|
|
469
|
+
endpoint_context_name = context_name or os.getenv("DOCKER_MANAGER_CONTEXT_NAME") or inferred_context_name or current_context_name
|
|
475
470
|
if manager_target:
|
|
476
471
|
resolved_manager_url, skip_tls_verify = _resolve_manager_endpoint(
|
|
477
472
|
raw_manager_value,
|
|
@@ -1038,9 +1033,7 @@ def browser_login(
|
|
|
1038
1033
|
refresh_token = str(refresh_token) if isinstance(refresh_token, str) and refresh_token.strip() else None
|
|
1039
1034
|
refresh_expires_in = token_response.get("refresh_expires_in")
|
|
1040
1035
|
refresh_expires_at = (
|
|
1041
|
-
int(time.time()) + int(refresh_expires_in)
|
|
1042
|
-
if isinstance(refresh_expires_in, int) and refresh_expires_in > 0
|
|
1043
|
-
else None
|
|
1036
|
+
int(time.time()) + int(refresh_expires_in) if isinstance(refresh_expires_in, int) and refresh_expires_in > 0 else None
|
|
1044
1037
|
)
|
|
1045
1038
|
return DockerManagerLoginResult(
|
|
1046
1039
|
access_token=access_token,
|
docker_stack/manager_api.py
CHANGED
|
@@ -189,11 +189,7 @@ def _public_deploy_options(options: Optional[Dict[str, Any]], compose: str) -> O
|
|
|
189
189
|
def _public_image_deploy_options(options: Optional[Dict[str, Any]], images: Dict[str, str]) -> Optional[Dict[str, Any]]:
|
|
190
190
|
prepared = dict(options or {})
|
|
191
191
|
if prepared.get("with_registry_auth"):
|
|
192
|
-
registries = {
|
|
193
|
-
_image_registry(image.strip())
|
|
194
|
-
for image in images.values()
|
|
195
|
-
if isinstance(image, str) and image.strip()
|
|
196
|
-
}
|
|
192
|
+
registries = {_image_registry(image.strip()) for image in images.values() if isinstance(image, str) and image.strip()}
|
|
197
193
|
registry_auth = _docker_config_registry_auths(registries)
|
|
198
194
|
if registry_auth:
|
|
199
195
|
prepared["registry_auth"] = registry_auth
|
|
@@ -711,9 +707,7 @@ class ManagerApiClient:
|
|
|
711
707
|
raise
|
|
712
708
|
now = time.monotonic()
|
|
713
709
|
if on_wait and (last_notice is None or now - last_notice >= DEPLOY_WAIT_NOTICE_SECS):
|
|
714
|
-
on_wait(
|
|
715
|
-
{**exc.active_deployment, "phase": "waiting", "waited_secs": waited, "wait_budget_secs": budget}
|
|
716
|
-
)
|
|
710
|
+
on_wait({**exc.active_deployment, "phase": "waiting", "waited_secs": waited, "wait_budget_secs": budget})
|
|
717
711
|
last_notice = now
|
|
718
712
|
if wait_for_poll is None:
|
|
719
713
|
time.sleep(DEPLOY_WAIT_POLL_SECS)
|
|
@@ -759,9 +753,7 @@ class ManagerApiClient:
|
|
|
759
753
|
return True
|
|
760
754
|
|
|
761
755
|
def list_stacks(self, *, namespace: Optional[str] = DEFAULT_NAMESPACE) -> Dict[str, Any]:
|
|
762
|
-
query = urllib.parse.urlencode(
|
|
763
|
-
{"namespace": namespace} if namespace is not None else {"all_namespaces": "true"}
|
|
764
|
-
)
|
|
756
|
+
query = urllib.parse.urlencode({"namespace": namespace} if namespace is not None else {"all_namespaces": "true"})
|
|
765
757
|
if self._detect_manager_backend():
|
|
766
758
|
return self._request_json(f"/api/docker-stack/stacks?{query}")
|
|
767
759
|
return self._request_json(f"{self._endpoint_path('/inventory/stacks')}?{query}")
|
|
@@ -858,9 +850,7 @@ class ManagerApiClient:
|
|
|
858
850
|
params["limit"] = str(limit)
|
|
859
851
|
if latest:
|
|
860
852
|
params["latest"] = "1"
|
|
861
|
-
payload = self._request_json(
|
|
862
|
-
f"/api/docker-stack/containers?{urllib.parse.urlencode(params)}"
|
|
863
|
-
)
|
|
853
|
+
payload = self._request_json(f"/api/docker-stack/containers?{urllib.parse.urlencode(params)}")
|
|
864
854
|
if not isinstance(payload, dict) or not isinstance(payload.get("containers"), list):
|
|
865
855
|
raise RuntimeError("Docker-Manager containers response is invalid")
|
|
866
856
|
return payload
|
|
@@ -1080,9 +1070,7 @@ class ManagerApiClient:
|
|
|
1080
1070
|
)
|
|
1081
1071
|
|
|
1082
1072
|
|
|
1083
|
-
def discover_manager_client(
|
|
1084
|
-
timeout_secs: int = 5, *, strict: bool = False
|
|
1085
|
-
) -> Optional[ManagerApiClient]:
|
|
1073
|
+
def discover_manager_client(timeout_secs: int = 5, *, strict: bool = False) -> Optional[ManagerApiClient]:
|
|
1086
1074
|
"""Build a client for the manager behind DOCKER_MANAGER_URL or the Docker context.
|
|
1087
1075
|
|
|
1088
1076
|
``strict`` raises the concrete reason instead of returning ``None``. Callers that
|
|
@@ -1105,9 +1093,7 @@ def discover_manager_client(
|
|
|
1105
1093
|
config = resolve_login_config(manager_target=context_target)
|
|
1106
1094
|
else:
|
|
1107
1095
|
if strict:
|
|
1108
|
-
raise RuntimeError(
|
|
1109
|
-
"no Docker-Manager endpoint found: DOCKER_MANAGER_URL is unset and the docker CLI is not installed"
|
|
1110
|
-
)
|
|
1096
|
+
raise RuntimeError("no Docker-Manager endpoint found: DOCKER_MANAGER_URL is unset and the docker CLI is not installed")
|
|
1111
1097
|
return None
|
|
1112
1098
|
except RuntimeError:
|
|
1113
1099
|
if strict:
|
docker_stack/shell_auth.py
CHANGED
|
@@ -542,13 +542,11 @@ def _write_shell_wrappers(
|
|
|
542
542
|
zsh_dir = session_dir / "zsh"
|
|
543
543
|
zsh_dir.mkdir(mode=0o700)
|
|
544
544
|
original_bashrc = Path.home() / ".bashrc"
|
|
545
|
-
original_zshrc_root = Path(
|
|
546
|
-
os.getenv(SHELL_ORIGINAL_ZDOTDIR_ENV) or os.getenv("ZDOTDIR", str(Path.home()))
|
|
547
|
-
)
|
|
545
|
+
original_zshrc_root = Path(os.getenv(SHELL_ORIGINAL_ZDOTDIR_ENV) or os.getenv("ZDOTDIR", str(Path.home())))
|
|
548
546
|
original_zshrc = original_zshrc_root / ".zshrc"
|
|
549
547
|
original_zsh_history = original_zshrc_root / ".zsh_history"
|
|
550
548
|
bash_rc.write_text(
|
|
551
|
-
f
|
|
549
|
+
f"""[ -f {json.dumps(str(original_bashrc))} ] && source {json.dumps(str(original_bashrc))}
|
|
552
550
|
__docker_stack_base_ps1="$PS1"
|
|
553
551
|
__docker_stack_prompt_update() {{
|
|
554
552
|
local state expires now color reset node
|
|
@@ -570,13 +568,13 @@ __docker_stack_prompt_update() {{
|
|
|
570
568
|
}}
|
|
571
569
|
case ";${{PROMPT_COMMAND:-}};" in *';__docker_stack_prompt_update;'*) ;; *) PROMPT_COMMAND="__docker_stack_prompt_update${{PROMPT_COMMAND:+;$PROMPT_COMMAND}}";; esac
|
|
572
570
|
docker() {{ docker-stack shell-auth ensure || return $?; docker-stack docker -- "$@"; }}
|
|
573
|
-
|
|
571
|
+
""",
|
|
574
572
|
encoding="utf-8",
|
|
575
573
|
)
|
|
576
574
|
os.chmod(bash_rc, 0o600)
|
|
577
575
|
zsh_rc = zsh_dir / ".zshrc"
|
|
578
576
|
zsh_rc.write_text(
|
|
579
|
-
f
|
|
577
|
+
f"""typeset -g __docker_stack_session_zdotdir="$ZDOTDIR"
|
|
580
578
|
typeset -g ZDOTDIR={json.dumps(str(original_zshrc_root))}
|
|
581
579
|
typeset -g HISTFILE={json.dumps(str(original_zsh_history))}
|
|
582
580
|
typeset -g SHELL_SESSION_HISTORY=0
|
|
@@ -606,7 +604,7 @@ __docker_stack_prompt_update() {{
|
|
|
606
604
|
}}
|
|
607
605
|
add-zsh-hook precmd __docker_stack_prompt_update
|
|
608
606
|
docker() {{ docker-stack shell-auth ensure || return $?; docker-stack docker -- "$@"; }}
|
|
609
|
-
|
|
607
|
+
""",
|
|
610
608
|
encoding="utf-8",
|
|
611
609
|
)
|
|
612
610
|
os.chmod(zsh_rc, 0o600)
|
|
@@ -738,9 +736,7 @@ def run_managed_shell(config: DockerManagerLoginConfig, result: DockerManagerLog
|
|
|
738
736
|
# need to re-probe it (a verified probe against a private CA only
|
|
739
737
|
# produces aborted TLS connections on the manager).
|
|
740
738
|
env[DOCKER_MANAGER_SKIP_TLS_VERIFY_ENV] = "1" if config.skip_tls_verify else "0"
|
|
741
|
-
env[SHELL_ORIGINAL_ZDOTDIR_ENV] = str(
|
|
742
|
-
Path(os.getenv(SHELL_ORIGINAL_ZDOTDIR_ENV) or os.getenv("ZDOTDIR", str(Path.home())))
|
|
743
|
-
)
|
|
739
|
+
env[SHELL_ORIGINAL_ZDOTDIR_ENV] = str(Path(os.getenv(SHELL_ORIGINAL_ZDOTDIR_ENV) or os.getenv("ZDOTDIR", str(Path.home()))))
|
|
744
740
|
command = [shell, "--rcfile", str(session_dir / "bashrc"), "-i"] if shell_name == "bash" else [shell, "-i"]
|
|
745
741
|
return subprocess.run(command, check=False, env=env).returncode
|
|
746
742
|
finally:
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
docker_stack/__init__.py,sha256=qgHdW8ZDBlyk6FzIf8Mld_cJD1SjOfqo2dELg4pK668,880
|
|
2
|
-
docker_stack/cli.py,sha256=
|
|
2
|
+
docker_stack/cli.py,sha256=MHSYBPiVdgj8DYiVUKZ-1f2Hrtj89L-0QKbjOyin6zA,108197
|
|
3
3
|
docker_stack/command_runner.py,sha256=mNaUAVKtrJbji_5ARVPLKhc92avqllNKrVQj1A6S0dA,1252
|
|
4
4
|
docker_stack/compose.py,sha256=_fAVesjyW5ecid46sYYhcu02yF1vqknDSCFYflJPDOE,534
|
|
5
5
|
docker_stack/conflict_prompt.py,sha256=1pmsyJZyZbzKYAtLJ8kYNVSC0LJb6fsu8Qz2zWvyqBE,4911
|
|
6
6
|
docker_stack/docker_objects.py,sha256=U6szytv5gKrmBYKqThSeMMXdiJ2u4STIQLkeqQ-Fn4Q,12103
|
|
7
|
-
docker_stack/envsubst.py,sha256=
|
|
7
|
+
docker_stack/envsubst.py,sha256=G-rRt6fdyO8iqovCm4gx-MSJHWXjQPHgrI3Zszr_fdc,10331
|
|
8
8
|
docker_stack/envsubst_merge.py,sha256=TwOu8BducG0rWnSreJGwesNi-VghCetljcf-aWceE8M,4971
|
|
9
9
|
docker_stack/helpers.py,sha256=ppN3VBf-OYGoXhIpJrNzyAFrAJVJR5N-oNQvpeJdg-g,6136
|
|
10
|
-
docker_stack/login.py,sha256=
|
|
11
|
-
docker_stack/manager_api.py,sha256=
|
|
10
|
+
docker_stack/login.py,sha256=p3YGUTJfOjKTGbZckqGrm5jtkXfxLacRYwL64vSCR14,41690
|
|
11
|
+
docker_stack/manager_api.py,sha256=T11gLRuexi_6M8v1fmGVJqi54H_6CBMaC3JpTEG48fw,44223
|
|
12
12
|
docker_stack/markers.py,sha256=gqStnr0tNWZ1kzqvREc2jVQVQrevheShgJtL5QzSx4g,2388
|
|
13
13
|
docker_stack/merge_conf.py,sha256=Pmsabcgf3SDyaWvf2OLsBPodzEqsvOIjj9xzn_qWXH0,1917
|
|
14
14
|
docker_stack/registry.py,sha256=sWC1J9JDIrcYyhei1POYCZHJ0xUCKC2pveisSHMgYsQ,9036
|
|
15
|
-
docker_stack/shell_auth.py,sha256=
|
|
15
|
+
docker_stack/shell_auth.py,sha256=RL60pPt9r-l9OxziWV3pnu7elpA7Lpg0ulWig4u6toI,33388
|
|
16
16
|
docker_stack/url_parser.py,sha256=Sk8GQE0nEiwCkijp1OltP2AfgtKEmM2hybcH_rBhfiI,6824
|
|
17
|
-
docker_stack-2.3.
|
|
18
|
-
docker_stack-2.3.
|
|
19
|
-
docker_stack-2.3.
|
|
20
|
-
docker_stack-2.3.
|
|
21
|
-
docker_stack-2.3.
|
|
17
|
+
docker_stack-2.3.3.dist-info/METADATA,sha256=TaYwdPNMfZhzxB4flx4s2rbeKDMhlJxxsKV04w_0SBw,13121
|
|
18
|
+
docker_stack-2.3.3.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
|
19
|
+
docker_stack-2.3.3.dist-info/entry_points.txt,sha256=mpe2RwIguARsosXIUBQEN2pKU53URqZCh2S4ATwDFL4,55
|
|
20
|
+
docker_stack-2.3.3.dist-info/top_level.txt,sha256=zT6TPL54cLrt9LO_MNkhEpGGOmsoe2HV6Na5Ohy3_2c,13
|
|
21
|
+
docker_stack-2.3.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|