omdev 0.0.0.dev222__py3-none-any.whl → 0.0.0.dev224__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 (48) hide show
  1. omdev/ci/cache.py +148 -23
  2. omdev/ci/ci.py +50 -110
  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/cacheserved.py +262 -0
  8. omdev/ci/{docker.py → docker/cmds.py} +1 -44
  9. omdev/ci/docker/dataserver.py +204 -0
  10. omdev/ci/docker/imagepulling.py +65 -0
  11. omdev/ci/docker/inject.py +37 -0
  12. omdev/ci/docker/packing.py +72 -0
  13. omdev/ci/docker/repositories.py +40 -0
  14. omdev/ci/docker/utils.py +48 -0
  15. omdev/ci/github/cache.py +35 -6
  16. omdev/ci/github/client.py +9 -2
  17. omdev/ci/github/inject.py +30 -0
  18. omdev/ci/inject.py +61 -0
  19. omdev/ci/utils.py +0 -49
  20. omdev/dataserver/__init__.py +1 -0
  21. omdev/dataserver/handlers.py +198 -0
  22. omdev/dataserver/http.py +69 -0
  23. omdev/dataserver/routes.py +49 -0
  24. omdev/dataserver/server.py +90 -0
  25. omdev/dataserver/targets.py +121 -0
  26. omdev/oci/building.py +107 -9
  27. omdev/oci/compression.py +8 -0
  28. omdev/oci/data.py +43 -0
  29. omdev/oci/datarefs.py +90 -50
  30. omdev/oci/dataserver.py +64 -0
  31. omdev/oci/loading.py +20 -0
  32. omdev/oci/media.py +20 -0
  33. omdev/oci/pack/__init__.py +0 -0
  34. omdev/oci/pack/packing.py +185 -0
  35. omdev/oci/pack/repositories.py +162 -0
  36. omdev/oci/pack/unpacking.py +204 -0
  37. omdev/oci/repositories.py +84 -2
  38. omdev/oci/tars.py +144 -0
  39. omdev/pyproject/resources/python.sh +1 -1
  40. omdev/scripts/ci.py +2137 -512
  41. omdev/scripts/interp.py +119 -22
  42. omdev/scripts/pyproject.py +141 -28
  43. {omdev-0.0.0.dev222.dist-info → omdev-0.0.0.dev224.dist-info}/METADATA +2 -2
  44. {omdev-0.0.0.dev222.dist-info → omdev-0.0.0.dev224.dist-info}/RECORD +48 -23
  45. {omdev-0.0.0.dev222.dist-info → omdev-0.0.0.dev224.dist-info}/LICENSE +0 -0
  46. {omdev-0.0.0.dev222.dist-info → omdev-0.0.0.dev224.dist-info}/WHEEL +0 -0
  47. {omdev-0.0.0.dev222.dist-info → omdev-0.0.0.dev224.dist-info}/entry_points.txt +0 -0
  48. {omdev-0.0.0.dev222.dist-info → omdev-0.0.0.dev224.dist-info}/top_level.txt +0 -0
