Skip to content

Commit

Permalink
New api endpoints for getting and listing container registries (#898)
Browse files Browse the repository at this point in the history
Issue: AAH-434
  • Loading branch information
ShaiahWren authored Aug 25, 2021
1 parent 9e6f82a commit 8348eaa
Show file tree
Hide file tree
Showing 11 changed files with 281 additions and 132 deletions.
1 change: 1 addition & 0 deletions CHANGES/434.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Create new api endpoints for listing, getting, and updating container registries.
6 changes: 4 additions & 2 deletions galaxy_ng/app/api/ui/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
ContainerRepositoryHistorySerializer,
ContainerReadmeSerializer,
ContainerManifestDetailSerializer,
ContainerNamespaceDetailSerializer
ContainerNamespaceDetailSerializer,
ContainerRegistryRemoteSerializer,
)

__all__ = (
Expand Down Expand Up @@ -64,5 +65,6 @@
'ContainerManifestSerializer',
'ContainerManifestDetailSerializer',
'ContainerReadmeSerializer',
'ContainerNamespaceDetailSerializer'
'ContainerNamespaceDetailSerializer',
'ContainerRegistryRemoteSerializer',
)
203 changes: 115 additions & 88 deletions galaxy_ng/app/api/ui/serializers/execution_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,44 @@

from pulp_container.app import models as container_models
from pulpcore.plugin import models as core_models
from pulpcore.plugin import serializers as core_serializers

from galaxy_ng.app import models
from galaxy_ng.app.access_control.fields import GroupPermissionField, MyPermissionsField
from galaxy_ng.app.api.v3 import serializers as v3_serializers
from galaxy_ng.app.api import utils

namespace_fields = (
'name',
'my_permissions',
'owners'
)
namespace_fields = ("name", "my_permissions", "owners")


class ContainerNamespaceSerializer(serializers.ModelSerializer):
my_permissions = MyPermissionsField(source='*', read_only=True)
my_permissions = MyPermissionsField(source="*", read_only=True)
owners = serializers.SerializerMethodField()

class Meta:
model = models.ContainerNamespace
fields = namespace_fields
read_only_fields = ('name', 'my_permissions',)
read_only_fields = (
"name",
"my_permissions",
)

def get_owners(self, namespace):
return get_users_with_perms(namespace, with_group_users=False).values_list(
'username', flat=True)
"username", flat=True
)


class ContainerNamespaceDetailSerializer(ContainerNamespaceSerializer):
groups = GroupPermissionField()

class Meta:
model = models.ContainerNamespace
fields = namespace_fields + ('groups', )
read_only_fields = ('name', 'my_permissions',)
fields = namespace_fields + ("groups",)
read_only_fields = (
"name",
"my_permissions",
)


class ContainerRepositorySerializer(serializers.ModelSerializer):
Expand All @@ -55,15 +61,15 @@ class ContainerRepositorySerializer(serializers.ModelSerializer):
class Meta:
model = models.ContainerDistribution
read_only_fields = (
'id',
'name',
"id",
"name",
# this field will return null on instances where hub is indexing a
# different repo
'pulp',
'namespace',
'description',
'created',
'updated',
"pulp",
"namespace",
"description",
"created",
"updated",
)

fields = read_only_fields
Expand All @@ -84,36 +90,28 @@ def get_pulp(self, distro):
repo = distro.repository

