6
6
# pylint: disable=too-many-lines
7
7
import functools
8
8
import hashlib
9
+ import json
9
10
from 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
11
12
12
13
from azure .core .credentials import TokenCredential
13
14
from azure .core .exceptions import (
22
23
from azure .core .tracing .decorator import distributed_trace
23
24
24
25
from ._base_client import ContainerRegistryBaseClient
25
- from ._generated .models import AcrErrors , OCIManifest , ManifestWrapper
26
+ from ._generated .models import AcrErrors
26
27
from ._download_stream import DownloadBlobStream
27
28
from ._helpers import (
28
29
_compute_digest ,
29
30
_is_tag ,
30
31
_parse_next_link ,
31
- _serialize_manifest ,
32
32
_validate_digest ,
33
- OCI_MANIFEST_MEDIA_TYPE ,
34
33
SUPPORTED_API_VERSIONS ,
34
+ OCI_IMAGE_MANIFEST ,
35
+ SUPPORTED_MANIFEST_MEDIA_TYPES ,
35
36
DEFAULT_AUDIENCE ,
36
37
DEFAULT_CHUNK_SIZE ,
37
38
)
38
39
from ._models import (
39
40
RepositoryProperties ,
40
41
ArtifactTagProperties ,
41
42
ArtifactManifestProperties ,
42
- DownloadManifestResult ,
43
+ GetManifestResult ,
43
44
)
44
45
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
47
50
48
51
def _return_response_and_headers (pipeline_response , _ , response_headers ):
49
52
return pipeline_response , response_headers
@@ -859,24 +862,35 @@ def update_repository_properties(
859
862
)
860
863
861
864
@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
864
873
) -> str :
865
- """Upload a manifest for an OCI artifact.
874
+ """Set a manifest for an artifact.
866
875
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
870
879
:keyword tag: Tag of the manifest.
871
880
: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.
873
886
:rtype: str
874
887
: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.
876
889
"""
877
890
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 ())
880
894
else :
881
895
data = manifest
882
896
tag_or_digest = tag
@@ -887,20 +901,55 @@ def upload_manifest(
887
901
name = repository ,
888
902
reference = tag_or_digest ,
889
903
payload = data ,
890
- content_type = OCI_MANIFEST_MEDIA_TYPE ,
891
- headers = {"Accept" : OCI_MANIFEST_MEDIA_TYPE },
904
+ content_type = media_type ,
892
905
cls = _return_response_headers ,
893
906
** kwargs
894
907
)
895
908
digest = response_headers ['Docker-Content-Digest' ]
896
909
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 ." )
898
911
except Exception as e :
899
912
if repository is None or manifest is None :
900
913
raise ValueError ("The parameter repository and manifest cannot be None." ) from e
901
914
raise
902
915
return digest
903
916
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
+
904
953
@distributed_trace
905
954
def upload_blob (self , repository : str , data : IO [bytes ], ** kwargs ) -> Tuple [str , int ]:
906
955
"""Upload an artifact blob.
@@ -949,40 +998,7 @@ def _upload_blob_chunk(self, location: str, data: IO[bytes], **kwargs) -> Tuple[
949
998
hasher .update (buffer )
950
999
buffer = data .read (DEFAULT_CHUNK_SIZE )
951
1000
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
986
1002
987
1003
@distributed_trace
988
1004
def download_blob (self , repository : str , digest : str , ** kwargs ) -> DownloadBlobStream :
0 commit comments