omdev 0.0.0.dev240__py3-none-any.whl → 0.0.0.dev242__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.
omdev/.manifests.json CHANGED
@@ -267,7 +267,7 @@
267
267
  "module": ".tools.docker",
268
268
  "attr": "_CLI_MODULE",
269
269
  "file": "omdev/tools/docker.py",
270
- "line": 267,
270
+ "line": 251,
271
271
  "value": {
272
272
  "$.cli.types.CliModule": {
273
273
  "cmd_name": "docker",
omdev/ci/cache.py CHANGED
@@ -118,7 +118,7 @@ class DirectoryFileCache(FileCache):
118
118
  elif not os.path.isdir(self.dir):
119
119
  os.makedirs(self.dir)
120
120
  with open(version_file, 'w') as f:
121
- f.write(str(self._version))
121
+ f.write(f'{self._version}\n')
122
122
  return
123
123
 
124
124
  # NOTE: intentionally raises FileNotFoundError to refuse to use an existing non-cache dir as a cache dir.
omdev/ci/ci.py CHANGED
@@ -1,8 +1,10 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  import dataclasses as dc
3
+ import functools
3
4
  import os.path
4
5
  import typing as ta
5
6
 
7
+ from omlish.asyncs.asyncio.asyncio import asyncio_wait_maybe_concurrent
6
8
  from omlish.lite.cached import async_cached_nullary
7
9
  from omlish.lite.cached import cached_nullary
8
10
  from omlish.lite.check import check
@@ -13,7 +15,9 @@ from omlish.os.temp import temp_file_context
13
15
  from .compose import DockerComposeRun
14
16
  from .compose import get_compose_service_dependencies
15
17
  from .docker.buildcaching import DockerBuildCaching
18
+ from .docker.cache import DockerCacheKey
16
19
  from .docker.cmds import build_docker_image
20
+ from .docker.cmds import ensure_docker_image_setup
17
21
  from .docker.imagepulling import DockerImagePulling
18
22
  from .docker.utils import build_docker_file_hash
19
23
  from .requirements import build_requirements_hash
@@ -44,8 +48,12 @@ class Ci(AsyncExitStacked):
44
48
  always_pull: bool = False
45
49
  always_build: bool = False
46
50
 
51
+ setup_concurrency: ta.Optional[int] = None
52
+
47
53
  no_dependencies: bool = False
48
54
 
55
+ setup_only: bool = False
56
+
49
57
  run_options: ta.Optional[ta.Sequence[str]] = None
50
58
 
51
59
  #
@@ -74,8 +82,8 @@ class Ci(AsyncExitStacked):
74
82
  return build_docker_file_hash(self._config.docker_file)[:self.KEY_HASH_LEN]
75
83
 
76
84
  @cached_nullary
77
- def ci_base_image_cache_key(self) -> str:
78
- return f'ci-base-{self.docker_file_hash()}'
85
+ def ci_base_image_cache_key(self) -> DockerCacheKey:
86
+ return DockerCacheKey(['ci-base'], self.docker_file_hash())
79
87
 
80
88
  async def _resolve_ci_base_image(self) -> str:
81
89
  async def build_and_tag(image_tag: str) -> str:
@@ -111,8 +119,8 @@ class Ci(AsyncExitStacked):
111
119
  return build_requirements_hash(self.requirements_txts())[:self.KEY_HASH_LEN]
112
120
 
113
121
  @cached_nullary
114
- def ci_image_cache_key(self) -> str:
115
- return f'ci-{self.docker_file_hash()}-{self.requirements_hash()}'
122
+ def ci_image_cache_key(self) -> DockerCacheKey:
123
+ return DockerCacheKey(['ci'], f'{self.docker_file_hash()}-{self.requirements_hash()}')
116
124
 
117
125
  async def _resolve_ci_image(self) -> str:
118
126
  async def build_and_tag(image_tag: str) -> str:
@@ -168,15 +176,40 @@ class Ci(AsyncExitStacked):
168
176
 
169
177
  #
170
178
 
171
- @async_cached_nullary
172
- async def pull_dependencies(self) -> None:
179
+ @cached_nullary
180
+ def get_dependency_images(self) -> ta.Sequence[str]:
173
181
  deps = get_compose_service_dependencies(
174
182
  self._config.compose_file,
175
183
  self._config.service,
176
184
  )
185
+ return sorted(deps.values())
186
+
187
+ @cached_nullary
188
+ def pull_dependencies_funcs(self) -> ta.Sequence[ta.Callable[[], ta.Awaitable]]:
189
+ return [
190
+ async_cached_nullary(functools.partial(
191
+ self._docker_image_pulling.pull_docker_image,
192
+ dep_image,
193
+ ))
194
+ for dep_image in self.get_dependency_images()
195
+ ]
196
+
197
+ #
198
+
199
+ @cached_nullary
200
+ def setup_funcs(self) -> ta.Sequence[ta.Callable[[], ta.Awaitable]]:
201
+ return [
202
+ self.resolve_ci_image,
177
203
 
178
- for dep_image in deps.values():
179
- await self._docker_image_pulling.pull_docker_image(dep_image)
204
+ *(self.pull_dependencies_funcs() if not self._config.no_dependencies else []),
205
+ ]
206
+
207
+ @async_cached_nullary
208
+ async def setup(self) -> None:
209
+ await asyncio_wait_maybe_concurrent(
210
+ [fn() for fn in self.setup_funcs()],
211
+ self._config.setup_concurrency,
212
+ )
180
213
 
181
214
  #
182
215
 
@@ -206,9 +239,26 @@ class Ci(AsyncExitStacked):
206
239
 
207
240
  #
208
241
 
209
- async def run(self) -> None:
210
- await self.resolve_ci_image()
242
+ async def _run_setup_only(self) -> None:
243
+ image_ids = [
244
+ await self.resolve_ci_image(),
245
+
246
+ *(self.get_dependency_images() if not self._config.no_dependencies else []),
247
+ ]
211
248
 
212
- await self.pull_dependencies()
249
+ for image_id in image_ids:
250
+ with log_timing_context(f'Run setup only: {image_id}'):
251
+ await ensure_docker_image_setup(
252
+ image_id,
253
+ cwd=self._config.project_dir,
254
+ )
255
+
256
+ #
257
+
258
+ async def run(self) -> None:
259
+ await self.setup()
213
260
 
214
- await self._run_compose()
261
+ if self._config.setup_only:
262
+ await self._run_setup_only()
263
+ else:
264
+ await self._run_compose()
omdev/ci/cli.py CHANGED
@@ -88,11 +88,17 @@ class CiCli(ArgparseCli):
88
88
  argparse_arg('--github', action='store_true'),
89
89
  argparse_arg('--github-detect', action='store_true'),
90
90
 
91
+ argparse_arg('--cache-served-docker', action='store_true'),
92
+
93
+ argparse_arg('--setup-concurrency', type=int),
94
+
91
95
  argparse_arg('--always-pull', action='store_true'),
92
96
  argparse_arg('--always-build', action='store_true'),
93
97
 
94
98
  argparse_arg('--no-dependencies', action='store_true'),
95
99
 
100
+ argparse_arg('--setup-only', action='store_true'),
101
+
96
102
  argparse_arg('-e', '--env', action='append'),
97
103
  argparse_arg('-v', '--volume', action='append'),
98
104
 
@@ -203,8 +209,12 @@ class CiCli(ArgparseCli):
203
209
  always_pull=self.args.always_pull,
204
210
  always_build=self.args.always_build,
205
211
 
212
+ setup_concurrency=self.args.setup_concurrency,
213
+
206
214
  no_dependencies=self.args.no_dependencies,
207
215
 
216
+ setup_only=self.args.setup_only,
217
+
208
218
  run_options=run_options,
209
219
  )
210
220
 
@@ -225,6 +235,8 @@ class CiCli(ArgparseCli):
225
235
  directory_file_cache_config=directory_file_cache_config,
226
236
 
227
237
  github=github,
238
+
239
+ cache_served_docker=self.args.cache_served_docker,
228
240
  ))
