Skip to content

Commit ba4d027

Browse files
committed
upload as image
Signed-off-by: Isabella do Amaral <[email protected]>
1 parent 18eaffa commit ba4d027

File tree

7 files changed

+176
-51
lines changed

7 files changed

+176
-51
lines changed

e2e/quay-lite/config.yaml

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ DATA_MODEL_CACHE_CONFIG:
44
primary:
55
host: quay-redis
66
SUPER_USERS:
7-
- admin
8-
- user1
7+
- admin
8+
- user1
99
AUTHENTICATION_TYPE: Database
1010
DB_URI: postgresql://quay:quay@quay-postgresql:5432/quay
1111
BUILDLOGS_REDIS:
@@ -15,15 +15,15 @@ USER_EVENTS_REDIS:
1515
host: quay-redis
1616
port: 6379
1717
BITTORRENT_FILENAME_PEPPER: 0ee18f90-5b6d-42d2-ab5e-ec9fcd846272
18-
DATABASE_SECRET_KEY: '30060361640793187613697366923211113205676925445650250274752125083971638376224'
18+
DATABASE_SECRET_KEY: "30060361640793187613697366923211113205676925445650250274752125083971638376224"
1919
DEFAULT_TAG_EXPIRATION: 2w
2020
DISTRIBUTED_STORAGE_CONFIG:
2121
default:
22-
- LocalStorage
23-
- storage_path: /datastorage/registry
22+
- LocalStorage
23+
- storage_path: /datastorage/registry
2424
DISTRIBUTED_STORAGE_DEFAULT_LOCATIONS: []
2525
DISTRIBUTED_STORAGE_PREFERENCE:
26-
- default
26+
- default
2727
ENTERPRISE_LOGO_URL: /static/img/quay-horizontal-color.svg
2828
EXTERNAL_TLS_TERMINATION: true
2929
FEATURE_ANONYMOUS_ACCESS: true
@@ -59,13 +59,13 @@ REPO_MIRROR_TLS_VERIFY: true
5959
SETUP_COMPLETE: true
6060
SIGNING_ENGINE: gpg2
6161
TAG_EXPIRATION_OPTIONS:
62-
- 0s
63-
- 1d
64-
- 1w
65-
- 2w
66-
- 4w
62+
- 0s
63+
- 1d
64+
- 1w
65+
- 2w
66+
- 4w
6767
TEAM_RESYNC_STALE_TIME: 60m
68-
TESTING: false
68+
TESTING: true
6969
USERFILES_LOCATION: default
7070
USERFILES_PATH: userfiles/
7171
USE_CDN: false
@@ -77,4 +77,4 @@ CORS_ORIGIN:
7777
- "http://localhost:9000"
7878
FEATURE_UI_V2: True
7979
FEATURE_USER_METADATA: True
80-
IGNORE_UNKNOWN_MEDIATYPES: True
80+
IGNORE_UNKNOWN_MEDIATYPES: false

