Skip to content

Commit

Permalink
adding views for manifest and tags
Browse files Browse the repository at this point in the history
Signed-off-by: vsoch <vsochat@stanford.edu>
  • Loading branch information
vsoch committed Sep 17, 2020
1 parent e99d3e7 commit c08f22d
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 22 deletions.
31 changes: 31 additions & 0 deletions django_oci/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
Copyright (c) 2020, Vanessa Sochat
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

from django.contrib import admin

from .models import Repository, Image


@admin.register(Repository)
class RepositoryAdmin(admin.ModelAdmin):
pass


@admin.register(Image)
class ImageAdmin(admin.ModelAdmin):
pass
16 changes: 16 additions & 0 deletions django_oci/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from django.middleware import cache
import uuid

import json
import os
import uuid

Expand Down Expand Up @@ -125,10 +126,25 @@ class Image(models.Model):
)

tag = models.CharField(max_length=250, null=False, blank=False, default="latest")
manifest = models.TextField(null=False, blank=False, default="{}")

# TODO: how do we define the version for this? digest of manifest?
version = models.CharField(max_length=250, null=True, blank=True)

# Manifest functions to get, save, and return download url
def get_manifest(self):
return json.loads(self.manifest)

def save_manifest(self, manifest):
self.manifest = json.dumps(manifest)
self.save()

def get_download_url(self):
return reverse(
"django_oci:image_manifest",
kwargs={"name": self.repository.name, "reference": self.tag},
)

# A container only gets a version when it's frozen, otherwise known by tag
def get_uri(self):
return "%s:%s" % (self.repository.name, self.tag)
Expand Down
7 changes: 7 additions & 0 deletions django_oci/urls/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,18 @@
views.APIVersionCheck.as_view(),
name="api-version-check",
),
url(
r"^%s/(?P<name>[a-z0-9\/]+(?:[._-][a-z0-9]+)*)/tags/list/?$"
% settings.URL_PREFIX,
views.ImageTags.as_view(),
name="image_tags",
),
# https://github.com/opencontainers/distribution-spec/blob/master/spec.md#pulling-an-image-manifest
url(
r"^%s/(?P<name>[a-z0-9\/]+(?:[._-][a-z0-9]+)*)/manifests/(?P<reference>[A-Za-z0-9_+.-]+:[A-Fa-f0-9]+)/?$"
% settings.URL_PREFIX,
views.ImageManifest.as_view(),
name="image_manifest",
),
url(
r"^%s/(?P<name>[a-z0-9\/]+(?:[._-][a-z0-9]+)*)/blobs/uploads/?$"
Expand Down
94 changes: 72 additions & 22 deletions django_oci/views/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,31 @@ def get(self, request, *args, **kwargs):
return storage.download_blob(name, digest)


class ImageTags(APIView):
"""Return a list of tags for an image."""

permission_classes = []
allowed_methods = ("GET",)

def get(self, request, *args, **kwargs):
"""GET /v2/<name>/tags/list"""
name = kwargs.get("name")
number = request.GET.get("n")
tags = list(repository.image_set.values_list("tag", flat=True))
if number:
tags = tags[:number]

# Ensure the repository exists
try:
repository = Repository.objects.get(name=name)
except Repository.DoesNotExist:
raise Http404

# Ensure tags sorted in lexical order
data = {"name": repository.name, "tags": sorted(tags)}
return Response(status=200, data=data)


class ImageBlobUpload(APIView):
"""An image push will receive a request to push, authenticate the user,
and return an upload url (url is /v2/<name>/blobs/uploads/)
Expand Down Expand Up @@ -235,10 +260,52 @@ def post(self, request, *args, **kwargs):


class ImageManifest(APIView):
"""GET an image manifest, the starting operation to pull a container image."""
"""An Image Manifest holds the configuration and metadata about an image
GET: is to retrieve an existing image manifest
PUT: is to push a manifest
"""

permission_classes = []
allowed_methods = ("GET",)
allowed_methods = (
"GET",
"PUT",
)

def put(self, request, *args, **kwargs):
"""PUT /v2/<name>/manifests/<reference>
https://github.com/opencontainers/distribution-spec/blob/master/spec.md#pushing-manifests
"""
# We likely can default to the v1 manifest, unless otherwise specified
content_type = request.META.get(
"CONTENT_TYPE", "application/vnd.oci.image.manifest.v1+json"
)
name = kwargs.get("name")
reference = kwargs.get("reference")

# Ensure the repository exists
try:
repository = Repository.objects.get(name=name)
except Repository.DoesNotExist:
raise Http404

# reference can be a tag (more likely) or digest
try:
image = repository.image_set.get(tag=reference)
except Repository.DoesNotExist:
try:
image = repository.image_set.get(version=reference)
except:
raise Http404

# The manifest is in the body, load to string
manifest = request.body.decode("utf-8")
image.manifest = manifest
image.save()

# TODO: do we want to parse or otherwise load the manifest?
# parse annotations
# validate layers?
return Response(status=201, headers={"Location": image.get_manifest_url()})

def get(self, request, *args, **kwargs):
"""GET /v2/<name>/manifests/<reference>"""
Expand All @@ -264,29 +331,12 @@ def get(self, request, *args, **kwargs):
except:
raise Http404

print(request.body)
print(image)

# Create and validate a manifest
manifest = Manifest()
manifest.load()

# TODO have this created by opencontainers python
# {
# "annotations": {
# "com.example.key1": "value1",
# "com.example.key2": "value2"
# },
# "config": {
# "digest": "sha256:6f4e69a5ff18d92e7315e3ee31c62165ebf25bfa05cad05c0d09d8f412dae401",
# "mediaType": "application/vnd.oci.image.config.v1+json",
# "size": 452
# },
# "layers": [
# {
# "digest": "sha256:6f4e69a5ff18d92e7315e3ee31c62165ebf25bfa05cad05c0d09d8f412dae401",
# "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
# "size": 78343
# }
# ],
# "schemaVersion": 2
# }
headers = {"Docker-Distribution-API-Version": "registry/2.0"}
return Response(manifest.to_dict())

0 comments on commit c08f22d

Please sign in to comment.