omdev 0.0.0.dev221__py3-none-any.whl → 0.0.0.dev223__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. omdev/ci/cache.py +40 -23
  2. omdev/ci/ci.py +49 -109
  3. omdev/ci/cli.py +24 -23
  4. omdev/ci/docker/__init__.py +0 -0
  5. omdev/ci/docker/buildcaching.py +69 -0
  6. omdev/ci/docker/cache.py +57 -0
  7. omdev/ci/{docker.py → docker/cmds.py} +1 -44
  8. omdev/ci/docker/imagepulling.py +64 -0
  9. omdev/ci/docker/inject.py +37 -0
  10. omdev/ci/docker/utils.py +48 -0
  11. omdev/ci/github/cache.py +15 -5
  12. omdev/ci/github/inject.py +30 -0
  13. omdev/ci/inject.py +61 -0
  14. omdev/dataserver/__init__.py +1 -0
  15. omdev/dataserver/handlers.py +198 -0
  16. omdev/dataserver/http.py +69 -0
  17. omdev/dataserver/routes.py +49 -0
  18. omdev/dataserver/server.py +90 -0
  19. omdev/dataserver/targets.py +89 -0
  20. omdev/oci/__init__.py +0 -0
  21. omdev/oci/building.py +221 -0
  22. omdev/oci/compression.py +8 -0
  23. omdev/oci/data.py +151 -0
  24. omdev/oci/datarefs.py +138 -0
  25. omdev/oci/dataserver.py +61 -0
  26. omdev/oci/loading.py +142 -0
  27. omdev/oci/media.py +179 -0
  28. omdev/oci/packing.py +381 -0
  29. omdev/oci/repositories.py +159 -0
  30. omdev/oci/tars.py +144 -0
  31. omdev/pyproject/resources/python.sh +1 -1
  32. omdev/scripts/ci.py +1841 -384
  33. omdev/scripts/interp.py +100 -22
  34. omdev/scripts/pyproject.py +122 -28
  35. {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/METADATA +2 -2
  36. {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/RECORD +40 -15
  37. {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/LICENSE +0 -0
  38. {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/WHEEL +0 -0
  39. {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/entry_points.txt +0 -0
  40. {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/top_level.txt +0 -0
omdev/ci/cache.py CHANGED
@@ -1,5 +1,6 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  import abc
3
+ import dataclasses as dc
3
4
  import os.path
4
5
  import shutil
5
6
  import typing as ta
@@ -11,24 +12,30 @@ from omlish.lite.logs import log
11
12
  from .consts import CI_CACHE_VERSION
12
13
 
13
14
 
15
+ CacheVersion = ta.NewType('CacheVersion', int)
16
+
17
+
14
18
  ##
15
19
 
16
20
 
17
- @abc.abstractmethod
18
21
  class FileCache(abc.ABC):
22
+ DEFAULT_CACHE_VERSION: ta.ClassVar[CacheVersion] = CacheVersion(CI_CACHE_VERSION)
23
+
19
24
  def __init__(
20
25
  self,
21
26
  *,
22
- version: int = CI_CACHE_VERSION,
27
+ version: ta.Optional[CacheVersion] = None,
23
28
  ) -> None:
24
29
  super().__init__()
25
30
 
31
+ if version is None:
32
+ version = self.DEFAULT_CACHE_VERSION
26
33
  check.isinstance(version, int)
27
34
  check.arg(version >= 0)
28
- self._version = version
35
+ self._version: CacheVersion = version
29
36
 
30
37
  @property
31
- def version(self) -> int:
38
+ def version(self) -> CacheVersion:
32
39
  return self._version
33
40
 
34
41
  #
@@ -52,19 +59,28 @@ class FileCache(abc.ABC):
52
59
 
53
60
 
54
61
  class DirectoryFileCache(FileCache):
62
+ @dc.dataclass(frozen=True)
63
+ class Config:
64
+ dir: str
65
+
66
+ no_create: bool = False
67
+ no_purge: bool = False
68
+
55
69
  def __init__(
56
70
  self,
57
- dir: str, # noqa
71
+ config: Config,
58
72
  *,
59
- no_create: bool = False,
60
- no_purge: bool = False,
61
- **kwargs: ta.Any,
73
+ version: ta.Optional[CacheVersion] = None,
62
74
  ) -> None: # noqa
63
- super().__init__(**kwargs)
75
+ super().__init__(
76
+ version=version,
77
+ )
78
+
79
+ self._config = config
64
80
 
65
- self._dir = dir
66
- self._no_create = no_create
67
- self._no_purge = no_purge
81
+ @property
82
+ def dir(self) -> str:
83
+ return self._config.dir
68
84
 
69
85
  #
70
86
 
@@ -72,37 +88,38 @@ class DirectoryFileCache(FileCache):
72
88
 
73
89
  @cached_nullary
74
90
  def setup_dir(self) -> None:
75
- version_file = os.path.join(self._dir, self.VERSION_FILE_NAME)
91
+ version_file = os.path.join(self.dir, self.VERSION_FILE_NAME)
76
92
 
77
- if self._no_create:
78
- check.state(os.path.isdir(self._dir))
93
+ if self._config.no_create:
94
+ check.state(os.path.isdir(self.dir))
79
95
 
80
- elif not os.path.isdir(self._dir):
81
- os.makedirs(self._dir)
96
+ elif not os.path.isdir(self.dir):
97
+ os.makedirs(self.dir)
82
98
  with open(version_file, 'w') as f:
83
99
  f.write(str(self._version))
84
100
  return
85
101
 
102
+ # NOTE: intentionally raises FileNotFoundError to refuse to use an existing non-cache dir as a cache dir.
86
103
  with open(version_file) as f:
87
104
  dir_version = int(f.read().strip())
88
105
 
89
106
  if dir_version == self._version:
90
107
  return
91
108
 
92
- if self._no_purge:
109
+ if self._config.no_purge:
93
110
  raise RuntimeError(f'{dir_version=} != {self._version=}')
94
111
 
95
- dirs = [n for n in sorted(os.listdir(self._dir)) if os.path.isdir(os.path.join(self._dir, n))]
112
+ dirs = [n for n in sorted(os.listdir(self.dir)) if os.path.isdir(os.path.join(self.dir, n))]
96
113
  if dirs:
97
114
  raise RuntimeError(
98
- f'Refusing to remove stale cache dir {self._dir!r} '
115
+ f'Refusing to remove stale cache dir {self.dir!r} '
99
116
  f'due to present directories: {", ".join(dirs)}',
100
117
  )
101
118
 
102
- for n in sorted(os.listdir(self._dir)):
119
+ for n in sorted(os.listdir(self.dir)):
103
120
  if n.startswith('.'):
104
121
  continue
105
- fp = os.path.join(self._dir, n)
122
+ fp = os.path.join(self.dir, n)
106
123
  check.state(os.path.isfile(fp))
107
124
  log.debug('Purging stale cache file: %s', fp)
108
125
  os.unlink(fp)
@@ -119,7 +136,7 @@ class DirectoryFileCache(FileCache):
119
136
  key: str,
120
137
  ) -> str:
121
138
  self.setup_dir()
122
- return os.path.join(self._dir, key)
139
+ return os.path.join(self.dir, key)
123
140
 
124
141
  def format_incomplete_file(self, f: str) -> str:
125
142
  return os.path.join(os.path.dirname(f), f'_{os.path.basename(f)}.incomplete')
omdev/ci/ci.py CHANGED
@@ -9,21 +9,20 @@ from omlish.lite.check import check
9
9
  from omlish.lite.contextmanagers import AsyncExitStacked
10
10
  from omlish.os.temp import temp_file_context
11
11
 
12
- from .cache import FileCache
13
12
  from .compose import DockerComposeRun
14
13
  from .compose import get_compose_service_dependencies
15
- from .docker import build_docker_file_hash
16
- from .docker import build_docker_image
17
- from .docker import is_docker_image_present
18
- from .docker import load_docker_tar_cmd
19
- from .docker import pull_docker_image
20
- from .docker import save_docker_tar_cmd
21
- from .docker import tag_docker_image
14
+ from .docker.buildcaching import DockerBuildCaching
15
+ from .docker.cmds import build_docker_image
16
+ from .docker.imagepulling import DockerImagePulling
17
+ from .docker.utils import build_docker_file_hash
22
18
  from .requirements import build_requirements_hash
23
19
  from .shell import ShellCmd
24
20
  from .utils import log_timing_context
25
21
 
26
22
 
23
+ ##
24
+
25
+
27
26
  class Ci(AsyncExitStacked):
28
27
  KEY_HASH_LEN = 16
29
28
 
@@ -56,104 +55,40 @@ class Ci(AsyncExitStacked):
56
55
 
57
56
  def __init__(
58
57
  self,
59
- cfg: Config,
58
+ config: Config,
60
59
  *,
61
- file_cache: ta.Optional[FileCache] = None,
60
+ docker_build_caching: DockerBuildCaching,
61
+ docker_image_pulling: DockerImagePulling,
62
62
  ) -> None:
63
63
  super().__init__()
64
64
 
65
- self._cfg = cfg
66
- self._file_cache = file_cache
67
-
68
- #
69
-
70
- async def _load_docker_image(self, image: str) -> None:
71
- if not self._cfg.always_pull and (await is_docker_image_present(image)):
72
- return
73
-
74
- dep_suffix = image
75
- for c in '/:.-_':
76
- dep_suffix = dep_suffix.replace(c, '-')
77
-
78
- cache_key = f'docker-{dep_suffix}'
79
- if (await self._load_cache_docker_image(cache_key)) is not None:
80
- return
81
-
82
- await pull_docker_image(image)
83
-
84
- await self._save_cache_docker_image(cache_key, image)
65
+ self._config = config
85
66
 
86
- async def load_docker_image(self, image: str) -> None:
87
- with log_timing_context(f'Load docker image: {image}'):
88
- await self._load_docker_image(image)
89
-
90
- #
91
-
92
- async def _load_cache_docker_image(self, key: str) -> ta.Optional[str]:
93
- if self._file_cache is None:
94
- return None
95
-
96
- cache_file = await self._file_cache.get_file(key)
97
- if cache_file is None:
98
- return None
99
-
100
- get_cache_cmd = ShellCmd(f'cat {cache_file} | zstd -cd --long')
101
-
102
- return await load_docker_tar_cmd(get_cache_cmd)
103
-
104
- async def _save_cache_docker_image(self, key: str, image: str) -> None:
105
- if self._file_cache is None:
106
- return
107
-
108
- with temp_file_context() as tmp_file:
109
- write_tmp_cmd = ShellCmd(f'zstd > {tmp_file}')
110
-
111
- await save_docker_tar_cmd(image, write_tmp_cmd)
112
-
113
- await self._file_cache.put_file(key, tmp_file, steal=True)
114
-
115
- #
116
-
117
- async def _resolve_docker_image(
118
- self,
119
- cache_key: str,
120
- build_and_tag: ta.Callable[[str], ta.Awaitable[str]],
121
- ) -> str:
122
- image_tag = f'{self._cfg.service}:{cache_key}'
123
-
124
- if not self._cfg.always_build and (await is_docker_image_present(image_tag)):
125
- return image_tag
126
-
127
- if (cache_image_id := await self._load_cache_docker_image(cache_key)) is not None:
128
- await tag_docker_image(
129
- cache_image_id,
130
- image_tag,
131
- )
132
- return image_tag
133
-
134
- image_id = await build_and_tag(image_tag)
135
-
136
- await self._save_cache_docker_image(cache_key, image_id)
137
-
138
- return image_tag
67
+ self._docker_build_caching = docker_build_caching
68
+ self._docker_image_pulling = docker_image_pulling
139
69
 
140
70
  #
141
71
 
142
72
  @cached_nullary
143
73
  def docker_file_hash(self) -> str:
144
- return build_docker_file_hash(self._cfg.docker_file)[:self.KEY_HASH_LEN]
74
+ return build_docker_file_hash(self._config.docker_file)[:self.KEY_HASH_LEN]
75
+
76
+ @cached_nullary
77
+ def ci_base_image_cache_key(self) -> str:
78
+ return f'ci-base-{self.docker_file_hash()}'
145
79
 
146
80
  async def _resolve_ci_base_image(self) -> str:
147
81
  async def build_and_tag(image_tag: str) -> str:
148
82
  return await build_docker_image(
149
- self._cfg.docker_file,
83
+ self._config.docker_file,
150
84
  tag=image_tag,
151
- cwd=self._cfg.project_dir,
85
+ cwd=self._config.project_dir,
152
86
  )
153
87
 
154
- cache_key = f'ci-base-{self.docker_file_hash()}'
155
-
156
- return await self._resolve_docker_image(cache_key, build_and_tag)
88
+ return await self._docker_build_caching.cached_build_docker_image(
89
+ self.ci_base_image_cache_key(),
90
+ build_and_tag,
91
+ )
157
92
 
158
93
  @async_cached_nullary
159
94
  async def resolve_ci_base_image(self) -> str:
@@ -167,14 +102,18 @@ class Ci(AsyncExitStacked):
167
102
  @cached_nullary
168
103
  def requirements_txts(self) -> ta.Sequence[str]:
169
104
  return [
170
- os.path.join(self._cfg.project_dir, rf)
171
- for rf in check.not_none(self._cfg.requirements_txts)
105
+ os.path.join(self._config.project_dir, rf)
106
+ for rf in check.not_none(self._config.requirements_txts)
172
107
  ]
173
108
 
174
109
  @cached_nullary
175
110
  def requirements_hash(self) -> str:
176
111
  return build_requirements_hash(self.requirements_txts())[:self.KEY_HASH_LEN]
177
112
 
113
+ @cached_nullary
114
+ def ci_image_cache_key(self) -> str:
115
+ return f'ci-{self.docker_file_hash()}-{self.requirements_hash()}'
116
+
178
117
  async def _resolve_ci_image(self) -> str:
179
118
  async def build_and_tag(image_tag: str) -> str:
180
119
  base_image = await self.resolve_ci_base_image()
@@ -191,7 +130,7 @@ class Ci(AsyncExitStacked):
191
130
  '--no-cache',
192
131
  '--index-strategy unsafe-best-match',
193
132
  '--system',
194
- *[f'-r /project/{rf}' for rf in self._cfg.requirements_txts or []],
133
+ *[f'-r /project/{rf}' for rf in self._config.requirements_txts or []],
195
134
  ]),
196
135
  ]
197
136
  setup_cmd = ' && '.join(setup_cmds)
@@ -199,7 +138,7 @@ class Ci(AsyncExitStacked):
199
138
  docker_file_lines = [
200
139
  f'FROM {base_image}',
201
140
  'RUN mkdir /project',
202
- *[f'COPY {rf} /project/{rf}' for rf in self._cfg.requirements_txts or []],
141
+ *[f'COPY {rf} /project/{rf}' for rf in self._config.requirements_txts or []],
203
142
  f'RUN {setup_cmd}',
204
143
  'RUN rm /project/*',
205
144
  'WORKDIR /project',
@@ -212,12 +151,13 @@ class Ci(AsyncExitStacked):
212
151
  return await build_docker_image(
213
152
  docker_file,
214
153
  tag=image_tag,
215
- cwd=self._cfg.project_dir,
154
+ cwd=self._config.project_dir,
216
155
  )
217
156
 
218
- cache_key = f'ci-{self.docker_file_hash()}-{self.requirements_hash()}'
219
-
220
- return await self._resolve_docker_image(cache_key, build_and_tag)
157
+ return await self._docker_build_caching.cached_build_docker_image(
158
+ self.ci_image_cache_key(),
159
+ build_and_tag,
160
+ )
221
161
 
222
162
  @async_cached_nullary
223
163
  async def resolve_ci_image(self) -> str:
@@ -229,34 +169,34 @@ class Ci(AsyncExitStacked):
229
169
  #
230
170
 
231
171
  @async_cached_nullary
232
- async def load_dependencies(self) -> None:
172
+ async def pull_dependencies(self) -> None:
233
173
  deps = get_compose_service_dependencies(
234
- self._cfg.compose_file,
235
- self._cfg.service,
174
+ self._config.compose_file,
175
+ self._config.service,
236
176
  )
237
177
 
238
178
  for dep_image in deps.values():
239
- await self.load_docker_image(dep_image)
179
+ await self._docker_image_pulling.pull_docker_image(dep_image)
240
180
 
241
181
  #
242
182
 
243
183
  async def _run_compose_(self) -> None:
244
184
  async with DockerComposeRun(DockerComposeRun.Config(
245
- compose_file=self._cfg.compose_file,
246
- service=self._cfg.service,
185
+ compose_file=self._config.compose_file,
186
+ service=self._config.service,
247
187
 
248
188
  image=await self.resolve_ci_image(),
249
189
 
250
- cmd=self._cfg.cmd,
190
+ cmd=self._config.cmd,
251
191
 
252
192
  run_options=[
253
- '-v', f'{os.path.abspath(self._cfg.project_dir)}:/project',
254
- *(self._cfg.run_options or []),
193
+ '-v', f'{os.path.abspath(self._config.project_dir)}:/project',
194
+ *(self._config.run_options or []),
255
195
  ],
256
196
 
257
- cwd=self._cfg.project_dir,
197
+ cwd=self._config.project_dir,
258
198
 
259
- no_dependencies=self._cfg.no_dependencies,
199
+ no_dependencies=self._config.no_dependencies,
260
200
  )) as ci_compose_run:
261
201
  await ci_compose_run.run()
262
202
 
@@ -269,6 +209,6 @@ class Ci(AsyncExitStacked):
269
209
  async def run(self) -> None:
270
210
  await self.resolve_ci_image()
271
211
 
272
- await self.load_dependencies()
212
+ await self.pull_dependencies()
273
213
 
274
214
  await self._run_compose()
omdev/ci/cli.py CHANGED
@@ -21,16 +21,15 @@ from omlish.argparse.cli import ArgparseCli
21
21
  from omlish.argparse.cli import argparse_arg
22
22
  from omlish.argparse.cli import argparse_cmd
23
23
  from omlish.lite.check import check
24
+ from omlish.lite.inject import inj
24
25
  from omlish.lite.logs import log
25
26
  from omlish.logs.standard import configure_standard_logging
26
27
 
27
- from .cache import DirectoryFileCache
28
- from .cache import FileCache
29
28
  from .ci import Ci
30
29
  from .compose import get_compose_service_dependencies
31
30
  from .github.bootstrap import is_in_github_actions
32
- from .github.cache import GithubFileCache
33
31
  from .github.cli import GithubCli
32
+ from .inject import bind_ci
34
33
  from .requirements import build_requirements_hash
35
34
  from .shell import ShellCmd
36
35
 
@@ -165,14 +164,9 @@ class CiCli(ArgparseCli):
165
164
 
166
165
  #
167
166
 
168
- file_cache: ta.Optional[FileCache] = None
169
167
  if cache_dir is not None:
170
168
  cache_dir = os.path.abspath(cache_dir)
171
169
  log.debug('Using cache dir %s', cache_dir)
172
- if github:
173
- file_cache = GithubFileCache(cache_dir)
174
- else:
175
- file_cache = DirectoryFileCache(cache_dir)
176
170
 
177
171
  #
178
172
 
@@ -188,28 +182,35 @@ class CiCli(ArgparseCli):
188
182
 
189
183
  #
190
184
 
191
- async with Ci(
192
- Ci.Config(
193
- project_dir=project_dir,
185
+ config = Ci.Config(
186
+ project_dir=project_dir,
187
+
188
+ docker_file=docker_file,
189
+
190
+ compose_file=compose_file,
191
+ service=self.args.service,
194
192
 
195
- docker_file=docker_file,
193
+ requirements_txts=requirements_txts,
196
194
 
197
- compose_file=compose_file,
198
- service=self.args.service,
195
+ cmd=ShellCmd(cmd),
199
196
 
200
- requirements_txts=requirements_txts,
197
+ always_pull=self.args.always_pull,
198
+ always_build=self.args.always_build,
201
199
 
202
- cmd=ShellCmd(cmd),
200
+ no_dependencies=self.args.no_dependencies,
203
201
 
204
- always_pull=self.args.always_pull,
205
- always_build=self.args.always_build,
202
+ run_options=run_options,
203
+ )
206
204
 
207
- no_dependencies=self.args.no_dependencies,
205
+ injector = inj.create_injector(bind_ci(
206
+ config=config,
207
+
208
+ github=github,
209
+
210
+ cache_dir=cache_dir,
211
+ ))
208
212
 
209
- run_options=run_options,
210
- ),
211
- file_cache=file_cache,
212
- ) as ci:
213
+ async with injector[Ci] as ci:
213
214
  await ci.run()
214
215
 
215
216
 
File without changes
@@ -0,0 +1,69 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import abc
3
+ import dataclasses as dc
4
+ import typing as ta
5
+
6
+ from .cache import DockerCache
7
+ from .cmds import is_docker_image_present
8
+ from .cmds import tag_docker_image
9
+
10
+
11
+ ##
12
+
13
+
14
+ class DockerBuildCaching(abc.ABC):
15
+ @abc.abstractmethod
16
+ def cached_build_docker_image(
17
+ self,
18
+ cache_key: str,
19
+ build_and_tag: ta.Callable[[str], ta.Awaitable[str]], # image_tag -> image_id
20
+ ) -> ta.Awaitable[str]:
21
+ raise NotImplementedError
22
+
23
+
24
+ class DockerBuildCachingImpl(DockerBuildCaching):
25
+ @dc.dataclass(frozen=True)
26
+ class Config:
27
+ service: str
28
+
29
+ always_build: bool = False
30
+
31
+ def __init__(
32
+ self,
33
+ *,
34
+ config: Config,
35
+
36
+ docker_cache: ta.Optional[DockerCache] = None,
37
+ ) -> None:
38
+ super().__init__()
39
+
40
+ self._config = config
41
+
42
+ self._docker_cache = docker_cache
43
+
44
+ async def cached_build_docker_image(
45
+ self,
46
+ cache_key: str,
47
+ build_and_tag: ta.Callable[[str], ta.Awaitable[str]],
48
+ ) -> str:
49
+ image_tag = f'{self._config.service}:{cache_key}'
50
+
51
+ if not self._config.always_build and (await is_docker_image_present(image_tag)):
52
+ return image_tag
53
+
54
+ if (
55
+ self._docker_cache is not None and
56
+ (cache_image_id := await self._docker_cache.load_cache_docker_image(cache_key)) is not None
57
+ ):
58
+ await tag_docker_image(
59
+ cache_image_id,
60
+ image_tag,
61
+ )
62
+ return image_tag
63
+
64
+ image_id = await build_and_tag(image_tag)
65
+
66
+ if self._docker_cache is not None:
67
+ await self._docker_cache.save_cache_docker_image(cache_key, image_id)
68
+
69
+ return image_tag
@@ -0,0 +1,57 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import abc
3
+ import typing as ta
4
+
5
+ from omlish.os.temp import temp_file_context
6
+
7
+ from ..cache import FileCache
8
+ from ..shell import ShellCmd
9
+ from .cmds import load_docker_tar_cmd
10
+ from .cmds import save_docker_tar_cmd
11
+
12
+
13
+ ##
14
+
15
+
16
+ class DockerCache(abc.ABC):
17
+ @abc.abstractmethod
18
+ def load_cache_docker_image(self, key: str) -> ta.Awaitable[ta.Optional[str]]:
19
+ raise NotImplementedError
20
+
21
+ @abc.abstractmethod
22
+ def save_cache_docker_image(self, key: str, image: str) -> ta.Awaitable[None]:
23
+ raise NotImplementedError
24
+
25
+
26
+ class DockerCacheImpl(DockerCache):
27
+ def __init__(
28
+ self,
29
+ *,
30
+ file_cache: ta.Optional[FileCache] = None,
31
+ ) -> None:
32
+ super().__init__()
33
+
34
+ self._file_cache = file_cache
35
+
36
+ async def load_cache_docker_image(self, key: str) -> ta.Optional[str]:
37
+ if self._file_cache is None:
38
+ return None
39
+
40
+ cache_file = await self._file_cache.get_file(key)
41
+ if cache_file is None:
42
+ return None
43
+
44
+ get_cache_cmd = ShellCmd(f'cat {cache_file} | zstd -cd --long')
45
+
46
+ return await load_docker_tar_cmd(get_cache_cmd)
47
+
48
+ async def save_cache_docker_image(self, key: str, image: str) -> None:
49
+ if self._file_cache is None:
50
+ return
51
+
52
+ with temp_file_context() as tmp_file:
53
+ write_tmp_cmd = ShellCmd(f'zstd > {tmp_file}')
54
+
55
+ await save_docker_tar_cmd(image, write_tmp_cmd)
56
+
57
+ await self._file_cache.put_file(key, tmp_file, steal=True)
@@ -1,58 +1,15 @@
1
1
  # ruff: noqa: UP006 UP007
2
- """
3
- TODO:
4
- - some less stupid Dockerfile hash
5
- - doesn't change too much though
6
- """
7
- import contextlib
8
2
  import dataclasses as dc
9
3
  import json
10
4
  import os.path
11
5
  import shlex
12
- import tarfile
13
6
  import typing as ta
14
7
 
15
8
  from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
16
9
  from omlish.lite.check import check
17
10
  from omlish.os.temp import temp_file_context
18
11
 
19
- from .shell import ShellCmd
20
- from .utils import sha256_str
21
-
22
-
23
- ##
24
-
25
-
26
- def build_docker_file_hash(docker_file: str) -> str:
27
- with open(docker_file) as f:
28
- contents = f.read()
29
-
30
- return sha256_str(contents)
31
-
32
-
33
- ##
34
-
35
-
36
- def read_docker_tar_image_tag(tar_file: str) -> str:
37
- with tarfile.open(tar_file) as tf:
38
- with contextlib.closing(check.not_none(tf.extractfile('manifest.json'))) as mf:
39
- m = mf.read()
40
-
41
- manifests = json.loads(m.decode('utf-8'))
42
- manifest = check.single(manifests)
43
- tag = check.non_empty_str(check.single(manifest['RepoTags']))
44
- return tag
45
-
46
-
47
- def read_docker_tar_image_id(tar_file: str) -> str:
48
- with tarfile.open(tar_file) as tf:
49
- with contextlib.closing(check.not_none(tf.extractfile('index.json'))) as mf:
50
- i = mf.read()
51
-
52
- index = json.loads(i.decode('utf-8'))
53
- manifest = check.single(index['manifests'])
54
- image_id = check.non_empty_str(manifest['digest'])
55
- return image_id
12
+ from ..shell import ShellCmd
56
13
 
57
14
 
58
15
  ##