229
241
 
230
242
  async with injector[Ci] as ci:
omdev/ci/consts.py CHANGED
@@ -1 +1 @@
1
- CI_CACHE_VERSION = 1
1
+ CI_CACHE_VERSION = 2
@@ -4,6 +4,7 @@ import dataclasses as dc
4
4
  import typing as ta
5
5
 
6
6
  from .cache import DockerCache
7
+ from .cache import DockerCacheKey
7
8
  from .cmds import is_docker_image_present
8
9
  from .cmds import tag_docker_image
9
10
 
@@ -15,7 +16,7 @@ class DockerBuildCaching(abc.ABC):
15
16
  @abc.abstractmethod
16
17
  def cached_build_docker_image(
17
18
  self,
18
- cache_key: str,
19
+ cache_key: DockerCacheKey,
19
20
  build_and_tag: ta.Callable[[str], ta.Awaitable[str]], # image_tag -> image_id
20
21
  ) -> ta.Awaitable[str]:
21
22
  raise NotImplementedError
@@ -43,10 +44,10 @@ class DockerBuildCachingImpl(DockerBuildCaching):
43
44
 
44
45
  async def cached_build_docker_image(
45
46
  self,
46
- cache_key: str,
47
+ cache_key: DockerCacheKey,
47
48
  build_and_tag: ta.Callable[[str], ta.Awaitable[str]],
48
49
  ) -> str:
