66# pylint: disable=too-many-lines
77import functools
88import hashlib
9+ import json
910from io import BytesIO
10- from typing import Any , Dict , IO , Optional , overload , Union , cast , Tuple
11+ from typing import Any , Dict , IO , Optional , overload , Union , cast , Tuple , MutableMapping
1112
1213from azure .core .credentials import TokenCredential
1314from azure .core .exceptions import (
2223from azure .core .tracing .decorator import distributed_trace
2324
2425from ._base_client import ContainerRegistryBaseClient
25- from ._generated .models import AcrErrors , OCIManifest , ManifestWrapper
26+ from ._generated .models import AcrErrors
2627from ._download_stream import DownloadBlobStream
2728from ._helpers import (
2829 _compute_digest ,
2930 _is_tag ,
3031 _parse_next_link ,
31- _serialize_manifest ,
3232 _validate_digest ,
33- OCI_MANIFEST_MEDIA_TYPE ,
3433 SUPPORTED_API_VERSIONS ,
34+ OCI_IMAGE_MANIFEST ,
35+ SUPPORTED_MANIFEST_MEDIA_TYPES ,
3536 DEFAULT_AUDIENCE ,
3637 DEFAULT_CHUNK_SIZE ,
3738)
3839from ._models import (
3940 RepositoryProperties ,
4041 ArtifactTagProperties ,
4142 ArtifactManifestProperties ,
42- DownloadManifestResult ,
43+ GetManifestResult ,
4344)
4445
45- def _return_response_and_deserialized (pipeline_response , deserialized , _ ):
46- return pipeline_response , deserialized
46+ JSON = MutableMapping [str , Any ]
47+
48+ def _return_response (pipeline_response , _ , __ ):
49+ return pipeline_response
4750
4851def _return_response_and_headers (pipeline_response , _ , response_headers ):
4952 return pipeline_response , response_headers
@@ -859,24 +862,35 @@ def update_repository_properties(
859862 )
860863
861864 @distributed_trace
862- def upload_manifest (
863- self , repository : str , manifest : Union [OCIManifest , IO ], * , tag : Optional [str ] = None , ** kwargs
865+ def set_manifest (
866+ self ,
867+ repository : str ,
868+ manifest : Union [JSON , IO [bytes ]],
869+ * ,
870+ tag : Optional [str ] = None ,
871+ media_type : str = OCI_IMAGE_MANIFEST ,
872+ ** kwargs
864873 ) -> str :
865- """Upload a manifest for an OCI artifact.
874+ """Set a manifest for an artifact.
866875
867- :param str repository: Name of the repository.
868- :param manifest: The manifest to upload. Note: This must be a seekable stream.
869- :type manifest: ~azure.containerregistry.models.OCIManifest or IO
876+ :param str repository: Name of the repository
877+ :param manifest: The manifest to set. It can be a JSON formatted dict or seekable stream.
878+ :type manifest: dict or IO
870879 :keyword tag: Tag of the manifest.
871880 :paramtype tag: str or None
872- :returns: The digest of the uploaded manifest, calculated by the registry.
881+ :keyword media_type: The media type of the manifest. If not specified, this value will be set to
882+ a default value of "application/vnd.oci.image.manifest.v1+json". Note: the current known media types are:
883+ "application/vnd.oci.image.manifest.v1+json", and "application/vnd.docker.distribution.manifest.v2+json".
884+ :paramtype media_type: str
885+ :returns: The digest of the set manifest, calculated by the registry.
873886 :rtype: str
874887 :raises ValueError: If the parameter repository or manifest is None,
875- or the digest in the response does not match the digest of the uploaded manifest.
888+ or the digest in the response does not match the digest of the set manifest.
876889 """
877890 try :
878- if isinstance (manifest , OCIManifest ):
879- data = _serialize_manifest (manifest )
891+ data : IO [bytes ]
892+ if isinstance (manifest , MutableMapping ):
893+ data = BytesIO (json .dumps (manifest ).encode ())
880894 else :
881895 data = manifest
882896 tag_or_digest = tag
@@ -887,20 +901,55 @@ def upload_manifest(
887901 name = repository ,
888902 reference = tag_or_digest ,
889903 payload = data ,
890- content_type = OCI_MANIFEST_MEDIA_TYPE ,
891- headers = {"Accept" : OCI_MANIFEST_MEDIA_TYPE },
904+ content_type = media_type ,
892905 cls = _return_response_headers ,
893906 ** kwargs
894907 )
895908 digest = response_headers ['Docker-Content-Digest' ]
896909 if not _validate_digest (data , digest ):
897- raise ValueError ("The digest in the response does not match the digest of the uploaded manifest ." )
910+ raise ValueError ("The server-computed digest does not match the client-computed digest ." )
898911 except Exception as e :
899912 if repository is None or manifest is None :
900913 raise ValueError ("The parameter repository and manifest cannot be None." ) from e
901914 raise
902915 return digest
903916
917+ @distributed_trace
918+ def get_manifest (self , repository : str , tag_or_digest : str , ** kwargs ) -> GetManifestResult :
919+ """Get the manifest for an artifact.
920+
921+ :param str repository: Name of the repository.
922+ :param str tag_or_digest: The tag or digest of the manifest to get.
923+ When digest is provided, will use this digest to compare with the one calculated by the response payload.
924+ When tag is provided, will use the digest in response headers to compare.
925+ :returns: GetManifestResult
926+ :rtype: ~azure.containerregistry.GetManifestResult
927+ :raises ValueError: If the requested digest does not match the digest of the received manifest.
928+ """
929+ response = cast (
930+ PipelineResponse ,
931+ self ._client .container_registry .get_manifest (
932+ name = repository ,
933+ reference = tag_or_digest ,
934+ accept = SUPPORTED_MANIFEST_MEDIA_TYPES ,
935+ cls = _return_response ,
936+ ** kwargs
937+ )
938+ )
939+ media_type = response .http_response .headers ['Content-Type' ]
940+ manifest_bytes = response .http_response .read ()
941+ manifest_json = response .http_response .json ()
942+ if tag_or_digest .startswith ("sha256:" ):
943+ digest = tag_or_digest
944+ if not _validate_digest (manifest_bytes , digest ):
945+ raise ValueError ("The requested digest does not match the digest of the received manifest." )
946+ else :
947+ digest = response .http_response .headers ['Docker-Content-Digest' ]
948+ if not _validate_digest (manifest_bytes , digest ):
949+ raise ValueError ("The server-computed digest does not match the client-computed digest." )
950+
951+ return GetManifestResult (digest = digest , manifest = manifest_json , media_type = media_type )
952+
904953 @distributed_trace
905954 def upload_blob (self , repository : str , data : IO [bytes ], ** kwargs ) -> Tuple [str , int ]:
906955 """Upload an artifact blob.
@@ -949,40 +998,7 @@ def _upload_blob_chunk(self, location: str, data: IO[bytes], **kwargs) -> Tuple[
949998 hasher .update (buffer )
950999 buffer = data .read (DEFAULT_CHUNK_SIZE )
9511000 blob_size += len (buffer )
952- return "sha256:" + hasher .hexdigest (), location , blob_size
953-
954- @distributed_trace
955- def download_manifest (self , repository : str , tag_or_digest : str , ** kwargs ) -> DownloadManifestResult :
956- """Download the manifest for an OCI artifact.
957-
958- :param str repository: Name of the repository.
959- :param str tag_or_digest: The tag or digest of the manifest to download.
960- When digest is provided, will use this digest to compare with the one calculated by the response payload.
961- When tag is provided, will use the digest in response headers to compare.
962- :returns: DownloadManifestResult
963- :rtype: ~azure.containerregistry.DownloadManifestResult
964- :raises ValueError: If the requested digest does not match the digest of the received manifest.
965- """
966- response , manifest_wrapper = cast (
967- Tuple [PipelineResponse , ManifestWrapper ],
968- self ._client .container_registry .get_manifest (
969- name = repository ,
970- reference = tag_or_digest ,
971- headers = {"Accept" : OCI_MANIFEST_MEDIA_TYPE },
972- cls = _return_response_and_deserialized ,
973- ** kwargs
974- )
975- )
976- manifest = OCIManifest .deserialize (cast (ManifestWrapper , manifest_wrapper ).serialize ())
977- manifest_stream = _serialize_manifest (manifest )
978- if tag_or_digest .startswith ("sha256:" ):
979- digest = tag_or_digest
980- else :
981- digest = response .http_response .headers ['Docker-Content-Digest' ]
982- if not _validate_digest (manifest_stream , digest ):
983- raise ValueError ("The requested digest does not match the digest of the received manifest." )
984-
985- return DownloadManifestResult (digest = digest , data = manifest_stream , manifest = manifest )
1001+ return f"sha256:{ hasher .hexdigest ()} " , location , blob_size
9861002
9871003 @distributed_trace
9881004 def download_blob (self , repository : str , digest : str , ** kwargs ) -> DownloadBlobStream :
0 commit comments