gpustack-runtime 0.1.39.post1__py3-none-any.whl → 0.1.39.post3__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.
- gpustack_runtime/__main__.py +6 -0
- gpustack_runtime/_version.py +2 -2
- gpustack_runtime/_version_appendix.py +1 -1
- gpustack_runtime/cmds/__init__.py +6 -0
- gpustack_runtime/cmds/deployer.py +170 -40
- gpustack_runtime/deployer/__init__.py +197 -0
- gpustack_runtime/deployer/__types__.py +382 -17
- gpustack_runtime/deployer/__utils__.py +34 -0
- gpustack_runtime/deployer/docker.py +280 -66
- gpustack_runtime/deployer/kuberentes.py +288 -45
- gpustack_runtime/deployer/podman.py +290 -66
- gpustack_runtime/detector/__utils__.py +23 -0
- gpustack_runtime/detector/amd.py +18 -10
- gpustack_runtime/detector/hygon.py +7 -2
- gpustack_runtime/detector/iluvatar.py +10 -2
- gpustack_runtime/detector/mthreads.py +8 -12
- gpustack_runtime/detector/nvidia.py +194 -86
- gpustack_runtime/detector/pyhsa/__init__.py +7 -7
- gpustack_runtime/detector/pyrocmsmi/__init__.py +3 -9
- gpustack_runtime/envs.py +30 -18
- {gpustack_runtime-0.1.39.post1.dist-info → gpustack_runtime-0.1.39.post3.dist-info}/METADATA +3 -2
- {gpustack_runtime-0.1.39.post1.dist-info → gpustack_runtime-0.1.39.post3.dist-info}/RECORD +25 -26
- gpustack_runtime/detector/pymtml/__init__.py +0 -770
- {gpustack_runtime-0.1.39.post1.dist-info → gpustack_runtime-0.1.39.post3.dist-info}/WHEEL +0 -0
- {gpustack_runtime-0.1.39.post1.dist-info → gpustack_runtime-0.1.39.post3.dist-info}/entry_points.txt +0 -0
- {gpustack_runtime-0.1.39.post1.dist-info → gpustack_runtime-0.1.39.post3.dist-info}/licenses/LICENSE +0 -0
|
@@ -26,7 +26,7 @@ from docker.utils import parse_repository_tag
|
|
|
26
26
|
from tqdm import tqdm
|
|
27
27
|
|
|
28
28
|
from .. import envs
|
|
29
|
-
from ..logging import debug_log_exception
|
|
29
|
+
from ..logging import debug_log_exception, debug_log_warning
|
|
30
30
|
from .__types__ import (
|
|
31
31
|
Container,
|
|
32
32
|
ContainerCheck,
|
|
@@ -34,7 +34,7 @@ from .__types__ import (
|
|
|
34
34
|
ContainerMountModeEnum,
|
|
35
35
|
ContainerProfileEnum,
|
|
36
36
|
ContainerRestartPolicyEnum,
|
|
37
|
-
|
|
37
|
+
EndoscopicDeployer,
|
|
38
38
|
OperationError,
|
|
39
39
|
UnsupportedError,
|
|
40
40
|
WorkloadExecStream,
|
|
@@ -46,7 +46,13 @@ from .__types__ import (
|
|
|
46
46
|
WorkloadStatusOperation,
|
|
47
47
|
WorkloadStatusStateEnum,
|
|
48
48
|
)
|
|
49
|
-
from .__utils__ import
|
|
49
|
+
from .__utils__ import (
|
|
50
|
+
_MiB,
|
|
51
|
+
bytes_to_human_readable,
|
|
52
|
+
replace_image_with,
|
|
53
|
+
safe_json,
|
|
54
|
+
sensitive_env_var,
|
|
55
|
+
)
|
|
50
56
|
|
|
51
57
|
if TYPE_CHECKING:
|
|
52
58
|
from collections.abc import Callable, Generator
|
|
@@ -141,7 +147,7 @@ class DockerWorkloadPlan(WorkloadPlan):
|
|
|
141
147
|
super().validate_and_default()
|
|
142
148
|
|
|
143
149
|
# Adjust default image namespace if needed.
|
|
144
|
-
if namespace := envs.
|
|
150
|
+
if namespace := envs.GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_NAMESPACE:
|
|
145
151
|
self.pause_image = replace_image_with(
|
|
146
152
|
image=self.pause_image,
|
|
147
153
|
namespace=namespace,
|
|
@@ -296,7 +302,7 @@ Name of the Docker deployer.
|
|
|
296
302
|
"""
|
|
297
303
|
|
|
298
304
|
|
|
299
|
-
class DockerDeployer(
|
|
305
|
+
class DockerDeployer(EndoscopicDeployer):
|
|
300
306
|
"""
|
|
301
307
|
Deployer implementation for Docker containers.
|
|
302
308
|
"""
|
|
@@ -426,12 +432,12 @@ class DockerDeployer(Deployer):
|
|
|
426
432
|
tag = tag or "latest"
|
|
427
433
|
auth_config = None
|
|
428
434
|
if (
|
|
429
|
-
envs.
|
|
430
|
-
and envs.
|
|
435
|
+
envs.GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_REGISTRY_USERNAME
|
|
436
|
+
and envs.GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_REGISTRY_PASSWORD
|
|
431
437
|
):
|
|
432
438
|
auth_config = {
|
|
433
|
-
"username": envs.
|
|
434
|
-
"password": envs.
|
|
439
|
+
"username": envs.GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_REGISTRY_USERNAME,
|
|
440
|
+
"password": envs.GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_REGISTRY_PASSWORD,
|
|
435
441
|
}
|
|
436
442
|
|
|
437
443
|
logs = self._client.api.pull(
|
|
@@ -1174,39 +1180,29 @@ class DockerDeployer(Deployer):
|
|
|
1174
1180
|
super().__init__(_NAME)
|
|
1175
1181
|
self._client = self._get_client()
|
|
1176
1182
|
|
|
1177
|
-
def
|
|
1183
|
+
def _prepare_mirrored_deployment(self):
|
|
1178
1184
|
"""
|
|
1179
|
-
Prepare for
|
|
1185
|
+
Prepare for mirrored deployment.
|
|
1180
1186
|
|
|
1181
1187
|
"""
|
|
1182
1188
|
# Prepare mirrored deployment if enabled.
|
|
1183
1189
|
if self._mutate_create_options:
|
|
1184
1190
|
return
|
|
1185
1191
|
self._mutate_create_options = lambda o: o
|
|
1186
|
-
if not envs.GPUSTACK_RUNTIME_DEPLOY_MIRRORED_DEPLOYMENT:
|
|
1187
|
-
logger.debug("Mirrored deployment disabled")
|
|
1188
|
-
return
|
|
1189
1192
|
|
|
1190
1193
|
# Retrieve self-container info.
|
|
1191
|
-
## - Get Container name, default to hostname if not set.
|
|
1192
|
-
self_container_id = envs.GPUSTACK_RUNTIME_DEPLOY_MIRRORED_NAME
|
|
1193
|
-
if not self_container_id:
|
|
1194
|
-
self_container_id = socket.gethostname()
|
|
1195
|
-
logger.warning(
|
|
1196
|
-
"Mirrored deployment enabled, but no Container name set, using hostname(%s) instead",
|
|
1197
|
-
self_container_id,
|
|
1198
|
-
)
|
|
1199
1194
|
try:
|
|
1200
|
-
self_container = self._find_self_container(
|
|
1195
|
+
self_container = self._find_self_container()
|
|
1196
|
+
if not self_container:
|
|
1197
|
+
return
|
|
1201
1198
|
logger.info(
|
|
1202
1199
|
"Mirrored deployment enabled, using self Container %s for options mirroring",
|
|
1203
|
-
self_container.
|
|
1200
|
+
self_container.short_id,
|
|
1204
1201
|
)
|
|
1205
1202
|
self_image = self_container.image
|
|
1206
1203
|
except docker.errors.APIError:
|
|
1207
1204
|
logger.exception(
|
|
1208
|
-
"Mirrored deployment enabled, but failed to get self Container
|
|
1209
|
-
self_container_id,
|
|
1205
|
+
"Mirrored deployment enabled, but failed to get self Container, skipping",
|
|
1210
1206
|
)
|
|
1211
1207
|
return
|
|
1212
1208
|
|
|
@@ -1217,8 +1213,12 @@ class DockerDeployer(Deployer):
|
|
|
1217
1213
|
self_container_envs: dict[str, str] = dict(
|
|
1218
1214
|
item.split("=", 1) for item in self_container.attrs["Config"].get("Env", [])
|
|
1219
1215
|
)
|
|
1220
|
-
self_image_envs: dict[str, str] =
|
|
1221
|
-
|
|
1216
|
+
self_image_envs: dict[str, str] = (
|
|
1217
|
+
dict(
|
|
1218
|
+
item.split("=", 1) for item in self_image.attrs["Config"].get("Env", [])
|
|
1219
|
+
)
|
|
1220
|
+
if self_image.attrs["Config"]
|
|
1221
|
+
else {}
|
|
1222
1222
|
)
|
|
1223
1223
|
mirrored_envs: dict[str, str] = {
|
|
1224
1224
|
# Filter out gpustack-internal envs and same-as-image envs.
|
|
@@ -1406,17 +1406,10 @@ class DockerDeployer(Deployer):
|
|
|
1406
1406
|
|
|
1407
1407
|
self._mutate_create_options = mutate_create_options
|
|
1408
1408
|
|
|
1409
|
-
def _find_self_container(
|
|
1410
|
-
self,
|
|
1411
|
-
self_container_id: str,
|
|
1412
|
-
) -> docker.models.containers.Container:
|
|
1409
|
+
def _find_self_container(self) -> docker.models.containers.Container | None:
|
|
1413
1410
|
"""
|
|
1414
1411
|
Find the current container if running inside a Docker container.
|
|
1415
1412
|
|
|
1416
|
-
Args:
|
|
1417
|
-
self_container_id:
|
|
1418
|
-
The container name or ID to find.
|
|
1419
|
-
|
|
1420
1413
|
Returns:
|
|
1421
1414
|
The Docker container if found, None otherwise.
|
|
1422
1415
|
|
|
@@ -1424,38 +1417,54 @@ class DockerDeployer(Deployer):
|
|
|
1424
1417
|
If failed to find itself.
|
|
1425
1418
|
|
|
1426
1419
|
"""
|
|
1427
|
-
if envs.
|
|
1428
|
-
|
|
1429
|
-
return
|
|
1430
|
-
|
|
1431
|
-
# Find containers that matches the hostname.
|
|
1432
|
-
containers: list[docker.models.containers.Container] = []
|
|
1433
|
-
for c in self._client.containers.list():
|
|
1434
|
-
# Ignore workload containers with host network enabled.
|
|
1435
|
-
if _LABEL_WORKLOAD in c.labels:
|
|
1436
|
-
continue
|
|
1437
|
-
# Ignore containers that do not match the hostname.
|
|
1438
|
-
if c.attrs["Config"].get("Hostname", "") != self_container_id:
|
|
1439
|
-
continue
|
|
1440
|
-
# Ignore containers that do not match the filter labels.
|
|
1441
|
-
if envs.GPUSTACK_RUNTIME_DOCKER_MIRRORED_NAME_FILTER_LABELS and any(
|
|
1442
|
-
c.labels.get(k) != v
|
|
1443
|
-
for k, v in envs.GPUSTACK_RUNTIME_DOCKER_MIRRORED_NAME_FILTER_LABELS.items()
|
|
1444
|
-
):
|
|
1445
|
-
continue
|
|
1446
|
-
containers.append(c)
|
|
1420
|
+
if not envs.GPUSTACK_RUNTIME_DEPLOY_MIRRORED_DEPLOYMENT:
|
|
1421
|
+
logger.debug("Mirrored deployment disabled")
|
|
1422
|
+
return None
|
|
1447
1423
|
|
|
1448
|
-
#
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
"
|
|
1424
|
+
# Get container ID or hostname.
|
|
1425
|
+
self_container_id = envs.GPUSTACK_RUNTIME_DEPLOY_MIRRORED_NAME
|
|
1426
|
+
if not self_container_id:
|
|
1427
|
+
self_container_id = socket.gethostname()
|
|
1428
|
+
debug_log_warning(
|
|
1429
|
+
logger,
|
|
1430
|
+
"Mirrored deployment enabled, but no Container name set, using hostname(%s) instead",
|
|
1431
|
+
self_container_id,
|
|
1455
1432
|
)
|
|
1456
|
-
raise docker.errors.NotFound(msg)
|
|
1457
1433
|
|
|
1458
|
-
|
|
1434
|
+
if envs.GPUSTACK_RUNTIME_DEPLOY_MIRRORED_NAME:
|
|
1435
|
+
# Directly get container.
|
|
1436
|
+
self_container = self._client.containers.get(self_container_id)
|
|
1437
|
+
else:
|
|
1438
|
+
# Find containers that matches the hostname.
|
|
1439
|
+
containers: list[docker.models.containers.Container] = []
|
|
1440
|
+
for c in self._client.containers.list():
|
|
1441
|
+
# Ignore workload containers with host network enabled.
|
|
1442
|
+
if _LABEL_WORKLOAD in c.labels:
|
|
1443
|
+
continue
|
|
1444
|
+
# Ignore containers that do not match the hostname.
|
|
1445
|
+
if c.attrs["Config"].get("Hostname", "") != self_container_id:
|
|
1446
|
+
continue
|
|
1447
|
+
# Ignore containers that do not match the filter labels.
|
|
1448
|
+
if envs.GPUSTACK_RUNTIME_DOCKER_MIRRORED_NAME_FILTER_LABELS and any(
|
|
1449
|
+
c.labels.get(k) != v
|
|
1450
|
+
for k, v in envs.GPUSTACK_RUNTIME_DOCKER_MIRRORED_NAME_FILTER_LABELS.items()
|
|
1451
|
+
):
|
|
1452
|
+
continue
|
|
1453
|
+
containers.append(c)
|
|
1454
|
+
|
|
1455
|
+
# Validate found containers.
|
|
1456
|
+
if len(containers) != 1:
|
|
1457
|
+
msg = (
|
|
1458
|
+
f"Found multiple Containers with the same hostname {self_container_id}, "
|
|
1459
|
+
if len(containers) > 1
|
|
1460
|
+
else f"Not found Container with hostname {self_container_id}, "
|
|
1461
|
+
"please use `--env GPUSTACK_RUNTIME_DEPLOY_MIRRORED_NAME=...` to specify the exact Container name"
|
|
1462
|
+
)
|
|
1463
|
+
raise docker.errors.NotFound(msg)
|
|
1464
|
+
|
|
1465
|
+
self_container = containers[0]
|
|
1466
|
+
|
|
1467
|
+
return self_container
|
|
1459
1468
|
|
|
1460
1469
|
@_supported
|
|
1461
1470
|
def _create(self, workload: WorkloadPlan):
|
|
@@ -1481,7 +1490,7 @@ class DockerDeployer(Deployer):
|
|
|
1481
1490
|
msg = f"Invalid workload type: {type(workload)}"
|
|
1482
1491
|
raise TypeError(msg)
|
|
1483
1492
|
|
|
1484
|
-
self.
|
|
1493
|
+
self._prepare_mirrored_deployment()
|
|
1485
1494
|
|
|
1486
1495
|
if isinstance(workload, WorkloadPlan):
|
|
1487
1496
|
workload = DockerWorkloadPlan(**workload.__dict__)
|
|
@@ -1881,6 +1890,211 @@ class DockerDeployer(Deployer):
|
|
|
1881
1890
|
return output
|
|
1882
1891
|
return DockerWorkloadExecStream(output)
|
|
1883
1892
|
|
|
1893
|
+
@_supported
|
|
1894
|
+
def _inspect(
|
|
1895
|
+
self,
|
|
1896
|
+
name: WorkloadName,
|
|
1897
|
+
namespace: WorkloadNamespace | None = None,
|
|
1898
|
+
) -> str | None:
|
|
1899
|
+
"""
|
|
1900
|
+
Inspect a Docker workload.
|
|
1901
|
+
|
|
1902
|
+
Args:
|
|
1903
|
+
name:
|
|
1904
|
+
The name of the workload.
|
|
1905
|
+
namespace:
|
|
1906
|
+
The namespace of the workload.
|
|
1907
|
+
|
|
1908
|
+
Returns:
|
|
1909
|
+
The inspection result as a JSON string. None if not found.
|
|
1910
|
+
|
|
1911
|
+
Raises:
|
|
1912
|
+
UnsupportedError:
|
|
1913
|
+
If Docker is not supported in the current environment.
|
|
1914
|
+
OperationError:
|
|
1915
|
+
If the Docker workload fails to inspect.
|
|
1916
|
+
|
|
1917
|
+
"""
|
|
1918
|
+
workload = self._get(name=name, namespace=namespace)
|
|
1919
|
+
if not workload:
|
|
1920
|
+
return None
|
|
1921
|
+
|
|
1922
|
+
d_containers = getattr(workload, "_d_containers", [])
|
|
1923
|
+
if not d_containers:
|
|
1924
|
+
return None
|
|
1925
|
+
|
|
1926
|
+
result = []
|
|
1927
|
+
for c in d_containers:
|
|
1928
|
+
c_attrs = c.attrs
|
|
1929
|
+
# Mask sensitive environment variables
|
|
1930
|
+
if "Env" in c_attrs["Config"]:
|
|
1931
|
+
for i, env in enumerate(c_attrs["Config"]["Env"] or []):
|
|
1932
|
+
env_name, _ = env.split("=", maxsplit=1)
|
|
1933
|
+
if sensitive_env_var(env_name):
|
|
1934
|
+
c_attrs["Config"]["Env"][i] = f"{env_name}=******"
|
|
1935
|
+
result.append(c_attrs)
|
|
1936
|
+
return safe_json(result, indent=2)
|
|
1937
|
+
|
|
1938
|
+
def _find_self_container_for_endoscopy(self) -> docker.models.containers.Container:
|
|
1939
|
+
"""
|
|
1940
|
+
Find the self container for endoscopy.
|
|
1941
|
+
Only works in mirrored deployment mode.
|
|
1942
|
+
|
|
1943
|
+
Returns:
|
|
1944
|
+
The self container object.
|
|
1945
|
+
|
|
1946
|
+
Raises:
|
|
1947
|
+
UnsupportedError:
|
|
1948
|
+
If endoscopy is not supported in the current environment.
|
|
1949
|
+
|
|
1950
|
+
"""
|
|
1951
|
+
try:
|
|
1952
|
+
self_container = self._find_self_container()
|
|
1953
|
+
except docker.errors.APIError as e:
|
|
1954
|
+
msg = "Endoscopy is not supported in the current environment: Mirrored deployment enabled, but failed to get self Container"
|
|
1955
|
+
raise UnsupportedError(msg) from e
|
|
1956
|
+
except Exception as e:
|
|
1957
|
+
msg = "Endoscopy is not supported in the current environment: Failed to get self Container"
|
|
1958
|
+
raise UnsupportedError(msg) from e
|
|
1959
|
+
|
|
1960
|
+
if not self_container:
|
|
1961
|
+
msg = "Endoscopy is not supported in the current environment: Mirrored deployment disabled"
|
|
1962
|
+
raise UnsupportedError(msg)
|
|
1963
|
+
return self_container
|
|
1964
|
+
|
|
1965
|
+
def _endoscopic_logs(
|
|
1966
|
+
self,
|
|
1967
|
+
timestamps: bool = False,
|
|
1968
|
+
tail: int | None = None,
|
|
1969
|
+
since: int | None = None,
|
|
1970
|
+
follow: bool = False,
|
|
1971
|
+
) -> Generator[bytes | str, None, None] | bytes | str:
|
|
1972
|
+
"""
|
|
1973
|
+
Get the logs of the deployer itself.
|
|
1974
|
+
Only works in mirrored deployment mode.
|
|
1975
|
+
|
|
1976
|
+
Args:
|
|
1977
|
+
timestamps:
|
|
1978
|
+
Show timestamps in the logs.
|
|
1979
|
+
tail:
|
|
1980
|
+
Number of lines to show from the end of the logs.
|
|
1981
|
+
since:
|
|
1982
|
+
Show logs since the given epoch in seconds.
|
|
1983
|
+
follow:
|
|
1984
|
+
Whether to follow the logs.
|
|
1985
|
+
|
|
1986
|
+
Returns:
|
|
1987
|
+
The logs as a byte string or a generator yielding byte strings if follow is True.
|
|
1988
|
+
|
|
1989
|
+
Raises:
|
|
1990
|
+
UnsupportedError:
|
|
1991
|
+
If endoscopy is not supported in the current environment.
|
|
1992
|
+
OperationError:
|
|
1993
|
+
If the deployer fails to get logs.
|
|
1994
|
+
|
|
1995
|
+
"""
|
|
1996
|
+
self_container = self._find_self_container_for_endoscopy()
|
|
1997
|
+
|
|
1998
|
+
logs_options = {
|
|
1999
|
+
"timestamps": timestamps,
|
|
2000
|
+
"tail": tail if tail >= 0 else None,
|
|
2001
|
+
"since": since,
|
|
2002
|
+
"follow": follow,
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
try:
|
|
2006
|
+
output = self_container.logs(
|
|
2007
|
+
stream=follow,
|
|
2008
|
+
**logs_options,
|
|
2009
|
+
)
|
|
2010
|
+
except docker.errors.APIError as e:
|
|
2011
|
+
msg = f"Failed to fetch logs for self Container {self_container.short_id}{_detail_api_call_error(e)}"
|
|
2012
|
+
raise OperationError(msg) from e
|
|
2013
|
+
else:
|
|
2014
|
+
return output
|
|
2015
|
+
|
|
2016
|
+
def _endoscopic_exec(
|
|
2017
|
+
self,
|
|
2018
|
+
detach: bool = True,
|
|
2019
|
+
command: list[str] | None = None,
|
|
2020
|
+
args: list[str] | None = None,
|
|
2021
|
+
) -> WorkloadExecStream | bytes | str:
|
|
2022
|
+
"""
|
|
2023
|
+
Execute a command in the deployer itself.
|
|
2024
|
+
Only works in mirrored deployment mode.
|
|
2025
|
+
|
|
2026
|
+
Args:
|
|
2027
|
+
detach:
|
|
2028
|
+
Whether to detach from the command.
|
|
2029
|
+
command:
|
|
2030
|
+
The command to execute.
|
|
2031
|
+
If not specified, use /bin/sh and implicitly attach.
|
|
2032
|
+
args:
|
|
2033
|
+
The arguments to pass to the command.
|
|
2034
|
+
|
|
2035
|
+
Returns:
|
|
2036
|
+
If detach is False, return a WorkloadExecStream.
|
|
2037
|
+
otherwise, return the output of the command as a byte string or string.
|
|
2038
|
+
|
|
2039
|
+
Raises:
|
|
2040
|
+
UnsupportedError:
|
|
2041
|
+
If endoscopy is not supported in the current environment.
|
|
2042
|
+
OperationError:
|
|
2043
|
+
If the deployer fails to execute the command.
|
|
2044
|
+
|
|
2045
|
+
"""
|
|
2046
|
+
self_container = self._find_self_container_for_endoscopy()
|
|
2047
|
+
|
|
2048
|
+
attach = not detach or not command
|
|
2049
|
+
exec_options = {
|
|
2050
|
+
"stdout": True,
|
|
2051
|
+
"stderr": True,
|
|
2052
|
+
"stdin": attach,
|
|
2053
|
+
"socket": attach,
|
|
2054
|
+
"tty": attach,
|
|
2055
|
+
"cmd": [*command, *(args or [])] if command else ["/bin/sh"],
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
try:
|
|
2059
|
+
_, output = self_container.exec_run(
|
|
2060
|
+
detach=False,
|
|
2061
|
+
**exec_options,
|
|
2062
|
+
)
|
|
2063
|
+
except docker.errors.APIError as e:
|
|
2064
|
+
msg = f"Failed to exec command in self Container {self_container.short_id}{_detail_api_call_error(e)}"
|
|
2065
|
+
raise OperationError(msg) from e
|
|
2066
|
+
else:
|
|
2067
|
+
if not attach:
|
|
2068
|
+
return output
|
|
2069
|
+
return DockerWorkloadExecStream(output)
|
|
2070
|
+
|
|
2071
|
+
def _endoscopic_inspect(self) -> str:
|
|
2072
|
+
"""
|
|
2073
|
+
Inspect the deployer itself.
|
|
2074
|
+
Only works in mirrored deployment mode.
|
|
2075
|
+
|
|
2076
|
+
Returns:
|
|
2077
|
+
The inspection result.
|
|
2078
|
+
|
|
2079
|
+
Raises:
|
|
2080
|
+
UnsupportedError:
|
|
2081
|
+
If endoscopy is not supported in the current environment.
|
|
2082
|
+
OperationError:
|
|
2083
|
+
If the deployer fails to execute the command.
|
|
2084
|
+
|
|
2085
|
+
"""
|
|
2086
|
+
self_container = self._find_self_container_for_endoscopy()
|
|
2087
|
+
|
|
2088
|
+
c_attrs = self_container.attrs
|
|
2089
|
+
# Mask sensitive environment variables
|
|
2090
|
+
if "Env" in c_attrs["Config"]:
|
|
2091
|
+
for i, env in enumerate(c_attrs["Config"]["Env"] or []):
|
|
2092
|
+
env_name, _ = env.split("=", maxsplit=1)
|
|
2093
|
+
if sensitive_env_var(env_name):
|
|
2094
|
+
c_attrs["Config"]["Env"][i] = f"{env_name}=******"
|
|
2095
|
+
|
|
2096
|
+
return safe_json(c_attrs, indent=2)
|
|
2097
|
+
|
|
1884
2098
|
|
|
1885
2099
|
def _has_restart_policy(
|
|
1886
2100
|
container: docker.models.containers.Container,
|