77from pathlib import PurePosixPath
88from typing import Any , Optional , Self , cast
99
10- import requests
10+ import httpx
1111from werkzeug .datastructures import WWWAuthenticate
1212
13- from ... errors . user import ImageParseError
13+ from renku_data_services . errors import errors
1414
1515
1616class ManifestTypes (Enum ):
@@ -29,16 +29,17 @@ class ImageRepoDockerAPI:
2929
3030 hostname : str
3131 oauth2_token : Optional [str ] = field (default = None , repr = False )
32+ client : httpx .AsyncClient = httpx .AsyncClient (timeout = 10 )
3233
33- def _get_docker_token (self , image : "Image" ) -> Optional [str ]:
34+ async def _get_docker_token (self , image : "Image" ) -> Optional [str ]:
3435 """Get an authorization token from the docker v2 API.
3536
3637 This will return the token provided by the API (or None if no token was found).
3738 """
3839 image_digest_url = f"https://{ self .hostname } /v2/{ image .name } /manifests/{ image .tag } "
3940 try :
40- auth_req = requests . get (image_digest_url , timeout = 10 )
41- except requests . ConnectionError :
41+ auth_req = await self . client . get (image_digest_url )
42+ except httpx . ConnectError :
4243 auth_req = None
4344 if auth_req is None or not (auth_req .status_code == 401 and "Www-Authenticate" in auth_req .headers ):
4445 # the request status code and header are not what is expected
@@ -54,56 +55,55 @@ def _get_docker_token(self, image: "Image") -> Optional[str]:
5455 if self .oauth2_token :
5556 creds = base64 .urlsafe_b64encode (f"oauth2:{ self .oauth2_token } " .encode ()).decode ()
5657 headers ["Authorization" ] = f"Basic { creds } "
57- token_req = requests . get (realm , params = params , headers = headers , timeout = 10 )
58+ token_req = await self . client . get (realm , params = params , headers = headers )
5859 return str (token_req .json ().get ("token" ))
5960
60- def get_image_manifest (self , image : "Image" ) -> Optional [dict [str , Any ]]:
61+ async def get_image_manifest (self , image : "Image" ) -> Optional [dict [str , Any ]]:
6162 """Query the docker API to get the manifest of an image."""
6263 if image .hostname != self .hostname :
63- raise ImageParseError (
64- f"The image hostname { image .hostname } does not match " f"the image repository { self .hostname } "
64+ raise errors . ValidationError (
65+ message = f"The image hostname { image .hostname } does not match " f"the image repository { self .hostname } "
6566 )
6667 token = self ._get_docker_token (image )
6768 image_digest_url = f"https://{ image .hostname } /v2/{ image .name } /manifests/{ image .tag } "
6869 headers = {"Accept" : ManifestTypes .docker_v2 .value }
6970 if token :
7071 headers ["Authorization" ] = f"Bearer { token } "
71- res = requests . get (image_digest_url , headers = headers , timeout = 10 )
72+ res = await self . client . get (image_digest_url , headers = headers )
7273 if res .status_code != 200 :
7374 headers ["Accept" ] = ManifestTypes .oci_v1 .value
74- res = requests . get (image_digest_url , headers = headers , timeout = 10 )
75+ res = await self . client . get (image_digest_url , headers = headers )
7576 if res .status_code != 200 :
7677 return None
7778 return cast (dict [str , Any ], res .json ())
7879
79- def image_exists (self , image : "Image" ) -> bool :
80+ async def image_exists (self , image : "Image" ) -> bool :
8081 """Check the docker repo API if the image exists."""
81- return self .get_image_manifest (image ) is not None
82+ return await self .get_image_manifest (image ) is not None
8283
83- def get_image_config (self , image : "Image" ) -> Optional [dict [str , Any ]]:
84+ async def get_image_config (self , image : "Image" ) -> Optional [dict [str , Any ]]:
8485 """Query the docker API to get the configuration of an image."""
85- manifest = self .get_image_manifest (image )
86+ manifest = await self .get_image_manifest (image )
8687 if manifest is None :
8788 return None
8889 config_digest = manifest .get ("config" , {}).get ("digest" )
8990 if config_digest is None :
9091 return None
91- token = self ._get_docker_token (image )
92- res = requests .get (
92+ token = await self ._get_docker_token (image )
93+ res = await self . client .get (
9394 f"https://{ image .hostname } /v2/{ image .name } /blobs/{ config_digest } " ,
9495 headers = {
9596 "Accept" : "application/json" ,
9697 "Authorization" : f"Bearer { token } " ,
9798 },
98- timeout = 10 ,
9999 )
100100 if res .status_code != 200 :
101101 return None
102102 return cast (dict [str , Any ], res .json ())
103103
104- def image_workdir (self , image : "Image" ) -> Optional [PurePosixPath ]:
104+ async def image_workdir (self , image : "Image" ) -> Optional [PurePosixPath ]:
105105 """Query the docker API to get the workdir of an image."""
106- config = self .get_image_config (image )
106+ config = await self .get_image_config (image )
107107 if config is None :
108108 return None
109109 nested_config = config .get ("config" , {})
@@ -204,9 +204,9 @@ def build_re(*parts: str) -> re.Pattern:
204204 if len (matches ) == 1 :
205205 return cls (matches [0 ]["hostname" ], matches [0 ]["image" ], matches [0 ]["tag" ])
206206 elif len (matches ) > 1 :
207- raise ImageParseError ( f"Cannot parse the image { path } , too many interpretations { matches } " )
207+ raise errors . ValidationError ( message = f"Cannot parse the image { path } , too many interpretations { matches } " )
208208 else :
209- raise ImageParseError ( f"Cannot parse the image { path } " )
209+ raise errors . ValidationError ( message = f"Cannot parse the image { path } " )
210210
211211 def repo_api (self ) -> ImageRepoDockerAPI :
212212 """Get the docker API from the image."""
0 commit comments