Skip to content

Commit

Permalink
Add retrieve functionality
Browse files Browse the repository at this point in the history
closes #1010

In particular we are adding the new behaviour for:

- ReleaseArchitecture
- ReleaseComponent

but deliberately not for PackageReleaseComponent, which should only be
created indirectly using package actions.

(cherry picked from commit 599104f)
  • Loading branch information
quba42 authored and hstct committed Apr 11, 2024
1 parent a02c372 commit 0caa062
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGES/1010.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added retrieve functionality for ReleaseArchitecture and ReleaseComponent content.
2 changes: 2 additions & 0 deletions CHANGES/1010.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The API endpoints for ReleaseArchitecture and ReleaseComponent creation will no longer return a 400 ``non_field_errors`` if the content to be created already exists.
Instead a task is triggered that will list the existing content in its ``created_resources`` field.
30 changes: 30 additions & 0 deletions pulp_deb/app/serializers/content_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,21 @@ class ReleaseArchitectureSerializer(NoArtifactContentSerializer):
architecture = CharField(help_text="Name of the architecture.")
distribution = CharField(help_text="Name of the distribution.")

def get_unique_together_validators(self):
"""
We do not want UniqueTogetherValidator since we have retrieve logic!
"""
return []

def retrieve(self, validated_data):
"""
If the ReleaseArchitecture already exists, retrieve it!
"""
return ReleaseArchitecture.objects.filter(
architecture=validated_data["architecture"],
distribution=validated_data["distribution"],
).first()

