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.
Files changed (40) hide show
  1. omdev/ci/cache.py +40 -23
  2. omdev/ci/ci.py +49 -109
  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.py → docker/cmds.py} +1 -44
  8. omdev/ci/docker/imagepulling.py +64 -0
  9. omdev/ci/docker/inject.py +37 -0
  10. omdev/ci/docker/utils.py +48 -0
  11. omdev/ci/github/cache.py +15 -5
  12. omdev/ci/github/inject.py +30 -0
  13. omdev/ci/inject.py +61 -0
  14. omdev/dataserver/__init__.py +1 -0
  15. omdev/dataserver/handlers.py +198 -0
  16. omdev/dataserver/http.py +69 -0
  17. omdev/dataserver/routes.py +49 -0
  18. omdev/dataserver/server.py +90 -0
  19. omdev/dataserver/targets.py +89 -0
  20. omdev/oci/__init__.py +0 -0
  21. omdev/oci/building.py +221 -0
  22. omdev/oci/compression.py +8 -0
  23. omdev/oci/data.py +151 -0
  24. omdev/oci/datarefs.py +138 -0
  25. omdev/oci/dataserver.py +61 -0
  26. omdev/oci/loading.py +142 -0
  27. omdev/oci/media.py +179 -0
  28. omdev/oci/packing.py +381 -0
  29. omdev/oci/repositories.py +159 -0
  30. omdev/oci/tars.py +144 -0
  31. omdev/pyproject/resources/python.sh +1 -1
  32. omdev/scripts/ci.py +1841 -384
  33. omdev/scripts/interp.py +100 -22
  34. omdev/scripts/pyproject.py +122 -28
  35. {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/METADATA +2 -2
  36. {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/RECORD +40 -15
  37. {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/LICENSE +0 -0
  38. {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/WHEEL +0 -0
  39. {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/entry_points.txt +0 -0
  40. {omdev-0.0.0.dev221.dist-info → omdev-0.0.0.dev223.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,89 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import abc
3
+ import dataclasses as dc
4
+ import typing as ta
5
+
6
+ from omlish.lite.check import check
7
+ from omlish.lite.dataclasses import dataclass_maybe_post_init
8
+ from omlish.lite.marshal import OBJ_MARSHALER_OMIT_IF_NONE
9
+
10
+
11
+ ##
12
+
13
+
14
+ @dc.dataclass(frozen=True)
15
+ class DataServerTarget(abc.ABC): # noqa
16
+ content_type: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
17
+ content_length: ta.Optional[int] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
18
+
19
+ @classmethod
20
+ def of(
21
+ cls,
22
+ obj: ta.Union[
23
+ 'DataServerTarget',
24
+ bytes,
25
+ None,
26
+ ] = None,
27
+ *,
28
+
29
+ file_path: ta.Optional[str] = None,
30
+ url: ta.Optional[str] = None,
31
+
32
+ **kwargs: ta.Any,
33
+ ) -> 'DataServerTarget':
34
+ if isinstance(obj, DataServerTarget):
35
+ check.none(file_path)
36
+ check.none(url)
37
+ check.empty(kwargs)
38
+ return obj
39
+
40
+ elif isinstance(obj, bytes):
41
+ return BytesDataServerTarget(
42
+ data=obj,
43
+ **kwargs,
44
+ )
45
+
46
+ elif file_path is not None:
47
+ check.none(obj)
48
+ check.none(url)
49
+ return FileDataServerTarget(
50
+ file_path=file_path,
51
+ **kwargs,
52
+ )
53
+
54
+ elif url is not None:
55
+ check.none(obj)
56
+ check.none(file_path)
57
+ return UrlDataServerTarget(
58
+ url=url,
59
+ **kwargs,
60
+ )
61
+
62
+ else:
63
+ raise TypeError('No target type provided')
64
+
65
+
66
+ @dc.dataclass(frozen=True)
67
+ class BytesDataServerTarget(DataServerTarget):
68
+ data: ta.Optional[bytes] = None # required
69
+
70
+
71
+ @dc.dataclass(frozen=True)
72
+ class FileDataServerTarget(DataServerTarget):
73
+ file_path: ta.Optional[str] = None # required
74
+
75
+ def __post_init__(self) -> None:
76
+ dataclass_maybe_post_init(super())
77
+ check.non_empty_str(self.file_path)
78
+
79
+
80
+ @dc.dataclass(frozen=True)
81
+ class UrlDataServerTarget(DataServerTarget):
82
+ url: ta.Optional[str] = None # required
83
+ methods: ta.Optional[ta.Sequence[str]] = None # required
84
+
85
+ def __post_init__(self) -> None:
86
+ dataclass_maybe_post_init(super())
87
+ check.non_empty_str(self.url)
88
+ check.not_none(self.methods)
89
+ check.not_isinstance(self.methods, str)
omdev/oci/__init__.py ADDED
File without changes
omdev/oci/building.py ADDED
@@ -0,0 +1,221 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import dataclasses as dc
4
+ import json
5
+ import typing as ta
6
+
7
+ from omlish.lite.check import check
8
+ from omlish.lite.json import json_dumps_compact
9
+ from omlish.lite.marshal import marshal_obj
10
+
11
+ from .data import OciDataclass
12
+ from .data import OciImageConfig
13
+ from .data import OciImageIndex
14
+ from .data import OciImageLayer
15
+ from .data import OciImageManifest
16
+ from .datarefs import BytesOciDataRef
17
+ from .datarefs import OciDataRef
18
+ from .datarefs import OciDataRefInfo
19
+ from .datarefs import open_oci_data_ref
20
+ from .media import OCI_IMAGE_LAYER_KIND_MEDIA_TYPES
21
+ from .media import OciMediaDataclass
22
+ from .media import OciMediaDescriptor
23
+ from .media import OciMediaImageConfig
24
+ from .media import OciMediaImageIndex
25
+ from .media import OciMediaImageManifest
26
+ from .media import unmarshal_oci_media_dataclass
27
+
28
+
29
+ OciMediaDataclassT = ta.TypeVar('OciMediaDataclassT', bound='OciMediaDataclass')
30
+
31
+
32
+ ##
33
+
34
+
35
+ class OciRepositoryBuilder:
36
+ @dc.dataclass(frozen=True)
37
+ class Blob:
38
+ digest: str
39
+
40
+ data: OciDataRef
41
+ info: OciDataRefInfo
42
+
43
+ media_type: ta.Optional[str] = None
44
+
45
+ #
46
+
47
+ def read(self) -> bytes:
48
+ with open_oci_data_ref(self.data) as f:
49
+ return f.read()
50
+
51
+ def read_json(self) -> ta.Any:
52
+ return json.loads(self.read().decode('utf-8'))
53
+
54
+ def read_media(
55
+ self,
56
+ cls: ta.Type[OciMediaDataclassT] = OciMediaDataclass, # type: ignore[assignment]
57
+ ) -> OciMediaDataclassT:
58
+ mt = check.non_empty_str(self.media_type)
59
+ dct = self.read_json()
60
+ obj = unmarshal_oci_media_dataclass(
61
+ dct,
62
+ media_type=mt,
63
+ )
64
+ return check.isinstance(obj, cls)
65
+
66
+ def __init__(self) -> None:
67
+ super().__init__()
68
+
69
+ self._blobs: ta.Dict[str, OciRepositoryBuilder.Blob] = {}
70
+
71
+ #
72
+
73
+ def get_blobs(self) -> ta.Dict[str, Blob]:
74
+ return dict(self._blobs)
75
+
76
+ def add_blob(
77
+ self,
78
+ r: OciDataRef,
79
+ ri: ta.Optional[OciDataRefInfo] = None,
80
+ *,
81
+ media_type: ta.Optional[str] = None,
82
+ ) -> Blob:
83
+ if ri is None:
84
+ ri = OciDataRefInfo(r)
85
+
86
+ if (dg := ri.digest()) in self._blobs:
87
+ raise KeyError(ri.digest())
88
+
89
+ blob = self.Blob(
90
+ digest=dg,
91
+
92
+ data=r,
93
+ info=ri,
94
+
95
+ media_type=media_type,
96
+ )
97
+
98
+ self._blobs[dg] = blob
99
+
100
+ return blob
101
+
102
+ #
103
+
104
+ def marshal_media(self, obj: OciMediaDataclass) -> bytes:
105
+ check.isinstance(obj, OciMediaDataclass)
106
+ m = marshal_obj(obj)
107
+ j = json_dumps_compact(m)
108
+ b = j.encode('utf-8')
109
+ return b
110
+
111
+ def add_media(self, obj: OciMediaDataclass) -> OciMediaDescriptor:
112
+ b = self.marshal_media(obj)
113
+
114
+ r = BytesOciDataRef(b)
115
+ ri = OciDataRefInfo(r)
116
+ self.add_blob(
117
+ r,
118
+ ri,
119
+ media_type=obj.media_type,
120
+ )
121
+
122
+ return OciMediaDescriptor(
123
+ media_type=obj.media_type,
124
+ digest=ri.digest(),
125
+ size=ri.size(),
126
+ )
127
+
128
+ #
129
+
130
+ def to_media(self, obj: OciDataclass) -> ta.Union[OciMediaDataclass, OciMediaDescriptor]:
131
+ def make_kw(*exclude):
132
+ return {
133
+ a: v
134
+ for f in dc.fields(obj)
135
+ if (a := f.name) not in exclude
136
+ for v in [getattr(obj, a)]
137
+ if v is not None
138
+ }
139
+
140
+ if isinstance(obj, OciImageIndex):
141
+ return OciMediaImageIndex(
142
+ **make_kw('manifests'),
143
+ manifests=[
144
+ self.add_data(m)
145
+ for m in obj.manifests
146
+ ],
147
+ )
148
+
149
+ elif isinstance(obj, OciImageManifest):
150
+ return OciMediaImageManifest(
151
+ **make_kw('config', 'layers'),
152
+ config=self.add_data(obj.config),
153
+ layers=[
154
+ self.add_data(l)
155
+ for l in obj.layers
156
+ ],
157
+ )
158
+
159
+ elif isinstance(obj, OciImageLayer):
160
+ ri = OciDataRefInfo(obj.data)
161
+ mt = OCI_IMAGE_LAYER_KIND_MEDIA_TYPES[obj.kind]
162
+ self.add_blob(
163
+ obj.data,
164
+ ri,
165
+ media_type=mt,
166
+ )
167
+ return OciMediaDescriptor(
168
+ media_type=mt,
169
+ digest=ri.digest(),
170
+ size=ri.size(),
171
+ )
172
+
173
+ elif isinstance(obj, OciImageConfig):
174
+ return OciMediaImageConfig(**make_kw())
175
+
176
+ else:
177
+ raise TypeError(obj)
178
+
179
+ def add_data(self, obj: OciDataclass) -> OciMediaDescriptor:
180
+ ret = self.to_media(obj)
181
+
182
+ if isinstance(ret, OciMediaDataclass):
183
+ return self.add_media(ret)
184
+
185
+ elif isinstance(ret, OciMediaDescriptor):
186
+ return ret
187
+
188
+ else:
189
+ raise TypeError(ret)
190
+
191
+
192
+ ##
193
+
194
+
195
+ @dc.dataclass(frozen=True)
196
+ class BuiltOciImageIndexRepository:
197
+ index: OciImageIndex
198
+
199
+ media_index_descriptor: OciMediaDescriptor
200
+ media_index: OciMediaImageIndex
201
+
202
+ blobs: ta.Mapping[str, OciRepositoryBuilder.Blob]
203
+
204
+
205
+ def build_oci_index_repository(index: OciImageIndex) -> BuiltOciImageIndexRepository:
206
+ builder = OciRepositoryBuilder()
207
+
208
+ media_index_descriptor = builder.add_data(index)
209
+
210
+ blobs = builder.get_blobs()
211
+
212
+ media_index = blobs[media_index_descriptor.digest].read_media(OciMediaImageIndex)
213
+
214
+ return BuiltOciImageIndexRepository(
215
+ index=index,
216
+
217
+ media_index_descriptor=media_index_descriptor,
218
+ media_index=media_index,
219
+
220
+ blobs=blobs,
221
+ )
@@ -0,0 +1,8 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import enum
4
+
5
+
6
+ class OciCompression(enum.Enum):
7
+ GZIP = enum.auto()
8
+ ZSTD = enum.auto()
omdev/oci/data.py ADDED
@@ -0,0 +1,151 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import abc
4
+ import dataclasses as dc
5
+ import enum
6
+ import typing as ta
7
+
8
+ from omlish.lite.marshal import OBJ_MARSHALER_FIELD_KEY
9
+ from omlish.lite.marshal import OBJ_MARSHALER_OMIT_IF_NONE
10
+
11
+ from .compression import OciCompression
12
+ from .datarefs import OciDataRef
13
+
14
+
15
+ ##
16
+
17
+
18
+ @dc.dataclass()
19
+ class OciDataclass(abc.ABC): # noqa
20
+ pass
21
+
22
+
23
+ ##
24
+
25
+
26
+ @dc.dataclass()
27
+ class OciImageIndex(OciDataclass):
28
+ manifests: ta.List[ta.Union['OciImageIndex', 'OciImageManifest']]
29
+
30
+ annotations: ta.Optional[ta.Dict[str, str]] = None
31
+
32
+
33
+ #
34
+
35
+
36
+ @dc.dataclass()
37
+ class OciImageManifest(OciDataclass):
38
+ config: 'OciImageConfig'
39
+
40
+ layers: ta.List['OciImageLayer']
41
+
42
+ annotations: ta.Optional[ta.Dict[str, str]] = None
43
+
44
+
45
+ #
46
+
47
+
48
+ @dc.dataclass()
49
+ class OciImageLayer(OciDataclass):
50
+ class Kind(enum.Enum):
51
+ TAR = enum.auto()
52
+ TAR_GZIP = enum.auto()
53
+ TAR_ZSTD = enum.auto()
54
+
55
+ @property
56
+ def compression(self) -> ta.Optional[OciCompression]:
57
+ if self is self.TAR:
58
+ return None
59
+ elif self is self.TAR_GZIP:
60
+ return OciCompression.GZIP
61
+ elif self is self.TAR_ZSTD:
62
+ return OciCompression.ZSTD
63
+ else:
64
+ raise ValueError(self)
65
+
66
+ @classmethod
67
+ def from_compression(cls, compression: ta.Optional[OciCompression]) -> 'OciImageLayer.Kind':
68
+ if compression is None:
69
+ return cls.TAR
70
+ elif compression == OciCompression.GZIP:
71
+ return cls.TAR_GZIP
72
+ elif compression == OciCompression.ZSTD:
73
+ return cls.TAR_ZSTD
74
+ else:
75
+ raise ValueError(compression)
76
+
77
+ kind: Kind
78
+
79
+ data: OciDataRef
80
+
81
+
82
+ #
83
+
84
+
85
+ @dc.dataclass()
86
+ class OciImageConfig(OciDataclass):
87
+ """https://github.com/opencontainers/image-spec/blob/92353b0bee778725c617e7d57317b568a7796bd0/config.md"""
88
+
89
+ architecture: str
90
+ os: str
91
+
92
+ @dc.dataclass()
93
+ class RootFs:
94
+ type: str
95
+ diff_ids: ta.List[str]
96
+
97
+ rootfs: RootFs
98
+
99
+ #
100
+
101
+ created: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
102
+ author: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
103
+ os_version: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_FIELD_KEY: 'os.version', OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
104
+ os_features: ta.Optional[ta.List[str]] = dc.field(default=None, metadata={OBJ_MARSHALER_FIELD_KEY: 'os.features', OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
105
+ variant: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
106
+
107
+ """
108
+ config object, OPTIONAL
109
+ User string, OPTIONAL
110
+ ExposedPorts object, OPTIONAL
111
+ Env array of strings, OPTIONAL
112
+ Entrypoint array of strings, OPTIONAL
113
+ Cmd array of strings, OPTIONAL
114
+ Volumes object, OPTIONAL
115
+ WorkingDir string, OPTIONAL
116
+ Labels object, OPTIONAL
117
+ StopSignal string, OPTIONAL
118
+ ArgsEscaped boolean, OPTIONAL
119
+ Memory integer, OPTIONAL
120
+ MemorySwap integer, OPTIONAL
121
+ CpuShares integer, OPTIONAL
122
+ Healthcheck object, OPTIONAL
123
+ """
124
+ config: ta.Optional[ta.Dict[str, ta.Any]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
125
+
126
+ @dc.dataclass()
127
+ class History:
128
+ created: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
129
+ author: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
130
+ created_by: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
131
+ comment: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
132
+ empty_layer: ta.Optional[bool] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
133
+
134
+ history: ta.Optional[ta.List[History]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
135
+
136
+
137
+ ##
138
+
139
+
140
+ def is_empty_oci_dataclass(obj: OciDataclass) -> bool:
141
+ if not isinstance(obj, OciDataclass):
142
+ raise TypeError(obj)
143
+
144
+ elif isinstance(obj, OciImageIndex):
145
+ return not obj.manifests
146
+
147
+ elif isinstance(obj, OciImageManifest):
148
+ return not obj.layers
149
+
150
+ else:
151
+ return False
omdev/oci/datarefs.py ADDED
@@ -0,0 +1,138 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import abc
4
+ import dataclasses as dc
5
+ import functools
6
+ import hashlib
7
+ import io
8
+ import os.path
9
+ import shutil
10
+ import tarfile
11
+ import typing as ta
12
+
13
+ from omlish.lite.cached import cached_nullary
14
+ from omlish.lite.check import check
15
+
16
+
17
+ ##
18
+
19
+
20
+ @dc.dataclass(frozen=True)
21
+ class OciDataRef(abc.ABC): # noqa
22
+ pass
23
+
24
+
25
+ @dc.dataclass(frozen=True)
26
+ class BytesOciDataRef(OciDataRef):
27
+ data: bytes
28
+
29
+
30
+ @dc.dataclass(frozen=True)
31
+ class FileOciDataRef(OciDataRef):
32
+ path: str
33
+
34
+
35
+ @dc.dataclass(frozen=True)
36
+ class TarFileOciDataRef(OciDataRef):
37
+ tar_file: tarfile.TarFile
38
+ tar_info: tarfile.TarInfo
39
+
40
+
41
+ ##
42
+
43
+
44
+ @functools.singledispatch
45
+ def write_oci_data_ref_to_file(
46
+ src_data: OciDataRef,
47
+ dst_file: str,
48
+ *,
49
+ symlink: bool = False, # noqa
50
+ chunk_size: int = 1024 * 1024,
51
+ ) -> None:
52
+ with open_oci_data_ref(src_data) as f_src:
53
+ with open(dst_file, 'wb') as f_dst:
54
+ shutil.copyfileobj(f_src, f_dst, length=chunk_size) # noqa
55
+
56
+
57
+ @write_oci_data_ref_to_file.register
58
+ def _(
59
+ src_data: FileOciDataRef,
60
+ dst_file: str,
61
+ *,
62
+ symlink: bool = False,
63
+ **kwargs: ta.Any,
64
+ ) -> None:
65
+ if symlink:
66
+ os.symlink(
67
+ os.path.relpath(src_data.path, os.path.dirname(dst_file)),
68
+ dst_file,
69
+ )
70
+ else:
71
+ shutil.copyfile(src_data.path, dst_file)
72
+
73
+
74
+ #
75
+
76
+
77
+ @functools.singledispatch
78
+ def open_oci_data_ref(data: OciDataRef) -> ta.BinaryIO:
79
+ raise TypeError(data)
80
+
81
+
82
+ @open_oci_data_ref.register
83
+ def _(data: FileOciDataRef) -> ta.BinaryIO:
84
+ return open(data.path, 'rb')
85
+
86
+
87
+ @open_oci_data_ref.register
88
+ def _(data: BytesOciDataRef) -> ta.BinaryIO:
89
+ return io.BytesIO(data.data)
90
+
91
+
92
+ @open_oci_data_ref.register
93
+ def _(data: TarFileOciDataRef) -> ta.BinaryIO:
94
+ return check.not_none(data.tar_file.extractfile(data.tar_info)) # type: ignore[return-value]
95
+
96
+
97
+ #
98
+
99
+
100
+ @functools.singledispatch
101
+ def get_oci_data_ref_size(data: OciDataRef) -> int:
102
+ raise TypeError(data)
103
+
104
+
105
+ @get_oci_data_ref_size.register
106
+ def _(data: FileOciDataRef) -> int:
107
+ return os.path.getsize(data.path)
108
+
109
+
110
+ @get_oci_data_ref_size.register
111
+ def _(data: BytesOciDataRef) -> int:
112
+ return len(data.data)
113
+
114
+
115
+ @get_oci_data_ref_size.register
116
+ def _(data: TarFileOciDataRef) -> int:
117
+ return data.tar_info.size
118
+
119
+
120
+ ##
121
+
122
+
123
+ @dc.dataclass(frozen=True)
124
+ class OciDataRefInfo:
125
+ data: OciDataRef
126
+
127
+ @cached_nullary
128
+ def sha256(self) -> str:
129
+ with open_oci_data_ref(self.data) as f:
130
+ return hashlib.file_digest(f, 'sha256').hexdigest() # type: ignore[arg-type]
131
+
132
+ @cached_nullary
133
+ def digest(self) -> str:
134
+ return f'sha256:{self.sha256()}'
135
+
136
+ @cached_nullary
137
+ def size(self) -> int:
138
+ return get_oci_data_ref_size(self.data)
@@ -0,0 +1,61 @@
1
+ # ruff: noqa: PT009 UP006 UP007
2
+ import typing as ta
3
+
4
+ from omlish.lite.check import check
5
+
6
+ from ..dataserver.routes import DataServerRoute
7
+ from ..dataserver.targets import DataServerTarget
8
+ from .building import BuiltOciImageIndexRepository
9
+ from .building import OciRepositoryBuilder
10
+ from .datarefs import BytesOciDataRef
11
+ from .datarefs import FileOciDataRef
12
+ from .datarefs import open_oci_data_ref
13
+ from .media import OCI_MANIFEST_MEDIA_TYPES
14
+
15
+
16
+ def build_oci_repository_data_server_routes(
17
+ repo_name: str,
18
+ built_repo: BuiltOciImageIndexRepository,
19
+ ) -> ta.List[DataServerRoute]:
20
+ base_url_path = f'/v2/{repo_name}'
21
+
22
+ repo_contents: ta.Dict[str, OciRepositoryBuilder.Blob] = {}
23
+
24
+ repo_contents[f'{base_url_path}/manifests/latest'] = built_repo.blobs[built_repo.media_index_descriptor.digest]
25
+
26
+ for blob in built_repo.blobs.values():
27
+ repo_contents['/'.join([
28
+ base_url_path,
29
+ 'manifests' if blob.media_type in OCI_MANIFEST_MEDIA_TYPES else 'blobs',
30
+ blob.digest,
31
+ ])] = blob
32
+
33
+ #
34
+
35
+ def build_blob_target(blob: OciRepositoryBuilder.Blob) -> ta.Optional[DataServerTarget]: # noqa
36
+ kw: dict = dict(
37
+ content_type=check.non_empty_str(blob.media_type),
38
+ )
39
+
40
+ if isinstance(blob.data, BytesOciDataRef):
41
+ return DataServerTarget.of(blob.data.data, **kw)
42
+
43
+ elif isinstance(blob.data, FileOciDataRef):
44
+ return DataServerTarget.of(file_path=blob.data.path, **kw)
45
+
46
+ else:
47
+ with open_oci_data_ref(blob.data) as f:
48
+ data = f.read()
49
+
50
+ return DataServerTarget.of(data, **kw)
51
+
52
+ #
53
+
54
+ return [
55
+ DataServerRoute(
56
+ paths=[path],
57
+ target=target,
58
+ )
59
+ for path, blob in repo_contents.items()
60
+ if (target := build_blob_target(blob)) is not None
61
+ ]