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.
- omdev/ci/cache.py +40 -23
- omdev/ci/ci.py +49 -109
- omdev/ci/cli.py +24 -23
- omdev/ci/docker/__init__.py +0 -0
- omdev/ci/docker/buildcaching.py +69 -0
- omdev/ci/docker/cache.py +57 -0
- omdev/ci/{docker.py → docker/cmds.py} +1 -44
- omdev/ci/docker/imagepulling.py +64 -0
- omdev/ci/docker/inject.py +37 -0
- omdev/ci/docker/utils.py +48 -0
- omdev/ci/github/cache.py +15 -5
- omdev/ci/github/inject.py +30 -0
- omdev/ci/inject.py +61 -0
- omdev/dataserver/__init__.py +1 -0
- omdev/dataserver/handlers.py +198 -0
- omdev/dataserver/http.py +69 -0
- omdev/dataserver/routes.py +49 -0
- omdev/dataserver/server.py +90 -0
- omdev/dataserver/targets.py +89 -0
- omdev/oci/__init__.py +0 -0
- omdev/oci/building.py +221 -0
- omdev/oci/compression.py +8 -0
- omdev/oci/data.py +151 -0
- omdev/oci/datarefs.py +138 -0
- omdev/oci/dataserver.py +61 -0
- omdev/oci/loading.py +142 -0
- omdev/oci/media.py +179 -0
- omdev/oci/packing.py +381 -0
- omdev/oci/repositories.py +159 -0
- omdev/oci/tars.py +144 -0
- omdev/pyproject/resources/python.sh +1 -1
- omdev/scripts/ci.py +1841 -384
- omdev/scripts/interp.py +100 -22
- omdev/scripts/pyproject.py +122 -28
- {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/RECORD +40 -15
- {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import abc
|
4
|
+
import os.path
|
5
|
+
import tarfile
|
6
|
+
import typing as ta
|
7
|
+
|
8
|
+
from omlish.lite.check import check
|
9
|
+
from omlish.os.paths import is_path_in_dir
|
10
|
+
|
11
|
+
from .datarefs import BytesOciDataRef
|
12
|
+
from .datarefs import FileOciDataRef
|
13
|
+
from .datarefs import OciDataRef
|
14
|
+
from .datarefs import TarFileOciDataRef
|
15
|
+
|
16
|
+
|
17
|
+
##
|
18
|
+
|
19
|
+
|
20
|
+
class OciRepository(abc.ABC):
|
21
|
+
@abc.abstractmethod
|
22
|
+
def contains_blob(self, digest: str) -> bool:
|
23
|
+
raise NotImplementedError
|
24
|
+
|
25
|
+
@abc.abstractmethod
|
26
|
+
def read_blob(self, digest: str) -> bytes:
|
27
|
+
raise NotImplementedError
|
28
|
+
|
29
|
+
@abc.abstractmethod
|
30
|
+
def ref_blob(self, digest: str) -> OciDataRef:
|
31
|
+
raise NotImplementedError
|
32
|
+
|
33
|
+
@classmethod
|
34
|
+
def of(
|
35
|
+
cls,
|
36
|
+
obj: ta.Union[
|
37
|
+
'OciRepository',
|
38
|
+
str,
|
39
|
+
tarfile.TarFile,
|
40
|
+
ta.Mapping[str, bytes],
|
41
|
+
],
|
42
|
+
) -> 'OciRepository':
|
43
|
+
if isinstance(obj, OciRepository):
|
44
|
+
return obj
|
45
|
+
|
46
|
+
elif isinstance(obj, str):
|
47
|
+
check.arg(os.path.isdir(obj))
|
48
|
+
return DirectoryOciRepository(obj)
|
49
|
+
|
50
|
+
elif isinstance(obj, tarfile.TarFile):
|
51
|
+
return TarFileOciRepository(obj)
|
52
|
+
|
53
|
+
elif isinstance(obj, ta.Mapping):
|
54
|
+
return DictOciRepository(obj)
|
55
|
+
|
56
|
+
else:
|
57
|
+
raise TypeError(obj)
|
58
|
+
|
59
|
+
|
60
|
+
class FileOciRepository(OciRepository, abc.ABC):
|
61
|
+
@abc.abstractmethod
|
62
|
+
def read_file(self, path: str) -> bytes:
|
63
|
+
raise NotImplementedError
|
64
|
+
|
65
|
+
|
66
|
+
#
|
67
|
+
|
68
|
+
|
69
|
+
class DirectoryOciRepository(FileOciRepository):
|
70
|
+
def __init__(self, data_dir: str) -> None:
|
71
|
+
super().__init__()
|
72
|
+
|
73
|
+
self._data_dir = check.non_empty_str(data_dir)
|
74
|
+
|
75
|
+
def read_file(self, path: str) -> bytes:
|
76
|
+
full_path = os.path.join(self._data_dir, path)
|
77
|
+
check.arg(is_path_in_dir(self._data_dir, full_path))
|
78
|
+
with open(full_path, 'rb') as f:
|
79
|
+
return f.read()
|
80
|
+
|
81
|
+
def blob_path(self, digest: str) -> str:
|
82
|
+
scheme, value = digest.split(':')
|
83
|
+
return os.path.join('blobs', scheme, value)
|
84
|
+
|
85
|
+
def blob_full_path(self, digest: str) -> str:
|
86
|
+
path = self.blob_path(digest)
|
87
|
+
full_path = os.path.join(self._data_dir, path)
|
88
|
+
check.arg(is_path_in_dir(self._data_dir, full_path))
|
89
|
+
return full_path
|
90
|
+
|
91
|
+
def contains_blob(self, digest: str) -> bool:
|
92
|
+
return os.path.isfile(self.blob_full_path(digest))
|
93
|
+
|
94
|
+
def read_blob(self, digest: str) -> bytes:
|
95
|
+
return self.read_file(self.blob_path(digest))
|
96
|
+
|
97
|
+
def ref_blob(self, digest: str) -> OciDataRef:
|
98
|
+
return FileOciDataRef(self.blob_full_path(digest))
|
99
|
+
|
100
|
+
|
101
|
+
#
|
102
|
+
|
103
|
+
|
104
|
+
class TarFileOciRepository(FileOciRepository):
|
105
|
+
def __init__(self, tar_file: tarfile.TarFile) -> None:
|
106
|
+
super().__init__()
|
107
|
+
|
108
|
+
check.arg('r' in tar_file.mode)
|
109
|
+
|
110
|
+
self._tar_file = tar_file
|
111
|
+
|
112
|
+
def read_file(self, path: str) -> bytes:
|
113
|
+
if (ti := self._tar_file.getmember(path)) is None:
|
114
|
+
raise FileNotFoundError(path)
|
115
|
+
with check.not_none(self._tar_file.extractfile(ti)) as f:
|
116
|
+
return f.read()
|
117
|
+
|
118
|
+
def blob_name(self, digest: str) -> str:
|
119
|
+
scheme, value = digest.split(':')
|
120
|
+
return os.path.join('blobs', scheme, value)
|
121
|
+
|
122
|
+
def contains_blob(self, digest: str) -> bool:
|
123
|
+
try:
|
124
|
+
self._tar_file.getmember(self.blob_name(digest))
|
125
|
+
except KeyError:
|
126
|
+
return False
|
127
|
+
else:
|
128
|
+
return True
|
129
|
+
|
130
|
+
def read_blob(self, digest: str) -> bytes:
|
131
|
+
if (ti := self._tar_file.getmember(self.blob_name(digest))) is None:
|
132
|
+
raise KeyError(digest)
|
133
|
+
with check.not_none(self._tar_file.extractfile(ti)) as f:
|
134
|
+
return f.read()
|
135
|
+
|
136
|
+
def ref_blob(self, digest: str) -> OciDataRef:
|
137
|
+
return TarFileOciDataRef(
|
138
|
+
tar_file=self._tar_file,
|
139
|
+
tar_info=self._tar_file.getmember(self.blob_name(digest)),
|
140
|
+
)
|
141
|
+
|
142
|
+
|
143
|
+
#
|
144
|
+
|
145
|
+
|
146
|
+
class DictOciRepository(OciRepository):
|
147
|
+
def __init__(self, blobs: ta.Mapping[str, bytes]) -> None:
|
148
|
+
super().__init__()
|
149
|
+
|
150
|
+
self._blobs = blobs
|
151
|
+
|
152
|
+
def contains_blob(self, digest: str) -> bool:
|
153
|
+
return digest in self._blobs
|
154
|
+
|
155
|
+
def read_blob(self, digest: str) -> bytes:
|
156
|
+
return self._blobs[digest]
|
157
|
+
|
158
|
+
def ref_blob(self, digest: str) -> OciDataRef:
|
159
|
+
return BytesOciDataRef(self._blobs[digest])
|
omdev/oci/tars.py
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import gzip
|
4
|
+
import hashlib
|
5
|
+
import tarfile
|
6
|
+
import typing as ta
|
7
|
+
|
8
|
+
from omlish.lite.contextmanagers import ExitStacked
|
9
|
+
|
10
|
+
from .compression import OciCompression
|
11
|
+
from .datarefs import OciDataRef
|
12
|
+
from .datarefs import OciDataRefInfo
|
13
|
+
from .datarefs import open_oci_data_ref
|
14
|
+
|
15
|
+
|
16
|
+
##
|
17
|
+
|
18
|
+
|
19
|
+
class WrittenOciDataTarFileInfo(ta.NamedTuple):
|
20
|
+
compressed_sz: int
|
21
|
+
compressed_sha256: str
|
22
|
+
|
23
|
+
tar_sz: int
|
24
|
+
tar_sha256: str
|
25
|
+
|
26
|
+
|
27
|
+
class OciDataTarWriter(ExitStacked):
|
28
|
+
def __init__(
|
29
|
+
self,
|
30
|
+
f: ta.BinaryIO,
|
31
|
+
compression: ta.Optional[OciCompression] = None,
|
32
|
+
*,
|
33
|
+
gzip_level: int = 1,
|
34
|
+
zstd_level: int = 10,
|
35
|
+
) -> None:
|
36
|
+
super().__init__()
|
37
|
+
|
38
|
+
self._f = f
|
39
|
+
self._compression = compression
|
40
|
+
|
41
|
+
self._gzip_level = gzip_level
|
42
|
+
self._zstd_level = zstd_level
|
43
|
+
|
44
|
+
class _FileWrapper:
|
45
|
+
def __init__(self, f):
|
46
|
+
super().__init__()
|
47
|
+
|
48
|
+
self._f = f
|
49
|
+
self._c = 0
|
50
|
+
self._h = hashlib.sha256()
|
51
|
+
|
52
|
+
@property
|
53
|
+
def size(self) -> int:
|
54
|
+
return self._c
|
55
|
+
|
56
|
+
def sha256(self) -> str:
|
57
|
+
return self._h.hexdigest()
|
58
|
+
|
59
|
+
def write(self, d):
|
60
|
+
self._c += len(d)
|
61
|
+
self._h.update(d)
|
62
|
+
self._f.write(d)
|
63
|
+
|
64
|
+
def tell(self) -> int:
|
65
|
+
return self._f.tell()
|
66
|
+
|
67
|
+
_cw: _FileWrapper
|
68
|
+
_cf: ta.BinaryIO
|
69
|
+
|
70
|
+
_tw: _FileWrapper
|
71
|
+
_tf: tarfile.TarFile
|
72
|
+
|
73
|
+
def info(self) -> WrittenOciDataTarFileInfo:
|
74
|
+
return WrittenOciDataTarFileInfo(
|
75
|
+
compressed_sz=self._cw.size,
|
76
|
+
compressed_sha256=self._cw.sha256(),
|
77
|
+
|
78
|
+
tar_sz=self._tw.size,
|
79
|
+
tar_sha256=self._tw.sha256(),
|
80
|
+
)
|
81
|
+
|
82
|
+
def __enter__(self) -> 'OciDataTarWriter':
|
83
|
+
super().__enter__()
|
84
|
+
|
85
|
+
#
|
86
|
+
|
87
|
+
self._cw = self._FileWrapper(self._f)
|
88
|
+
|
89
|
+
if self._compression is OciCompression.GZIP:
|
90
|
+
self._cf = self._enter_context(
|
91
|
+
gzip.GzipFile( # type: ignore
|
92
|
+
fileobj=self._cw,
|
93
|
+
mode='wb',
|
94
|
+
compresslevel=self._gzip_level,
|
95
|
+
),
|
96
|
+
)
|
97
|
+
|
98
|
+
elif self._compression is OciCompression.ZSTD:
|
99
|
+
zc = __import__('zstandard').ZstdCompressor(
|
100
|
+
level=self._zstd_level,
|
101
|
+
)
|
102
|
+
self._cf = self._enter_context(zc.stream_writer(self._cw))
|
103
|
+
|
104
|
+
elif self._compression is None:
|
105
|
+
self._cf = self._cw # type: ignore
|
106
|
+
|
107
|
+
else:
|
108
|
+
raise ValueError(self._compression)
|
109
|
+
|
110
|
+
#
|
111
|
+
|
112
|
+
self._tw = self._FileWrapper(self._cf)
|
113
|
+
|
114
|
+
self._tf = self._enter_context(
|
115
|
+
tarfile.open( # type: ignore
|
116
|
+
fileobj=self._tw,
|
117
|
+
mode='w',
|
118
|
+
),
|
119
|
+
)
|
120
|
+
|
121
|
+
#
|
122
|
+
|
123
|
+
return self
|
124
|
+
|
125
|
+
def tar_file(self) -> tarfile.TarFile:
|
126
|
+
return self._tf
|
127
|
+
|
128
|
+
def add_file(self, ti: tarfile.TarInfo, f: ta.Optional[ta.BinaryIO] = None) -> None:
|
129
|
+
self._tf.addfile(ti, f)
|
130
|
+
|
131
|
+
|
132
|
+
def write_oci_data_tar_file(
|
133
|
+
f: ta.BinaryIO,
|
134
|
+
data: ta.Mapping[str, OciDataRef],
|
135
|
+
) -> WrittenOciDataTarFileInfo:
|
136
|
+
with OciDataTarWriter(f) as tgw:
|
137
|
+
for n, dr in data.items():
|
138
|
+
ti = tarfile.TarInfo(name=n)
|
139
|
+
ri = OciDataRefInfo(dr)
|
140
|
+
ti.size = ri.size()
|
141
|
+
with open_oci_data_ref(dr) as df:
|
142
|
+
tgw.add_file(ti, df)
|
143
|
+
|
144
|
+
return tgw.info()
|
@@ -2,7 +2,7 @@
|
|
2
2
|
set -e
|
3
3
|
|
4
4
|
if [ -z "${VENV}" ] ; then
|
5
|
-
if [ $(uname) = "Linux" ] &&
|
5
|
+
if [ $(uname) = "Linux" ] && grep -E '^overlay / .*/(docker|desktop-containerd)/' /proc/mounts > /dev/null ; then
|
6
6
|
VENV=docker
|
7
7
|
else
|
8
8
|
VENV=default
|