Repository: pulp/pulpcore

Issue:
Facilitate removal of signing services in functional tests
Allowing testers to call a function for removing a signing service would be useful. Currently, we remove the signing service in the following manner: https://github.com/pulp/pulpcore/blob/6287ef36d2fc20ca316e1e889296fc55072ff477/pulpcore/tests/functional/gpg_ascii_armor_signing_service.py#L158-L168

Having a fixture `remove_signing_service(service_name, service_type)` accessible globally would suffice.


Discussion:
Actually, i think we should have a management command for admins too.
Also reading the original issue, i think we should not provide a `remove_signing_service` fixture per se, be call the management command in the cleanup code of the signing service fixture.

Resolving patch:
diff --git a/pulpcore/app/management/commands/add-signing-service.py b/pulpcore/app/management/commands/add-signing-service.py
--- a/pulpcore/app/management/commands/add-signing-service.py
+++ b/pulpcore/app/management/commands/add-signing-service.py
@@ -11,6 +11,8 @@
 from django.apps import apps
 from django.db.utils import IntegrityError
 
+from pulpcore.app.models.content import SigningService as BaseSigningService
+
 
 class Command(BaseCommand):
     """
@@ -65,6 +67,12 @@ def handle(self, *args, **options):
             SigningService = apps.get_model(app_label, service_class)
         except LookupError as e:
             raise CommandError(str(e))
+        if not issubclass(SigningService, BaseSigningService):
+            raise CommandError(
+                _("Class '{}' is not a subclass of the base 'core:SigningService' class.").format(
+                    options["class"]
+                )
+            )
 
         gpg = gnupg.GPG(gnupghome=options["gnupghome"], keyring=options["keyring"])
 
diff --git a/pulpcore/app/management/commands/remove-signing-service.py b/pulpcore/app/management/commands/remove-signing-service.py
new file mode 100644
--- /dev/null
+++ b/pulpcore/app/management/commands/remove-signing-service.py
@@ -0,0 +1,66 @@
+from gettext import gettext as _
+
+from django.core.management import BaseCommand, CommandError
+
+from django.core.exceptions import ObjectDoesNotExist
+
+from django.apps import apps
+from django.db.utils import IntegrityError
+
+from pulpcore.app.models.content import SigningService as BaseSigningService
+
+
+class Command(BaseCommand):
+    """
+    Django management command for removing a signing service.
+
+    This command is in tech-preview.
+    """
+
+    help = "Removes an existing AsciiArmoredDetachedSigningService. [tech-preview]"
+
+    def add_arguments(self, parser):
+        parser.add_argument(
+            "name",
+            help=_("Name that the signing_service has in the database."),
+        )
+        parser.add_argument(
+            "--class",
+            default="core:AsciiArmoredDetachedSigningService",
+            required=False,
+            help=_("Signing service class prefixed by the app label separated by a colon."),
+        )
+
+    def handle(self, *args, **options):
+        name = options["name"]
+        if ":" not in options["class"]:
+            raise CommandError(_("The signing service class was not provided in a proper format."))
+        app_label, service_class = options["class"].split(":")
+
+        try:
+            SigningService = apps.get_model(app_label, service_class)
+        except LookupError as e:
+            raise CommandError(str(e))
+        if not issubclass(SigningService, BaseSigningService):
+            raise CommandError(
+                _("Class '{}' is not a subclass of the base 'core:SigningService' class.").format(
+                    options["class"]
+                )
+            )
+
+        try:
+            SigningService.objects.get(name=name).delete()
+        except IntegrityError:
+            raise CommandError(
+                _("Signing service '{}' could not be removed because it's still in use.").format(
+                    name
+                )
+            )
+        except ObjectDoesNotExist:
+            raise CommandError(
+                _("Signing service '{}' of class '{}' does not exists.").format(
+                    name, options["class"]
+                )
+            )
+        else:
+            self.stdout.write(_("Signing service '{}' has been successfully removed.").format(name))


Test patch:
diff --git a/pulpcore/tests/functional/api/test_signing_service.py b/pulpcore/tests/functional/api/test_signing_service.py
new file mode 100644
--- /dev/null
+++ b/pulpcore/tests/functional/api/test_signing_service.py
@@ -0,0 +1,7 @@
+import pytest
+
+
+@pytest.mark.parallel
+def test_crud_signing_service(ascii_armored_detached_signing_service):
+    service = ascii_armored_detached_signing_service
+    assert "/api/v3/signing-services/" in service.pulp_href
diff --git a/pulpcore/tests/functional/gpg_ascii_armor_signing_service.py b/pulpcore/tests/functional/gpg_ascii_armor_signing_service.py
--- a/pulpcore/tests/functional/gpg_ascii_armor_signing_service.py
+++ b/pulpcore/tests/functional/gpg_ascii_armor_signing_service.py
@@ -157,9 +157,10 @@ def _ascii_armored_detached_signing_service_name(
 
     cmd = (
         "pulpcore-manager",
-        "shell",
-        "-c",
-        f'from pulpcore.app.models import AsciiArmoredDetachedSigningService;print(AsciiArmoredDetachedSigningService.objects.get(name="{service_name}").delete())',  # noqa: E501
+        "remove-signing-service",
+        service_name,
+        "--class",
+        "core:AsciiArmoredDetachedSigningService",
     )
     subprocess.run(
         cmd,