omdev 0.0.0.dev221__py3-none-any.whl → 0.0.0.dev223__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|