diff --git a/docs/getting_started/user-guide.md b/docs/getting_started/user-guide.md index cf52fed..a5b6ee1 100644 --- a/docs/getting_started/user-guide.md +++ b/docs/getting_started/user-guide.md @@ -489,6 +489,28 @@ def push(uri, root): +
+ +Example of basic artifact attachment + +We are assuming an `derived-artifact.txt` in the present working directory and that there's already a `localhost:5000/dinosaur/artifact:v1` artifact present in the registry. Here is an example of how to [attach](https://oras.land/docs/concepts/reftypes/) a derived artifact to the existing artifact. + +```python +import oras.client +import oras.oci + +client = oras.client.OrasClient(insecure=True) + +manifest = client.remote.get_manifest("localhost:5000/dinosaur/artifact:v1") +subject = oras.oci.Subject.from_manifest(manifest) + +client.push(files=["derived-artifact.txt"], target="localhost:5000/dinosaur/artifact:v1-derived", subject=subject) +Successfully pushed localhost:5000/dinosaur/artifact:v1-derived +Out[4]: +``` + +
+ The above examples are just a start! See our [examples](https://github.com/oras-project/oras-py/tree/main/examples) folder alongside the repository for more code examples and clients. If you would like help for an example, or to contribute an example, [you know what to do](https://github.com/oras-project/oras-py/issues)! diff --git a/oras/oci.py b/oras/oci.py index 4265f7d..4865f8e 100644 --- a/oras/oci.py +++ b/oras/oci.py @@ -3,7 +3,10 @@ __license__ = "Apache-2.0" import copy +import hashlib +import json import os +from dataclasses import dataclass from typing import Dict, Optional, Tuple import jsonschema @@ -151,3 +154,27 @@ def NewManifest() -> dict: Get an empty manifest config. """ return copy.deepcopy(EmptyManifest) + + +@dataclass +class Subject: + mediaType: str + digest: str + size: int + + @classmethod + def from_manifest(cls, manifest: dict) -> "Subject": + """ + Create a new Subject from a Manifest + + :param manifest: manifest to convert to subject + """ + manifest_string = json.dumps(manifest).encode("utf-8") + digest = "sha256:" + hashlib.sha256(manifest_string).hexdigest() + size = len(manifest_string) + + return cls( + manifest["mediaType"] or oras.defaults.default_manifest_media_type, + digest, + size, + ) diff --git a/oras/provider.py b/oras/provider.py index 17e93f9..c3e37ec 100644 --- a/oras/provider.py +++ b/oras/provider.py @@ -6,7 +6,7 @@ import os import urllib from contextlib import contextmanager, nullcontext -from dataclasses import asdict, dataclass +from dataclasses import asdict from http.cookiejar import DefaultCookiePolicy from tempfile import TemporaryDirectory from typing import Callable, Generator, List, Optional, Tuple, Union @@ -35,13 +35,6 @@ def temporary_empty_config() -> Generator[str, None, None]: yield config_file -@dataclass -class Subject: - mediaType: str - digest: str - size: int - - class Registry: """ Direct interactions with an OCI registry. @@ -697,7 +690,7 @@ def push(self, *args, **kwargs) -> requests.Response: :param refresh_headers: if true or None, headers are refreshed :type refresh_headers: bool :param subject: optional subject reference - :type subject: Subject + :type subject: oras.oci.Subject """ container = self.get_container(kwargs["target"]) self.load_configs(container, configs=kwargs.get("config_path")) diff --git a/oras/tests/test_oci.py b/oras/tests/test_oci.py new file mode 100644 index 0000000..f47fe29 --- /dev/null +++ b/oras/tests/test_oci.py @@ -0,0 +1,20 @@ +import pytest + +import oras.defaults +import oras.oci + + +@pytest.mark.with_auth(False) +def test_create_subject_from_manifest(): + """ + Basic tests for oras Subject creation from empty manifest + """ + manifest = oras.oci.NewManifest() + subject = oras.oci.Subject.from_manifest(manifest) + + assert subject.mediaType == oras.defaults.default_manifest_media_type + assert ( + subject.digest + == "sha256:7a6f84d8c73a71bf9417c13f721ed102f74afac9e481f89e5a72d28954e7d0c5" + ) + assert subject.size == 126 diff --git a/oras/tests/test_provider.py b/oras/tests/test_provider.py index d3b014b..2babd5a 100644 --- a/oras/tests/test_provider.py +++ b/oras/tests/test_provider.py @@ -9,6 +9,7 @@ import oras.client import oras.defaults +import oras.oci import oras.provider import oras.utils