omlish 0.0.0.dev39__py3-none-any.whl → 0.0.0.dev41__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.
omlish/docker.py DELETED
@@ -1,270 +0,0 @@
1
- """
2
- TODO:
3
- - merged compose configs: https://github.com/wrmsr/bane/blob/27647abdcfb323b73e6982a5c318c7029496b203/core/dev/docker/compose.go#L38
4
- - https://github.com/mag37/dockcheck/blob/3d122f2b868eb53a25a3014f0f6bd499390a3a29/dockcheck.sh
5
- - https://github.com/regclient/regclient
6
- - https://stackoverflow.com/questions/71409458/how-to-download-docker-image-using-http-api-using-docker-hub-credentials
7
- - https://stackoverflow.com/questions/55386202/how-can-i-use-the-docker-registry-api-to-pull-information-about-a-container-get
8
- - https://ops.tips/blog/inspecting-docker-image-without-pull/
9
-
10
- """ # noqa
11
- import datetime
12
- import os
13
- import re
14
- import shlex
15
- import subprocess
16
- import sys
17
- import typing as ta
18
- import urllib.request
19
-
20
- from . import check
21
- from . import dataclasses as dc
22
- from . import lang
23
- from . import marshal as msh
24
- from .formats import json
25
-
26
-
27
- if ta.TYPE_CHECKING:
28
- import yaml
29
- else:
30
- yaml = lang.proxy_import('yaml')
31
-
32
-
33
- ##
34
-
35
-
36
- @dc.dataclass(frozen=True)
37
- @msh.update_object_metadata(field_naming=msh.Naming.CAMEL, unknown_field='x')
38
- @msh.update_fields_metadata(['id'], name='ID')
39
- class PsItem(lang.Final):
40
- command: str
41
- created_at: datetime.datetime
42
- id: str
43
- image: str
44
- labels: str
45
- local_volumes: str
46
- mounts: str
47
- names: str
48
- networks: str
49
- ports: str
50
- running_for: str
51
- size: str
52
- state: str
53
- status: str
54
-
55
- x: ta.Mapping[str, ta.Any] | None = None
56
-
57
-
58
- class Port(ta.NamedTuple):
59
- ip: str
60
- from_port: int
61
- to_port: int
62
- proto: str
63
-
64
-
65
- _PORT_PAT = re.compile(r'(?P<ip>[^:]+):(?P<from_port>\d+)->(?P<to_port>\d+)/(?P<proto>\w+)')
66
-
67
-
68
- def parse_port(s: str) -> Port:
69
- # '0.0.0.0:35221->22/tcp, 0.0.0.0:35220->8000/tcp'
70
- m = check.not_none(_PORT_PAT.fullmatch(s))
71
- return Port(
72
- m.group('ip'),
73
- int(m.group('from_port')),
74
- int(m.group('to_port')),
75
- m.group('proto'),
76
- )
77
-
78
-
79
- def cli_ps() -> list[PsItem]:
80
- o = subprocess.check_output([
81
- 'docker',
82
- 'ps',
83
- '--no-trunc',
84
- '--format', '{{json .}}',
85
- ])
86
-
87
- ret: list[PsItem] = []
88
- for l in o.decode().splitlines():
89
- d = json.loads(l)
90
- pi = msh.unmarshal(d, PsItem)
91
- ret.append(pi)
92
-
93
- return ret
94
-
95
-
96
- @dc.dataclass(frozen=True)
97
- @msh.update_object_metadata(field_naming=msh.Naming.CAMEL, unknown_field='x')
98
- class Inspect(lang.Final):
99
- id: str
100
- created: datetime.datetime
101
-
102
- x: ta.Mapping[str, ta.Any] | None = None
103
-
104
-
105
- def cli_inspect(ids: list[str]) -> list[Inspect]:
106
- o = subprocess.check_output(['docker', 'inspect', *ids])
107
- return msh.unmarshal(json.loads(o.decode()), list[Inspect])
108
-
109
-
110
- def has_cli() -> bool:
111
- try:
112
- proc = subprocess.run(['docker', '--version']) # noqa
113
- except (FileNotFoundError, subprocess.CalledProcessError):
114
- return False
115
- else:
116
- return not proc.returncode
117
-
118
-
119
- ##
120
-
121
-
122
- class ComposeConfig:
123
- def __init__(
124
- self,
125
- prefix: str,
126
- *,
127
- file_path: str | None = None,
128
- ) -> None:
129
- super().__init__()
130
-
131
- self._prefix = prefix
132
- self._file_path = file_path
133
-
134
- @lang.cached_function
135
- def get_config(self) -> ta.Mapping[str, ta.Any]:
136
- with open(check.not_none(self._file_path)) as f:
137
- buf = f.read()
138
- return yaml.safe_load(buf)
139
-
140
- @lang.cached_function
141
- def get_services(self) -> ta.Mapping[str, ta.Any]:
142
- ret = {}
143
- for n, c in self.get_config()['services'].items():
144
- check.state(n.startswith(self._prefix))
145
- ret[n[len(self._prefix):]] = c
146
-
147
- return ret
148
-
149
-
150
- def get_compose_port(cfg: ta.Mapping[str, ta.Any], default: int) -> int:
151
- return check.single(
152
- int(l)
153
- for p in cfg['ports']
154
- for l, r in [p.split(':')]
155
- if int(r) == default
156
- )
157
-
158
-
159
- ##
160
-
161
-
162
- def timebomb_payload(delay_s: float, name: str = 'omlish-docker-timebomb') -> str:
163
- return (
164
- '('
165
- f'echo {shlex.quote(name)} && '
166
- f'sleep {delay_s:g} && '
167
- 'sh -c \'killall5 -9 -o $PPID -o $$ ; kill 1\''
168
- ') &'
169
- )
170
-
171
-
172
- ##
173
-
174
-
175
- DOCKER_FOR_MAC_HOSTNAME = 'docker.for.mac.localhost'
176
-
177
-
178
- _LIKELY_IN_DOCKER_PATTERN = re.compile(r'^overlay / .*/docker/')
179
-
180
-
181
- def is_likely_in_docker() -> bool:
182
- if getattr(sys, 'platform') != 'linux':
183
- return False
184
- with open('/proc/mounts') as f:
185
- ls = f.readlines()
186
- return any(_LIKELY_IN_DOCKER_PATTERN.match(l) for l in ls)
187
-
188
-
189
- ##
190
-
191
-
192
- # Set by pyproject, docker-dev script
193
- DOCKER_HOST_PLATFORM_KEY = 'DOCKER_HOST_PLATFORM'
194
-
195
-
196
- def get_docker_host_platform() -> str | None:
197
- return os.environ.get(DOCKER_HOST_PLATFORM_KEY)
198
-
199
-
200
- ##
201
-
202
-
203
- @dc.dataclass(frozen=True)
204
- class HubRepoInfo:
205
- repo: str
206
- tags: ta.Mapping[str, ta.Any]
207
- latest_manifests: ta.Mapping[str, ta.Any]
208
-
209
-
210
- def get_hub_repo_info(
211
- repo: str,
212
- *,
213
- auth_url: str = 'https://auth.docker.io/',
214
- api_url: str = 'https://registry-1.docker.io/v2/',
215
- ) -> HubRepoInfo:
216
- """
217
- https://stackoverflow.com/a/39376254
218
-
219
- ==
220
-
221
- repo=library/nginx
222
- token=$(
223
- curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull" \
224
- | jq -r '.token' \
225
- )
226
- curl -H "Authorization: Bearer $token" -s "https://registry-1.docker.io/v2/${repo}/tags/list" | jq
227
- curl \
228
- -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
229
- -H "Accept: application/vnd.docker.distribution.manifest.list.v2+json" \
230
- -H "Authorization: Bearer $token" \
231
- -s "https://registry-1.docker.io/v2/${repo}/manifests/latest" \
232
- | jq .
233
- """
234
-
235
- auth_url = auth_url.rstrip('/')
236
- api_url = api_url.rstrip('/')
237
-
238
- #
239
-
240
- def req_json(url: str, **kwargs: ta.Any) -> ta.Any:
241
- with urllib.request.urlopen(urllib.request.Request(url, **kwargs)) as resp: # noqa
242
- return json.loads(resp.read().decode('utf-8'))
243
-
244
- #
245
-
246
- token_dct = req_json(f'{auth_url}/token?service=registry.docker.io&scope=repository:{repo}:pull')
247
- token = token_dct['token']
248
-
249
- req_hdrs = {'Authorization': f'Bearer {token}'}
250
-
251
- #
252
-
253
- tags_dct = req_json(
254
- f'{api_url}/{repo}/tags/list',
255
- headers=req_hdrs,
256
- )
257
-
258
- latest_mani_dct = req_json(
259
- f'{api_url}/{repo}/manifests/latest',
260
- headers={
261
- **req_hdrs,
262
- 'Accept': 'application/vnd.docker.distribution.manifest.v2+json',
263
- },
264
- )
265
-
266
- return HubRepoInfo(
267
- repo,
268
- tags_dct,
269
- latest_mani_dct,
270
- )