Skip to content

Commit 3fbfe6b

Browse files
committed
Add ExternalRegistryHelper for working with microservice
1 parent c9c86bf commit 3fbfe6b

File tree

1 file changed

+97
-0
lines changed

1 file changed

+97
-0
lines changed

binderhub/registry.py

+97
Original file line numberDiff line numberDiff line change
@@ -315,3 +315,100 @@ class FakeRegistry(DockerRegistry):
315315

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

Comments
 (0)