class Meta(NoArtifactContentSerializer.Meta):
model = ReleaseArchitecture
fields = NoArtifactContentSerializer.Meta.fields + (
Expand All @@ -763,6 +778,21 @@ class ReleaseComponentSerializer(NoArtifactContentSerializer):
A Serializer for ReleaseComponent.
"""

def get_unique_together_validators(self):
"""
We do not want UniqueTogetherValidator since we have retrieve logic!
"""
return []

def retrieve(self, validated_data):
"""
If the ReleaseComponent already exists, retrieve it!
"""
return ReleaseComponent.objects.filter(
distribution=validated_data["distribution"],
component=validated_data["component"],
).first()

component = CharField(help_text="Name of the component.")
distribution = CharField(help_text="Name of the distribution.")

Expand Down
39 changes: 37 additions & 2 deletions pulp_deb/app/viewsets/content.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,54 @@
from gettext import gettext as _ # noqa

from django_filters import Filter
from drf_spectacular.utils import extend_schema
from pulpcore.plugin.tasking import general_create
from pulpcore.plugin.models import Repository, RepositoryVersion
from pulpcore.plugin.serializers import AsyncOperationResponseSerializer
from pulpcore.plugin.serializers.content import ValidationError
from pulpcore.plugin.tasking import dispatch
from pulpcore.plugin.viewsets import (
NAME_FILTER_OPTIONS,
ContentFilter,
ContentViewSet,
NamedModelViewSet,
OperationPostponedResponse,
SingleArtifactContentUploadViewSet,
)
from pulpcore.plugin.viewsets.content import DefaultDeferredContextMixin

from pulp_deb.app import models, serializers


class NoArtifactContentViewSet(DefaultDeferredContextMixin, ContentViewSet):
"""A ViewSet for content creation that does not require a file to be uploaded."""

@extend_schema(
description="Trigger an asynchronous task to create content,"
"optionally create new repository version.",
responses={202: AsyncOperationResponseSerializer},
)
def create(self, request):
"""Create a content unit."""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)

exclusive_resources = [
item for item in (serializer.validated_data.get(key) for key in ("repository",)) if item
]

task = dispatch(
general_create,
exclusive_resources=exclusive_resources,
args=(self.queryset.model._meta.app_label, serializer.__class__.__name__),
kwargs={
"data": {k: v for k, v in request.data.items()},
"context": self.get_deferred_context(request),
},
)
return OperationPostponedResponse(task, request)


class GenericContentFilter(ContentFilter):
"""
FilterSet for GenericContent.
Expand Down Expand Up @@ -440,7 +475,7 @@ class Meta:
fields = ["architecture", "distribution"]


class ReleaseArchitectureViewSet(ContentViewSet):
class ReleaseArchitectureViewSet(NoArtifactContentViewSet):
# The doc string is a top level element of the user facing REST API documentation:
"""
A ReleaseArchitecture represents a single dpkg architecture string.
Expand Down Expand Up @@ -479,7 +514,7 @@ class Meta:
fields = ["component", "distribution"]


class ReleaseComponentViewSet(ContentViewSet):
class ReleaseComponentViewSet(NoArtifactContentViewSet):
# The doc string is a top level element of the user facing REST API documentation:
"""
A ReleaseComponent represents a single APT repository component.
Expand Down
68 changes: 68 additions & 0 deletions pulp_deb/tests/functional/api/test_crud_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,71 @@ def test_structured_package_upload(

results = package_list.results[0]
assert results.relative_path == attrs["relative_path"]


def test_release_component_upload(
apt_release_component_api,
deb_get_repository_by_href,
deb_release_component_factory,
deb_repository_factory,
):
"""Test creating a ReleaseComponent directly in a repository."""
repository = deb_repository_factory()
assert repository.latest_version_href.endswith("/0/")

attrs = {
"repository": repository.pulp_href,
"distribution": str(uuid4()),
"component": str(uuid4()),
}

component = deb_release_component_factory(**attrs)
repository = deb_get_repository_by_href(repository.pulp_href)
assert repository.latest_version_href.endswith("/1/")

repo_version_components = apt_release_component_api.list(
repository_version=repository.latest_version_href
)

assert len(repo_version_components.results) == 1
assert repo_version_components.results[0].pulp_href == component.pulp_href

component2 = deb_release_component_factory(**attrs)
repository = deb_get_repository_by_href(repository.pulp_href)

assert repository.latest_version_href.endswith("/1/")
assert component.pulp_href == component2.pulp_href


def test_release_architecture_upload(
apt_release_architecture_api,
deb_get_repository_by_href,
deb_release_architecture_factory,
deb_repository_factory,
):
"""Test creating a ReleaseArchitecture directly in a repository."""
repository = deb_repository_factory()
assert repository.latest_version_href.endswith("/0/")

attrs = {
"repository": repository.pulp_href,
"distribution": str(uuid4()),
"architecture": str(uuid4()),
}

architecture = deb_release_architecture_factory(**attrs)
repository = deb_get_repository_by_href(repository.pulp_href)
assert repository.latest_version_href.endswith("/1/")

repo_version_architectures = apt_release_architecture_api.list(
repository_version=repository.latest_version_href
)

assert len(repo_version_architectures.results) == 1
assert repo_version_architectures.results[0].pulp_href == architecture.pulp_href

architecture2 = deb_release_architecture_factory(**attrs)
repository = deb_get_repository_by_href(repository.pulp_href)

assert repository.latest_version_href.endswith("/1/")
assert architecture.pulp_href == architecture2.pulp_href
44 changes: 43 additions & 1 deletion pulp_deb/tests/functional/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@
ContentPackageIndicesApi,
ContentPackageReleaseComponentsApi,
ContentReleasesApi,
ContentReleaseArchitecturesApi,
ContentReleaseComponentsApi,
ContentReleaseFilesApi,
Copy,
DebAptPublication,
DebCopyApi,
DebReleaseArchitecture,
DebReleaseComponent,
DebVerbatimPublication,
DistributionsAptApi,
PublicationsAptApi,
Expand Down Expand Up @@ -116,9 +119,15 @@ def apt_release_api(apt_client):
return ContentReleasesApi(apt_client)


@pytest.fixture(scope="session")
def apt_release_architecture_api(apt_client):
"""Fixture for APT release architecture API."""
return ContentReleaseArchitecturesApi(apt_client)


@pytest.fixture(scope="session")
def apt_release_component_api(apt_client):
"""Fixture for APT release API."""
"""Fixture for APT release component API."""
return ContentReleaseComponentsApi(apt_client)


Expand Down Expand Up @@ -188,6 +197,39 @@ def _deb_publication_factory(repo, **kwargs):

return _deb_publication_factory

@pytest.fixture(scope="class")
def deb_release_component_factory(apt_release_component_api, gen_object_with_cleanup):
"""Fixture that generates deb package with cleanup."""

def _deb_release_component_factory(component, distribution, **kwargs):
"""Create an APT ReleaseComponent.
:returns: The created ReleaseComponent.
"""
release_component_object = DebReleaseComponent(
component=component, distribution=distribution, **kwargs
)
return gen_object_with_cleanup(apt_release_component_api, release_component_object)

return _deb_release_component_factory


@pytest.fixture(scope="class")
def deb_release_architecture_factory(apt_release_architecture_api, gen_object_with_cleanup):
"""Fixture that generates deb package with cleanup."""

def _deb_release_architecture_factory(architecture, distribution, **kwargs):
"""Create an APT ReleaseArchitecture.
:returns: The created ReleaseArchitecture.
"""
release_architecture_object = DebReleaseArchitecture(
architecture=architecture, distribution=distribution, **kwargs
)
return gen_object_with_cleanup(apt_release_architecture_api, release_architecture_object)

return _deb_release_architecture_factory


@pytest.fixture
def deb_publication_by_version_factory(apt_publication_api, gen_object_with_cleanup):
Expand Down

0 comments on commit 0caa062

Please sign in to comment.