gpustack-runtime 0.1.38.post4__py3-none-any.whl → 0.1.39__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/_version.py +2 -2
- gpustack_runtime/_version_appendix.py +1 -1
- gpustack_runtime/deployer/__init__.py +24 -49
- gpustack_runtime/deployer/__patches__.py +455 -0
- gpustack_runtime/deployer/__types__.py +60 -27
- gpustack_runtime/deployer/docker.py +115 -41
- gpustack_runtime/deployer/kuberentes.py +23 -22
- gpustack_runtime/deployer/podman.py +2114 -0
- gpustack_runtime/detector/amd.py +4 -13
- gpustack_runtime/detector/hygon.py +1 -1
- gpustack_runtime/detector/nvidia.py +1 -1
- gpustack_runtime/detector/pyhsa/__init__.py +7 -7
- gpustack_runtime/detector/pyrocmsmi/__init__.py +9 -3
- gpustack_runtime/envs.py +217 -46
- {gpustack_runtime-0.1.38.post4.dist-info → gpustack_runtime-0.1.39.dist-info}/METADATA +6 -4
- {gpustack_runtime-0.1.38.post4.dist-info → gpustack_runtime-0.1.39.dist-info}/RECORD +19 -17
- {gpustack_runtime-0.1.38.post4.dist-info → gpustack_runtime-0.1.39.dist-info}/WHEEL +0 -0
- {gpustack_runtime-0.1.38.post4.dist-info → gpustack_runtime-0.1.39.dist-info}/entry_points.txt +0 -0
- {gpustack_runtime-0.1.38.post4.dist-info → gpustack_runtime-0.1.39.dist-info}/licenses/LICENSE +0 -0
gpustack_runtime/_version.py
CHANGED
|
@@ -27,8 +27,8 @@ version_tuple: VERSION_TUPLE
|
|
|
27
27
|
__commit_id__: COMMIT_ID
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
|
|
30
|
-
__version__ = version = '0.1.
|
|
31
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
30
|
+
__version__ = version = '0.1.39'
|
|
31
|
+
__version_tuple__ = version_tuple = (0, 1, 39)
|
|
32
32
|
try:
|
|
33
33
|
from ._version_appendix import git_commit
|
|
34
34
|
__commit_id__ = commit_id = git_commit
|
|
@@ -1 +1 @@
|
|
|
1
|
-
git_commit = "
|
|
1
|
+
git_commit = "c8c93ed"
|
|
@@ -41,6 +41,11 @@ from .kuberentes import (
|
|
|
41
41
|
KubernetesWorkloadPlan,
|
|
42
42
|
KubernetesWorkloadStatus,
|
|
43
43
|
)
|
|
44
|
+
from .podman import (
|
|
45
|
+
PodmanDeployer,
|
|
46
|
+
PodmanWorkloadPlan,
|
|
47
|
+
PodmanWorkloadStatus,
|
|
48
|
+
)
|
|
44
49
|
|
|
45
50
|
if TYPE_CHECKING:
|
|
46
51
|
from collections.abc import AsyncGenerator, Generator
|
|
@@ -50,6 +55,7 @@ if TYPE_CHECKING:
|
|
|
50
55
|
_DEPLOYERS: list[Deployer] = [
|
|
51
56
|
DockerDeployer(),
|
|
52
57
|
KubernetesDeployer(),
|
|
58
|
+
PodmanDeployer(),
|
|
53
59
|
]
|
|
54
60
|
"""
|
|
55
61
|
List of all deployers.
|
|
@@ -60,6 +66,14 @@ _DEPLOYERS_MAP: dict[str, Deployer] = {dep.name: dep for dep in _DEPLOYERS}
|
|
|
60
66
|
Mapping from deployer name to deployer.
|
|
61
67
|
"""
|
|
62
68
|
|
|
69
|
+
_NO_AVAILABLE_DEPLOYER_MSG = (
|
|
70
|
+
"No available deployer. "
|
|
71
|
+
"Please provide a container runtime, e.g. "
|
|
72
|
+
"bind mount the host `/var/run/docker.sock` on Docker, "
|
|
73
|
+
"or allow (in-)cluster access on Kubernetes, "
|
|
74
|
+
"or bind mount the host `/run/podman/podman.sock` on Podman."
|
|
75
|
+
)
|
|
76
|
+
|
|
63
77
|
|
|
64
78
|
def supported_list() -> list[Deployer]:
|
|
65
79
|
"""
|
|
@@ -98,13 +112,7 @@ def create_workload(workload: WorkloadPlan):
|
|
|
98
112
|
dep.create(workload=workload)
|
|
99
113
|
return
|
|
100
114
|
|
|
101
|
-
|
|
102
|
-
"No available deployer. "
|
|
103
|
-
"Please provide a container runtime, e.g. "
|
|
104
|
-
"bind mount the host `/var/run/docker.sock` on Docker, "
|
|
105
|
-
"or allow (in-)cluster access on Kubernetes"
|
|
106
|
-
)
|
|
107
|
-
raise UnsupportedError(msg)
|
|
115
|
+
raise UnsupportedError(_NO_AVAILABLE_DEPLOYER_MSG)
|
|
108
116
|
|
|
109
117
|
|
|
110
118
|
def get_workload(
|
|
@@ -136,13 +144,7 @@ def get_workload(
|
|
|
136
144
|
|
|
137
145
|
return dep.get(name=name, namespace=namespace)
|
|
138
146
|
|
|
139
|
-
|
|
140
|
-
"No available deployer. "
|
|
141
|
-
"Please provide a container runtime, e.g. "
|
|
142
|
-
"bind mount the host `/var/run/docker.sock` on Docker, "
|
|
143
|
-
"or allow (in-)cluster access on Kubernetes"
|
|
144
|
-
)
|
|
145
|
-
raise UnsupportedError(msg)
|
|
147
|
+
raise UnsupportedError(_NO_AVAILABLE_DEPLOYER_MSG)
|
|
146
148
|
|
|
147
149
|
|
|
148
150
|
def delete_workload(
|
|
@@ -174,13 +176,7 @@ def delete_workload(
|
|
|
174
176
|
|
|
175
177
|
return dep.delete(name=name, namespace=namespace)
|
|
176
178
|
|
|
177
|
-
|
|
178
|
-
"No available deployer. "
|
|
179
|
-
"Please provide a container runtime, e.g. "
|
|
180
|
-
"bind mount the host `/var/run/docker.sock` on Docker, "
|
|
181
|
-
"or allow (in-)cluster access on Kubernetes"
|
|
182
|
-
)
|
|
183
|
-
raise UnsupportedError(msg)
|
|
179
|
+
raise UnsupportedError(_NO_AVAILABLE_DEPLOYER_MSG)
|
|
184
180
|
|
|
185
181
|
|
|
186
182
|
def list_workloads(
|
|
@@ -212,13 +208,7 @@ def list_workloads(
|
|
|
212
208
|
|
|
213
209
|
return dep.list(namespace=namespace, labels=labels)
|
|
214
210
|
|
|
215
|
-
|
|
216
|
-
"No available deployer. "
|
|
217
|
-
"Please provide a container runtime, e.g. "
|
|
218
|
-
"bind mount the host `/var/run/docker.sock` on Docker, "
|
|
219
|
-
"or allow (in-)cluster access on Kubernetes"
|
|
220
|
-
)
|
|
221
|
-
raise UnsupportedError(msg)
|
|
211
|
+
raise UnsupportedError(_NO_AVAILABLE_DEPLOYER_MSG)
|
|
222
212
|
|
|
223
213
|
|
|
224
214
|
def logs_workload(
|
|
@@ -273,13 +263,7 @@ def logs_workload(
|
|
|
273
263
|
follow=follow,
|
|
274
264
|
)
|
|
275
265
|
|
|
276
|
-
|
|
277
|
-
"No available deployer. "
|
|
278
|
-
"Please provide a container runtime, e.g. "
|
|
279
|
-
"bind mount the host `/var/run/docker.sock` on Docker, "
|
|
280
|
-
"or allow (in-)cluster access on Kubernetes"
|
|
281
|
-
)
|
|
282
|
-
raise UnsupportedError(msg)
|
|
266
|
+
raise UnsupportedError(_NO_AVAILABLE_DEPLOYER_MSG)
|
|
283
267
|
|
|
284
268
|
|
|
285
269
|
async def async_logs_workload(
|
|
@@ -334,13 +318,7 @@ async def async_logs_workload(
|
|
|
334
318
|
follow=follow,
|
|
335
319
|
)
|
|
336
320
|
|
|
337
|
-
|
|
338
|
-
"No available deployer. "
|
|
339
|
-
"Please provide a container runtime, e.g. "
|
|
340
|
-
"bind mount the host `/var/run/docker.sock` on Docker, "
|
|
341
|
-
"or allow (in-)cluster access on Kubernetes"
|
|
342
|
-
)
|
|
343
|
-
raise UnsupportedError(msg)
|
|
321
|
+
raise UnsupportedError(_NO_AVAILABLE_DEPLOYER_MSG)
|
|
344
322
|
|
|
345
323
|
|
|
346
324
|
def exec_workload(
|
|
@@ -392,13 +370,7 @@ def exec_workload(
|
|
|
392
370
|
args=args,
|
|
393
371
|
)
|
|
394
372
|
|
|
395
|
-
|
|
396
|
-
"No available deployer. "
|
|
397
|
-
"Please provide a container runtime, e.g. "
|
|
398
|
-
"bind mount the host `/var/run/docker.sock` on Docker, "
|
|
399
|
-
"or allow (in-)cluster access on Kubernetes"
|
|
400
|
-
)
|
|
401
|
-
raise UnsupportedError(msg)
|
|
373
|
+
raise UnsupportedError(_NO_AVAILABLE_DEPLOYER_MSG)
|
|
402
374
|
|
|
403
375
|
|
|
404
376
|
__all__ = [
|
|
@@ -424,6 +396,9 @@ __all__ = [
|
|
|
424
396
|
"KubernetesWorkloadPlan",
|
|
425
397
|
"KubernetesWorkloadStatus",
|
|
426
398
|
"OperationError",
|
|
399
|
+
"PodmanDeployer",
|
|
400
|
+
"PodmanWorkloadPlan",
|
|
401
|
+
"PodmanWorkloadStatus",
|
|
427
402
|
"UnsupportedError",
|
|
428
403
|
"WorkloadExecStream",
|
|
429
404
|
"WorkloadOperationToken",
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import copy
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import MutableMapping
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from podman.domain.containers_create import NAMED_VOLUME_PATTERN, CreateMixin
|
|
10
|
+
from podman.domain.pods import Pod
|
|
11
|
+
from podman.domain.secrets import Secret
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# NB(thxCode): Modify from podman-py 5.6.0 source code to fit our need,
|
|
15
|
+
# see https://github.com/containers/podman-py/blob/435e7a904b92560b8c0d883ed8994f57eea27171/podman/domain/containers_create.py#L432-L844.
|
|
16
|
+
def patch_render_payload(kwargs: MutableMapping[str, Any]) -> dict[str, Any]:
|
|
17
|
+
"""Map create/run kwargs into body parameters."""
|
|
18
|
+
args = copy.copy(kwargs)
|
|
19
|
+
|
|
20
|
+
if "links" in args:
|
|
21
|
+
if len(args["links"]) > 0:
|
|
22
|
+
msg = "'links' are not supported by Podman service."
|
|
23
|
+
raise ValueError(msg)
|
|
24
|
+
del args["links"]
|
|
25
|
+
|
|
26
|
+
# Ignore these keywords
|
|
27
|
+
for key in (
|
|
28
|
+
"cpu_count",
|
|
29
|
+
"cpu_percent",
|
|
30
|
+
"nano_cpus",
|
|
31
|
+
"platform", # used by caller
|
|
32
|
+
"remove", # used by caller
|
|
33
|
+
"stderr", # used by caller
|
|
34
|
+
"stdout", # used by caller
|
|
35
|
+
"stream", # used by caller
|
|
36
|
+
"detach", # used by caller
|
|
37
|
+
"volume_driver",
|
|
38
|
+
):
|
|
39
|
+
with contextlib.suppress(KeyError):
|
|
40
|
+
del args[key]
|
|
41
|
+
|
|
42
|
+
# Handle environment variables
|
|
43
|
+
environment = args.pop("environment", None)
|
|
44
|
+
if environment is not None:
|
|
45
|
+
if isinstance(environment, list):
|
|
46
|
+
try:
|
|
47
|
+
environment = CreateMixin._convert_env_list_to_dict(environment)
|
|
48
|
+
except ValueError as e:
|
|
49
|
+
msg = (
|
|
50
|
+
"Failed to convert environment variables list to dictionary. "
|
|
51
|
+
f"Error: {e!s}"
|
|
52
|
+
)
|
|
53
|
+
raise ValueError(
|
|
54
|
+
msg,
|
|
55
|
+
) from e
|
|
56
|
+
elif not isinstance(environment, dict):
|
|
57
|
+
msg = (
|
|
58
|
+
"Environment variables must be provided as either a dictionary "
|
|
59
|
+
"or a list of strings in the format ['KEY=value']"
|
|
60
|
+
)
|
|
61
|
+
raise TypeError(
|
|
62
|
+
msg,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# These keywords are not supported for various reasons.
|
|
66
|
+
unsupported_keys = set(
|
|
67
|
+
args.keys(),
|
|
68
|
+
).intersection(
|
|
69
|
+
(
|
|
70
|
+
"blkio_weight",
|
|
71
|
+
"blkio_weight_device", # FIXME In addition to device Major/Minor include path
|
|
72
|
+
"device_cgroup_rules", # FIXME Where to map for Podman API?
|
|
73
|
+
"device_read_bps", # FIXME In addition to device Major/Minor include path
|
|
74
|
+
"device_read_iops", # FIXME In addition to device Major/Minor include path
|
|
75
|
+
"device_requests", # FIXME In addition to device Major/Minor include path
|
|
76
|
+
"device_write_bps", # FIXME In addition to device Major/Minor include path
|
|
77
|
+
"device_write_iops", # FIXME In addition to device Major/Minor include path
|
|
78
|
+
"domainname",
|
|
79
|
+
"network_disabled", # FIXME Where to map for Podman API?
|
|
80
|
+
"storage_opt", # FIXME Where to map for Podman API?
|
|
81
|
+
"tmpfs", # FIXME Where to map for Podman API?
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
if len(unsupported_keys) > 0:
|
|
85
|
+
msg = (
|
|
86
|
+
f"""Keyword(s) '{" ,".join(unsupported_keys)}' are"""
|
|
87
|
+
f""" currently not supported by Podman API."""
|
|
88
|
+
)
|
|
89
|
+
raise TypeError(
|
|
90
|
+
msg,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def pop(k):
|
|
94
|
+
return args.pop(k, None)
|
|
95
|
+
|
|
96
|
+
def normalize_nsmode(
|
|
97
|
+
mode: str | MutableMapping[str, str],
|
|
98
|
+
) -> dict[str, str]:
|
|
99
|
+
if isinstance(mode, dict):
|
|
100
|
+
return mode
|
|
101
|
+
return {"nsmode": mode}
|
|
102
|
+
|
|
103
|
+
def to_bytes(size: int | str | None) -> int | None:
|
|
104
|
+
"""
|
|
105
|
+
Converts str or int to bytes.
|
|
106
|
+
Input can be in the following forms :
|
|
107
|
+
0) None - e.g. None -> returns None
|
|
108
|
+
1) int - e.g. 100 == 100 bytes
|
|
109
|
+
2) str - e.g. '100' == 100 bytes
|
|
110
|
+
3) str with suffix - available suffixes:
|
|
111
|
+
b | B - bytes
|
|
112
|
+
k | K = kilobytes
|
|
113
|
+
m | M = megabytes
|
|
114
|
+
g | G = gigabytes
|
|
115
|
+
e.g. '100m' == 104857600 bytes.
|
|
116
|
+
"""
|
|
117
|
+
size_type = type(size)
|
|
118
|
+
if size is None:
|
|
119
|
+
return size
|
|
120
|
+
if size_type is int:
|
|
121
|
+
return size
|
|
122
|
+
if size_type is str:
|
|
123
|
+
try:
|
|
124
|
+
return int(size)
|
|
125
|
+
except ValueError as bad_size:
|
|
126
|
+
mapping = {"b": 0, "k": 1, "m": 2, "g": 3}
|
|
127
|
+
mapping_regex = "".join(mapping.keys())
|
|
128
|
+
search = re.search(rf"^(\d+)([{mapping_regex}])$", size.lower())
|
|
129
|
+
if search:
|
|
130
|
+
return int(search.group(1)) * (1024 ** mapping[search.group(2)])
|
|
131
|
+
msg = f"Passed string size {size} should be in format\\d+[bBkKmMgG] (e.g. '100m')"
|
|
132
|
+
raise TypeError(
|
|
133
|
+
msg,
|
|
134
|
+
) from bad_size
|
|
135
|
+
else:
|
|
136
|
+
msg = (
|
|
137
|
+
f"Passed size {size} should be a type of unicode, str "
|
|
138
|
+
f"or int (found : {size_type})"
|
|
139
|
+
)
|
|
140
|
+
raise TypeError(
|
|
141
|
+
msg,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Transform keywords into parameters
|
|
145
|
+
params = {
|
|
146
|
+
"annotations": pop("annotations"), # TODO document, podman only
|
|
147
|
+
"apparmor_profile": pop("apparmor_profile"), # TODO document, podman only
|
|
148
|
+
"cap_add": pop("cap_add"),
|
|
149
|
+
"cap_drop": pop("cap_drop"),
|
|
150
|
+
"cgroup_parent": pop("cgroup_parent"),
|
|
151
|
+
"cgroups_mode": pop("cgroups_mode"), # TODO document, podman only
|
|
152
|
+
"cni_networks": [pop("network")],
|
|
153
|
+
"command": args.pop("command", args.pop("cmd", None)),
|
|
154
|
+
"conmon_pid_file": pop("conmon_pid_file"), # TODO document, podman only
|
|
155
|
+
"containerCreateCommand": pop(
|
|
156
|
+
"containerCreateCommand",
|
|
157
|
+
), # TODO document, podman only
|
|
158
|
+
"devices": [],
|
|
159
|
+
"dns_option": pop("dns_opt"),
|
|
160
|
+
"dns_search": pop("dns_search"),
|
|
161
|
+
"dns_server": pop("dns"),
|
|
162
|
+
"entrypoint": pop("entrypoint"),
|
|
163
|
+
"env": environment,
|
|
164
|
+
"env_host": pop("env_host"), # TODO document, podman only
|
|
165
|
+
"expose": {},
|
|
166
|
+
"groups": pop("group_add"),
|
|
167
|
+
"healthconfig": pop("healthcheck"),
|
|
168
|
+
"health_check_on_failure_action": pop("health_check_on_failure_action"),
|
|
169
|
+
"hostadd": [],
|
|
170
|
+
"hostname": pop("hostname"),
|
|
171
|
+
"httpproxy": pop("use_config_proxy"),
|
|
172
|
+
"idmappings": pop("idmappings"), # TODO document, podman only
|
|
173
|
+
"image": pop("image"),
|
|
174
|
+
"image_volume_mode": pop("image_volume_mode"), # TODO document, podman only
|
|
175
|
+
"image_volumes": pop("image_volumes"), # TODO document, podman only
|
|
176
|
+
"init": pop("init"),
|
|
177
|
+
"init_path": pop("init_path"),
|
|
178
|
+
"isolation": pop("isolation"),
|
|
179
|
+
"labels": pop("labels"),
|
|
180
|
+
"log_configuration": {},
|
|
181
|
+
"lxc_config": pop("lxc_config"),
|
|
182
|
+
"mask": pop("masked_paths"),
|
|
183
|
+
"mounts": [],
|
|
184
|
+
"name": pop("name"),
|
|
185
|
+
"namespace": pop("namespace"), # TODO What is this for?
|
|
186
|
+
"network_options": pop("network_options"), # TODO document, podman only
|
|
187
|
+
"networks": pop("networks"),
|
|
188
|
+
"no_new_privileges": pop("no_new_privileges"), # TODO document, podman only
|
|
189
|
+
"oci_runtime": pop("runtime"),
|
|
190
|
+
"oom_score_adj": pop("oom_score_adj"),
|
|
191
|
+
"overlay_volumes": pop("overlay_volumes"), # TODO document, podman only
|
|
192
|
+
"portmappings": [],
|
|
193
|
+
"privileged": pop("privileged"),
|
|
194
|
+
"procfs_opts": pop("procfs_opts"), # TODO document, podman only
|
|
195
|
+
"publish_image_ports": pop("publish_all_ports"),
|
|
196
|
+
"r_limits": [],
|
|
197
|
+
"raw_image_name": pop("raw_image_name"), # TODO document, podman only
|
|
198
|
+
"read_only_filesystem": pop("read_only"),
|
|
199
|
+
"read_write_tmpfs": pop("read_write_tmpfs"),
|
|
200
|
+
"remove": args.pop("remove", args.pop("auto_remove", None)),
|
|
201
|
+
"resource_limits": {},
|
|
202
|
+
"rootfs": pop("rootfs"),
|
|
203
|
+
"rootfs_propagation": pop("rootfs_propagation"),
|
|
204
|
+
"sdnotifyMode": pop("sdnotifyMode"), # TODO document, podman only
|
|
205
|
+
"seccomp_policy": pop("seccomp_policy"), # TODO document, podman only
|
|
206
|
+
"seccomp_profile_path": pop(
|
|
207
|
+
"seccomp_profile_path",
|
|
208
|
+
), # TODO document, podman only
|
|
209
|
+
"secrets": [], # TODO document, podman only
|
|
210
|
+
"selinux_opts": pop("security_opt"),
|
|
211
|
+
"shm_size": to_bytes(pop("shm_size")),
|
|
212
|
+
"static_mac": pop("mac_address"),
|
|
213
|
+
"stdin": pop("stdin_open"),
|
|
214
|
+
"stop_signal": pop("stop_signal"),
|
|
215
|
+
"stop_timeout": pop("stop_timeout"), # TODO document, podman only
|
|
216
|
+
"sysctl": pop("sysctls"),
|
|
217
|
+
"systemd": pop("systemd"), # TODO document, podman only
|
|
218
|
+
"terminal": pop("tty"),
|
|
219
|
+
"timezone": pop("timezone"),
|
|
220
|
+
"umask": pop("umask"), # TODO document, podman only
|
|
221
|
+
"unified": pop("unified"), # TODO document, podman only
|
|
222
|
+
"unmask": pop("unmasked_paths"), # TODO document, podman only
|
|
223
|
+
"use_image_hosts": pop("use_image_hosts"), # TODO document, podman only
|
|
224
|
+
"use_image_resolve_conf": pop(
|
|
225
|
+
"use_image_resolve_conf",
|
|
226
|
+
), # TODO document, podman only
|
|
227
|
+
"user": pop("user"),
|
|
228
|
+
"version": pop("version"),
|
|
229
|
+
"volumes": [],
|
|
230
|
+
"volumes_from": pop("volumes_from"),
|
|
231
|
+
"work_dir": pop("workdir") or pop("working_dir"),
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for device in args.pop("devices", []):
|
|
235
|
+
params["devices"].append({"path": device})
|
|
236
|
+
|
|
237
|
+
for item in args.pop("exposed_ports", []):
|
|
238
|
+
port, protocol = item.split("/")
|
|
239
|
+
params["expose"][int(port)] = protocol
|
|
240
|
+
|
|
241
|
+
for hostname, ip in args.pop("extra_hosts", {}).items():
|
|
242
|
+
params["hostadd"].append(f"{hostname}:{ip}")
|
|
243
|
+
|
|
244
|
+
if "log_config" in args:
|
|
245
|
+
params["log_configuration"]["driver"] = args["log_config"].get("Type")
|
|
246
|
+
|
|
247
|
+
if "Config" in args["log_config"]:
|
|
248
|
+
params["log_configuration"]["path"] = args["log_config"]["Config"].get(
|
|
249
|
+
"path",
|
|
250
|
+
)
|
|
251
|
+
params["log_configuration"]["size"] = args["log_config"]["Config"].get(
|
|
252
|
+
"size",
|
|
253
|
+
)
|
|
254
|
+
params["log_configuration"]["options"] = args["log_config"]["Config"].get(
|
|
255
|
+
"options",
|
|
256
|
+
)
|
|
257
|
+
args.pop("log_config")
|
|
258
|
+
|
|
259
|
+
for item in args.pop("mounts", []):
|
|
260
|
+
normalized_item = {key.lower(): value for key, value in item.items()}
|
|
261
|
+
mount_point = {
|
|
262
|
+
"destination": normalized_item.get("target"),
|
|
263
|
+
"options": [],
|
|
264
|
+
"source": normalized_item.get("source"),
|
|
265
|
+
"type": normalized_item.get("type"),
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
# some names are different for podman-py vs REST API due to compatibility with docker
|
|
269
|
+
# some (e.g. chown) despite listed in podman-run documentation fails with error
|
|
270
|
+
names_dict = {"read_only": "ro", "chown": "U"}
|
|
271
|
+
|
|
272
|
+
options = []
|
|
273
|
+
simple_options = ["propagation", "relabel"]
|
|
274
|
+
bool_options = ["read_only", "U", "chown"]
|
|
275
|
+
regular_options = ["consistency", "mode", "size"]
|
|
276
|
+
|
|
277
|
+
for k, v in item.items():
|
|
278
|
+
_k = k.lower()
|
|
279
|
+
option_name = names_dict.get(_k, _k)
|
|
280
|
+
if _k in bool_options and v is True:
|
|
281
|
+
options.append(option_name)
|
|
282
|
+
elif _k in regular_options:
|
|
283
|
+
options.append(f"{option_name}={v}")
|
|
284
|
+
elif _k in simple_options:
|
|
285
|
+
options.append(v)
|
|
286
|
+
|
|
287
|
+
mount_point["options"] = options
|
|
288
|
+
|
|
289
|
+
params["mounts"].append(mount_point)
|
|
290
|
+
|
|
291
|
+
if "pod" in args:
|
|
292
|
+
pod = args.pop("pod")
|
|
293
|
+
if isinstance(pod, Pod):
|
|
294
|
+
pod = pod.id
|
|
295
|
+
params["pod"] = pod # TODO document, podman only
|
|
296
|
+
|
|
297
|
+
def parse_host_port(_container_port, _protocol, _host):
|
|
298
|
+
result = []
|
|
299
|
+
port_map = {"container_port": int(_container_port), "protocol": _protocol}
|
|
300
|
+
if _host is None:
|
|
301
|
+
result.append(port_map)
|
|
302
|
+
elif isinstance(_host, int) or (isinstance(_host, str) and _host.isdigit()):
|
|
303
|
+
port_map["host_port"] = int(_host)
|
|
304
|
+
result.append(port_map)
|
|
305
|
+
elif isinstance(_host, tuple):
|
|
306
|
+
port_map["host_ip"] = _host[0]
|
|
307
|
+
port_map["host_port"] = int(_host[1])
|
|
308
|
+
result.append(port_map)
|
|
309
|
+
elif isinstance(_host, list):
|
|
310
|
+
for host_list in _host:
|
|
311
|
+
host_list_result = parse_host_port(
|
|
312
|
+
_container_port,
|
|
313
|
+
_protocol,
|
|
314
|
+
host_list,
|
|
315
|
+
)
|
|
316
|
+
result.extend(host_list_result)
|
|
317
|
+
elif isinstance(_host, dict):
|
|
318
|
+
_host_port = _host.get("port")
|
|
319
|
+
if _host_port is not None:
|
|
320
|
+
if isinstance(_host_port, int) or (
|
|
321
|
+
isinstance(_host_port, str) and _host_port.isdigit()
|
|
322
|
+
):
|
|
323
|
+
port_map["host_port"] = int(_host_port)
|
|
324
|
+
elif isinstance(_host_port, tuple):
|
|
325
|
+
port_map["host_ip"] = _host_port[0]
|
|
326
|
+
port_map["host_port"] = int(_host_port[1])
|
|
327
|
+
if _host.get("range"):
|
|
328
|
+
port_map["range"] = _host.get("range")
|
|
329
|
+
if _host.get("ip"):
|
|
330
|
+
port_map["host_ip"] = _host.get("ip")
|
|
331
|
+
result.append(port_map)
|
|
332
|
+
return result
|
|
333
|
+
|
|
334
|
+
for container, host in args.pop("ports", {}).items():
|
|
335
|
+
# avoid redefinition of the loop variable, then ensure it's a string
|
|
336
|
+
str_container = container
|
|
337
|
+
if isinstance(str_container, int):
|
|
338
|
+
str_container = str(str_container)
|
|
339
|
+
|
|
340
|
+
if "/" in str_container:
|
|
341
|
+
container_port, protocol = str_container.split("/")
|
|
342
|
+
else:
|
|
343
|
+
container_port, protocol = str_container, "tcp"
|
|
344
|
+
|
|
345
|
+
port_map_list = parse_host_port(container_port, protocol, host)
|
|
346
|
+
params["portmappings"].extend(port_map_list)
|
|
347
|
+
|
|
348
|
+
if "restart_policy" in args:
|
|
349
|
+
params["restart_policy"] = args["restart_policy"].get("Name")
|
|
350
|
+
params["restart_tries"] = args["restart_policy"].get("MaximumRetryCount")
|
|
351
|
+
args.pop("restart_policy")
|
|
352
|
+
|
|
353
|
+
params["resource_limits"]["pids"] = {"limit": args.pop("pids_limit", None)}
|
|
354
|
+
|
|
355
|
+
params["resource_limits"]["cpu"] = {
|
|
356
|
+
"cpus": args.pop("cpuset_cpus", None),
|
|
357
|
+
"mems": args.pop("cpuset_mems", None),
|
|
358
|
+
"period": args.pop("cpu_period", None),
|
|
359
|
+
"quota": args.pop("cpu_quota", None),
|
|
360
|
+
"realtimePeriod": args.pop("cpu_rt_period", None),
|
|
361
|
+
"realtimeRuntime": args.pop("cpu_rt_runtime", None),
|
|
362
|
+
"shares": args.pop("cpu_shares", None),
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
params["resource_limits"]["memory"] = {
|
|
366
|
+
"disableOOMKiller": args.pop("oom_kill_disable", None),
|
|
367
|
+
"kernel": to_bytes(args.pop("kernel_memory", None)),
|
|
368
|
+
"kernelTCP": args.pop("kernel_memory_tcp", None),
|
|
369
|
+
"limit": to_bytes(args.pop("mem_limit", None)),
|
|
370
|
+
"reservation": to_bytes(args.pop("mem_reservation", None)),
|
|
371
|
+
"swap": to_bytes(args.pop("memswap_limit", None)),
|
|
372
|
+
"swappiness": args.pop("mem_swappiness", None),
|
|
373
|
+
"useHierarchy": args.pop("mem_use_hierarchy", None),
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
for item in args.pop("ulimits", []):
|
|
377
|
+
params["r_limits"].append(
|
|
378
|
+
{
|
|
379
|
+
"type": item["Name"],
|
|
380
|
+
"hard": item["Hard"],
|
|
381
|
+
"soft": item["Soft"],
|
|
382
|
+
},
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
for item in args.pop("volumes", {}).items():
|
|
386
|
+
key, value = item
|
|
387
|
+
extended_mode = value.get("extended_mode", [])
|
|
388
|
+
if not isinstance(extended_mode, list):
|
|
389
|
+
msg = "'extended_mode' value should be a list"
|
|
390
|
+
raise ValueError(msg)
|
|
391
|
+
|
|
392
|
+
options = extended_mode
|
|
393
|
+
mode = value.get("mode")
|
|
394
|
+
if mode is not None:
|
|
395
|
+
if not isinstance(mode, str):
|
|
396
|
+
msg = "'mode' value should be a str"
|
|
397
|
+
raise ValueError(msg)
|
|
398
|
+
options.append(mode)
|
|
399
|
+
|
|
400
|
+
# The Podman API only supports named volumes through the ``volume`` parameter. Directory
|
|
401
|
+
# mounting needs to happen through the ``mounts`` parameter. Luckily the translation
|
|
402
|
+
# isn't too complicated so we can just do it for the user if we suspect that the key
|
|
403
|
+
# isn't a named volume.
|
|
404
|
+
if NAMED_VOLUME_PATTERN.match(key):
|
|
405
|
+
volume = {"Name": key, "Dest": value["bind"], "Options": options}
|
|
406
|
+
params["volumes"].append(volume)
|
|
407
|
+
else:
|
|
408
|
+
mount_point = {
|
|
409
|
+
"destination": value["bind"],
|
|
410
|
+
"options": options,
|
|
411
|
+
"source": key,
|
|
412
|
+
"type": "bind",
|
|
413
|
+
}
|
|
414
|
+
params["mounts"].append(mount_point)
|
|
415
|
+
|
|
416
|
+
for item in args.pop("secrets", []):
|
|
417
|
+
if isinstance(item, Secret):
|
|
418
|
+
params["secrets"].append({"source": item.id})
|
|
419
|
+
elif isinstance(item, str):
|
|
420
|
+
params["secrets"].append({"source": item})
|
|
421
|
+
elif isinstance(item, dict):
|
|
422
|
+
secret = {}
|
|
423
|
+
secret_opts = ["source", "target", "uid", "gid", "mode"]
|
|
424
|
+
for k, v in item.items():
|
|
425
|
+
if k in secret_opts:
|
|
426
|
+
secret.update({k: v})
|
|
427
|
+
params["secrets"].append(secret)
|
|
428
|
+
|
|
429
|
+
if "secret_env" in args:
|
|
430
|
+
params["secret_env"] = args.pop("secret_env", {})
|
|
431
|
+
|
|
432
|
+
if "cgroupns" in args:
|
|
433
|
+
params["cgroupns"] = normalize_nsmode(args.pop("cgroupns"))
|
|
434
|
+
|
|
435
|
+
if "ipc_mode" in args:
|
|
436
|
+
params["ipcns"] = normalize_nsmode(args.pop("ipc_mode"))
|
|
437
|
+
|
|
438
|
+
if "network_mode" in args:
|
|
439
|
+
params["netns"] = normalize_nsmode(args.pop("network_mode"))
|
|
440
|
+
|
|
441
|
+
if "pid_mode" in args:
|
|
442
|
+
params["pidns"] = normalize_nsmode(args.pop("pid_mode"))
|
|
443
|
+
|
|
444
|
+
if "userns_mode" in args:
|
|
445
|
+
params["userns"] = normalize_nsmode(args.pop("userns_mode"))
|
|
446
|
+
|
|
447
|
+
if "uts_mode" in args:
|
|
448
|
+
params["utsns"] = normalize_nsmode(args.pop("uts_mode"))
|
|
449
|
+
|
|
450
|
+
if len(args) > 0:
|
|
451
|
+
raise TypeError(
|
|
452
|
+
"Unknown keyword argument(s): " + " ,".join(f"'{k}'" for k in args),
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
return params
|