e2e/quay-lite/quay-app-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ metadata:
44
name: quay-app-config
55
type: Opaque
66
data:
7-
config.yaml: REFUQV9NT0RFTF9DQUNIRV9DT05GSUc6CiAgZW5naW5lOiByZWRpcwogIHJlZGlzX2NvbmZpZzoKICAgIHByaW1hcnk6CiAgICAgIGhvc3Q6IHF1YXktcmVkaXMKU1VQRVJfVVNFUlM6Ci0gYWRtaW4KLSB1c2VyMQpBVVRIRU5USUNBVElPTl9UWVBFOiBEYXRhYmFzZQpEQl9VUkk6IHBvc3RncmVzcWw6Ly9xdWF5OnF1YXlAcXVheS1wb3N0Z3Jlc3FsOjU0MzIvcXVheQpCVUlMRExPR1NfUkVESVM6CiAgaG9zdDogcXVheS1yZWRpcwogIHBvcnQ6IDYzNzkKVVNFUl9FVkVOVFNfUkVESVM6CiAgaG9zdDogcXVheS1yZWRpcwogIHBvcnQ6IDYzNzkKQklUVE9SUkVOVF9GSUxFTkFNRV9QRVBQRVI6IDBlZTE4ZjkwLTViNmQtNDJkMi1hYjVlLWVjOWZjZDg0NjI3MgpEQVRBQkFTRV9TRUNSRVRfS0VZOiAnMzAwNjAzNjE2NDA3OTMxODc2MTM2OTczNjY5MjMyMTExMTMyMDU2NzY5MjU0NDU2NTAyNTAyNzQ3NTIxMjUwODM5NzE2MzgzNzYyMjQnCkRFRkFVTFRfVEFHX0VYUElSQVRJT046IDJ3CkRJU1RSSUJVVEVEX1NUT1JBR0VfQ09ORklHOgogIGRlZmF1bHQ6CiAgLSBMb2NhbFN0b3JhZ2UKICAtIHN0b3JhZ2VfcGF0aDogL2RhdGFzdG9yYWdlL3JlZ2lzdHJ5CkRJU1RSSUJVVEVEX1NUT1JBR0VfREVGQVVMVF9MT0NBVElPTlM6IFtdCkRJU1RSSUJVVEVEX1NUT1JBR0VfUFJFRkVSRU5DRToKLSBkZWZhdWx0CkVOVEVSUFJJU0VfTE9HT19VUkw6IC9zdGF0aWMvaW1nL3F1YXktaG9yaXpvbnRhbC1jb2xvci5zdmcKRVhURVJOQUxfVExTX1RFUk1JTkFUSU9OOiB0cnVlCkZFQVRVUkVfQU5PTllNT1VTX0FDQ0VTUzogdHJ1ZQpGRUFUVVJFX0FQUF9SRUdJU1RSWTogZmFsc2UKRkVBVFVSRV9BUFBfU1BFQ0lGSUNfVE9LRU5TOiB0cnVlCkZFQVRVUkVfQlVJTERfU1VQUE9SVDogZmFsc2UKRkVBVFVSRV9DSEFOR0VfVEFHX0VYUElSQVRJT046IHRydWUKRkVBVFVSRV9ESVJFQ1RfTE9HSU46IHRydWUKRkVBVFVSRV9NQUlMSU5HOiBmYWxzZQpGRUFUVVJFX1BBUlRJQUxfVVNFUl9BVVRPQ09NUExFVEU6IHRydWUKRkVBVFVSRV9SRVBPX01JUlJPUjogZmFsc2UKRkVBVFVSRV9SRVFVSVJFX1RFQU1fSU5WSVRFOiB0cnVlCkZFQVRVUkVfUkVTVFJJQ1RFRF9WMV9QVVNIOiBmYWxzZQpGRUFUVVJFX1NFQ1VSSVRZX05PVElGSUNBVElPTlM6IGZhbHNlCkZFQVRVUkVfU0VDVVJJVFlfU0NBTk5FUjogZmFsc2UKRkVBVFVSRV9VU0VSTkFNRV9DT05GSVJNQVRJT046IHRydWUKRkVBVFVSRV9VU0VSX0lOSVRJQUxJWkU6IHRydWUKRkVBVFVSRV9VU0VSX0NSRUFUSU9OOiB0cnVlCkZFQVRVUkVfVVNFUl9MT0dfQUNDRVNTOiB0cnVlCkZFQVRVUkVfUFJPWFlfQ0FDSEU6IHRydWUKR0lUSFVCX0xPR0lOX0NPTkZJRzoge30KR0lUSFVCX1RSSUdHRVJfQ09ORklHOiB7fQpHSVRMQUJfVFJJR0dFUl9LSU5EOiB7fQpMT0dfQVJDSElWRV9MT0NBVElPTjogZGVmYXVsdApNQUlMX0RFRkFVTFRfU0VOREVSOiBhZG1pbkBleGFtcGxlLmNvbQpNQUlMX1BPUlQ6IDU4NwpNQUlMX1VTRV9UTFM6IHRydWUKUFJFRkVSUkVEX1VSTF9TQ0hFTUU6IGh0dHAKUkVHSVNUUllfVElUTEU6IFJlZCBIYXQgUXVheSBMSVRFClJFR0lTVFJZX1RJVExFX1NIT1JUOiBSZWQgSGF0IFF1YXkgTElURQpSRVBPX01JUlJPUl9TRVJWRVJfSE9TVE5BTUU6IG51bGwKUkVQT19NSVJST1JfVExTX1ZFUklGWTogdHJ1ZQpTRVRVUF9DT01QTEVURTogdHJ1ZQpTSUdOSU5HX0VOR0lORTogZ3BnMgpUQUdfRVhQSVJBVElPTl9PUFRJT05TOgotIDBzCi0gMWQKLSAxdwotIDJ3Ci0gNHcKVEVBTV9SRVNZTkNfU1RBTEVfVElNRTogNjBtClRFU1RJTkc6IGZhbHNlClVTRVJGSUxFU19MT0NBVElPTjogZGVmYXVsdApVU0VSRklMRVNfUEFUSDogdXNlcmZpbGVzLwpVU0VfQ0ROOiBmYWxzZQpGRUFUVVJFX1FVT1RBX01BTkFHRU1FTlQ6IFRydWUKU0VSVkVSX0hPU1ROQU1FOiBsb2NhbGhvc3Q6NTAwMQpCUk9XU0VSX0FQSV9DQUxMU19YSFJfT05MWTogRmFsc2UKQ09SU19PUklHSU46CiAgLSAiaHR0cHM6Ly9zdGFnZS5mb28ucmVkaGF0LmNvbToxMzM3IgogIC0gImh0dHA6Ly9sb2NhbGhvc3Q6OTAwMCIKRkVBVFVSRV9VSV9WMjogVHJ1ZQpGRUFUVVJFX1VTRVJfTUVUQURBVEE6IFRydWUKSUdOT1JFX1VOS05PV05fTUVESUFUWVBFUzogVHJ1ZQo=
7+
config.yaml: REFUQV9NT0RFTF9DQUNIRV9DT05GSUc6CiAgZW5naW5lOiByZWRpcwogIHJlZGlzX2NvbmZpZzoKICAgIHByaW1hcnk6CiAgICAgIGhvc3Q6IHF1YXktcmVkaXMKU1VQRVJfVVNFUlM6CiAgLSBhZG1pbgogIC0gdXNlcjEKQVVUSEVOVElDQVRJT05fVFlQRTogRGF0YWJhc2UKREJfVVJJOiBwb3N0Z3Jlc3FsOi8vcXVheTpxdWF5QHF1YXktcG9zdGdyZXNxbDo1NDMyL3F1YXkKQlVJTERMT0dTX1JFRElTOgogIGhvc3Q6IHF1YXktcmVkaXMKICBwb3J0OiA2Mzc5ClVTRVJfRVZFTlRTX1JFRElTOgogIGhvc3Q6IHF1YXktcmVkaXMKICBwb3J0OiA2Mzc5CkJJVFRPUlJFTlRfRklMRU5BTUVfUEVQUEVSOiAwZWUxOGY5MC01YjZkLTQyZDItYWI1ZS1lYzlmY2Q4NDYyNzIKREFUQUJBU0VfU0VDUkVUX0tFWTogIjMwMDYwMzYxNjQwNzkzMTg3NjEzNjk3MzY2OTIzMjExMTEzMjA1Njc2OTI1NDQ1NjUwMjUwMjc0NzUyMTI1MDgzOTcxNjM4Mzc2MjI0IgpERUZBVUxUX1RBR19FWFBJUkFUSU9OOiAydwpESVNUUklCVVRFRF9TVE9SQUdFX0NPTkZJRzoKICBkZWZhdWx0OgogICAgLSBMb2NhbFN0b3JhZ2UKICAgIC0gc3RvcmFnZV9wYXRoOiAvZGF0YXN0b3JhZ2UvcmVnaXN0cnkKRElTVFJJQlVURURfU1RPUkFHRV9ERUZBVUxUX0xPQ0FUSU9OUzogW10KRElTVFJJQlVURURfU1RPUkFHRV9QUkVGRVJFTkNFOgogIC0gZGVmYXVsdApFTlRFUlBSSVNFX0xPR09fVVJMOiAvc3RhdGljL2ltZy9xdWF5LWhvcml6b250YWwtY29sb3Iuc3ZnCkVYVEVSTkFMX1RMU19URVJNSU5BVElPTjogdHJ1ZQpGRUFUVVJFX0FOT05ZTU9VU19BQ0NFU1M6IHRydWUKRkVBVFVSRV9BUFBfUkVHSVNUUlk6IGZhbHNlCkZFQVRVUkVfQVBQX1NQRUNJRklDX1RPS0VOUzogdHJ1ZQpGRUFUVVJFX0JVSUxEX1NVUFBPUlQ6IGZhbHNlCkZFQVRVUkVfQ0hBTkdFX1RBR19FWFBJUkFUSU9OOiB0cnVlCkZFQVRVUkVfRElSRUNUX0xPR0lOOiB0cnVlCkZFQVRVUkVfTUFJTElORzogZmFsc2UKRkVBVFVSRV9QQVJUSUFMX1VTRVJfQVVUT0NPTVBMRVRFOiB0cnVlCkZFQVRVUkVfUkVQT19NSVJST1I6IGZhbHNlCkZFQVRVUkVfUkVRVUlSRV9URUFNX0lOVklURTogdHJ1ZQpGRUFUVVJFX1JFU1RSSUNURURfVjFfUFVTSDogZmFsc2UKRkVBVFVSRV9TRUNVUklUWV9OT1RJRklDQVRJT05TOiBmYWxzZQpGRUFUVVJFX1NFQ1VSSVRZX1NDQU5ORVI6IGZhbHNlCkZFQVRVUkVfVVNFUk5BTUVfQ09ORklSTUFUSU9OOiB0cnVlCkZFQVRVUkVfVVNFUl9JTklUSUFMSVpFOiB0cnVlCkZFQVRVUkVfVVNFUl9DUkVBVElPTjogdHJ1ZQpGRUFUVVJFX1VTRVJfTE9HX0FDQ0VTUzogdHJ1ZQpGRUFUVVJFX1BST1hZX0NBQ0hFOiB0cnVlCkdJVEhVQl9MT0dJTl9DT05GSUc6IHt9CkdJVEhVQl9UUklHR0VSX0NPTkZJRzoge30KR0lUTEFCX1RSSUdHRVJfS0lORDoge30KTE9HX0FSQ0hJVkVfTE9DQVRJT046IGRlZmF1bHQKTUFJTF9ERUZBVUxUX1NFTkRFUjogYWRtaW5AZXhhbXBsZS5jb20KTUFJTF9QT1JUOiA1ODcKTUFJTF9VU0VfVExTOiB0cnVlClBSRUZFUlJFRF9VUkxfU0NIRU1FOiBodHRwClJFR0lTVFJZX1RJVExFOiBSZWQgSGF0IFF1YXkgTElURQpSRUdJU1RSWV9USVRMRV9TSE9SVDogUmVkIEhhdCBRdWF5IExJVEUKUkVQT19NSVJST1JfU0VSVkVSX0hPU1ROQU1FOiBudWxsClJFUE9fTUlSUk9SX1RMU19WRVJJRlk6IHRydWUKU0VUVVBfQ09NUExFVEU6IHRydWUKU0lHTklOR19FTkdJTkU6IGdwZzIKVEFHX0VYUElSQVRJT05fT1BUSU9OUzoKICAtIDBzCiAgLSAxZAogIC0gMXcKICAtIDJ3CiAgLSA0dwpURUFNX1JFU1lOQ19TVEFMRV9USU1FOiA2MG0KVEVTVElORzogdHJ1ZQpVU0VSRklMRVNfTE9DQVRJT046IGRlZmF1bHQKVVNFUkZJTEVTX1BBVEg6IHVzZXJmaWxlcy8KVVNFX0NETjogZmFsc2UKRkVBVFVSRV9RVU9UQV9NQU5BR0VNRU5UOiBUcnVlClNFUlZFUl9IT1NUTkFNRTogbG9jYWxob3N0OjUwMDEKQlJPV1NFUl9BUElfQ0FMTFNfWEhSX09OTFk6IEZhbHNlCkNPUlNfT1JJR0lOOgogIC0gImh0dHBzOi8vc3RhZ2UuZm9vLnJlZGhhdC5jb206MTMzNyIKICAtICJodHRwOi8vbG9jYWxob3N0OjkwMDAiCkZFQVRVUkVfVUlfVjI6IFRydWUKRkVBVFVSRV9VU0VSX01FVEFEQVRBOiBUcnVlCklHTk9SRV9VTktOT1dOX01FRElBVFlQRVM6IGZhbHNlCg==

