omdev 0.0.0.dev222__py3-none-any.whl → 0.0.0.dev224__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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)