49
- image_tag = f'{self._config.service}:{cache_key}'
50
+ image_tag = f'{self._config.service}:{cache_key!s}'
50
51
 
51
52
  if not self._config.always_build and (await is_docker_image_present(image_tag)):
52
53
  return image_tag
omdev/ci/docker/cache.py CHANGED
@@ -1,7 +1,9 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  import abc
3
+ import dataclasses as dc
3
4
  import typing as ta
4
5
 
6
+ from omlish.lite.check import check
5
7
  from omlish.os.temp import temp_file_context
6
8
 
7
9
  from ..cache import FileCache
@@ -13,13 +15,33 @@ from .cmds import save_docker_tar_cmd
13
15
  ##
14
16
 
15
17
 
18
+ @dc.dataclass(frozen=True)
19
+ class DockerCacheKey:
20
+ prefixes: ta.Sequence[str]
21
+ content: str
22
+
23
+ def __post_init__(self) -> None:
24
+ check.not_isinstance(self.prefixes, str)
25
+
26
+ def append_prefix(self, *prefixes: str) -> 'DockerCacheKey':
27
+ return dc.replace(self, prefixes=(*self.prefixes, *prefixes))
28
+
29
+ SEPARATOR: ta.ClassVar[str] = '--'
30
+
31
+ def __str__(self) -> str:
32
+ return self.SEPARATOR.join([*self.prefixes, self.content])
33
+
34
+
35
+ ##
36
+
37
+
16
38
  class DockerCache(abc.ABC):
17
39
  @abc.abstractmethod
18
- def load_cache_docker_image(self, key: str) -> ta.Awaitable[ta.Optional[str]]:
40
+ def load_cache_docker_image(self, key: DockerCacheKey) -> ta.Awaitable[ta.Optional[str]]:
19
41
  raise NotImplementedError
20
42
 
21
43
  @abc.abstractmethod
22
- def save_cache_docker_image(self, key: str, image: str) -> ta.Awaitable[None]:
44
+ def save_cache_docker_image(self, key: DockerCacheKey, image: str) -> ta.Awaitable[None]:
23
45
  raise NotImplementedError
24
46
 
25
47
 
@@ -33,11 +55,11 @@ class DockerCacheImpl(DockerCache):
33
55
 
