Skip to content

Commit d79651c

Browse files
committed
Add ExternalRegistryHelper for working with microservice
1 parent 5c05e7a commit d79651c

File tree

1 file changed

+99
-0
lines changed

1 file changed

+99
-0
lines changed

binderhub/registry.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,3 +346,102 @@ class FakeRegistry(DockerRegistry):
346346

347347
async def get_image_manifest(self, image, tag):
348348
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

Comments
 (0)