Skip to content

Commit

Permalink
Merge pull request #131 from tumido/simplify-subject-creation
Browse files Browse the repository at this point in the history
feat: simplify how subjects are created from existing manifest
  • Loading branch information
vsoch authored May 22, 2024
2 parents cb575ab + 5db7ebe commit e5d2307
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and **Merged pull requests**. Critical items to know are:
The versions coincide with releases on pip. Only major versions will be released as tags on Github.

## [0.0.x](https://github.com/oras-project/oras-py/tree/main) (0.0.x)
- Allow generating a Subject from a pre-existing Manifest (0.1.30)
- add option to not refresh headers during the pushing flow, useful for push with basic auth (0.1.29)
- enable additionalProperties in schema validation (0.1.28)
- Introduce the option to not refresh headers when fetching manifests when pulling artifacts (0.1.27)
Expand Down
22 changes: 22 additions & 0 deletions docs/getting_started/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,28 @@ def push(uri, root):

</details>

<details>

<summary>Example of basic artifact attachment</summary>

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]: <Response [201]>
```

</details>

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)!
Expand Down
27 changes: 27 additions & 0 deletions oras/oci.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
)
11 changes: 2 additions & 9 deletions oras/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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"))
Expand Down
20 changes: 20 additions & 0 deletions oras/tests/test_oci.py
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions oras/tests/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import oras.client
import oras.defaults
import oras.oci
import oras.provider
import oras.utils

Expand Down
2 changes: 1 addition & 1 deletion oras/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
__copyright__ = "Copyright The ORAS Authors."
__license__ = "Apache-2.0"

__version__ = "0.1.29"
__version__ = "0.1.30"
AUTHOR = "Vanessa Sochat"
EMAIL = "[email protected]"
NAME = "oras"
Expand Down

0 comments on commit e5d2307

Please sign in to comment.