From 8fdb145042da87bd6cf90e827de7a48764d6ac5f Mon Sep 17 00:00:00 2001 From: Jitka Halova Date: Thu, 7 May 2026 18:50:31 +0200 Subject: [PATCH] Add package blocklist to Python repos closes #1391 Assisted By: Claude Opus 4.6 --- CHANGES/1391.feature | 1 + CHANGES/pulp-glue/1391.feature | 1 + pulp-glue/src/pulp_glue/python/context.py | 23 +++++++ src/pulpcore/cli/python/repository.py | 71 +++++++++++++++++++++ tests/scripts/pulp_python/test_blocklist.sh | 36 +++++++++++ 5 files changed, 132 insertions(+) create mode 100644 CHANGES/1391.feature create mode 100644 CHANGES/pulp-glue/1391.feature create mode 100755 tests/scripts/pulp_python/test_blocklist.sh diff --git a/CHANGES/1391.feature b/CHANGES/1391.feature new file mode 100644 index 000000000..5bd772cff --- /dev/null +++ b/CHANGES/1391.feature @@ -0,0 +1 @@ +Added `blocklist` subcommand group to `pulp python repository` for managing blocklist entries (add, list, remove, show). diff --git a/CHANGES/pulp-glue/1391.feature b/CHANGES/pulp-glue/1391.feature new file mode 100644 index 000000000..261eaf03a --- /dev/null +++ b/CHANGES/pulp-glue/1391.feature @@ -0,0 +1 @@ +Added `PulpPythonRepositoryBlocklistEntryContext` for `pulp_python>=3.29.0`. diff --git a/pulp-glue/src/pulp_glue/python/context.py b/pulp-glue/src/pulp_glue/python/context.py index 5b669263d..c93be2f69 100644 --- a/pulp-glue/src/pulp_glue/python/context.py +++ b/pulp-glue/src/pulp_glue/python/context.py @@ -4,7 +4,9 @@ EntityDefinition, PluginRequirement, PulpContentContext, + PulpContext, PulpDistributionContext, + PulpEntityContext, PulpPublicationContext, PulpRemoteContext, PulpRepositoryContext, @@ -165,3 +167,24 @@ def preprocess_entity(self, body: EntityDefinition, partial: bool = False) -> En def repair_metadata(self) -> t.Any: self.needs_capability("repair_metadata") return self.call("repair_metadata", parameters={self.HREF: self.pulp_href}) + + +class PulpPythonRepositoryBlocklistEntryContext(PulpEntityContext): + ENTITY = _("blocklist entry") + ENTITIES = _("blocklist entries") + HREF = "python_python_python_blocklist_entry_href" + ID_PREFIX = "repositories_python_python_blocklist_entries" + NEEDS_PLUGINS = [PluginRequirement("python", specifier=">=3.29.0")] + repository_ctx: PulpPythonRepositoryContext + + def __init__( + self, + pulp_ctx: PulpContext, + repository_ctx: PulpPythonRepositoryContext, + ) -> None: + super().__init__(pulp_ctx) + self.repository_ctx = repository_ctx + + @property + def scope(self) -> dict[str, t.Any]: + return {self.repository_ctx.HREF: self.repository_ctx.pulp_href} diff --git a/src/pulpcore/cli/python/repository.py b/src/pulpcore/cli/python/repository.py index 543543d97..3c2ec78b2 100644 --- a/src/pulpcore/cli/python/repository.py +++ b/src/pulpcore/cli/python/repository.py @@ -6,6 +6,7 @@ from pulp_glue.common.context import ( EntityFieldDefinition, PluginRequirement, + PulpEntityContext, PulpRemoteContext, PulpRepositoryContext, ) @@ -14,6 +15,7 @@ PulpPythonContentContext, PulpPythonProvenanceContext, PulpPythonRemoteContext, + PulpPythonRepositoryBlocklistEntryContext, PulpPythonRepositoryContext, ) @@ -28,6 +30,7 @@ list_command, lookup_callback, name_option, + pass_entity_context, pass_pulp_context, pass_repository_context, pulp_group, @@ -152,6 +155,74 @@ def repository() -> None: ) +@repository.group(needs_plugins=[PluginRequirement("python", specifier=">=3.29.0")]) +@pass_repository_context +@pass_pulp_context +@click.pass_context +def blocklist( + ctx: click.Context, + pulp_ctx: PulpCLIContext, + repository_ctx: PulpRepositoryContext, + /, +) -> None: + """ + Manage blocklist entries for a Python repository. + """ + assert isinstance(repository_ctx, PulpPythonRepositoryContext) + ctx.obj = PulpPythonRepositoryBlocklistEntryContext(pulp_ctx, repository_ctx) + + +blocklist.add_command(list_command(decorators=nested_lookup_options)) +blocklist.add_command(show_command(decorators=nested_lookup_options + [href_option])) +blocklist.add_command( + destroy_command(name="remove", decorators=nested_lookup_options + [href_option]) +) + + +@blocklist.command(name="add") +@repository_href_option +@repository_lookup_option +@click.option( + "--name", + "pkg_name", + default=None, + help=_("Package name to block. Required when 'filename' is not provided."), +) +@click.option( + "--version", + "pkg_version", + default=None, + help=_("Exact version to block. Only used when 'name' is set."), +) +@click.option( + "--filename", + "pkg_filename", + default=None, + help=_("Exact filename to block. Required when 'name' is not provided."), +) +@pass_entity_context +@pass_pulp_context +def blocklist_add( + pulp_ctx: PulpCLIContext, + entity_ctx: PulpEntityContext, + /, + pkg_name: str | None, + pkg_version: str | None, + pkg_filename: str | None, +) -> None: + """Add a blocklist entry to the repository.""" + assert isinstance(entity_ctx, PulpPythonRepositoryBlocklistEntryContext) + body: dict[str, t.Any] = {} + if pkg_name: + body["name"] = pkg_name + if pkg_version: + body["version"] = pkg_version + if pkg_filename: + body["filename"] = pkg_filename + result = entity_ctx.create(body=body) + pulp_ctx.output_result(result) + + @repository.command() @name_option @href_option diff --git a/tests/scripts/pulp_python/test_blocklist.sh b/tests/scripts/pulp_python/test_blocklist.sh new file mode 100755 index 000000000..6b24d0330 --- /dev/null +++ b/tests/scripts/pulp_python/test_blocklist.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -eu +# shellcheck source=tests/scripts/config.source +. "$(dirname "$(dirname "$(realpath "$0")")")"/config.source + +pulp debug has-plugin --name "python" --specifier ">=3.29.0" || exit 23 + +cleanup() { + pulp python repository destroy --name "cli_test_python_blocklist" || true +} +trap cleanup EXIT + +expect_succ pulp python repository create --name "cli_test_python_blocklist" + +# Test adding a blocklist entry by package name + version +expect_succ pulp python repository blocklist add --repository "cli_test_python_blocklist" --name "pkg" --version "1.0" +test "$(echo "$OUTPUT" | jq -r '.name')" = "pkg" +test "$(echo "$OUTPUT" | jq -r '.version')" = "1.0" +test "$(echo "$OUTPUT" | jq -r '.filename')" = "null" +ENTRY_HREF="$(echo "$OUTPUT" | jq -r '.pulp_href')" + +# Test listing blocklist entries +expect_succ pulp python repository blocklist list --repository "cli_test_python_blocklist" +expect_succ test "$(echo "$OUTPUT" | jq -r '.|length')" = "1" + +# Test showing a specific blocklist entry +expect_succ pulp python repository blocklist show --repository "cli_test_python_blocklist" --href "$ENTRY_HREF" +expect_succ test "$(echo "$OUTPUT" | jq -r '.name')" = "pkg" + +# Test removing a blocklist entry +expect_succ pulp python repository blocklist remove --repository "cli_test_python_blocklist" --href "$ENTRY_HREF" +expect_succ pulp python repository blocklist list --repository "cli_test_python_blocklist" +expect_succ test "$(echo "$OUTPUT" | jq -r '.|length')" = "0" + +expect_succ pulp python repository destroy --name "cli_test_python_blocklist"