From 4740fbdedbb505a9c354f1b96f33cb0656bc737e Mon Sep 17 00:00:00 2001
From: Matthias Dellweg <mdellweg@redhat.com>
Date: Thu, 3 Feb 2022 12:48:53 +0100
Subject: [PATCH] Add needs_capability to EntityContext

This also makes use of it for tagging and untagging in container
repositories as well as for all roles related subcommands.

fixes #465
---
 .ci/scripts/validate_commit_message.py | 12 +++++++++++-
 CHANGES/465.devel                      |  1 +
 pulpcore/cli/common/context.py         | 15 +++++++++++++++
 pulpcore/cli/container/context.py      | 24 ++++++++++++++++++++++--
 pulpcore/cli/container/repository.py   | 23 +++++------------------
 pulpcore/cli/core/content_guard.py     |  4 +---
 pulpcore/cli/core/context.py           |  3 +++
 7 files changed, 58 insertions(+), 24 deletions(-)
 create mode 100644 CHANGES/465.devel

diff --git a/.ci/scripts/validate_commit_message.py b/.ci/scripts/validate_commit_message.py
index 627adc9aa..660a085e8 100644
--- a/.ci/scripts/validate_commit_message.py
+++ b/.ci/scripts/validate_commit_message.py
@@ -8,7 +8,17 @@
 
 KEYWORDS = ["fixes", "closes"]
 NO_ISSUE = "[noissue]"
-CHANGELOG_EXTS = [".feature", ".bugfix", ".doc", ".removal", ".misc", ".deprecation"]
+# TODO (On a rainy afternoon) Fetch the extensions from pyproject.toml
+CHANGELOG_EXTS = [
+    ".feature",
+    ".bugfix",
+    ".doc",
+    ".removal",
+    ".misc",
+    ".deprecation",
+    ".translation",
+    ".devel",
+]
 
 sha = sys.argv[1]
 project = "pulp-cli"
diff --git a/CHANGES/465.devel b/CHANGES/465.devel
new file mode 100644
index 000000000..1ee97edbf
--- /dev/null
+++ b/CHANGES/465.devel
@@ -0,0 +1 @@
+Added `needs_capability` to `EntityContext` so context member function can require capabilities.
diff --git a/pulpcore/cli/common/context.py b/pulpcore/cli/common/context.py
index ecd719547..a47c0cc35 100644
--- a/pulpcore/cli/common/context.py
+++ b/pulpcore/cli/common/context.py
@@ -551,12 +551,15 @@ def show_label(self, href: str, key: str) -> Any:
             raise click.ClickException(_("Could not find label with key '{key}'.").format(key=key))
 
     def my_permissions(self) -> Any:
+        self.needs_capability("roles")
         return self.call("my_permissions", parameters={self.HREF: self.pulp_href})
 
     def list_roles(self) -> Any:
+        self.needs_capability("roles")
         return self.call("list_roles", parameters={self.HREF: self.pulp_href})
 
     def add_role(self, role: str, users: List[str], groups: List[str]) -> Any:
+        self.needs_capability("roles")
         return self.call(
             "add_role",
             parameters={self.HREF: self.pulp_href},
@@ -564,6 +567,7 @@ def add_role(self, role: str, users: List[str], groups: List[str]) -> Any:
         )
 
     def remove_role(self, role: str, users: List[str], groups: List[str]) -> Any:
+        self.needs_capability("roles")
         return self.call(
             "remove_role",
             parameters={self.HREF: self.pulp_href},
@@ -575,6 +579,17 @@ def capable(self, capability: str) -> bool:
             (self.pulp_ctx.has_plugin(pr) for pr in self.CAPABILITIES[capability])
         )
 
