Commit 70d67df2 authored by BAIRE Anthony's avatar BAIRE Anthony
Browse files

allow setting the WebappVersion.description when pushing an image

parent ee53df63
......@@ -40,6 +40,8 @@ import http
import json
import logging
import os
import re
import sys
import time
import weakref
......@@ -466,6 +468,53 @@ class AllgoAio:
except Exception:
log.exception("error in the redis notification loop")
async def get_image_description(self, request, repo: str, manifest: bytes) -> str:
"""Get the description of an image in the registry
The WebappVersion.description string is provided by the user:
- in a UI form (if committing from a sandbox)
- in the 'allgo.description' label (if pushing a docker image)
'manifest' is the manifest of the image being pushed to the registry.
It points to a config blob which contains the image labels.
This function parses the manifests, downloads and parses the config
blob, then return the label if present.
To be future-proof (the manifest format may change in the future), the
function returns an empty string and logs a warning if it fails.
"""
def error(msg, *k, **kw):
log.warning("unable to get the allgo.description label from pushed image (%s)",
msg % k, **kw)
return ""
try:
js = json.loads(manifest.decode())
if js["schemaVersion"] != 2:
return error("unknown schemaVersion=%r" % js["schemaVersion"])
cfg = js["config"]
digest, size = cfg["digest"], cfg["size"]
if size > 1024**2:
return error("config blob too big (%d bytes)" % size)
if not re.fullmatch("[A-Za-z0-9:]+", digest):
return error("malformatted digest %r" % digest)
async with self.http_client.get("%s/v2/%s/blobs/%s" % (
config.env.ALLGO_REGISTRY_PRIVATE_URL, repo, digest),
headers=prepare_headers(request)) as cfg_reply:
if not is_ok(cfg_reply):
return error("unable to get the config blob (Error %d)" % cfg_reply.status)
js = json.loads(await cfg_reply.text())
try:
return str(js["config"]["Labels"]["allgo.description"])
except (TypeError, KeyError):
return ""
except Exception as e:
return error("unhandled exception", exc_info=sys.exc_info())
async def handle_image_manifest(self, request):
"""Registry endpoint for pushing/pulling image manifests
......@@ -492,14 +541,17 @@ class AllgoAio:
headers = prepare_headers(request)
headers["Content-Type"] = request.content_type
manifest = await request.read()
repo = request.match_info["repo"]
tag = request.match_info["tag"]
description = (await self.get_image_description(request, repo, manifest)
) if action == "push" else ""
# call django's pre hook
async with self.django_request("POST", "/jwt/pre-"+action,
headers=headers, params={"repo": repo, "tag": tag},
) as django_reply:
headers=headers, params={"repo": repo, "tag": tag,
"description": description}) as django_reply:
if not is_ok(django_reply):
return await forward_response(django_reply)
......@@ -509,7 +561,7 @@ class AllgoAio:
real_url = "%s/v2/%s/manifests/id%d" % (
config.env.ALLGO_REGISTRY_PRIVATE_URL, repo, version_id)
async with self.http_client.request(request.method, real_url,
headers=headers, data=await request.read()) as registry_reply:
headers=headers, data=manifest) as registry_reply:
if action == "pull":
# pull
......
......@@ -46,6 +46,7 @@ def pre_pushpull(request, action):
repo = request.GET["repo"]
tag = request.GET["tag"]
description = request.GET["description"]
try:
# find the relevant webapp
......@@ -66,7 +67,7 @@ def pre_pushpull(request, action):
# create a new WebappVersion entry in state USER
version = WebappVersion(
webapp=webapp, number=tag, state=WebappVersion.USER,
published=True, description="TODO")
published=True, description=description)
version.save()
else:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment