@@ -346,3 +346,102 @@ class FakeRegistry(DockerRegistry):
346
346
347
347
async def get_image_manifest (self , image , tag ):
348
348
return None
349
+
350
+
351
+ class ExternalRegistryHelper (DockerRegistry ):
352
+ """
353
+ A registry that uses a micro-service to check and create image
354
+ repositories.
355
+
356
+ Also handles creation of tokens for pushing to a registry if required.
357
+ """
358
+
359
+ service_url = Unicode (
360
+ "http://binderhub-container-registry-helper:8080" ,
361
+ allow_none = False ,
362
+ help = "The URL of the registry helper micro-service." ,
363
+ config = True ,
364
+ )
365
+
366
+ auth_token = Unicode (
367
+ os .getenv ("BINDERHUB_CONTAINER_REGISTRY_HELPER_AUTH_TOKEN" ),
368
+ help = "The auth token to use when accessing the registry helper micro-service." ,
369
+ config = True ,
370
+ )
371
+
372
+ async def _request (self , endpoint , ** kwargs ):
373
+ client = httpclient .AsyncHTTPClient ()
374
+ repo_url = f"{ self .service_url } { endpoint } "
375
+ headers = {"Authorization" : f"Bearer { self .auth_token } " }
376
+ repo = await client .fetch (repo_url , headers = headers , ** kwargs )
377
+ return json .loads (repo .body .decode ("utf-8" ))
378
+
379
+ async def _get_image (self , image , tag ):
380
+ repo_url = f"/image/{ image } :{ tag } "
381
+ self .log .debug (f"Checking whether image exists: { repo_url } " )
382
+ try :
383
+ image_json = await self ._request (repo_url )
384
+ return image_json
385
+ except httpclient .HTTPError as e :
386
+ if e .code == 404 :
387
+ return None
388
+ raise
389
+
390
+ async def get_image_manifest (self , image , tag ):
391
+ """
392
+ Checks whether the image exists in the registry.
393
+
394
+ If the container repository doesn't exist create the repository.
395
+
396
+ The container repository name may not be the same as the BinderHub image name.
397
+
398
+ E.g. Oracle Container Registry (OCIR) has the form:
399
+ OCIR_NAMESPACE/OCIR_REPOSITORY_NAME:TAG
400
+
401
+ These extra components are handled automatically by the registry helper
402
+ so BinderHub repository names such as OCIR_NAMESPACE/OCIR_REPOSITORY_NAME
403
+ can be used directly, it is not necessary to remove the extra components.
404
+
405
+ Returns the image manifest if the image exists, otherwise None
406
+ """
407
+
408
+ repo_url = f"/repo/{ image } "
409
+ self .log .debug (f"Checking whether repository exists: { repo_url } " )
410
+ try :
411
+ repo_json = await self ._request (repo_url )
412
+ except httpclient .HTTPError as e :
413
+ if e .code == 404 :
414
+ repo_json = None
415
+ else :
416
+ raise
417
+
418
+ if repo_json :
419
+ return await self ._get_image (image , tag )
420
+ else :
421
+ self .log .debug (f"Creating repository: { repo_url } " )
422
+ await self ._request (repo_url , method = "POST" , body = "" )
423
+ return None
424
+
425
+ async def get_credentials (self , image , tag ):
426
+ """
427
+ Get the registry credentials for the given image and tag if supported
428
+ by the remote helper, otherwise returns None
429
+
430
+ Returns a dictionary of login fields.
431
+ """
432
+ token_url = f"/token/{ image } :{ tag } "
433
+ self .log .debug (f"Getting registry token: { token_url } " )
434
+ token_json = None
435
+ try :
436
+ token_json = await self ._request (token_url , method = "POST" , body = "" )
437
+ except httpclient .HTTPError as e :
438
+ if e .code == 404 :
439
+ return None
440
+ raise
441
+ self .log .debug (f"Token: { * token_json .keys (),} " )
442
+ token = {
443
+ k : v
444
+ for (k , v ) in token_json .items ()
445
+ if k in ["username" , "password" , "registry" ]
446
+ }
447
+ return token
0 commit comments