diff --git a/CHANGES/6328.feature b/CHANGES/6328.feature new file mode 100644 index 00000000..ffcde311 --- /dev/null +++ b/CHANGES/6328.feature @@ -0,0 +1 @@ +Added history for filesystem exports at ``/exporters/file/filesystem//exports/``. diff --git a/CHANGES/6328.removal b/CHANGES/6328.removal new file mode 100644 index 00000000..6f61877b --- /dev/null +++ b/CHANGES/6328.removal @@ -0,0 +1,5 @@ +The fileystem exporter endpoint has been moved from ``/exporters/file/file/`` to +``/exporters/file/filesystem/`` and the export endpoint is now at POST +``/exporters/file/filesystem//exports/``. Additionally, the table is being dropped and +recreated due to a data structure change in core so users will lose any filesystem exporter data on +upgrade. diff --git a/docs/_scripts/export.sh b/docs/_scripts/export.sh new file mode 100644 index 00000000..364913f8 --- /dev/null +++ b/docs/_scripts/export.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +export EXPORTER_NAME=$(head /dev/urandom | tr -dc a-z | head -c5) +export DEST_DIR="/data" + +echo "Created a new file system exporter $EXPORTER_NAME." +export EXPORTER_HREF=$(http POST $BASE_ADDR/pulp/api/v3/exporters/file/filesystem/ \ + name=$EXPORTER_NAME path=$DEST_DIR | jq -r '.pulp_href') + +export TASK_URL=$(http POST $BASE_ADDR$EXPORTER_HREF'exports/' publication=$PUBLICATION_HREF + | jq -r '.task') + +# Poll the task (here we use a function defined in docs/_scripts/base.sh) +wait_until_task_finished $BASE_ADDR$TASK_URL + +echo "Inspecting export at $DEST_DIR" +ls $DEST_DIR diff --git a/pulp_file/app/migrations/0004_filefilesystemexporter.py b/pulp_file/app/migrations/0004_filefilesystemexporter.py index e72a7282..e194c65b 100644 --- a/pulp_file/app/migrations/0004_filefilesystemexporter.py +++ b/pulp_file/app/migrations/0004_filefilesystemexporter.py @@ -1,7 +1,9 @@ # Generated by Django 2.2.6 on 2019-11-04 20:00 -from django.db import migrations, models -import django.db.models.deletion +from django.db import migrations + +# Note: This migration was throwing exceptions so all of the fields/etc were removed. +# The table gets deleted later in 0006_delete_filefilesystemexporter.py. class Migration(migrations.Migration): @@ -14,12 +16,6 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( name='FileFileSystemExporter', - fields=[ - ('filesystemexporter_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='file_filefilesystemexporter', serialize=False, to='core.FileSystemExporter')), - ], - options={ - 'default_related_name': '%(app_label)s_%(model_name)s', - }, - bases=('core.filesystemexporter',), + fields=[], ), ] diff --git a/pulp_file/app/migrations/0006_delete_filefilesystemexporter.py b/pulp_file/app/migrations/0006_delete_filefilesystemexporter.py new file mode 100644 index 00000000..9b613e2b --- /dev/null +++ b/pulp_file/app/migrations/0006_delete_filefilesystemexporter.py @@ -0,0 +1,16 @@ +# Generated by Django 2.2.6 on 2020-03-13 16:18 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('file', '0005_filerepository'), + ] + + operations = [ + migrations.DeleteModel( + name='FileFileSystemExporter', + ), + ] diff --git a/pulp_file/app/migrations/0007_filefilesystemexporter.py b/pulp_file/app/migrations/0007_filefilesystemexporter.py new file mode 100644 index 00000000..44d8dcd4 --- /dev/null +++ b/pulp_file/app/migrations/0007_filefilesystemexporter.py @@ -0,0 +1,26 @@ +# Generated by Django 2.2.6 on 2020-03-17 13:07 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0023_change_exporter_models'), + ('file', '0006_delete_filefilesystemexporter'), + ] + + operations = [ + migrations.CreateModel( + name='FileFileSystemExporter', + fields=[ + ('exporter_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='file_filefilesystemexporter', serialize=False, to='core.Exporter')), + ('path', models.TextField()), + ], + options={ + 'default_related_name': '%(app_label)s_%(model_name)s', + }, + bases=('core.exporter',), + ), + ] diff --git a/pulp_file/app/models.py b/pulp_file/app/models.py index a5cd9d87..988b2bc1 100644 --- a/pulp_file/app/models.py +++ b/pulp_file/app/models.py @@ -4,7 +4,7 @@ from pulpcore.plugin.models import ( Content, - FileSystemPublicationExporter, + FileSystemExporter, Publication, PublicationDistribution, Remote, @@ -106,12 +106,12 @@ class Meta: default_related_name = "%(app_label)s_%(model_name)s" -class FileFileSystemExporter(FileSystemPublicationExporter): +class FileFileSystemExporter(FileSystemExporter): """ File system exporter for 'file' content. """ - TYPE = "file" + TYPE = "filesystem" class Meta: default_related_name = "%(app_label)s_%(model_name)s" diff --git a/pulp_file/app/tasks/__init__.py b/pulp_file/app/tasks/__init__.py index e19750cb..c0972874 100644 --- a/pulp_file/app/tasks/__init__.py +++ b/pulp_file/app/tasks/__init__.py @@ -1,3 +1,2 @@ -from .exporting import file_export # noqa from .publishing import publish # noqa from .synchronizing import synchronize # noqa diff --git a/pulp_file/app/tasks/exporting.py b/pulp_file/app/tasks/exporting.py deleted file mode 100644 index 3ffc042a..00000000 --- a/pulp_file/app/tasks/exporting.py +++ /dev/null @@ -1,31 +0,0 @@ -import logging - -from gettext import gettext as _ - -from pulp_file.app.models import FileFileSystemExporter, FilePublication - - -log = logging.getLogger(__name__) - - -def file_export(exporter_pk, publication_pk): - """ - Export a Publication to the file system. - - Args: - exporter_pk (str): FileFileSystemExporter pk - publication_pk (str): FilePublication pk - - """ - exporter = FileFileSystemExporter.objects.get(pk=exporter_pk) - publication = FilePublication.objects.get(pk=publication_pk) - - log.info( - _( - "Exporting: file_system_exporter={exporter}, publication={publication}, path=path" - ).format(exporter=exporter.name, publication=publication.pk, path=exporter.path) - ) - - exporter.export(publication) - - log.info(_("Publication: {publication} exported").format(publication=publication.pk)) diff --git a/pulp_file/app/viewsets.py b/pulp_file/app/viewsets.py index 3708fc39..44c2b12d 100644 --- a/pulp_file/app/viewsets.py +++ b/pulp_file/app/viewsets.py @@ -1,3 +1,4 @@ +from django.http import Http404 from django_filters import CharFilter from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import action @@ -8,11 +9,12 @@ PublicationExportSerializer, RepositorySyncURLSerializer, ) -from pulpcore.plugin.tasking import enqueue_with_reservation +from pulpcore.plugin.tasking import enqueue_with_reservation, fs_publication_export from pulpcore.plugin.viewsets import ( BaseDistributionViewSet, ContentFilter, - FileSystemExporterViewSet, + ExporterViewSet, + ExportViewSet, OperationPostponedResponse, PublicationViewSet, RemoteViewSet, @@ -178,7 +180,7 @@ class FileDistributionViewSet(BaseDistributionViewSet): serializer_class = FileDistributionSerializer -class FileFileSystemExporterViewSet(FileSystemExporterViewSet): +class FileFileSystemExporterViewSet(ExporterViewSet): """ FileSystemExporters export content from a publication to a path on the file system. @@ -186,27 +188,39 @@ class FileFileSystemExporterViewSet(FileSystemExporterViewSet): compatibility is not guaranteed. """ - endpoint_name = "file" + endpoint_name = "filesystem" queryset = FileFileSystemExporter.objects.all() serializer_class = FileFileSystemExporterSerializer + +class FileFileSystemExportViewSet(ExportViewSet): + """ + FileSystemExports provide a history of previous exports. + """ + + parent_viewset = FileFileSystemExporterViewSet + @swagger_auto_schema( + request_body=PublicationExportSerializer, operation_description="Trigger an asynchronous task to export a file publication.", responses={202: AsyncOperationResponseSerializer}, ) - @action(detail=True, methods=["post"], serializer_class=PublicationExportSerializer) - def export(self, request, pk): + def create(self, request, exporter_pk): """ Export a publication to the file system. The ``repository`` field has to be provided. """ - exporter = self.get_object() + try: + exporter = FileFileSystemExporter.objects.get(pk=exporter_pk) + except FileFileSystemExporter.DoesNotExist: + raise Http404 + serializer = PublicationExportSerializer(data=request.data, context={"request": request}) serializer.is_valid(raise_exception=True) publication = serializer.validated_data.get("publication") result = enqueue_with_reservation( - tasks.file_export, + fs_publication_export, [publication, exporter], kwargs={"exporter_pk": exporter.pk, "publication_pk": publication.pk}, )