omdev 0.0.0.dev210__py3-none-any.whl → 0.0.0.dev212__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.
- omdev/.manifests.json +15 -1
- omdev/__about__.py +0 -4
- omdev/amalg/gen.py +2 -3
- omdev/amalg/imports.py +4 -5
- omdev/amalg/manifests.py +7 -10
- omdev/amalg/resources.py +24 -27
- omdev/amalg/srcfiles.py +7 -10
- omdev/amalg/strip.py +4 -5
- omdev/amalg/types.py +1 -1
- omdev/amalg/typing.py +9 -8
- omdev/ci/cache.py +137 -10
- omdev/ci/ci.py +110 -75
- omdev/ci/cli.py +51 -11
- omdev/ci/compose.py +34 -15
- omdev/ci/{dockertars.py → docker.py} +43 -30
- omdev/ci/github/__init__.py +0 -0
- omdev/ci/github/bootstrap.py +11 -0
- omdev/ci/github/cache.py +355 -0
- omdev/ci/github/cacheapi.py +207 -0
- omdev/ci/github/cli.py +39 -0
- omdev/ci/requirements.py +3 -2
- omdev/ci/shell.py +42 -0
- omdev/ci/utils.py +49 -0
- omdev/scripts/ci.py +1734 -473
- omdev/scripts/interp.py +22 -22
- omdev/scripts/pyproject.py +22 -22
- omdev/tokens/__init__.py +0 -0
- omdev/tokens/all.py +35 -0
- omdev/tokens/tokenizert.py +217 -0
- omdev/{tokens.py → tokens/utils.py} +6 -12
- omdev/tools/mkenv.py +131 -0
- omdev/tools/mkrelimp.py +4 -6
- {omdev-0.0.0.dev210.dist-info → omdev-0.0.0.dev212.dist-info}/METADATA +2 -5
- {omdev-0.0.0.dev210.dist-info → omdev-0.0.0.dev212.dist-info}/RECORD +38 -28
- {omdev-0.0.0.dev210.dist-info → omdev-0.0.0.dev212.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev210.dist-info → omdev-0.0.0.dev212.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev210.dist-info → omdev-0.0.0.dev212.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev210.dist-info → omdev-0.0.0.dev212.dist-info}/top_level.txt +0 -0
omdev/ci/ci.py
CHANGED
@@ -13,19 +13,19 @@ from omlish.lite.contextmanagers import ExitStacked
|
|
13
13
|
from omlish.lite.contextmanagers import defer
|
14
14
|
|
15
15
|
from .cache import FileCache
|
16
|
+
from .cache import ShellCache
|
16
17
|
from .compose import DockerComposeRun
|
17
18
|
from .compose import get_compose_service_dependencies
|
18
|
-
from .
|
19
|
-
from .
|
20
|
-
from .
|
21
|
-
from .
|
22
|
-
from .
|
23
|
-
from .
|
19
|
+
from .docker import build_docker_file_hash
|
20
|
+
from .docker import build_docker_image
|
21
|
+
from .docker import is_docker_image_present
|
22
|
+
from .docker import load_docker_tar_cmd
|
23
|
+
from .docker import pull_docker_image
|
24
|
+
from .docker import save_docker_tar_cmd
|
24
25
|
from .requirements import build_requirements_hash
|
25
26
|
from .requirements import download_requirements
|
26
|
-
|
27
|
-
|
28
|
-
##
|
27
|
+
from .shell import ShellCmd
|
28
|
+
from .utils import log_timing_context
|
29
29
|
|
30
30
|
|
31
31
|
class Ci(ExitStacked):
|
@@ -40,8 +40,12 @@ class Ci(ExitStacked):
|
|
40
40
|
compose_file: str
|
41
41
|
service: str
|
42
42
|
|
43
|
+
cmd: ShellCmd
|
44
|
+
|
43
45
|
requirements_txts: ta.Optional[ta.Sequence[str]] = None
|
44
46
|
|
47
|
+
always_pull: bool = False
|
48
|
+
|
45
49
|
def __post_init__(self) -> None:
|
46
50
|
check.not_isinstance(self.requirements_txts, str)
|
47
51
|
|
@@ -49,40 +53,61 @@ class Ci(ExitStacked):
|
|
49
53
|
self,
|
50
54
|
cfg: Config,
|
51
55
|
*,
|
56
|
+
shell_cache: ta.Optional[ShellCache] = None,
|
52
57
|
file_cache: ta.Optional[FileCache] = None,
|
53
58
|
) -> None:
|
54
59
|
super().__init__()
|
55
60
|
|
56
61
|
self._cfg = cfg
|
62
|
+
self._shell_cache = shell_cache
|
57
63
|
self._file_cache = file_cache
|
58
64
|
|
59
65
|
#
|
60
66
|
|
61
|
-
def
|
62
|
-
if
|
67
|
+
def _load_cache_docker_image(self, key: str) -> ta.Optional[str]:
|
68
|
+
if self._shell_cache is None:
|
69
|
+
return None
|
70
|
+
|
71
|
+
get_cache_cmd = self._shell_cache.get_file_cmd(key)
|
72
|
+
if get_cache_cmd is None:
|
73
|
+
return None
|
74
|
+
|
75
|
+
get_cache_cmd = dc.replace(get_cache_cmd, s=f'{get_cache_cmd.s} | zstd -cd --long') # noqa
|
76
|
+
|
77
|
+
return load_docker_tar_cmd(get_cache_cmd)
|
78
|
+
|
79
|
+
def _save_cache_docker_image(self, key: str, image: str) -> None:
|
80
|
+
if self._shell_cache is None:
|
81
|
+
return
|
82
|
+
|
83
|
+
with self._shell_cache.put_file_cmd(key) as put_cache:
|
84
|
+
put_cache_cmd = put_cache.cmd
|
85
|
+
|
86
|
+
put_cache_cmd = dc.replace(put_cache_cmd, s=f'zstd | {put_cache_cmd.s}')
|
87
|
+
|
88
|
+
save_docker_tar_cmd(image, put_cache_cmd)
|
89
|
+
|
90
|
+
#
|
91
|
+
|
92
|
+
def _load_docker_image(self, image: str) -> None:
|
93
|
+
if not self._cfg.always_pull and is_docker_image_present(image):
|
63
94
|
return
|
64
95
|
|
65
96
|
dep_suffix = image
|
66
97
|
for c in '/:.-_':
|
67
98
|
dep_suffix = dep_suffix.replace(c, '-')
|
68
99
|
|
69
|
-
|
70
|
-
|
71
|
-
if self._file_cache is not None and (cache_tar_file := self._file_cache.get_file(tar_file_name)):
|
72
|
-
load_docker_tar(cache_tar_file)
|
100
|
+
cache_key = f'docker-{dep_suffix}'
|
101
|
+
if self._load_cache_docker_image(cache_key) is not None:
|
73
102
|
return
|
74
103
|
|
75
|
-
|
76
|
-
with defer(lambda: shutil.rmtree(temp_dir)):
|
77
|
-
temp_tar_file = os.path.join(temp_dir, tar_file_name)
|
104
|
+
pull_docker_image(image)
|
78
105
|
|
79
|
-
|
80
|
-
image,
|
81
|
-
temp_tar_file,
|
82
|
-
)
|
106
|
+
self._save_cache_docker_image(cache_key, image)
|
83
107
|
|
84
|
-
|
85
|
-
|
108
|
+
def load_docker_image(self, image: str) -> None:
|
109
|
+
with log_timing_context(f'Load docker image: {image}'):
|
110
|
+
self._load_docker_image(image)
|
86
111
|
|
87
112
|
@cached_nullary
|
88
113
|
def load_compose_service_dependencies(self) -> None:
|
@@ -96,46 +121,46 @@ class Ci(ExitStacked):
|
|
96
121
|
|
97
122
|
#
|
98
123
|
|
99
|
-
|
100
|
-
def build_ci_image(self) -> str:
|
124
|
+
def _resolve_ci_image(self) -> str:
|
101
125
|
docker_file_hash = build_docker_file_hash(self._cfg.docker_file)[:self.FILE_NAME_HASH_LEN]
|
102
126
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
image_id = read_docker_tar_image_id(cache_tar_file)
|
107
|
-
load_docker_tar(cache_tar_file)
|
108
|
-
return image_id
|
127
|
+
cache_key = f'ci-{docker_file_hash}'
|
128
|
+
if (cache_image_id := self._load_cache_docker_image(cache_key)) is not None:
|
129
|
+
return cache_image_id
|
109
130
|
|
110
|
-
|
111
|
-
|
112
|
-
|
131
|
+
image_id = build_docker_image(
|
132
|
+
self._cfg.docker_file,
|
133
|
+
cwd=self._cfg.project_dir,
|
134
|
+
)
|
113
135
|
|
114
|
-
|
115
|
-
self._cfg.docker_file,
|
116
|
-
temp_tar_file,
|
117
|
-
cwd=self._cfg.project_dir,
|
118
|
-
)
|
136
|
+
self._save_cache_docker_image(cache_key, image_id)
|
119
137
|
|
120
|
-
|
121
|
-
self._file_cache.put_file(temp_tar_file)
|
138
|
+
return image_id
|
122
139
|
|
140
|
+
@cached_nullary
|
141
|
+
def resolve_ci_image(self) -> str:
|
142
|
+
with log_timing_context('Resolve ci image') as ltc:
|
143
|
+
image_id = self._resolve_ci_image()
|
144
|
+
ltc.set_description(f'Resolve ci image: {image_id}')
|
123
145
|
return image_id
|
124
146
|
|
125
147
|
#
|
126
148
|
|
127
|
-
|
128
|
-
|
129
|
-
|
149
|
+
def _resolve_requirements_dir(self) -> str:
|
150
|
+
requirements_txts = [
|
151
|
+
os.path.join(self._cfg.project_dir, rf)
|
152
|
+
for rf in check.not_none(self._cfg.requirements_txts)
|
153
|
+
]
|
130
154
|
|
131
155
|
requirements_hash = build_requirements_hash(requirements_txts)[:self.FILE_NAME_HASH_LEN]
|
132
156
|
|
133
|
-
|
157
|
+
tar_file_key = f'requirements-{requirements_hash}'
|
158
|
+
tar_file_name = f'{tar_file_key}.tar'
|
134
159
|
|
135
160
|
temp_dir = tempfile.mkdtemp()
|
136
161
|
self._enter_context(defer(lambda: shutil.rmtree(temp_dir))) # noqa
|
137
162
|
|
138
|
-
if self._file_cache is not None and (cache_tar_file := self._file_cache.get_file(
|
163
|
+
if self._file_cache is not None and (cache_tar_file := self._file_cache.get_file(tar_file_key)):
|
139
164
|
with tarfile.open(cache_tar_file) as tar:
|
140
165
|
tar.extractall(path=temp_dir) # noqa
|
141
166
|
|
@@ -145,7 +170,7 @@ class Ci(ExitStacked):
|
|
145
170
|
os.makedirs(temp_requirements_dir)
|
146
171
|
|
147
172
|
download_requirements(
|
148
|
-
self.
|
173
|
+
self.resolve_ci_image(),
|
149
174
|
temp_requirements_dir,
|
150
175
|
requirements_txts,
|
151
176
|
)
|
@@ -160,21 +185,20 @@ class Ci(ExitStacked):
|
|
160
185
|
arcname=requirement_file,
|
161
186
|
)
|
162
187
|
|
163
|
-
self._file_cache.put_file(temp_tar_file)
|
188
|
+
self._file_cache.put_file(os.path.basename(tar_file_key), temp_tar_file)
|
164
189
|
|
165
190
|
return temp_requirements_dir
|
166
191
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
requirements_dir = self.build_requirements_dir()
|
192
|
+
@cached_nullary
|
193
|
+
def resolve_requirements_dir(self) -> str:
|
194
|
+
with log_timing_context('Resolve requirements dir') as ltc:
|
195
|
+
requirements_dir = self._resolve_requirements_dir()
|
196
|
+
ltc.set_description(f'Resolve requirements dir: {requirements_dir}')
|
197
|
+
return requirements_dir
|
175
198
|
|
176
|
-
|
199
|
+
#
|
177
200
|
|
201
|
+
def _run_compose_(self) -> None:
|
178
202
|
setup_cmds = [
|
179
203
|
'pip install --root-user-action ignore --find-links /requirements --no-index uv',
|
180
204
|
(
|
@@ -185,30 +209,41 @@ class Ci(ExitStacked):
|
|
185
209
|
|
186
210
|
#
|
187
211
|
|
188
|
-
|
189
|
-
|
190
|
-
|
212
|
+
ci_cmd = dc.replace(self._cfg.cmd, s=' && '.join([
|
213
|
+
*setup_cmds,
|
214
|
+
f'({self._cfg.cmd.s})',
|
215
|
+
]))
|
191
216
|
|
192
217
|
#
|
193
218
|
|
194
|
-
bash_src = ' && '.join([
|
195
|
-
*setup_cmds,
|
196
|
-
*test_cmds,
|
197
|
-
])
|
198
|
-
|
199
219
|
with DockerComposeRun(DockerComposeRun.Config(
|
200
|
-
|
201
|
-
|
220
|
+
compose_file=self._cfg.compose_file,
|
221
|
+
service=self._cfg.service,
|
202
222
|
|
203
|
-
|
223
|
+
image=self.resolve_ci_image(),
|
204
224
|
|
205
|
-
|
225
|
+
cmd=ci_cmd,
|
206
226
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
227
|
+
run_options=[
|
228
|
+
'-v', f'{os.path.abspath(self._cfg.project_dir)}:/project',
|
229
|
+
'-v', f'{os.path.abspath(self.resolve_requirements_dir())}:/requirements',
|
230
|
+
],
|
211
231
|
|
212
|
-
|
232
|
+
cwd=self._cfg.project_dir,
|
213
233
|
)) as ci_compose_run:
|
214
234
|
ci_compose_run.run()
|
235
|
+
|
236
|
+
def _run_compose(self) -> None:
|
237
|
+
with log_timing_context('Run compose'):
|
238
|
+
self._run_compose_()
|
239
|
+
|
240
|
+
#
|
241
|
+
|
242
|
+
def run(self) -> None:
|
243
|
+
self.load_compose_service_dependencies()
|
244
|
+
|
245
|
+
self.resolve_ci_image()
|
246
|
+
|
247
|
+
self.resolve_requirements_dir()
|
248
|
+
|
249
|
+
self._run_compose()
|
omdev/ci/cli.py
CHANGED
@@ -20,15 +20,18 @@ from omlish.argparse.cli import ArgparseCli
|
|
20
20
|
from omlish.argparse.cli import argparse_arg
|
21
21
|
from omlish.argparse.cli import argparse_cmd
|
22
22
|
from omlish.lite.check import check
|
23
|
+
from omlish.logs.standard import configure_standard_logging
|
23
24
|
|
24
25
|
from .cache import DirectoryFileCache
|
26
|
+
from .cache import DirectoryShellCache
|
25
27
|
from .cache import FileCache
|
28
|
+
from .cache import ShellCache
|
26
29
|
from .ci import Ci
|
27
30
|
from .compose import get_compose_service_dependencies
|
31
|
+
from .github.cache import GithubShellCache
|
32
|
+
from .github.cli import GithubCli
|
28
33
|
from .requirements import build_requirements_hash
|
29
|
-
|
30
|
-
|
31
|
-
##
|
34
|
+
from .shell import ShellCmd
|
32
35
|
|
33
36
|
|
34
37
|
class CiCli(ArgparseCli):
|
@@ -59,23 +62,32 @@ class CiCli(ArgparseCli):
|
|
59
62
|
|
60
63
|
#
|
61
64
|
|
65
|
+
@argparse_cmd(
|
66
|
+
accepts_unknown=True,
|
67
|
+
)
|
68
|
+
def github(self) -> ta.Optional[int]:
|
69
|
+
return GithubCli(self.unknown_args).cli_run()
|
70
|
+
|
71
|
+
#
|
72
|
+
|
62
73
|
@argparse_cmd(
|
63
74
|
argparse_arg('project-dir'),
|
64
75
|
argparse_arg('service'),
|
65
76
|
argparse_arg('--docker-file'),
|
66
77
|
argparse_arg('--compose-file'),
|
67
78
|
argparse_arg('-r', '--requirements-txt', action='append'),
|
79
|
+
argparse_arg('--github-cache', action='store_true'),
|
68
80
|
argparse_arg('--cache-dir'),
|
81
|
+
argparse_arg('--always-pull', action='store_true'),
|
69
82
|
)
|
70
83
|
async def run(self) -> None:
|
71
|
-
await asyncio.sleep(1)
|
72
|
-
|
73
84
|
project_dir = self.args.project_dir
|
74
85
|
docker_file = self.args.docker_file
|
75
86
|
compose_file = self.args.compose_file
|
76
87
|
service = self.args.service
|
77
88
|
requirements_txts = self.args.requirements_txt
|
78
89
|
cache_dir = self.args.cache_dir
|
90
|
+
always_pull = self.args.always_pull
|
79
91
|
|
80
92
|
#
|
81
93
|
|
@@ -85,7 +97,7 @@ class CiCli(ArgparseCli):
|
|
85
97
|
|
86
98
|
def find_alt_file(*alts: str) -> ta.Optional[str]:
|
87
99
|
for alt in alts:
|
88
|
-
alt_file = os.path.join(project_dir, alt)
|
100
|
+
alt_file = os.path.abspath(os.path.join(project_dir, alt))
|
89
101
|
if os.path.isfile(alt_file):
|
90
102
|
return alt_file
|
91
103
|
return None
|
@@ -100,10 +112,16 @@ class CiCli(ArgparseCli):
|
|
100
112
|
check.state(os.path.isfile(docker_file))
|
101
113
|
|
102
114
|
if compose_file is None:
|
103
|
-
compose_file = find_alt_file(
|
104
|
-
'
|
105
|
-
|
106
|
-
|
115
|
+
compose_file = find_alt_file(*[
|
116
|
+
f'{f}.{x}'
|
117
|
+
for f in [
|
118
|
+
'docker/docker-compose',
|
119
|
+
'docker/compose',
|
120
|
+
'docker-compose',
|
121
|
+
'compose',
|
122
|
+
]
|
123
|
+
for x in ['yaml', 'yml']
|
124
|
+
])
|
107
125
|
check.state(os.path.isfile(compose_file))
|
108
126
|
|
109
127
|
if not requirements_txts:
|
@@ -121,24 +139,44 @@ class CiCli(ArgparseCli):
|
|
121
139
|
|
122
140
|
#
|
123
141
|
|
142
|
+
shell_cache: ta.Optional[ShellCache] = None
|
124
143
|
file_cache: ta.Optional[FileCache] = None
|
125
144
|
if cache_dir is not None:
|
126
145
|
if not os.path.exists(cache_dir):
|
127
146
|
os.makedirs(cache_dir)
|
128
147
|
check.state(os.path.isdir(cache_dir))
|
129
|
-
|
148
|
+
|
149
|
+
directory_file_cache = DirectoryFileCache(cache_dir)
|
150
|
+
|
151
|
+
file_cache = directory_file_cache
|
152
|
+
|
153
|
+
if self.args.github_cache:
|
154
|
+
shell_cache = GithubShellCache(cache_dir)
|
155
|
+
else:
|
156
|
+
shell_cache = DirectoryShellCache(directory_file_cache)
|
130
157
|
|
131
158
|
#
|
132
159
|
|
133
160
|
with Ci(
|
134
161
|
Ci.Config(
|
135
162
|
project_dir=project_dir,
|
163
|
+
|
136
164
|
docker_file=docker_file,
|
165
|
+
|
137
166
|
compose_file=compose_file,
|
138
167
|
service=service,
|
168
|
+
|
139
169
|
requirements_txts=requirements_txts,
|
170
|
+
|
171
|
+
cmd=ShellCmd(' && '.join([
|
172
|
+
'cd /project',
|
173
|
+
'python3 -m pytest -svv test.py',
|
174
|
+
])),
|
175
|
+
|
176
|
+
always_pull=always_pull,
|
140
177
|
),
|
141
178
|
file_cache=file_cache,
|
179
|
+
shell_cache=shell_cache,
|
142
180
|
) as ci:
|
143
181
|
ci.run()
|
144
182
|
|
@@ -148,6 +186,8 @@ async def _async_main() -> ta.Optional[int]:
|
|
148
186
|
|
149
187
|
|
150
188
|
def _main() -> None:
|
189
|
+
configure_standard_logging('DEBUG')
|
190
|
+
|
151
191
|
sys.exit(rc if isinstance(rc := asyncio.run(_async_main()), int) else 0)
|
152
192
|
|
153
193
|
|
omdev/ci/compose.py
CHANGED
@@ -4,8 +4,11 @@
|
|
4
4
|
TODO:
|
5
5
|
- fix rmi - only when not referenced anymore
|
6
6
|
"""
|
7
|
+
import contextlib
|
7
8
|
import dataclasses as dc
|
9
|
+
import itertools
|
8
10
|
import os.path
|
11
|
+
import shlex
|
9
12
|
import typing as ta
|
10
13
|
|
11
14
|
from omlish.lite.cached import cached_nullary
|
@@ -15,6 +18,7 @@ from omlish.lite.contextmanagers import defer
|
|
15
18
|
from omlish.lite.json import json_dumps_pretty
|
16
19
|
from omlish.subprocesses import subprocesses
|
17
20
|
|
21
|
+
from .shell import ShellCmd
|
18
22
|
from .utils import make_temp_file
|
19
23
|
from .utils import read_yaml_file
|
20
24
|
|
@@ -50,7 +54,7 @@ class DockerComposeRun(ExitStacked):
|
|
50
54
|
|
51
55
|
image: str
|
52
56
|
|
53
|
-
|
57
|
+
cmd: ShellCmd
|
54
58
|
|
55
59
|
#
|
56
60
|
|
@@ -60,9 +64,11 @@ class DockerComposeRun(ExitStacked):
|
|
60
64
|
|
61
65
|
#
|
62
66
|
|
63
|
-
|
64
|
-
|
67
|
+
no_dependency_cleanup: bool = False
|
68
|
+
|
69
|
+
#
|
65
70
|
|
71
|
+
def __post_init__(self) -> None:
|
66
72
|
check.not_isinstance(self.run_options, str)
|
67
73
|
|
68
74
|
def __init__(self, cfg: Config) -> None:
|
@@ -171,28 +177,41 @@ class DockerComposeRun(ExitStacked):
|
|
171
177
|
|
172
178
|
#
|
173
179
|
|
180
|
+
def _cleanup_dependencies(self) -> None:
|
181
|
+
subprocesses.check_call(
|
182
|
+
'docker',
|
183
|
+
'compose',
|
184
|
+
'-f', self.rewrite_compose_file(),
|
185
|
+
'down',
|
186
|
+
)
|
187
|
+
|
174
188
|
def run(self) -> None:
|
175
189
|
self.tag_image()
|
176
190
|
|
177
191
|
compose_file = self.rewrite_compose_file()
|
178
192
|
|
179
|
-
|
180
|
-
|
193
|
+
with contextlib.ExitStack() as es:
|
194
|
+
if not self._cfg.no_dependency_cleanup:
|
195
|
+
es.enter_context(defer(self._cleanup_dependencies)) # noqa
|
196
|
+
|
197
|
+
sh_cmd = ' '.join([
|
181
198
|
'docker',
|
182
199
|
'compose',
|
183
200
|
'-f', compose_file,
|
184
201
|
'run',
|
185
202
|
'--rm',
|
186
|
-
*
|
203
|
+
*itertools.chain.from_iterable(
|
204
|
+
['-e', k]
|
205
|
+
for k in (self._cfg.cmd.env or [])
|
206
|
+
),
|
207
|
+
*(self._cfg.run_options or []),
|
187
208
|
self._cfg.service,
|
188
|
-
|
189
|
-
|
190
|
-
)
|
209
|
+
'sh', '-c', shlex.quote(self._cfg.cmd.s),
|
210
|
+
])
|
191
211
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
'down',
|
212
|
+
run_cmd = dc.replace(self._cfg.cmd, s=sh_cmd)
|
213
|
+
|
214
|
+
run_cmd.run(
|
215
|
+
subprocesses.check_call,
|
216
|
+
**self._subprocess_kwargs,
|
198
217
|
)
|
@@ -6,8 +6,10 @@ TODO:
|
|
6
6
|
- doesn't change too much though
|
7
7
|
"""
|
8
8
|
import contextlib
|
9
|
+
import dataclasses as dc
|
9
10
|
import json
|
10
11
|
import os.path
|
12
|
+
import shlex
|
11
13
|
import tarfile
|
12
14
|
import typing as ta
|
13
15
|
|
@@ -15,6 +17,7 @@ from omlish.lite.check import check
|
|
15
17
|
from omlish.lite.contextmanagers import defer
|
16
18
|
from omlish.subprocesses import subprocesses
|
17
19
|
|
20
|
+
from .shell import ShellCmd
|
18
21
|
from .utils import make_temp_file
|
19
22
|
from .utils import sha256_str
|
20
23
|
|
@@ -73,12 +76,8 @@ def is_docker_image_present(image: str) -> bool:
|
|
73
76
|
return True
|
74
77
|
|
75
78
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
def pull_docker_tar(
|
79
|
+
def pull_docker_image(
|
80
80
|
image: str,
|
81
|
-
tar_file: str,
|
82
81
|
) -> None:
|
83
82
|
subprocesses.check_call(
|
84
83
|
'docker',
|
@@ -86,19 +85,11 @@ def pull_docker_tar(
|
|
86
85
|
image,
|
87
86
|
)
|
88
87
|
|
89
|
-
subprocesses.check_call(
|
90
|
-
'docker',
|
91
|
-
'save',
|
92
|
-
image,
|
93
|
-
'-o', tar_file,
|
94
|
-
)
|
95
|
-
|
96
88
|
|
97
|
-
def
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
cwd: ta.Optional[str] = None,
|
89
|
+
def build_docker_image(
|
90
|
+
docker_file: str,
|
91
|
+
*,
|
92
|
+
cwd: ta.Optional[str] = None,
|
102
93
|
) -> str:
|
103
94
|
id_file = make_temp_file()
|
104
95
|
with defer(lambda: os.unlink(id_file)):
|
@@ -115,24 +106,46 @@ def build_docker_tar(
|
|
115
106
|
with open(id_file) as f:
|
116
107
|
image_id = check.single(f.read().strip().splitlines()).strip()
|
117
108
|
|
118
|
-
|
119
|
-
'docker',
|
120
|
-
'save',
|
121
|
-
image_id,
|
122
|
-
'-o', tar_file,
|
123
|
-
)
|
124
|
-
|
125
|
-
return image_id
|
109
|
+
return image_id
|
126
110
|
|
127
111
|
|
128
112
|
##
|
129
113
|
|
130
114
|
|
131
|
-
def
|
115
|
+
def save_docker_tar_cmd(
|
116
|
+
image: str,
|
117
|
+
output_cmd: ShellCmd,
|
118
|
+
) -> None:
|
119
|
+
cmd = dc.replace(output_cmd, s=f'docker save {image} | {output_cmd.s}')
|
120
|
+
cmd.run(subprocesses.check_call)
|
121
|
+
|
122
|
+
|
123
|
+
def save_docker_tar(
|
124
|
+
image: str,
|
132
125
|
tar_file: str,
|
133
126
|
) -> None:
|
134
|
-
|
135
|
-
|
136
|
-
'
|
137
|
-
'-i', tar_file,
|
127
|
+
return save_docker_tar_cmd(
|
128
|
+
image,
|
129
|
+
ShellCmd(f'cat > {shlex.quote(tar_file)}'),
|
138
130
|
)
|
131
|
+
|
132
|
+
|
133
|
+
#
|
134
|
+
|
135
|
+
|
136
|
+
def load_docker_tar_cmd(
|
137
|
+
input_cmd: ShellCmd,
|
138
|
+
) -> str:
|
139
|
+
cmd = dc.replace(input_cmd, s=f'{input_cmd.s} | docker load')
|
140
|
+
|
141
|
+
out = cmd.run(subprocesses.check_output).decode()
|
142
|
+
|
143
|
+
line = check.single(out.strip().splitlines())
|
144
|
+
loaded = line.partition(':')[2].strip()
|
145
|
+
return loaded
|
146
|
+
|
147
|
+
|
148
|
+
def load_docker_tar(
|
149
|
+
tar_file: str,
|
150
|
+
) -> str:
|
151
|
+
return load_docker_tar_cmd(ShellCmd(f'cat {shlex.quote(tar_file)}'))
|
File without changes
|