omdev 0.0.0.dev210__py3-none-any.whl → 0.0.0.dev212__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|