@@ -315,3 +315,100 @@ class FakeRegistry(DockerRegistry):
315
315
316
316
async def get_image_manifest (self , image , tag ):
317
317
return None
318
+
319
+
320
+ class ExternalRegistryHelper (DockerRegistry ):
321
+ """
322
+ A registry that uses a micro-service to check and create image
323
+ repositories.
324
+
325
+ Also handles creation of tokens for pushing to a registry if required.
326
+ """
327
+
328
+ service_url = Unicode (
329
+ "http://binderhub-container-registry-helper:8080" ,
330
+ allow_none = False ,
331
+ help = "The URL of the registry helper micro-service." ,
332
+ config = True ,
333
+ )
334
+
335
+ auth_token = Unicode (
336
+ "secret-token" ,
337
+ help = "The auth token to use when accessing the registry helper micro-service." ,
338
+ config = True ,
339
+ )
340
+
341
+ async def _request (self , endpoint , ** kwargs ):
342
+ client = httpclient .AsyncHTTPClient ()
343
+ repo_url = f"{ self .service_url } { endpoint } "
344
+ headers = {"Authorization" : f"Bearer { self .auth_token } " }
345
+ repo = await client .fetch (repo_url , headers = headers , ** kwargs )
346
+ return json .loads (repo .body .decode ("utf-8" ))
347
+
348
+ async def _get_image (self , image , tag ):
349
+ repo_url = f"/image/{ image } :{ tag } "
350
+ self .log .debug (f"Checking whether image exists: { repo_url } " )
351
+ try :
352
+ image_json = await self ._request (repo_url )
353
+ return image_json
354
+ except httpclient .HTTPError as e :
355
+ if e .code == 404 :
356
+ return None
357
+ else :
358
+ raise
359
+
360
+ async def get_image_manifest (self , image , tag ):
361
+ """
362
+ Checks whether the image exists in the registry.
363
+
364
+ If the container repository doesn't exist create the repository.
365
+
366
+ image: The image name without the registry domain and tag, but
367
+ including all prefix components (e.g. a namespace). The registry
368
+ helper should automatically take care of converting the full path
369
+ into the necessary API components
370
+ tag: The image tag
371
+
372
+ Returns the image manifest if the image exists, otherwise None
373
+ """
374
+
375
+ repo_url = f"/repo/{ image } "
376
+ self .log .debug (f"Checking whether repository exists: { repo_url } " )
377
+ try :
378
+ repo_json = await self ._request (repo_url )
379
+ except httpclient .HTTPError as e :
380
+ if e .code == 404 :
381
+ repo_json = None
382
+ else :
383
+ raise
384
+
385
+ if repo_json :
386
+ return await self ._get_image (image , tag )
387
+ else :
388
+ self .log .debug (f"Creating repository: { repo_url } " )
389
+ await self ._request (repo_url , method = "POST" , body = "" )
390
+ return None
391
+
392
+ async def get_credentials (self , image , tag ):
393
+ """
394
+ Get the registry credentials for the given image and tag if supported
395
+ by the remote helper, otherwise returns None
396
+
397
+ Returns a dictionary of login fields.
398
+ """
399
+ token_url = f"/token/{ image } :{ tag } "
400
+ self .log .debug (f"Getting registry token: { token_url } " )
401
+ token_json = None
402
+ try :
403
+ token_json = await self ._request (token_url , method = "POST" , body = "" )
404
+ except httpclient .HTTPError as e :
405
+ if e .code != 404 :
406
+ raise
407
+ # https://docker-py.readthedocs.io/en/stable/api.html#docker.api.daemon.DaemonApiMixin.login
408
+ token = {
409
+ k : v
410
+ for (k , v ) in token_json .items ()
411
+ if k in ["username" , "password" , "registry" ]
412
+ }
413
+ self .log .debug (f"Returning registry token: { token } " )
414
+ return token
0 commit comments