Commit 5451a6df authored by BAIRE Anthony's avatar BAIRE Anthony

implement registry endpoint for push/pull of image manfests

Manifest push/pull are expected to be routed through the django server
which forwards them to the real registry.

This allows:
- (before push) ensuring there is no commit/push in progress on the
  controller side, and refuse the request until the commit is done
- (after push) ensure that the image is transactionally inserted into
  the db before the '201 Created' response is forwarded to the user
parent 326b9cd9
......@@ -170,6 +170,10 @@ with env_loader.EnvironmentVarLoader(__name__, "ALLGO_",
default="4567",
help="TCP port of the allgo controller (for the notifications)")
env_var("ALLGO_REGISTRY_PRIVATE_URL",
default="http://{ENV}-registry:5000",
help="Backend URL of the registry")
#
# allgo authentication tokens
#
......
......@@ -5,4 +5,11 @@ app_name = 'jwt'
urlpatterns = [
url(r'^jwt/auth$', views.jwt_auth, name="jwt_auth"), # REGISTRY_AUTH_TOKEN_REALM for docker registry
# docker registry urls
# - image manifest push/pull
url(r'^v2/(?P<repo>.*)/manifests/(?P<tag>[^/]+)$', views.registry_manifest, name="registry_manifest"),
# - default catch-all route (normally unused because the reverse-proxy is
# expected to route them directly to the registry)
url(r'^v2/', views.registry_notfound),
]
......@@ -2,14 +2,112 @@ import base64
import logging
import config.env
import requests
from django.http import JsonResponse, HttpResponse
from main.models import User, AllgoUser, Runner
from django.views.decorators.csrf import csrf_exempt
from main.models import User, AllgoUser, Runner, Webapp, WebappVersion
from .tokens import Token
log = logging.getLogger('jwt')
@csrf_exempt
def registry_manifest(request, repo, tag):
"""Registry endpoint for pushing/pulling image manifests
The nginx reverse proxy is configured to forward these requests through the
django server, so that:
- we are notified when a new image is pushed
- we can later implement fine-grained permissions (tag-based rather than
repository-based)
Note: the DB is transactionally updated before the client receives the 201
response
"""
headers = {"Content-Type": request.content_type}
for key in (
"HTTP_AUTHORIZATION",
"HTTP_CONTENT_TYPE",
"HTTP_USER_AGENT",
"HTTP_X_FORWARDED_FOR",
"HTTP_X_FORWARDED_HOST",
"HTTP_X_FORWARDED_PROTO"):
val = request.META.get(key);
if val is not None:
headers[key[5:].replace("_", "-")] = val
# NOTE: the registry endpoint also supports the DELETE method to delete the
# image. Since the deletion of a WebappVersion is not yet implemented, we
# do not allow the delete method here
if request.method not in ("HEAD", "GET", "PUT"):
# method not allowed
return HttpResponse(status=405)
# forward the HTTP request to the registry
# FIXME: this function is a blocking, i do not know if there is a way of
# doing a request without blocking the thread
# FIXME: should allow tuning the timeout value
forward_request = lambda: requests.request(request.method,
config.env.ALLGO_REGISTRY_PRIVATE_URL + request.path,
timeout=.5, headers=headers, data=request.body)
try:
if request.method == "PUT":
# push
# find the relevant webapp+version
webapp = Webapp.objects.filter(docker_name=repo).first()
if webapp is None:
return JsonResponse({"error": "unknown repository"}, status=404)
# abort if we have a commit in progress in the controller
# NOTE: there is still a (very narrow) conflict window if the commmit
# starts between this check and the result of this request
if WebappVersion.objects.filter(webapp=webapp, number=tag,
state__in=(WebappVersion.SANDBOX, WebappVersion.COMMITED)
).exists():
return JsonResponse({
"error": "there is a commit in progress for %s:%s"
% (repo, tag)}, status=409)
rep = forward_request()
if rep.ok:
if request.method == "PUT":
# Save the new app in the DB
WebappVersion.objects.update_or_create(
webapp=webapp, state=WebappVersion.READY, number=tag,
defaults={"published": True})
# TODO: notify the controller
else:
# pull
rep = forward_request()
except requests.ConnectTimeout:
return JsonResponse({"error": "registry not available"}, status=502)
# forward the registry reply to the client
if "Content-Encoding" in rep.headers:
del rep.headers["Content-Encoding"]
response = HttpResponse(rep.content, status=rep.status_code)
for key, val in rep.headers.items():
response[key] = val
return response
@csrf_exempt
def registry_notfound(request):
"""Default endpoint for all other registry urls
should never be served (if the reverse-proxy is well configured)
"""
return JsonResponse({"error":
"registry not found (this is very likely a reverse-proxy config issue)"},
status=404)
def jwt_auth(request):
"""
......
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