Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Make oras-py behave the same way as oras-go for deciding whether to unpack a layer #179

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The versions coincide with releases on pip. Only major versions will be released
- check for blob existence before uploading (0.2.26)
- fix get_tags for ECR when limit is None, closes issue [173](https://github.com/oras-project/oras-py/issues/173)
- fix empty token for anon tokens to work, closes issue [167](https://github.com/oras-project/oras-py/issues/167)
- use same annotation as oras-go for determining whether to unpack a layer or not [119](https://github.com/oras-project/oras-py/issues/119)
- retry on 500 (0.2.25)
- align provider config_path type annotations (0.2.24)
- add missing prefix property to auth backend (0.2.23)
Expand Down
48 changes: 24 additions & 24 deletions oras/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -781,23 +781,24 @@ def push(
f"Blob {blob} is not in the present working directory context."
)

# Save directory or blob name before compressing
blob_name = os.path.basename(blob)

# If it's a directory, we need to compress
cleanup_blob = False
if os.path.isdir(blob):
blob = oras.utils.make_targz(blob)
cleanup_blob = True
is_dir_layer = os.path.isdir(blob)

blob_to_use_for_layer = (
oras.utils.make_targz(blob) if is_dir_layer else blob
)
# Create a new layer from the blob
layer = oras.oci.NewLayer(blob, is_dir=cleanup_blob, media_type=media_type)
layer = oras.oci.NewLayer(
blob_to_use_for_layer, is_dir=is_dir_layer, media_type=media_type
)
annotations = annotset.get_annotations(blob)

# Always strip blob_name of path separator
layer["annotations"] = {
oras.defaults.annotation_title: blob_name.strip(os.sep)
}
layer["annotations"] = {oras.defaults.annotation_title: blob.strip(os.sep)}

if is_dir_layer:
layer["annotations"][oras.defaults.annotation_unpack] = "True"

if annotations:
layer["annotations"].update(annotations)

Expand All @@ -807,7 +808,7 @@ def push(

# Upload the blob layer
response = self.upload_blob(
blob,
blob_to_use_for_layer,
container,
layer,
do_chunked=do_chunked,
Expand All @@ -816,8 +817,8 @@ def push(
self._check_200_response(response)

# Do we need to cleanup a temporary targz?
if cleanup_blob and os.path.exists(blob):
os.remove(blob)
if is_dir_layer and os.path.exists(blob_to_use_for_layer):
os.remove(blob_to_use_for_layer)

# Add annotations to the manifest, if provided
manifest_annots = annotset.get_annotations("$manifest") or {}
Expand Down Expand Up @@ -872,22 +873,21 @@ def pull(
allowed_media_type: Optional[List] = None,
overwrite: bool = True,
outdir: Optional[str] = None,
skip_unpack: bool = False,
) -> List[str]:
"""
Pull an artifact from a target

:param target: target location to pull from
:type target: str
:param config_path: path to a config file
:type config_path: str
:param allowed_media_type: list of allowed media types
:type allowed_media_type: list or None
:param overwrite: if output file exists, overwrite
:type overwrite: bool
:param manifest_config_ref: save manifest config to this file
:type manifest_config_ref: str
:param outdir: output directory path
:type outdir: str
:param target: target location to pull from
:type target: str
"""
container = self.get_container(target)
self.auth.load_configs(
Expand All @@ -899,9 +899,10 @@ def pull(

files = []
for layer in manifest.get("layers", []):
filename = (layer.get("annotations") or {}).get(
oras.defaults.annotation_title
)
annotations: dict = layer.get("annotations", {})
filename = annotations.get(oras.defaults.annotation_title)
# A directory will need to be uncompressed and moved
unpack_layer = annotations.get(oras.defaults.annotation_unpack, False)

# If we don't have a filename, default to digest. Hopefully does not happen
if not filename:
Expand All @@ -916,8 +917,7 @@ def pull(
)
continue

# A directory will need to be uncompressed and moved
if layer["mediaType"] == oras.defaults.default_blob_dir_media_type:
if unpack_layer:
targz = oras.utils.get_tmpfile(suffix=".tar.gz")
self.download_blob(container, layer["digest"], targz)

Expand All @@ -927,7 +927,7 @@ def pull(
# Anything else just extracted directly
else:
self.download_blob(container, layer["digest"], outfile)
logger.info(f"Successfully pulled {outfile}.")
logger.info(f"Successfully pulled {outfile}")
files.append(outfile)
return files

Expand Down
25 changes: 25 additions & 0 deletions oras/tests/test_oras.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
__copyright__ = "Copyright The ORAS Authors."
__license__ = "Apache-2.0"

import json
import os
import shutil

import pytest

import oras.client
import oras.oci

here = os.path.abspath(os.path.dirname(__file__))

Expand Down Expand Up @@ -161,6 +163,29 @@ def test_directory_push_pull(tmp_path, registry, credentials, target_dir):
assert "artifact.txt" in os.listdir(files[0])


@pytest.mark.with_auth(False)
def test_directory_push_pull_skip_unpack(tmp_path, registry, credentials, target_dir):
"""
Test push and pull for directory
"""
client = oras.client.OrasClient(hostname=registry, insecure=True)

# Test upload of a directory with an annotation file setting oras.defaults.annotation_unpack to False
upload_dir = os.path.join(here, "upload_data")
annotation_file = os.path.join(here, "annotations.json")
with open(annotation_file, "w") as f:
json.dump({str(upload_dir):{"oras.defaults.annotation_unpack": "False"}}, f)

res = client.push(files=[upload_dir], target=target_dir, annotation_file=annotation_file)
assert res.status_code == 201
files = client.pull(target=target_dir, outdir=tmp_path)

assert len(files) == 1
assert os.path.basename(files[0]) == os.path.basename(upload_dir)
assert str(tmp_path) in files[0]
assert os.path.exists(files[0])


@pytest.mark.with_auth(True)
def test_directory_push_pull_selfsigned_auth(
tmp_path, registry, credentials, target_dir
Expand Down