e2e/test_cli.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ else
2525
fi
2626

2727
DIR="tmp/b"
28-
omlmd pull localhost:5001/mmortari/mlartifact:v1 -o "$DIR" --media-types "application/x-mlmodel" --plain-http
28+
omlmd pull localhost:5001/mmortari/mlartifact:v1 -o "$DIR" --media-types "application/vnd.oci.image.layer.v1.tar" --plain-http
2929
file_count=$(find "$DIR" -type f | wc -l)
3030
if [ "$file_count" -eq 1 ]; then
3131
echo "Expected 1 file in $DIR, ok."

omlmd/cli.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ def crawl(plain_http: bool, targets: tuple[str]):
7373
required=True,
7474
type=click.Path(path_type=Path, exists=True, resolve_path=True),
7575
)
76+
@click.option(
77+
"--as-artifact",
78+
is_flag=True,
79+
help="Push as an artifact (default is as a blob)",
80+
)
7681
@cloup.option_group(
7782
"Metadata options",
7883
cloup.option(
@@ -88,6 +93,7 @@ def push(
8893
plain_http: bool,
8994
target: str,
9095
path: Path,
96+
as_artifact: bool,
9197
metadata: Path | None,
9298
empty_metadata: bool,
9399
):
@@ -96,4 +102,6 @@ def push(
96102
if empty_metadata:
97103
logger.warning(f"Pushing to {target} with empty metadata.")
98104
md = deserialize_mdfile(metadata) if metadata else {}
99-
click.echo(Helper.from_default_registry(plain_http).push(target, path, **md))
105+
click.echo(
106+
Helper.from_default_registry(plain_http).push(target, path, as_artifact, **md)
107+
)

omlmd/constants.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from oras.defaults import default_blob_media_type
2+
13
FILENAME_METADATA_JSON = "model_metadata.omlmd.json"
2-
MIME_APPLICATION_CONFIG = "application/x-config"
34
MIME_APPLICATION_MLMODEL = "application/x-mlmodel"
5+
MIME_APPLICATION_MLMETADATA = "application/x-mlmetadata+json"
6+
MIME_BLOB = default_blob_media_type
7+
MIME_MANIFEST_CONFIG = "application/vnd.oci.image.config.v1+json"

omlmd/helpers.py

Lines changed: 108 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
from __future__ import annotations
22

3+
import json
34
import logging
45
import os
6+
import platform
7+
import tarfile
8+
import typing as t
59
import urllib.request
610
from collections.abc import Sequence
711
from dataclasses import dataclass, field
@@ -10,8 +14,10 @@
1014

1115
from .constants import (
1216
FILENAME_METADATA_JSON,
13-
MIME_APPLICATION_CONFIG,
17+
MIME_APPLICATION_MLMETADATA,
1418
MIME_APPLICATION_MLMODEL,
19+
MIME_BLOB,
20+
MIME_MANIFEST_CONFIG,
1521
)
1622
from .listener import Event, Listener, PushEvent
1723
from .model_metadata import ModelMetadata
@@ -20,6 +26,54 @@
2026
logger = logging.getLogger(__name__)
2127

2228

29+
@dataclass
30+
class DeferredLayer:
31+
src: Path
32+
dest: Path
33+
media_type: str
34+
transform: t.Callable[[], None] | None = None
35+
owned: bool = True
36+
37+
def __post_init__(self):
38+
if self.dest.exists():
39+
self.owned = False
40+
41+
@classmethod
42+
def raw(cls, src: Path, media_type: str) -> DeferredLayer:
43+
return cls(src, src, media_type)
44+
45+
@classmethod
46+
def blob(cls, src: Path, gz: bool = False) -> DeferredLayer:
47+
oflag = "w"
48+
media_type = MIME_BLOB
49+
if gz:
50+
oflag += ":gz"
51+
media_type += "+gzip"
52+
53+
dest = src.with_suffix(".tar")
54+
55+
def _tar():
56+
with tarfile.open(dest, oflag) as tf:
57+
tf.add(src, arcname=src.name)
58+
59+
return cls(src, dest, media_type, _tar)
60+
61+
def as_layer(self) -> str:
62+
if self.owned and self.transform:
63+
self.transform()
64+
return f"{self.dest}:{self.media_type}"
65+
66+
67+
def get_arch() -> str:
68+
mac = platform.machine()
69+
if mac == "x86_64":
70+
return "amd64"
71+
if mac == "arm64" or mac == "aarch64":
72+
return "arm64"
73+
msg = f"Unsupported architecture: {mac}"
74+
raise NotImplementedError(msg)
75+
76+
2377
def download_file(uri: str):
2478
file_name = os.path.basename(uri)
2579
urllib.request.urlretrieve(uri, file_name)
@@ -41,54 +95,83 @@ def push(
4195
self,
4296
target: str,
4397
path: Path | str,
98+
as_artifact: bool = False,
4499
**kwargs,
45100
):
46101
owns_meta = True
47102
if isinstance(path, str):
48103
path = Path(path)
49104

50105
meta_path = path.parent / FILENAME_METADATA_JSON
51-
if not kwargs and meta_path.exists():
106+
if meta_path.exists():
52107
owns_meta = False
53108
logger.warning("Reusing intermediate metadata files.")
54109
logger.debug(f"{meta_path}")
55-
with open(meta_path, "r") as f:
56-
model_metadata = ModelMetadata.from_json(f.read())
57-
elif meta_path.exists():
58-
err = dedent(f"""
59-
OMLMD intermediate metadata files found at '{meta_path}'.
60-
Cannot resolve with conflicting keyword args: {kwargs}.
61-
You can reuse the existing metadata by omitting any keywords.
62-
If that was NOT intended, please REMOVE that file from your environment before re-running.
63-
64-
Note for advanced users: if merging keys with existing metadata is desired, you should create a Feature Request upstream: https://github.com/containers/omlmd""")
65-
raise RuntimeError(err)
110+
model_metadata = ModelMetadata(**json.loads(meta_path.read_bytes()))
111+
if kwargs and ModelMetadata.from_dict(kwargs) != model_metadata:
112+
err = dedent(f"""
113+
OMLMD intermediate metadata files found at '{meta_path}'.
114+
Cannot resolve with conflicting keyword args: {kwargs}.
115+
You can reuse the existing metadata by omitting any keywords.
116+
If that was NOT intended, please REMOVE that file from your environment before re-running.
117+
118+
Note for advanced users: if merging keys with existing metadata is desired, you should create a Feature Request upstream: https://github.com/containers/omlmd""")
119+
raise RuntimeError(err)
66120
else:
67121
model_metadata = ModelMetadata.from_dict(kwargs)
68-
meta_path.write_text(model_metadata.to_json())
122+
meta_path.write_text(json.dumps(model_metadata.to_dict()))
123+
124+
manifest_path = path.parent / "manifest.json"
125+
model: DeferredLayer | None = None
126+
meta: DeferredLayer | None = None
127+
if not as_artifact:
128+
manifest_path.write_text(
129+
json.dumps(
130+
{
131+
"architecture": get_arch(),
132+
"os": "linux",
133+
}
134+
)
135+
)
136+
config = DeferredLayer.raw(manifest_path, MIME_MANIFEST_CONFIG)
137+
model = DeferredLayer.blob(path)
138+
meta = DeferredLayer.blob(meta_path, gz=True)
139+
else:
140+
manifest_path.write_text(
141+
json.dumps(
142+
{
143+
"artifactType": MIME_APPLICATION_MLMODEL,
144+
}
145+
)
146+
)
147+
config = DeferredLayer.raw(manifest_path, MIME_APPLICATION_MLMODEL)
148+
model = DeferredLayer.raw(path, MIME_APPLICATION_MLMODEL)
149+
meta = DeferredLayer.raw(meta_path, MIME_APPLICATION_MLMETADATA)
150+
meta.owned = owns_meta
69151

70-
config = f"{meta_path}:{MIME_APPLICATION_CONFIG}"
71-
files = [
72-
f"{path}:{MIME_APPLICATION_MLMODEL}",
152+
layers = [
73153
config,
154+
model,
155+
meta,
74156
]
75157
try:
76-
# print(target, files, model_metadata.to_annotations_dict())
77158
result = self._registry.push(
78159
target=target,
79-
files=files,
160+
files=[lay.as_layer() for lay in layers],
80161
manifest_annotations=model_metadata.to_annotations_dict(),
81-
manifest_config=config,
162+
manifest_config=config.as_layer(),
82163
do_chunked=True,
83164
)
84-
self.notify_listeners(
85-
PushEvent.from_response(result, target, model_metadata)
86-
)
87-
return result
88165
finally:
89-
if owns_meta:
166+
for lay in layers:
167+
if lay.owned:
168+
lay.dest.unlink()
169+
if owns_meta and meta_path.exists():
90170
meta_path.unlink()
91171

172+
self.notify_listeners(PushEvent.from_response(result, target, model_metadata))
173+
return result
174+
92175
def pull(
93176
self, target: str, outdir: Path | str, media_types: Sequence[str] | None = None
94177
):

tests/test_helpers.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
1+
import io
12
import json
23
import subprocess
4+
import tarfile
35
import tempfile
46
import typing as t
57
from hashlib import sha256
68
from pathlib import Path
79

810
import pytest
911

10-
from omlmd.constants import MIME_APPLICATION_MLMODEL
12+
from omlmd.constants import MIME_BLOB
1113
from omlmd.helpers import Helper
1214
from omlmd.listener import Event, Listener
1315
from omlmd.model_metadata import ModelMetadata, deserialize_mdfile
1416
from omlmd.provider import OMLMDRegistry
1517

1618

19+
def untar(tar: Path, out: Path):
20+
out.write_bytes(
21+
t.cast(io.BufferedReader, tarfile.open(tar, "r:*").extractfile(tar.stem)).read()
22+
)
23+
24+
1725
def test_call_push_using_md_from_file(mocker):
1826
helper = Helper()
1927
mocker.patch.object(helper, "push", return_value=None)
@@ -100,12 +108,33 @@ def test_push_pull_chunked(tmp_path, target):
100108

101109
omlmd.push(target, temp, **md)
102110
omlmd.pull(target, tmp_path)
103-
assert len(list(tmp_path.iterdir())) == 3
104-
assert tmp_path.joinpath(temp.name).stat().st_size == base_size
111+
files = list(tmp_path.iterdir())
112+
print(files)
113+
assert len(files) == 3
114+
print(tmp_path)
115+
out = tmp_path.joinpath(temp.name)
116+
untar(out.with_suffix(".tar"), out)
117+
assert temp.stat().st_size == base_size
105118
finally:
106119
temp.unlink()
107120

108121

122+
@pytest.mark.e2e
123+
def test_e2e_push_pull_as_artifact(tmp_path, target):
124+
omlmd = Helper()
125+
omlmd.push(
126+
target,
127+
Path(__file__).parent / ".." / "README.md",
128+
as_artifact=True,
129+
name="mnist",
130+
description="Lorem ipsum",
131+
author="John Doe",
132+
accuracy=0.987,
133+
)
134+
omlmd.pull(target, tmp_path)
135+
assert len(list(tmp_path.iterdir())) == 3
136+
137+
109138
@pytest.mark.e2e
110139
def test_e2e_push_pull(tmp_path, target):
111140
omlmd = Helper()
@@ -132,7 +161,7 @@ def test_e2e_push_pull_with_filters(tmp_path, target):
132161
author="John Doe",
133162
accuracy=0.987,
134163
)
135-
omlmd.pull(target, tmp_path, media_types=[MIME_APPLICATION_MLMODEL])
164+
omlmd.pull(target, tmp_path, media_types=[MIME_BLOB])
136165
assert len(list(tmp_path.iterdir())) == 1
137166

138167

@@ -155,10 +184,11 @@ def test_e2e_push_pull_column(tmp_path, target):
155184

156185
omlmd.push(target, temp, **md)
157186
omlmd.pull(target, tmp_path)
158-
with open(tmp_path.joinpath(temp.name), "r") as f:
159-
pulled = f.read()
160-
assert pulled == content
161-
pulled_sha = sha256(pulled.encode("utf-8")).hexdigest()
162-
assert pulled_sha == content_sha
187+
out = tmp_path.joinpath(temp.name)
188+
untar(out.with_suffix(".tar"), out)
189+
pulled = out.read_text()
190+
assert pulled == content
191+
pulled_sha = sha256(pulled.encode("utf-8")).hexdigest()
192+
assert pulled_sha == content_sha
163193
finally:
164194
temp.unlink()

0 commit comments

Comments
 (0)