omdev 0.0.0.dev240__py3-none-any.whl → 0.0.0.dev241__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 +1 -1
- omdev/ci/cache.py +1 -1
- omdev/ci/ci.py +36 -11
- omdev/ci/cli.py +8 -0
- omdev/ci/consts.py +1 -1
- omdev/ci/docker/buildcaching.py +4 -3
- omdev/ci/docker/cache.py +28 -6
- omdev/ci/docker/cacheserved/__init__.py +0 -0
- omdev/ci/docker/cacheserved/cache.py +209 -0
- omdev/ci/docker/cacheserved/manifests.py +122 -0
- omdev/ci/docker/dataserver.py +9 -1
- omdev/ci/docker/imagepulling.py +4 -4
- omdev/ci/docker/inject.py +36 -7
- omdev/ci/github/client.py +2 -2
- omdev/ci/github/inject.py +2 -0
- omdev/ci/inject.py +16 -1
- omdev/dataserver/http.py +1 -0
- omdev/precheck/lite.py +3 -0
- omdev/scripts/bumpversion.py +4 -0
- omdev/scripts/ci.py +7254 -1345
- omdev/scripts/interp.py +21 -12
- omdev/scripts/pyproject.py +21 -12
- omdev/tools/docker.py +11 -27
- omdev/tools/git/cli.py +1 -0
- {omdev-0.0.0.dev240.dist-info → omdev-0.0.0.dev241.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev240.dist-info → omdev-0.0.0.dev241.dist-info}/RECORD +30 -28
- omdev/ci/docker/cacheserved.py +0 -262
- {omdev-0.0.0.dev240.dist-info → omdev-0.0.0.dev241.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev240.dist-info → omdev-0.0.0.dev241.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev240.dist-info → omdev-0.0.0.dev241.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev240.dist-info → omdev-0.0.0.dev241.dist-info}/top_level.txt +0 -0
omdev/.manifests.json
CHANGED
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(
|
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,6 +15,7 @@ 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
|
17
20
|
from .docker.imagepulling import DockerImagePulling
|
18
21
|
from .docker.utils import build_docker_file_hash
|
@@ -44,6 +47,8 @@ class Ci(AsyncExitStacked):
|
|
44
47
|
always_pull: bool = False
|
45
48
|
always_build: bool = False
|
46
49
|
|
50
|
+
setup_concurrency: ta.Optional[int] = None
|
51
|
+
|
47
52
|
no_dependencies: bool = False
|
48
53
|
|
49
54
|
run_options: ta.Optional[ta.Sequence[str]] = None
|
@@ -74,8 +79,8 @@ class Ci(AsyncExitStacked):
|
|
74
79
|
return build_docker_file_hash(self._config.docker_file)[:self.KEY_HASH_LEN]
|
75
80
|
|
76
81
|
@cached_nullary
|
77
|
-
def ci_base_image_cache_key(self) ->
|
78
|
-
return
|
82
|
+
def ci_base_image_cache_key(self) -> DockerCacheKey:
|
83
|
+
return DockerCacheKey(['ci-base'], self.docker_file_hash())
|
79
84
|
|
80
85
|
async def _resolve_ci_base_image(self) -> str:
|
81
86
|
async def build_and_tag(image_tag: str) -> str:
|
@@ -111,8 +116,8 @@ class Ci(AsyncExitStacked):
|
|
111
116
|
return build_requirements_hash(self.requirements_txts())[:self.KEY_HASH_LEN]
|
112
117
|
|
113
118
|
@cached_nullary
|
114
|
-
def ci_image_cache_key(self) ->
|
115
|
-
return f'
|
119
|
+
def ci_image_cache_key(self) -> DockerCacheKey:
|
120
|
+
return DockerCacheKey(['ci'], f'{self.docker_file_hash()}-{self.requirements_hash()}')
|
116
121
|
|
117
122
|
async def _resolve_ci_image(self) -> str:
|
118
123
|
async def build_and_tag(image_tag: str) -> str:
|
@@ -168,15 +173,37 @@ class Ci(AsyncExitStacked):
|
|
168
173
|
|
169
174
|
#
|
170
175
|
|
171
|
-
@
|
172
|
-
|
176
|
+
@cached_nullary
|
177
|
+
def pull_dependencies_funcs(self) -> ta.Sequence[ta.Callable[[], ta.Awaitable]]:
|
173
178
|
deps = get_compose_service_dependencies(
|
174
179
|
self._config.compose_file,
|
175
180
|
self._config.service,
|
176
181
|
)
|
177
182
|
|
178
|
-
|
179
|
-
|
183
|
+
return [
|
184
|
+
async_cached_nullary(functools.partial(
|
185
|
+
self._docker_image_pulling.pull_docker_image,
|
186
|
+
dep_image,
|
187
|
+
))
|
188
|
+
for dep_image in deps.values()
|
189
|
+
]
|
190
|
+
|
191
|
+
#
|
192
|
+
|
193
|
+
@cached_nullary
|
194
|
+
def setup_funcs(self) -> ta.Sequence[ta.Callable[[], ta.Awaitable]]:
|
195
|
+
return [
|
196
|
+
self.resolve_ci_image,
|
197
|
+
|
198
|
+
*(self.pull_dependencies_funcs() if not self._config.no_dependencies else []),
|
199
|
+
]
|
200
|
+
|
201
|
+
@async_cached_nullary
|
202
|
+
async def setup(self) -> None:
|
203
|
+
await asyncio_wait_maybe_concurrent(
|
204
|
+
[fn() for fn in self.setup_funcs()],
|
205
|
+
self._config.setup_concurrency,
|
206
|
+
)
|
180
207
|
|
181
208
|
#
|
182
209
|
|
@@ -207,8 +234,6 @@ class Ci(AsyncExitStacked):
|
|
207
234
|
#
|
208
235
|
|
209
236
|
async def run(self) -> None:
|
210
|
-
await self.
|
211
|
-
|
212
|
-
await self.pull_dependencies()
|
237
|
+
await self.setup()
|
213
238
|
|
214
239
|
await self._run_compose()
|
omdev/ci/cli.py
CHANGED
@@ -88,6 +88,10 @@ 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
|
|
@@ -203,6 +207,8 @@ class CiCli(ArgparseCli):
|
|
203
207
|
always_pull=self.args.always_pull,
|
204
208
|
always_build=self.args.always_build,
|
205
209
|
|
210
|
+
setup_concurrency=self.args.setup_concurrency,
|
211
|
+
|
206
212
|
no_dependencies=self.args.no_dependencies,
|
207
213
|
|
208
214
|
run_options=run_options,
|
@@ -225,6 +231,8 @@ class CiCli(ArgparseCli):
|
|
225
231
|
directory_file_cache_config=directory_file_cache_config,
|
226
232
|
|
227
233
|
github=github,
|
234
|
+
|
235
|
+
cache_served_docker=self.args.cache_served_docker,
|
228
236
|
))
|
229
237
|
|
230
238
|
async with injector[Ci] as ci:
|
omdev/ci/consts.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
CI_CACHE_VERSION =
|
1
|
+
CI_CACHE_VERSION = 2
|
omdev/ci/docker/buildcaching.py
CHANGED
@@ -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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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/dataserver.py
CHANGED
@@ -7,7 +7,7 @@ import sys
|
|
7
7
|
import threading
|
8
8
|
import typing as ta
|
9
9
|
|
10
|
-
from omlish.docker.
|
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
|
omdev/ci/docker/imagepulling.py
CHANGED
@@ -4,9 +4,11 @@ import dataclasses as dc
|
|
4
4
|
import typing as ta
|
5
5
|
|
6
6
|
from omlish.lite.timing import log_timing_context
|
7
|
+
from omlish.text.mangle import StringMangler
|
7
8
|
|
8
9
|
from ..cache import FileCache
|
9
10
|
from .cache import DockerCache
|
11
|
+
from .cache import DockerCacheKey
|
10
12
|
from .cmds import is_docker_image_present
|
11
13
|
from .cmds import pull_docker_image
|
12
14
|
|
@@ -44,11 +46,9 @@ class DockerImagePullingImpl(DockerImagePulling):
|
|
44
46
|
if not self._config.always_pull and (await is_docker_image_present(image)):
|
45
47
|
return
|
46
48
|
|
47
|
-
|
48
|
-
for c in '/:.-_':
|
49
|
-
dep_suffix = dep_suffix.replace(c, '-')
|
49
|
+
key_content = StringMangler.of('-', '/:._').mangle(image)
|
50
50
|
|
51
|
-
cache_key =
|
51
|
+
cache_key = DockerCacheKey(['docker'], key_content)
|
52
52
|
if (
|
53
53
|
self._docker_cache is not None and
|
54
54
|
(await self._docker_cache.load_cache_docker_image(cache_key)) is not None
|