@@ -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)
@@ -0,0 +1,262 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import abc
3
+ import asyncio
4
+ import contextlib
5
+ import dataclasses as dc
6
+ import json
7
+ import os.path
8
+ import typing as ta
9
+
10
+ from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
11
+ from omlish.lite.check import check
12
+ from omlish.lite.json import json_dumps_compact
13
+ from omlish.lite.logs import log
14
+ from omlish.lite.marshal import marshal_obj
15
+ from omlish.lite.marshal import unmarshal_obj
16
+
17
+ from ...dataserver.routes import DataServerRoute
18
+ from ...dataserver.server import DataServer
19
+ from ...dataserver.targets import BytesDataServerTarget
20
+ from ...dataserver.targets import DataServerTarget
21
+ from ...dataserver.targets import FileDataServerTarget
22
+ from ...oci.building import build_oci_index_repository
23
+ from ...oci.data import get_single_leaf_oci_image_index
24
+ from ...oci.dataserver import build_oci_repository_data_server_routes
25
+ from ...oci.loading import read_oci_repository_root_index
26
+ from ...oci.pack.repositories import OciPackedRepositoryBuilder
27
+ from ...oci.repositories import OciRepository
28
+ from ..cache import DataCache
29
+ from ..cache import read_data_cache_data
30
+ from .cache import DockerCache
31
+ from .dataserver import DockerDataServer
32
+ from .repositories import DockerImageRepositoryOpener
33
+
34
+
35
+ ##
36
+
37
+
38
+ @dc.dataclass(frozen=True)
39
+ class CacheServedDockerImageManifest:
40
+ @dc.dataclass(frozen=True)
41
+ class Route:
42
+ paths: ta.Sequence[str]
43
+
44
+ content_type: str
45
+ content_length: int
46
+
47
+ @dc.dataclass(frozen=True)
48
+ class Target(abc.ABC): # noqa
49
+ pass
50
+
51
+ @dc.dataclass(frozen=True)
52
+ class BytesTarget(Target):
53
+ data: bytes
54
+
55
+ @dc.dataclass(frozen=True)
56
+ class CacheKeyTarget(Target):
57
+ key: str
58
+
59
+ target: Target
60
+
61
+ def __post_init__(self) -> None:
62
+ check.not_isinstance(self.paths, str)
63
+
64
+ routes: ta.Sequence[Route]
65
+
66
+
67
+ #
68
+
69
+
70
+ async def build_cache_served_docker_image_manifest(
71
+ data_server_routes: ta.Iterable[DataServerRoute],
72
+ make_file_cache_key: ta.Callable[[str], ta.Awaitable[str]],
73
+ ) -> CacheServedDockerImageManifest:
74
+ routes: ta.List[CacheServedDockerImageManifest.Route] = []
75
+
76
+ for data_server_route in data_server_routes:
77
+ content_length: int
78
+
79
+ data_server_target = data_server_route.target
80
+ target: CacheServedDockerImageManifest.Route.Target
81
+ if isinstance(data_server_target, BytesDataServerTarget):
82
+ bytes_data = check.isinstance(data_server_target.data, bytes)
83
+ content_length = len(bytes_data)
84
+ target = CacheServedDockerImageManifest.Route.BytesTarget(bytes_data)
85
+
86
+ elif isinstance(data_server_target, FileDataServerTarget):
87
+ file_path = check.non_empty_str(data_server_target.file_path)
88
+ content_length = os.path.getsize(file_path)
89
+ cache_key = await make_file_cache_key(file_path)
90
+ target = CacheServedDockerImageManifest.Route.CacheKeyTarget(cache_key)
91
+
92
+ else:
93
+ raise TypeError(data_server_target)
94
+
95
+ routes.append(CacheServedDockerImageManifest.Route(
96
+ paths=data_server_route.paths,
97
+
98
+ content_type=check.non_empty_str(data_server_target.content_type),
99
+ content_length=content_length,
100
+
101
+ target=target,
102
+ ))
103
+
104
+ return CacheServedDockerImageManifest(
105
+ routes=routes,
106
+ )
107
+
108
+
109
+ #
110
+
111
+
112
+ async def build_cache_served_docker_image_data_server_routes(
113
+ manifest: CacheServedDockerImageManifest,
114
+ make_cache_key_target: ta.Callable[..., ta.Awaitable[DataServerTarget]],
115
+ ) -> ta.List[DataServerRoute]:
116
+ routes: ta.List[DataServerRoute] = []
117
+
118
+ for manifest_route in manifest.routes:
119
+ manifest_target = manifest_route.target
120
+
121
+ target_kwargs: dict = dict(
122
+ content_type=manifest_route.content_type,
123
+ content_length=manifest_route.content_length,
124
+ )
125
+
126
+ target: DataServerTarget
127
+
128
+ if isinstance(manifest_target, CacheServedDockerImageManifest.Route.BytesTarget):
129
+ target = DataServerTarget.of(manifest_target.data, **target_kwargs)
130
+
131
+ elif isinstance(manifest_target, CacheServedDockerImageManifest.Route.CacheKeyTarget):
132
+ target = await make_cache_key_target(manifest_target.key, **target_kwargs)
133
+
134
+ else:
135
+ raise TypeError(manifest_target)
136
+
137
+ routes.append(DataServerRoute(
138
+ paths=manifest_route.paths,
139
+ target=target,
140
+ ))
141
+
142
+ return routes
143
+
144
+
145
+ ##
146
+
147
+
148
+ class CacheServedDockerCache(DockerCache):
149
+ @dc.dataclass(frozen=True)
150
+ class Config:
151
+ port: int = 5021
152
+
153
+ repack: bool = True
154
+
155
+ def __init__(
156
+ self,
157
+ *,
158
+ config: Config = Config(),
159
+
160
+ image_repo_opener: DockerImageRepositoryOpener,
161
+ data_cache: DataCache,
162
+ ) -> None:
163
+ super().__init__()
164
+
165
+ self._config = config
166
+
167
+ self._image_repo_opener = image_repo_opener
168
+ self._data_cache = data_cache
169
+
170
+ async def load_cache_docker_image(self, key: str) -> ta.Optional[str]:
171
+ if (manifest_data := await self._data_cache.get_data(key)) is None:
172
+ return None
173
+
174
+ manifest_bytes = await read_data_cache_data(manifest_data)
175
+
176
+ manifest: CacheServedDockerImageManifest = unmarshal_obj(
177
+ json.loads(manifest_bytes.decode('utf-8')),
178
+ CacheServedDockerImageManifest,
179
+ )
180
+
181
+ async def make_cache_key_target(target_cache_key: str, **target_kwargs: ta.Any) -> DataServerTarget: # noqa
182
+ # FIXME: url
183
+ cache_data = check.not_none(await self._data_cache.get_data(target_cache_key))
184
+ file_path = check.isinstance(cache_data, DataCache.FileData).file_path
185
+ return DataServerTarget.of(
186
+ file_path=file_path,
187
+ **target_kwargs,
188
+ )
189
+
190
+ data_server_routes = await build_cache_served_docker_image_data_server_routes(
191
+ manifest,
192
+ make_cache_key_target,
193
+ )
194
+
195
+ data_server = DataServer(DataServer.HandlerRoute.of_(*data_server_routes))
196
+
197
+ image_url = f'localhost:{self._config.port}/{key}'
198
+
199
+ async with DockerDataServer(
200
+ self._config.port,
201
+ data_server,
202
+ handler_log=log,
203
+ ) as dds:
204
+ dds_run_task = asyncio.create_task(dds.run())
205
+ try:
206
+ # FIXME: lol
207
+ await asyncio.sleep(3.)
208
+
209
+ await asyncio_subprocesses.check_call(
210
+ 'docker',
211
+ 'pull',
212
+ image_url,
213
+ )
214
+
215
+ finally:
216
+ dds.stop_event.set()
217
+ await dds_run_task
218
+
219
+ return image_url
220
+
221
+ async def save_cache_docker_image(self, key: str, image: str) -> None:
222
+ async with contextlib.AsyncExitStack() as es:
223
+ image_repo: OciRepository = await es.enter_async_context(
224
+ self._image_repo_opener.open_docker_image_repository(image),
225
+ )
226
+
227
+ root_image_index = read_oci_repository_root_index(image_repo)
228
+ image_index = get_single_leaf_oci_image_index(root_image_index)
229
+
230
+ if self._config.repack:
231
+ prb: OciPackedRepositoryBuilder = es.enter_context(OciPackedRepositoryBuilder(
232
+ image_repo,
233
+ ))
234
+ built_repo = await asyncio.get_running_loop().run_in_executor(None, prb.build)
235
+
236
+ else:
237
+ built_repo = build_oci_index_repository(image_index)
238
+
239
+ data_server_routes = build_oci_repository_data_server_routes(
240
+ key,
241
+ built_repo,
242
+ )
243
+
244
+ async def make_file_cache_key(file_path: str) -> str:
245
+ target_cache_key = f'{key}--{os.path.basename(file_path).split(".")[0]}'
246
+ await self._data_cache.put_data(
247
+ target_cache_key,
248
+ DataCache.FileData(file_path),
249
+ )
250
+ return target_cache_key
251
+
252
+ cache_served_manifest = await build_cache_served_docker_image_manifest(
253
+ data_server_routes,
254
+ make_file_cache_key,
255
+ )
256
+
257
+ manifest_data = json_dumps_compact(marshal_obj(cache_served_manifest)).encode('utf-8')
258
+
259
+ await self._data_cache.put_data(
260
+ key,
261
+ DataCache.BytesData(manifest_data),
262
+ )
@@ -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
  ##