+    def needs_capability(self, capability: str) -> None:
+        if capability in self.CAPABILITIES:
+            for pr in self.CAPABILITIES[capability]:
+                self.pulp_ctx.needs_plugin(pr)
+        else:
+            raise click.ClickException(
+                _("Capability '{capability}' needed on '{entity}' for this command.").format(
+                    capability=capability, entity=self.ENTITY
+                )
+            )
+
 
 class PulpRemoteContext(PulpEntityContext):
     """
diff --git a/pulpcore/cli/container/context.py b/pulpcore/cli/container/context.py
index ba4c07f67..1691837c7 100644
--- a/pulpcore/cli/container/context.py
+++ b/pulpcore/cli/container/context.py
@@ -1,3 +1,5 @@
+from typing import Any
+
 from pulpcore.cli.common.context import (
     EntityDefinition,
     PluginRequirement,
@@ -76,7 +78,25 @@ class PulpContainerPushRepositoryVersionContext(PulpRepositoryVersionContext):
     ID_PREFIX = "repositories_container_container_push_versions"
 
 
-class PulpContainerRepositoryContext(PulpRepositoryContext):
+class PulpContainerBaseRepositoryContext(PulpRepositoryContext):
+    def tag(self, tag: str, digest: str) -> Any:
+        self.needs_capability("tag")
+        return self.call(
+            "tag",
+            parameters={self.HREF: self.pulp_href},
+            body={"tag": tag, "digest": digest},
+        )
+
+    def untag(self, tag: str) -> Any:
+        self.needs_capability("tag")
+        return self.call(
+            "untag",
+            parameters={self.HREF: self.pulp_href},
+            body={"tag": tag},
+        )
+
+
+class PulpContainerRepositoryContext(PulpContainerBaseRepositoryContext):
     HREF = "container_container_repository_href"
     ID_PREFIX = "repositories_container_container"
     VERSION_CONTEXT = PulpContainerRepositoryVersionContext
@@ -87,7 +107,7 @@ class PulpContainerRepositoryContext(PulpRepositoryContext):
     }
 
 
-class PulpContainerPushRepositoryContext(PulpRepositoryContext):
+class PulpContainerPushRepositoryContext(PulpContainerBaseRepositoryContext):
     HREF = "container_container_push_repository_href"
     ID_PREFIX = "repositories_container_container_push"
     VERSION_CONTEXT = PulpContainerPushRepositoryVersionContext
diff --git a/pulpcore/cli/container/repository.py b/pulpcore/cli/container/repository.py
index 4b2e89f64..498359024 100644
--- a/pulpcore/cli/container/repository.py
+++ b/pulpcore/cli/container/repository.py
@@ -30,6 +30,7 @@
 )
 from pulpcore.cli.common.i18n import get_translation
 from pulpcore.cli.container.context import (
+    PulpContainerBaseRepositoryContext,
     PulpContainerPushRepositoryContext,
     PulpContainerRemoteContext,
     PulpContainerRepositoryContext,
@@ -142,24 +143,17 @@ def sync(
 @click.option("--digest", help=_("SHA256 digest of the Manifest file"), required=True)
 @pass_repository_context
 def add_tag(
-    repository_ctx: PulpRepositoryContext,
+    repository_ctx: PulpContainerBaseRepositoryContext,
     digest: str,
     tag: str,
 ) -> None:
-    if not repository_ctx.capable("tag"):
-        raise click.ClickException(_("pulp_container 2.3.0 is required to tag images"))
-
     digest = digest.strip()
     if not digest.startswith("sha256:"):
         digest = f"sha256:{digest}"
     if len(digest) != 71:  # len("sha256:") + 64
         raise click.ClickException("Improper SHA256, please provide a valid 64 digit digest.")
 
-    repository_ctx.call(
-        "tag",
-        parameters={repository_ctx.HREF: repository_ctx.pulp_href},
-        body={"tag": tag, "digest": digest},
-    )
+    repository_ctx.tag(tag, digest)
 
 
 @repository.command(name="untag")
@@ -167,12 +161,5 @@ def add_tag(
 @href_option
 @click.option("--tag", help=_("Name of tag to remove"), required=True, callback=_tag_callback)
 @pass_repository_context
-def remove_tag(repository_ctx: PulpRepositoryContext, tag: str) -> None:
-    if not repository_ctx.capable("tag"):
-        raise click.ClickException(_("pulp_container 2.3.0 is required to untag images"))
-
-    repository_ctx.call(
-        "untag",
-        parameters={repository_ctx.HREF: repository_ctx.pulp_href},
-        body={"tag": tag},
-    )
+def remove_tag(repository_ctx: PulpContainerBaseRepositoryContext, tag: str) -> None:
+    repository_ctx.untag(tag)
diff --git a/pulpcore/cli/core/content_guard.py b/pulpcore/cli/core/content_guard.py
index f3f590292..df05b09a4 100644
--- a/pulpcore/cli/core/content_guard.py
+++ b/pulpcore/cli/core/content_guard.py
@@ -52,9 +52,7 @@ def rbac(ctx: click.Context, pulp_ctx: PulpContext) -> None:
 rbac.add_command(show_command(decorators=lookup_options))
 rbac.add_command(update_command(decorators=lookup_options))
 rbac.add_command(destroy_command(decorators=lookup_options))
-rbac.add_command(
-    role_command(decorators=lookup_options, needs_plugins=[PluginRequirement("core", min="3.17")])
-)
+rbac.add_command(role_command(decorators=lookup_options))
 
 
 @rbac.command()
diff --git a/pulpcore/cli/core/context.py b/pulpcore/cli/core/context.py
index 0ae5325b7..a3497f475 100644
--- a/pulpcore/cli/core/context.py
+++ b/pulpcore/cli/core/context.py
@@ -117,6 +117,7 @@ class PulpGroupContext(PulpEntityContext):
     # Handled by a workaround
     # HREF = "group_href"
     ID_PREFIX = "groups"
+    CAPABILITIES = {"roles": [PluginRequirement("core", "3.17.0")]}
 
     @property
     def HREF(self) -> str:  # type:ignore
@@ -245,6 +246,7 @@ class PulpRbacContentGuardContext(PulpContentGuardContext):
     HREF = "r_b_a_c_content_guard_href"
     ID_PREFIX = "contentguards_core_rbac"
     DOWNLOAD_ROLE: ClassVar[str] = "core.rbaccontentguard_downloader"
+    CAPABILITIES = {"roles": [PluginRequirement("core", "3.17.0")]}
 
     def assign(self, href: str, users: Optional[List[str]], groups: Optional[List[str]]) -> Any:
         if self.pulp_ctx.has_plugin(PluginRequirement("core", min="3.17.0.dev")):
@@ -285,6 +287,7 @@ class PulpTaskContext(PulpEntityContext):
     ENTITIES = _("tasks")
     HREF = "task_href"
     ID_PREFIX = "tasks"
+    CAPABILITIES = {"roles": [PluginRequirement("core", "3.17.0")]}
 
     resource_context: Optional[PulpEntityContext] = None