return {
'repository':
{
'pulp_id': repo.pk,
'pulp_type': repo.pulp_type,
'version': repo.latest_version().number,
'name': repo.name,
'description': repo.description,
'pulp_created': repo.pulp_created,
'last_sync_task': _get_last_sync_task(repo),
'pulp_labels': {
label.key: label.value for label in repo.pulp_labels.all()
},
"repository": {
"pulp_id": repo.pk,
"pulp_type": repo.pulp_type,
"version": repo.latest_version().number,
"name": repo.name,
"description": repo.description,
"pulp_created": repo.pulp_created,
"last_sync_task": _get_last_sync_task(repo),
"pulp_labels": {label.key: label.value for label in repo.pulp_labels.all()},
},
"distribution": {
"pulp_id": distro.pk,
"name": distro.name,
"pulp_created": distro.pulp_created,
"base_path": distro.base_path,
"pulp_labels": {label.key: label.value for label in distro.pulp_labels.all()},
},
'distribution':
{
'pulp_id': distro.pk,
'name': distro.name,
'pulp_created': distro.pulp_created,
'base_path': distro.base_path,
'pulp_labels': {
label.key: label.value for label in distro.pulp_labels.all()
},
}
}


def _get_last_sync_task(repo):
sync_task = models.container.ContainerSyncTask.objects.filter(
repository=repo
).first()
sync_task = models.container.ContainerSyncTask.objects.filter(repository=repo).first()
if not sync_task:
# UI handles `null` as "no status"
return
Expand All @@ -123,7 +121,7 @@ def _get_last_sync_task(repo):
"state": sync_task.task.state,
"started_at": sync_task.task.started_at,
"finished_at": sync_task.task.finished_at,
"error": sync_task.task.error
"error": sync_task.task.error,
}


Expand All @@ -135,33 +133,27 @@ class ContainerManifestSerializer(serializers.ModelSerializer):
class Meta:
model = container_models.Manifest
fields = (
'pulp_id',
'digest',
'schema_version',
'media_type',
'config_blob',
'tags',
'pulp_created',
'layers'
"pulp_id",
"digest",
"schema_version",
"media_type",
"config_blob",
"tags",
"pulp_created",
"layers",
)

def get_layers(self, obj):
layers = []
# use the prefetched blob_list and artifact_list instead of obj.blobs and
# blob._artifacts to cut down on queries made.
for blob in obj.blob_list:
layers.append({
'digest': blob.digest,
'size': blob.artifact_list[0].size
})
layers.append({"digest": blob.digest, "size": blob.artifact_list[0].size})

return layers

def get_config_blob(self, obj):
return {
'digest': obj.config_blob.digest,
'media_type': obj.config_blob.media_type
}
return {"digest": obj.config_blob.digest, "media_type": obj.config_blob.media_type}

def get_tags(self, obj):
tags = []
Expand All @@ -179,9 +171,9 @@ def get_config_blob(self, obj):
config_json = json.load(f)

return {
'digest': obj.config_blob.digest,
'media_type': obj.config_blob.media_type,
'data': config_json
"digest": obj.config_blob.digest,
"media_type": obj.config_blob.media_type,
"data": config_json,
}


Expand All @@ -191,23 +183,13 @@ class ContainerRepositoryHistorySerializer(serializers.ModelSerializer):

class Meta:
model = core_models.RepositoryVersion
fields = (
'pulp_id',
'added',
'removed',
'pulp_created',
'number'
)
fields = ("pulp_id", "added", "removed", "pulp_created", "number")

def get_added(self, obj):
return [
self._content_info(content.content) for content in obj.added_memberships.all()
]
return [self._content_info(content.content) for content in obj.added_memberships.all()]

def get_removed(self, obj):
return [
self._content_info(content.content) for content in obj.removed_memberships.all()
]
return [self._content_info(content.content) for content in obj.removed_memberships.all()]

def _content_info(self, content):
return_data = {
Expand All @@ -218,29 +200,74 @@ def _content_info(self, content):
}

# TODO: Figure out if there is a way to prefetch Manifest and Tag objects
if content.pulp_type == 'container.manifest':
if content.pulp_type == "container.manifest":
manifest = container_models.Manifest.objects.get(pk=content.pk)
return_data['manifest_digest'] = manifest.digest
elif content.pulp_type == 'container.tag':
tag = container_models.Tag.objects.select_related('tagged_manifest')\
.get(pk=content.pk)
return_data['manifest_digest'] = tag.tagged_manifest.digest
return_data['tag_name'] = tag.name
return_data["manifest_digest"] = manifest.digest
elif content.pulp_type == "container.tag":
tag = container_models.Tag.objects.select_related("tagged_manifest").get(pk=content.pk)
return_data["manifest_digest"] = tag.tagged_manifest.digest
return_data["tag_name"] = tag.name