34
56
  self._file_cache = file_cache
35
57
 
36
- async def load_cache_docker_image(self, key: str) -> ta.Optional[str]:
58
+ async def load_cache_docker_image(self, key: DockerCacheKey) -> ta.Optional[str]:
37
59
  if self._file_cache is None:
38
60
  return None
39
61
 
40
- cache_file = await self._file_cache.get_file(key)
62
+ cache_file = await self._file_cache.get_file(str(key))
41
63
  if cache_file is None:
42
64
  return None
43
65
 
@@ -45,7 +67,7 @@ class DockerCacheImpl(DockerCache):
45
67
 
46
68
  return await load_docker_tar_cmd(get_cache_cmd)
47
69
 
48
- async def save_cache_docker_image(self, key: str, image: str) -> None:
70
+ async def save_cache_docker_image(self, key: DockerCacheKey, image: str) -> None:
49
71
  if self._file_cache is None:
50
72
  return
51
73
 
@@ -54,4 +76,4 @@ class DockerCacheImpl(DockerCache):
54
76
 
55
77
  await save_docker_tar_cmd(image, write_tmp_cmd)
56
78
 
57
- await self._file_cache.put_file(key, tmp_file, steal=True)
79
+ await self._file_cache.put_file(str(key), tmp_file, steal=True)
File without changes
@@ -0,0 +1,209 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import asyncio
3
+ import contextlib
4
+ import dataclasses as dc
5
+ import json
6
+ import os.path
7
+ import typing as ta
8
+
9
+ from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
10
+ from omlish.lite.check import check
11
+ from omlish.lite.json import json_dumps_compact
12
+ from omlish.lite.logs import log
13
+ from omlish.lite.marshal import marshal_obj
14
+ from omlish.lite.marshal import unmarshal_obj
15
+ from omlish.lite.timeouts import Timeout
16
+ from omlish.lite.timeouts import TimeoutLike
17
+
18
+ from ....dataserver.server import DataServer
19
+ from ....dataserver.targets import DataServerTarget
20
+ from ....oci.building import build_oci_index_repository
21
+ from ....oci.data import get_single_leaf_oci_image_index
22
+ from ....oci.dataserver import build_oci_repository_data_server_routes
23
+ from ....oci.loading import read_oci_repository_root_index
24
+ from ....oci.pack.repositories import OciPackedRepositoryBuilder
25
+ from ....oci.repositories import OciRepository
26
+ from ...cache import DataCache
27
+ from ...cache import read_data_cache_data
28
+ from ..cache import DockerCache
29
+ from ..cache import DockerCacheKey
30
+ from ..dataserver import DockerDataServer
31
+ from ..repositories import DockerImageRepositoryOpener
32
+ from .manifests import CacheServedDockerImageManifest
33
+ from .manifests import build_cache_served_docker_image_data_server_routes
34
+ from .manifests import build_cache_served_docker_image_manifest
35
+
36
+
37
+ ##
38
+
39
+
40
+ class CacheServedDockerCache(DockerCache):
41
+ @dc.dataclass(frozen=True)
42
+ class Config:
43
+ port: int = 5021
44
+
45
+ repack: bool = True
46
+
47
+ key_prefix: ta.Optional[str] = 'cs'
48
+
49
+ #
50
+
51
+ pull_run_cmd: ta.Optional[str] = 'true'
52
+
53
+ #
54
+
55
+ server_start_timeout: TimeoutLike = 5.
56
+ server_start_sleep: float = .1
57
+
58
+ def __init__(
59
+ self,
60
+ *,
61
+ config: Config = Config(),
62
+
63
+ image_repo_opener: DockerImageRepositoryOpener,
64
+ data_cache: DataCache,
65
+ ) -> None:
66
+ super().__init__()
67
+
68
+ self._config = config
69
+
70
+ self._image_repo_opener = image_repo_opener
71
+ self._data_cache = data_cache
72
+
73
+ async def load_cache_docker_image(self, key: DockerCacheKey) -> ta.Optional[str]:
74
+ if (kp := self._config.key_prefix) is not None:
75
+ key = key.append_prefix(kp)
76
+
77
+ if (manifest_data := await self._data_cache.get_data(str(key))) is None:
78
+ return None
79
+
80
+ manifest_bytes = await read_data_cache_data(manifest_data)
81
+
82
+ manifest: CacheServedDockerImageManifest = unmarshal_obj(
83
+ json.loads(manifest_bytes.decode('utf-8')),
84
+ CacheServedDockerImageManifest,
85
+ )
86
+
87
+ async def make_cache_key_target(target_cache_key: str, **target_kwargs: ta.Any) -> DataServerTarget: # noqa
88
+ cache_data = check.not_none(await self._data_cache.get_data(target_cache_key))
89
+
90
+ if isinstance(cache_data, DataCache.BytesData):
91
+ return DataServerTarget.of(
92
+ cache_data.data,
93
+ **target_kwargs,
94
+ )
95
+
96
+ elif isinstance(cache_data, DataCache.FileData):
97
+ return DataServerTarget.of(
98
+ file_path=cache_data.file_path,
99
+ **target_kwargs,
100
+ )
101
+
102
+ elif isinstance(cache_data, DataCache.UrlData):
103
+ return DataServerTarget.of(
104
+ url=cache_data.url,
105
+ methods=['GET'],
106
+ **target_kwargs,
107
+ )
108
+
109
+ else:
110
+ raise TypeError(cache_data)
111
+
112
+ data_server_routes = await build_cache_served_docker_image_data_server_routes(
113
+ manifest,
114
+ make_cache_key_target,
115
+ )
116
+
117
+ data_server = DataServer(DataServer.HandlerRoute.of_(*data_server_routes))
118
+
119
+ image_url = f'localhost:{self._config.port}/{key!s}'
120
+
121
+ async with DockerDataServer(
122
+ self._config.port,
123
+ data_server,
124
+ handler_log=log,
125
+ ) as dds:
126
+ dds_run_task = asyncio.create_task(dds.run())
127
+ try:
128
+ timeout = Timeout.of(self._config.server_start_timeout)
129
+ while True:
130
+ timeout()
131
+ try:
132
+ reader, writer = await asyncio.open_connection('localhost', self._config.port)
133
+ except Exception as e: # noqa
134
+ log.exception('Failed to connect to cache server - will try again')
135
+ else:
136
+ writer.close()
137
+ await asyncio.wait_for(writer.wait_closed(), timeout=timeout.remaining())
138
+ break
139
+ await asyncio.sleep(self._config.server_start_sleep)
140
+
141
+ if (prc := self._config.pull_run_cmd) is not None:
142
+ pull_cmd = [
143
+ 'run',
144
+ '--rm',
145
+ image_url,
146
+ prc,
147
+ ]
148
+ else:
149
+ pull_cmd = [
150
+ 'pull',
151
+ image_url,
152
+ ]
153
+
154
+ await asyncio_subprocesses.check_call(
155
+ 'docker',
156
+ *pull_cmd,
157
+ )
158
+
159
+ finally:
160
+ dds.stop_event.set()
161
+ await dds_run_task
162
+
163
+ return image_url
164
+
165
+ async def save_cache_docker_image(self, key: DockerCacheKey, image: str) -> None:
166
+ if (kp := self._config.key_prefix) is not None:
167
+ key = key.append_prefix(kp)
168
+
169
+ async with contextlib.AsyncExitStack() as es:
170
+ image_repo: OciRepository = await es.enter_async_context(
171
+ self._image_repo_opener.open_docker_image_repository(image),
172
+ )
173
+
174
+ root_image_index = read_oci_repository_root_index(image_repo)
175
+ image_index = get_single_leaf_oci_image_index(root_image_index)
176
+
177
+ if self._config.repack:
178
+ prb: OciPackedRepositoryBuilder = es.enter_context(OciPackedRepositoryBuilder(
179
+ image_repo,
180
+ ))
181
+ built_repo = await asyncio.get_running_loop().run_in_executor(None, prb.build) # noqa
182
+
183
+ else:
184
+ built_repo = build_oci_index_repository(image_index)
185
+
186
+ data_server_routes = build_oci_repository_data_server_routes(
187
+ str(key),
188
+ built_repo,
189
+ )
190
+
191
+ async def make_file_cache_key(file_path: str) -> str:
192
+ target_cache_key = f'{key!s}--{os.path.basename(file_path).split(".")[0]}'
193
+ await self._data_cache.put_data(
194
+ target_cache_key,
195
+ DataCache.FileData(file_path),
196
+ )
197
+ return target_cache_key
198
+
199
+ cache_served_manifest = await build_cache_served_docker_image_manifest(
200
+ data_server_routes,
201
+ make_file_cache_key,
202
+ )
203
+
204
+ manifest_data = json_dumps_compact(marshal_obj(cache_served_manifest)).encode('utf-8')
205
+
206
+ await self._data_cache.put_data(
207
+ str(key),
208
+ DataCache.BytesData(manifest_data),
209
+ )
@@ -0,0 +1,122 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import abc
3
+ import dataclasses as dc
4
+ import os.path
5
+ import typing as ta
6
+
7
+ from omlish.lite.check import check
8
+
9
+ from ....dataserver.routes import DataServerRoute
10
+ from ....dataserver.targets import BytesDataServerTarget
11
+ from ....dataserver.targets import DataServerTarget
12
+ from ....dataserver.targets import FileDataServerTarget
13
+
14
+
15
+ ##
16
+
17
+
18
+ @dc.dataclass(frozen=True)
19
+ class CacheServedDockerImageManifest:
20
+ @dc.dataclass(frozen=True)
21
+ class Route:
22
+ paths: ta.Sequence[str]
23
+
24
+ content_type: str
25
+ content_length: int
26
+
27
+ @dc.dataclass(frozen=True)
28
+ class Target(abc.ABC): # noqa
29
+ pass
30
+
31
+ @dc.dataclass(frozen=True)
32
+ class BytesTarget(Target):
33
+ data: bytes
34
+
35
+ @dc.dataclass(frozen=True)
36
+ class CacheKeyTarget(Target):
37
+ key: str
38
+
39
+ target: Target
40
+
41
+ def __post_init__(self) -> None:
42
+ check.not_isinstance(self.paths, str)
43
+
44
+ routes: ta.Sequence[Route]
45
+
46
+
47
+ #
48
+
49
+
50
+ async def build_cache_served_docker_image_manifest(
51
+ data_server_routes: ta.Iterable[DataServerRoute],
52
+ make_file_cache_key: ta.Callable[[str], ta.Awaitable[str]],
53
+ ) -> CacheServedDockerImageManifest:
54
+ routes: ta.List[CacheServedDockerImageManifest.Route] = []
55
+
56
+ for data_server_route in data_server_routes:
57
+ content_length: int
58
+
59
+ data_server_target = data_server_route.target
60
+ target: CacheServedDockerImageManifest.Route.Target
61
+ if isinstance(data_server_target, BytesDataServerTarget):
62
+ bytes_data = check.isinstance(data_server_target.data, bytes)
63
+ content_length = len(bytes_data)
64
+ target = CacheServedDockerImageManifest.Route.BytesTarget(bytes_data)
65
+
66
+ elif isinstance(data_server_target, FileDataServerTarget):
67
+ file_path = check.non_empty_str(data_server_target.file_path)
68
+ content_length = os.path.getsize(file_path)
69
+ cache_key = await make_file_cache_key(file_path)
70
+ target = CacheServedDockerImageManifest.Route.CacheKeyTarget(cache_key)
71
+
72
+ else:
73
+ raise TypeError(data_server_target)
74
+
75
+ routes.append(CacheServedDockerImageManifest.Route(
76
+ paths=data_server_route.paths,
77
+
78
+ content_type=check.non_empty_str(data_server_target.content_type),
79
+ content_length=content_length,
80
+
81
+ target=target,
82
+ ))
83
+
84
+ return CacheServedDockerImageManifest(
85
+ routes=routes,
86
+ )
87
+
88
+
89
+ #
90
+
91
+
92
+ async def build_cache_served_docker_image_data_server_routes(
93
+ manifest: CacheServedDockerImageManifest,
94
+ make_cache_key_target: ta.Callable[..., ta.Awaitable[DataServerTarget]],
95
+ ) -> ta.List[DataServerRoute]:
96
+ routes: ta.List[DataServerRoute] = []
97
+
98
+ for manifest_route in manifest.routes:
99
+ manifest_target = manifest_route.target
100
+
101
+ target_kwargs: dict = dict(
102
+ content_type=manifest_route.content_type,
103
+ content_length=manifest_route.content_length,
104
+ )
105
+
106
+ target: DataServerTarget
107
+
108
+ if isinstance(manifest_target, CacheServedDockerImageManifest.Route.BytesTarget):
109
+ target = DataServerTarget.of(manifest_target.data, **target_kwargs)
110
+
111
+ elif isinstance(manifest_target, CacheServedDockerImageManifest.Route.CacheKeyTarget):
112
+ target = await make_cache_key_target(manifest_target.key, **target_kwargs)
113
+
114
+ else:
115
+ raise TypeError(manifest_target)
116
+
117
+ routes.append(DataServerRoute(
118
+ paths=manifest_route.paths,
119
+ target=target,
120
+ ))
121
+
122
+ return routes
omdev/ci/docker/cmds.py CHANGED
@@ -123,3 +123,21 @@ async def load_docker_tar(
123
123
  tar_file: str,
124
124
  ) -> str:
