diff --git a/.gitignore b/.gitignore index 0569f39..2eb46af 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ oras.egg-info/ .env env __pycache__ +.python-version diff --git a/CHANGELOG.md b/CHANGELOG.md index d90e12a..28936e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/oras/provider.py b/oras/provider.py index 0abd2d5..17e93f9 100644 --- a/oras/provider.py +++ b/oras/provider.py @@ -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. @@ -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) @@ -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 ( @@ -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. @@ -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) @@ -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. @@ -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) @@ -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. @@ -627,6 +656,8 @@ 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) @@ -634,6 +665,10 @@ def upload_manifest( "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", @@ -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 """ @@ -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 @@ -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? @@ -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 diff --git a/oras/version.py b/oras/version.py index 40aaf2a..5fa3cc4 100644 --- a/oras/version.py +++ b/oras/version.py @@ -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 = "vsoch@users.noreply.github.com" NAME = "oras"