return return_data


class ContainerReadmeSerializer(serializers.ModelSerializer):

class Meta:
model = models.ContainerDistroReadme
fields = (
'updated',
'created',
'text',
"updated",
"created",
"text",
)

read_only_fields = (
'updated',
'created',
"updated",
"created",
)


class ContainerRegistryRemoteSerializer(
v3_serializers.LastSyncTaskMixin,
core_serializers.RemoteSerializer,
):
created_at = serializers.DateTimeField(source='pulp_created', required=False)
updated_at = serializers.DateTimeField(source='pulp_last_updated', required=False)
last_sync_task = serializers.SerializerMethodField()
write_only_fields = serializers.SerializerMethodField()

class Meta:
model = models.ContainerRegistryRemote
fields = [
"pk",
"name",
"url",
"policy",
"created_at",
"updated_at",
"username",
"password",
"tls_validation",
"client_key",
"client_cert",
"ca_cert",
"last_sync_task",
"download_concurrency",
"proxy_url",
"proxy_username",
"proxy_password",
"write_only_fields",
"rate_limit",
]
extra_kwargs = {
'name': {'read_only': True},
'client_key': {'write_only': True},
}

def get_write_only_fields(self, obj):
return utils.get_write_only_fields(self, obj)

def get_last_sync_task_queryset(self, obj):
"""Gets last_sync_task from Pulp using remote->repository relation"""

return models.ContainerSyncTask.objects.filter(
repository=obj.repository_set.order_by('-pulp_last_updated').first()).first()
8 changes: 8 additions & 0 deletions galaxy_ng/app/api/ui/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@
"namespaces/",
viewsets.ContainerNamespaceViewSet.as_view({'get': 'list'}),
name='container-namespace-list'),
path(
"registries/<str:pk>/",
viewsets.ContainerRegistryRemoteViewSet.as_view({'get': 'retrieve', 'put': 'update'}),
name='execution-environments-registry-detail'),
path(
"registries/",
viewsets.ContainerRegistryRemoteViewSet.as_view({'get': 'list', 'post': 'create'}),
name='execution-environments-registry-list'),

# image names can't start with _, so namespacing all the nested views
# under _content prevents cases where an image could be named foo/images
Expand Down
6 changes: 4 additions & 2 deletions galaxy_ng/app/api/ui/viewsets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
ContainerRepositoryManifestViewSet,
ContainerRepositoryHistoryViewSet,
ContainerReadmeViewSet,
ContainerNamespaceViewSet
ContainerNamespaceViewSet,
ContainerRegistryRemoteViewSet,
)

__all__ = (
Expand All @@ -46,5 +47,6 @@
'ContainerRepositoryManifestViewSet',
'ContainerRepositoryHistoryViewSet',
'ContainerReadmeViewSet',
'ContainerNamespaceViewSet'
'ContainerNamespaceViewSet',
'ContainerRegistryRemoteViewSet',
)
8 changes: 8 additions & 0 deletions galaxy_ng/app/api/ui/viewsets/execution_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,11 @@ class ContainerReadmeViewSet(ContainerContentBaseViewset):
def get_object(self):
distro = self.get_distro()
return self.queryset.get_or_create(container=distro)[0]


class ContainerRegistryRemoteViewSet(
api_base.ModelViewSet,
):
queryset = models.ContainerRegistryRemote.objects.all()
serializer_class = serializers.ContainerRegistryRemoteSerializer
permission_classes = []
Loading

0 comments on commit 8348eaa

Please sign in to comment.