125
125
  return await load_docker_tar_cmd(ShellCmd(f'cat {shlex.quote(tar_file)}'))
126
+
127
+
128
+ ##
129
+
130
+
131
+ async def ensure_docker_image_setup(
132
+ image: str,
133
+ *,
134
+ cwd: ta.Optional[str] = None,
135
+ ) -> None:
136
+ await asyncio_subprocesses.check_call(
137
+ 'docker',
138
+ 'run',
139
+ '--rm',
140
+ '--entrypoint', '/bin/true', # FIXME: lol
141
+ image,
142
+ **(dict(cwd=cwd) if cwd is not None else {}),
143
+ )
@@ -7,7 +7,7 @@ import sys
7
7
  import threading
8
8
  import typing as ta
9
9
 
10
- from omlish.docker.portrelay import DockerPortRelay
10
+ from omlish.docker.ports import DockerPortRelay
11
11
  from omlish.http.coro.simple import make_simple_http_server
12
12
  from omlish.http.handlers import HttpHandler
13
13
  from omlish.http.handlers import LoggingHttpHandler
@@ -93,6 +93,7 @@ class AsyncioManagedSimpleHttpServer(AsyncExitStacked):
93
93
  self._port,
94
94
  self._handler,
95
95
  ssl_context=self._ssl_context(),
96
+ ignore_ssl_errors=True,
96
97
  use_threads=True,
97
98
  ) as server:
98
99
  yield server
@@ -163,6 +164,13 @@ class DockerDataServer(AsyncExitStacked):
163
164
  return self._stop_event
164
165
 
165
166
  async def run(self) -> None:
167
+ # FIXME:
168
+ # - shared single server with updatable routes
169
+ # - get docker used ports with ns1
170
+ # - discover server port with get_available_port
171
+ # - discover relay port pair with get_available_ports
172
+ # relay_port: ta.Optional[ta.Tuple[int, int]] = None
173
+
166
174
  relay_port: ta.Optional[int] = None
167
175
  if sys.platform == 'darwin':
168
176
  relay_port = self._port