omdev 0.0.0.dev221__py3-none-any.whl → 0.0.0.dev223__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.
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
  ##