@@ -0,0 +1,204 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import asyncio
3
+ import contextlib
4
+ import logging
5
+ import ssl
6
+ import sys
7
+ import threading
8
+ import typing as ta
9
+
10
+ from omlish.docker.portrelay import DockerPortRelay
11
+ from omlish.http.coro.simple import make_simple_http_server
12
+ from omlish.http.handlers import HttpHandler
13
+ from omlish.http.handlers import LoggingHttpHandler
14
+ from omlish.lite.cached import cached_nullary
15
+ from omlish.lite.check import check
16
+ from omlish.lite.contextmanagers import AsyncExitStacked
17
+ from omlish.secrets.tempssl import generate_temp_localhost_ssl_cert
18
+ from omlish.sockets.server.server import SocketServer
19
+
20
+ from ...dataserver.http import DataServerHttpHandler
21
+ from ...dataserver.server import DataServer
22
+
23
+
24
+ ##
25
+
26
+
27
+ @contextlib.asynccontextmanager
28
+ async def start_docker_port_relay(
29
+ docker_port: int,
30
+ host_port: int,
31
+ **kwargs: ta.Any,
32
+ ) -> ta.AsyncGenerator[None, None]:
33
+ proc = await asyncio.create_subprocess_exec(*DockerPortRelay(
34
+ docker_port,
35
+ host_port,
36
+ **kwargs,
37
+ ).run_cmd())
38
+
39
+ try:
40
+ yield
41
+
42
+ finally:
43
+ try:
44
+ proc.kill()
45
+ except ProcessLookupError:
46
+ pass
47
+ await proc.wait()
48
+
49
+
50
+ ##
51
+
52
+
53
+ class AsyncioManagedSimpleHttpServer(AsyncExitStacked):
54
+ def __init__(
55
+ self,
56
+ port: int,
57
+ handler: HttpHandler,
58
+ *,
59
+ temp_ssl: bool = False,
60
+ ) -> None:
61
+ super().__init__()
62
+
63
+ self._port = port
64
+ self._handler = handler
65
+
66
+ self._temp_ssl = temp_ssl
67
+
68
+ self._lock = threading.RLock()
69
+
70
+ self._loop: ta.Optional[asyncio.AbstractEventLoop] = None
71
+ self._thread: ta.Optional[threading.Thread] = None
72
+ self._thread_exit_event = asyncio.Event()
73
+ self._server: ta.Optional[SocketServer] = None
74
+
75
+ @cached_nullary
76
+ def _ssl_context(self) -> ta.Optional['ssl.SSLContext']:
77
+ if not self._temp_ssl:
78
+ return None
79
+
80
+ ssl_cert = generate_temp_localhost_ssl_cert().cert # FIXME: async blocking
81
+
82
+ ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
83
+ ssl_context.load_cert_chain(
84
+ keyfile=ssl_cert.key_file,
85
+ certfile=ssl_cert.cert_file,
86
+ )
87
+
88
+ return ssl_context
89
+
90
+ @contextlib.contextmanager
91
+ def _make_server(self) -> ta.Iterator[SocketServer]:
92
+ with make_simple_http_server(
93
+ self._port,
94
+ self._handler,
95
+ ssl_context=self._ssl_context(),
96
+ use_threads=True,
97
+ ) as server:
98
+ yield server
99
+
100
+ def _thread_main(self) -> None:
101
+ try:
102
+ check.none(self._server)
103
+ with self._make_server() as server:
104
+ self._server = server
105
+
106
+ server.run()
107
+
108
+ finally:
109
+ check.not_none(self._loop).call_soon_threadsafe(self._thread_exit_event.set)
110
+
111
+ def is_running(self) -> bool:
112
+ return self._server is not None
113
+
114
+ def shutdown(self) -> None:
115
+ if (server := self._server) is not None:
116
+ server.shutdown(block=False)
117
+
118
+ async def run(self) -> None:
119
+ with self._lock:
120
+ check.none(self._loop)
121
+ check.none(self._thread)
122
+ check.state(not self._thread_exit_event.is_set())
123
+
124
+ if self._temp_ssl:
125
+ # Hit the ExitStack from this thread
126
+ self._ssl_context()
127
+
128
+ self._loop = check.not_none(asyncio.get_running_loop())
129
+ self._thread = threading.Thread(
130
+ target=self._thread_main,
131
+ daemon=True,
132
+ )
133
+ self._thread.start()
134
+
135
+ await self._thread_exit_event.wait()
136
+
137
+
138
+ ##
139
+
140
+
141
+ class DockerDataServer(AsyncExitStacked):
142
+ def __init__(
143
+ self,
144
+ port: int,
145
+ data_server: DataServer,
146
+ *,
147
+ handler_log: ta.Optional[logging.Logger] = None,
148
+ stop_event: ta.Optional[asyncio.Event] = None,
149
+ ) -> None:
150
+ super().__init__()
151
+
152
+ self._port = port
153
+ self._data_server = data_server
154
+
155
+ self._handler_log = handler_log
156
+
157
+ if stop_event is None:
158
+ stop_event = asyncio.Event()
159
+ self._stop_event = stop_event
160
+
161
+ @property
162
+ def stop_event(self) -> asyncio.Event:
163
+ return self._stop_event
164
+
165
+ async def run(self) -> None:
166
+ relay_port: ta.Optional[int] = None
167
+ if sys.platform == 'darwin':
168
+ relay_port = self._port
169
+ server_port = self._port + 1
170
+ else:
171
+ server_port = self._port
172
+
173
+ #
174
+
175
+ handler: HttpHandler = DataServerHttpHandler(self._data_server)
176
+
177
+ if self._handler_log is not None:
178
+ handler = LoggingHttpHandler(
179
+ handler,
180
+ self._handler_log,
181
+ )
182
+
183
+ #
184
+
185
+ async with contextlib.AsyncExitStack() as es:
186
+ if relay_port is not None:
187
+ await es.enter_async_context(start_docker_port_relay( # noqa
188
+ relay_port,
189
+ server_port,
190
+ intermediate_port=server_port + 1,
191
+ ))
192
+
193
+ async with AsyncioManagedSimpleHttpServer(
194
+ server_port,
195
+ handler,
196
+ temp_ssl=True,
197
+ ) as server:
198
+ server_run_task = asyncio.create_task(server.run())
199
+ try:
200
+ await self._stop_event.wait()
201
+
202
+ finally:
203
+ server.shutdown()
204
+ await server_run_task
@@ -0,0 +1,65 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import abc
3
+ import dataclasses as dc
4
+ import typing as ta
5
+
6
+ from omlish.lite.timing import log_timing_context
7
+
8
+ from ..cache import FileCache
9
+ from .cache import DockerCache
10
+ from .cmds import is_docker_image_present
11
+ from .cmds import pull_docker_image
12
+
13
+
14
+ ##
15
+
16
+
17
+ class DockerImagePulling(abc.ABC):
18
+ @abc.abstractmethod
19
+ def pull_docker_image(self, image: str) -> ta.Awaitable[None]:
20
+ raise NotImplementedError
21
+
22
+
23
+ class DockerImagePullingImpl(DockerImagePulling):
24
+ @dc.dataclass(frozen=True)
25
+ class Config:
26
+ always_pull: bool = False
27
+
28
+ def __init__(
29
+ self,
30
+ *,
31
+ config: Config = Config(),
32
+
33
+ file_cache: ta.Optional[FileCache] = None,
34
+ docker_cache: ta.Optional[DockerCache] = None,
35
+ ) -> None:
36
+ super().__init__()
37
+
38
+ self._config = config
39
+
40
+ self._file_cache = file_cache
41
+ self._docker_cache = docker_cache
42
+
43
+ async def _pull_docker_image(self, image: str) -> None:
44
+ if not self._config.always_pull and (await is_docker_image_present(image)):
45
+ return
46
+
47
+ dep_suffix = image
48
+ for c in '/:.-_':
49
+ dep_suffix = dep_suffix.replace(c, '-')
50
+
51
+ cache_key = f'docker-{dep_suffix}'
52
+ if (
53
+ self._docker_cache is not None and
54
+ (await self._docker_cache.load_cache_docker_image(cache_key)) is not None
55
+ ):
56
+ return
57
+
58
+ await pull_docker_image(image)
59
+
60
+ if self._docker_cache is not None:
61
+ await self._docker_cache.save_cache_docker_image(cache_key, image)
62
+
63
+ async def pull_docker_image(self, image: str) -> None:
64
+ with log_timing_context(f'Load docker image: {image}'):
65
+ await self._pull_docker_image(image)
@@ -0,0 +1,37 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import typing as ta
3
+
4
+ from omlish.lite.inject import InjectorBindingOrBindings
5
+ from omlish.lite.inject import InjectorBindings
6
+ from omlish.lite.inject import inj
7
+
8
+ from .buildcaching import DockerBuildCaching
9
+ from .buildcaching import DockerBuildCachingImpl
10
+ from .cache import DockerCache
11
+ from .cache import DockerCacheImpl
12
+ from .imagepulling import DockerImagePulling
13
+ from .imagepulling import DockerImagePullingImpl
14
+
15
+
16
+ ##
17
+
18
+
19
+ def bind_docker(
20
+ *,
21
+ build_caching_config: DockerBuildCachingImpl.Config,
22
+ image_pulling_config: DockerImagePullingImpl.Config = DockerImagePullingImpl.Config(),
23
+ ) -> InjectorBindings:
24
+ lst: ta.List[InjectorBindingOrBindings] = [
25
+ inj.bind(build_caching_config),
26
+ inj.bind(DockerBuildCachingImpl, singleton=True),
27
+ inj.bind(DockerBuildCaching, to_key=DockerBuildCachingImpl),
28
+
29
+ inj.bind(DockerCacheImpl, singleton=True),
30
+ inj.bind(DockerCache, to_key=DockerCacheImpl),
31
+
32
+ inj.bind(image_pulling_config),
33
+ inj.bind(DockerImagePullingImpl, singleton=True),
34
+ inj.bind(DockerImagePulling, to_key=DockerImagePullingImpl),
35
+ ]
36
+
37
+ return inj.as_bindings(*lst)