Skip to content

Commit

Permalink
Update headers in do_request method, provider.py (#129)
Browse files Browse the repository at this point in the history
* Update headers in do_request method, provider.py

During the push operation POST and PUT methods loose provided auth headers. It is crucial for remote registry, since further re-auth logic tries to reach registry locally. 
do_request method is updated to include always 'Authorization' header if it is provided.

Added refresh_headers flag to allow user to keep basic auth in headers during the push flow

Signed-off-by: my5cents <[email protected]>
  • Loading branch information
my5cents authored Apr 5, 2024
1 parent 592cf4c commit 545b16f
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ oras.egg-info/
.env
env
__pycache__
.python-version
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)
- 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)
- To make it available for more OCI registries, the value of config used when `manifest_config` is not specified in `client.push()` has been changed from a pure empty string to `{}` (0.1.26)
Expand Down
63 changes: 55 additions & 8 deletions oras/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ def upload_blob(
container: container_type,
layer: dict,
do_chunked: bool = False,
refresh_headers: bool = True,
) -> requests.Response:
"""
Prepare and upload a blob.
Expand All @@ -239,6 +240,8 @@ def upload_blob(
:type container: oras.container.Container or str
:param layer: dict from oras.oci.NewLayer
:type layer: dict
:param refresh_headers: if true, headers are refreshed
:type refresh_headers: bool
"""
blob = os.path.abspath(blob)
container = self.get_container(container)
Expand All @@ -250,9 +253,13 @@ def upload_blob(
# This is currently disabled unless the user asks for it, as
# it doesn't seem to work for all registries
if not do_chunked:
response = self.put_upload(blob, container, layer)
response = self.put_upload(
blob, container, layer, refresh_headers=refresh_headers
)
else:
response = self.chunked_upload(blob, container, layer)
response = self.chunked_upload(
blob, container, layer, refresh_headers=refresh_headers
)

# If we have an empty layer digest and the registry didn't accept, just return dummy successful response
if (
Expand Down Expand Up @@ -474,7 +481,11 @@ def download_blob(
return outfile

def put_upload(
self, blob: str, container: oras.container.Container, layer: dict
self,
blob: str,
container: oras.container.Container,
layer: dict,
refresh_headers: bool = True,
) -> requests.Response:
"""
Upload to a registry via put.
Expand All @@ -485,9 +496,15 @@ def put_upload(
:type container: oras.container.Container or str
:param layer: dict from oras.oci.NewLayer
:type layer: dict
:param refresh_headers: if true, headers are refreshed
:type refresh_headers: bool
"""
# Start an upload session
headers = {"Content-Type": "application/octet-stream"}

if not refresh_headers:
headers.update(self.headers)

upload_url = f"{self.prefix}://{container.upload_blob_url()}"
r = self.do_request(upload_url, "POST", headers=headers)

Expand Down Expand Up @@ -541,7 +558,11 @@ def _get_location(
return session_url

def chunked_upload(
self, blob: str, container: oras.container.Container, layer: dict
self,
blob: str,
container: oras.container.Container,
layer: dict,
refresh_headers: bool = True,
) -> requests.Response:
"""
Upload via a chunked upload.
Expand All @@ -552,9 +573,14 @@ def chunked_upload(
:type container: oras.container.Container or str
:param layer: dict from oras.oci.NewLayer
:type layer: dict
:param refresh_headers: if true, headers are refreshed
:type refresh_headers: bool
"""
# Start an upload session
headers = {"Content-Type": "application/octet-stream", "Content-Length": "0"}
if not refresh_headers:
headers.update(self.headers)

upload_url = f"{self.prefix}://{container.upload_blob_url()}"
r = self.do_request(upload_url, "POST", headers=headers)

Expand Down Expand Up @@ -618,7 +644,10 @@ def _parse_response_errors(self, response: requests.Response):
pass

def upload_manifest(
self, manifest: dict, container: oras.container.Container
self,
manifest: dict,
container: oras.container.Container,
refresh_headers: bool = True,
) -> requests.Response:
"""
Read a manifest file and upload it.
Expand All @@ -627,13 +656,19 @@ def upload_manifest(
:type manifest: dict
:param container: parsed container URI
:type container: oras.container.Container or str
:param refresh_headers: if true, headers are refreshed
:type refresh_headers: bool
"""
self.reset_basic_auth()
jsonschema.validate(manifest, schema=oras.schemas.manifest)
headers = {
"Content-Type": oras.defaults.default_manifest_media_type,
"Content-Length": str(len(manifest)),
}

if not refresh_headers:
headers.update(self.headers)

return self.do_request(
f"{self.prefix}://{container.manifest_url()}", # noqa
"PUT",
Expand All @@ -659,6 +694,8 @@ def push(self, *args, **kwargs) -> requests.Response:
:type manifest_annotations: dict
:param target: target location to push to
:type target: str
:param refresh_headers: if true or None, headers are refreshed
:type refresh_headers: bool
:param subject: optional subject reference
:type subject: Subject
"""
Expand All @@ -675,6 +712,10 @@ def push(self, *args, **kwargs) -> requests.Response:
annotset = oras.oci.Annotations(kwargs.get("annotation_file"))
media_type = None

refresh_headers = kwargs.get("refresh_headers")
if refresh_headers is None:
refresh_headers = True

# Upload files as blobs
for blob in kwargs.get("files", []):
# You can provide a blob + content type
Expand Down Expand Up @@ -720,7 +761,9 @@ def push(self, *args, **kwargs) -> requests.Response:
logger.debug(f"Preparing layer {layer}")

# Upload the blob layer
response = self.upload_blob(blob, container, layer)
response = self.upload_blob(
blob, container, layer, refresh_headers=refresh_headers
)
self._check_200_response(response)

# Do we need to cleanup a temporary targz?
Expand Down Expand Up @@ -762,13 +805,17 @@ def push(self, *args, **kwargs) -> requests.Response:
if config_file is None
else nullcontext(config_file)
) as config_file:
response = self.upload_blob(config_file, container, conf)
response = self.upload_blob(
config_file, container, conf, refresh_headers=refresh_headers
)

self._check_200_response(response)

# Final upload of the manifest
manifest["config"] = conf
self._check_200_response(self.upload_manifest(manifest, container))
self._check_200_response(
self.upload_manifest(manifest, container, refresh_headers=refresh_headers)
)
print(f"Successfully pushed {container}")
return response

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.28"
__version__ = "0.1.29"
AUTHOR = "Vanessa Sochat"
EMAIL = "[email protected]"
NAME = "oras"
Expand Down

0 comments on commit 545b16f

Please sign in to comment.