44# Licensed under the MIT License.
55# ------------------------------------
66# pylint: disable=too-many-lines
7+ import hashlib
78from io import BytesIO
89from typing import Any , Dict , IO , Optional , overload , Union , cast , Tuple
910from azure .core .credentials import TokenCredential
2930 OCI_MANIFEST_MEDIA_TYPE ,
3031 SUPPORTED_API_VERSIONS ,
3132 AZURE_RESOURCE_MANAGER_PUBLIC_CLOUD ,
33+ DEFAULT_CHUNK_SIZE ,
3234)
3335from ._models import (
3436 RepositoryProperties ,
@@ -857,16 +859,15 @@ def upload_manifest(
857859 ) -> str :
858860 """Upload a manifest for an OCI artifact.
859861
860- :param str repository: Name of the repository
862+ :param str repository: Name of the repository.
861863 :param manifest: The manifest to upload. Note: This must be a seekable stream.
862864 :type manifest: ~azure.containerregistry.models.OCIManifest or IO
863865 :keyword tag: Tag of the manifest.
864866 :paramtype tag: str or None
865867 :returns: The digest of the uploaded manifest, calculated by the registry.
866868 :rtype: str
867- :raises ValueError: If the parameter repository or manifest is None.
868- :raises ~azure.core.exceptions.HttpResponseError:
869- If the digest in the response does not match the digest of the uploaded manifest.
869+ :raises ValueError: If the parameter repository or manifest is None,
870+ or the digest in the response does not match the digest of the uploaded manifest.
870871 """
871872 try :
872873 if isinstance (manifest , OCIManifest ):
@@ -889,86 +890,93 @@ def upload_manifest(
889890 digest = response_headers ['Docker-Content-Digest' ]
890891 if not _validate_digest (data , digest ):
891892 raise ValueError ("The digest in the response does not match the digest of the uploaded manifest." )
892- except ValueError :
893+ except Exception as e :
893894 if repository is None or manifest is None :
894- raise ValueError ("The parameter repository and manifest cannot be None." )
895+ raise ValueError ("The parameter repository and manifest cannot be None." ) from e
895896 raise
896897 return digest
897898
898899 @distributed_trace
899- def upload_blob (self , repository : str , data : IO , ** kwargs ) -> str :
900+ def upload_blob (self , repository : str , data : IO [ bytes ] , ** kwargs ) -> Tuple [ str , int ] :
900901 """Upload an artifact blob.
901902
902- :param str repository: Name of the repository
903+ :param str repository: Name of the repository.
903904 :param data: The blob to upload. Note: This must be a seekable stream.
904905 :type data: IO
905- :returns: The digest of the uploaded blob, calculated by the registry .
906- :rtype: str
906+ :returns: The digest and size in bytes of the uploaded blob .
907+ :rtype: Tuple[ str, int]
907908 :raises ValueError: If the parameter repository or data is None.
908909 """
909910 try :
910911 start_upload_response_headers = cast (Dict [str , str ], self ._client .container_registry_blob .start_upload (
911912 repository , cls = _return_response_headers , ** kwargs
912913 ))
913- upload_chunk_response_headers = cast (Dict [str , str ], self ._client .container_registry_blob .upload_chunk (
914- start_upload_response_headers ['Location' ],
915- data ,
916- cls = _return_response_headers ,
917- ** kwargs
918- ))
919- digest = _compute_digest (data )
914+ digest , location , blob_size = self ._upload_blob_chunk (
915+ start_upload_response_headers ['Location' ], data , ** kwargs
916+ )
920917 complete_upload_response_headers = cast (
921918 Dict [str , str ],
922919 self ._client .container_registry_blob .complete_upload (
923920 digest = digest ,
924- next_link = upload_chunk_response_headers [ 'Location' ] ,
921+ next_link = location ,
925922 cls = _return_response_headers ,
926923 ** kwargs
927924 )
928925 )
929- except ValueError :
926+ except Exception as e :
930927 if repository is None or data is None :
931- raise ValueError ("The parameter repository and data cannot be None." )
928+ raise ValueError ("The parameter repository and data cannot be None." ) from e
932929 raise
933- return complete_upload_response_headers ['Docker-Content-Digest' ]
930+ return complete_upload_response_headers ['Docker-Content-Digest' ], blob_size
931+
932+ def _upload_blob_chunk (self , location : str , data : IO [bytes ], ** kwargs ) -> Tuple [str , str , int ]:
933+ hasher = hashlib .sha256 ()
934+ buffer = data .read (DEFAULT_CHUNK_SIZE )
935+ blob_size = len (buffer )
936+ while len (buffer ) > 0 :
937+ response_headers = cast (Dict [str , str ], self ._client .container_registry_blob .upload_chunk (
938+ location ,
939+ BytesIO (buffer ),
940+ cls = _return_response_headers ,
941+ ** kwargs
942+ ))
943+ location = response_headers ['Location' ]
944+ hasher .update (buffer )
945+ buffer = data .read (DEFAULT_CHUNK_SIZE )
946+ blob_size += len (buffer )
947+ return "sha256:" + hasher .hexdigest (), location , blob_size
934948
935949 @distributed_trace
936950 def download_manifest (self , repository : str , tag_or_digest : str , ** kwargs ) -> DownloadManifestResult :
937951 """Download the manifest for an OCI artifact.
938952
939- :param str repository: Name of the repository
953+ :param str repository: Name of the repository.
940954 :param str tag_or_digest: The tag or digest of the manifest to download.
941955 When digest is provided, will use this digest to compare with the one calculated by the response payload.
942956 When tag is provided, will use the digest in response headers to compare.
943957 :returns: DownloadManifestResult
944958 :rtype: ~azure.containerregistry.models.DownloadManifestResult
945- :raises ValueError: If the parameter repository or tag_or_digest is None.
946- :raises ~azure.core.exceptions.HttpResponseError:
947- If the requested digest does not match the digest of the received manifest.
959+ :raises ValueError: If the requested digest does not match the digest of the received manifest.
948960 """
949- try :
950- response , manifest_wrapper = cast (
951- Tuple [PipelineResponse , ManifestWrapper ],
952- self ._client .container_registry .get_manifest (
953- name = repository ,
954- reference = tag_or_digest ,
955- headers = {"Accept" : OCI_MANIFEST_MEDIA_TYPE },
956- cls = _return_response_and_deserialized ,
957- ** kwargs
958- )
961+ response , manifest_wrapper = cast (
962+ Tuple [PipelineResponse , ManifestWrapper ],
963+ self ._client .container_registry .get_manifest (
964+ name = repository ,
965+ reference = tag_or_digest ,
966+ headers = {"Accept" : OCI_MANIFEST_MEDIA_TYPE },
967+ cls = _return_response_and_deserialized ,
968+ ** kwargs
959969 )
960- manifest = OCIManifest .deserialize (cast (ManifestWrapper , manifest_wrapper ).serialize ())
961- manifest_stream = _serialize_manifest (manifest )
962- if tag_or_digest .startswith ("sha256:" ):
963- digest = tag_or_digest
964- else :
965- digest = response .http_response .headers ['Docker-Content-Digest' ]
966- if not _validate_digest (manifest_stream , digest ):
967- raise ValueError ("The requested digest does not match the digest of the received manifest." )
968- except ValueError :
969- if repository is None or tag_or_digest is None :
970- raise ValueError ("The parameter repository and tag_or_digest cannot be None." )
971- raise
970+ )
971+ manifest = OCIManifest .deserialize (cast (ManifestWrapper , manifest_wrapper ).serialize ())
972+ manifest_stream = _serialize_manifest (manifest )
973+ if tag_or_digest .startswith ("sha256:" ):
974+ digest = tag_or_digest
975+ else :
976+ digest = response .http_response .headers ['Docker-Content-Digest' ]
977+ if not _validate_digest (manifest_stream , digest ):
978+ raise ValueError ("The requested digest does not match the digest of the received manifest." )
979+
972980 return DownloadManifestResult (digest = digest , data = manifest_stream , manifest = manifest )
973981
974982 @distributed_trace
@@ -979,14 +987,8 @@ def download_blob(self, repository: str, digest: str, **kwargs) -> DownloadBlobR
979987 :param str digest: The digest of the blob to download.
980988 :returns: DownloadBlobResult
981989 :rtype: ~azure.containerregistry.DownloadBlobResult
982- :raises ValueError: If the parameter repository or digest is None.
983990 """
984- try :
985- deserialized = self ._client .container_registry_blob .get_blob (repository , digest , ** kwargs )
986- except ValueError :
987- if repository is None or digest is None :
988- raise ValueError ("The parameter repository and digest cannot be None." )
989- raise
991+ deserialized = self ._client .container_registry_blob .get_blob (repository , digest , ** kwargs )
990992
991993 blob_content = b''
992994 for chunk in deserialized : # type: ignore
0 commit comments