From 0a58401d81e056ce76114022d69007f6b423d1cd Mon Sep 17 00:00:00 2001 From: Gerrod Ubben Date: Thu, 9 Jul 2026 16:40:10 -0400 Subject: [PATCH 1/3] Add --distribution filter to container content list. Resolve the served repository version from a distribution so users can list tags and other content for an image without manual lookup steps. Co-authored-by: Cursor --- CHANGES/+distribution-content-filter.feature | 1 + src/pulpcore/cli/container/content.py | 68 +++++++++++++++++++- tests/scripts/pulp_container/test_content.sh | 14 ++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 CHANGES/+distribution-content-filter.feature diff --git a/CHANGES/+distribution-content-filter.feature b/CHANGES/+distribution-content-filter.feature new file mode 100644 index 000000000..f64b3bde6 --- /dev/null +++ b/CHANGES/+distribution-content-filter.feature @@ -0,0 +1 @@ +Added `--distribution` filter to `pulp container content list` to resolve the served repository version and list tags (or other content) for that image. diff --git a/src/pulpcore/cli/container/content.py b/src/pulpcore/cli/container/content.py index 4abe222da..f54ecdde7 100644 --- a/src/pulpcore/cli/container/content.py +++ b/src/pulpcore/cli/container/content.py @@ -3,21 +3,33 @@ import click -from pulp_glue.common.context import PluginRequirement, PulpContentContext +from pulp_glue.common.context import ( + EntityDefinition, + PluginRequirement, + PulpContentContext, + PulpDistributionContext, + PulpRepositoryContext, +) from pulp_glue.container.context import ( PulpContainerBlobContext, + PulpContainerDistributionContext, PulpContainerManifestContext, + PulpContainerPushRepositoryContext, + PulpContainerRepositoryContext, PulpContainerTagContext, ) from pulp_cli.generic import ( + PulpCLIContext, content_filter_options, href_option, label_command, list_command, option_group, + option_processor, pulp_group, pulp_option, + resource_option, show_command, type_option, ) @@ -34,6 +46,44 @@ def _content_callback(ctx: click.Context, value: dict[str, t.Any]) -> None: entity_ctx.entity = value +def _repository_version_from_distribution( + pulp_ctx: PulpCLIContext, distribution: EntityDefinition +) -> str: + if repository_version := distribution.get("repository_version"): + return t.cast(str, repository_version) + + repository_href = distribution.get("repository") + if not repository_href: + raise click.ClickException( + _( + "Distribution '{name}' is not associated with a repository or repository version." + ).format(name=distribution.get("name", "")) + ) + if "/container-push/" in repository_href: + repo_ctx: PulpRepositoryContext = PulpContainerPushRepositoryContext( + pulp_ctx, pulp_href=repository_href + ) + else: + repo_ctx = PulpContainerRepositoryContext(pulp_ctx, pulp_href=repository_href) + + return t.cast(str, repo_ctx.entity["latest_version_href"]) + + +def _process_distribution_filter(ctx: click.Context) -> None: + distribution = ctx.params.pop("distribution", None) + if not distribution: + return + if ctx.params.get("repository_version"): + raise click.UsageError(_("Cannot use --distribution together with --repository-version.")) + + assert isinstance(distribution, PulpContainerDistributionContext) + pulp_ctx = ctx.find_object(PulpCLIContext) + assert pulp_ctx is not None + ctx.params["repository_version"] = _repository_version_from_distribution( + pulp_ctx, distribution.entity + ) + + @pulp_group() @type_option( choices={ @@ -47,6 +97,20 @@ def content() -> None: pass +distribution_filter_option = resource_option( + "--distribution", + default_plugin="container", + default_type="container", + context_table={ + "container:container": PulpContainerDistributionContext, + }, + href_pattern=PulpDistributionContext.HREF_PATTERN, + help=_( + "Filter {entities} by the repository version served by this distribution (name or href)." + ), +) + + list_options = [ pulp_option( "--media-type", @@ -86,6 +150,8 @@ def content() -> None: allowed_with_contexts=(PulpContainerManifestContext,), ), *content_filter_options, + distribution_filter_option, + option_processor(callback=_process_distribution_filter), ] lookup_options = [ diff --git a/tests/scripts/pulp_container/test_content.sh b/tests/scripts/pulp_container/test_content.sh index a59f79f0d..8be0d6907 100755 --- a/tests/scripts/pulp_container/test_content.sh +++ b/tests/scripts/pulp_container/test_content.sh @@ -7,6 +7,7 @@ set -eu pulp debug has-plugin --name "container" || exit 23 cleanup() { + pulp container distribution destroy --name "cli_test_container_content_distro" || true pulp container repository destroy --name "cli_test_container_content_repository" || true pulp container remote destroy --name "cli_test_container_content_remote" || true } @@ -16,6 +17,9 @@ trap cleanup EXIT pulp container remote create --name "cli_test_container_content_remote" --url "$CONTAINER_REMOTE_URL" --upstream-name "$CONTAINER_IMAGE" pulp container repository create --name "cli_test_container_content_repository" pulp container repository sync --name "cli_test_container_content_repository" --remote "cli_test_container_content_remote" +pulp container distribution create --name "cli_test_container_content_distro" \ + --base-path "cli_test_container_content_distro" \ + --repository "cli_test_container_content_repository" # Check each content list expect_succ pulp container content -t blob list @@ -69,3 +73,13 @@ test "$(echo "$OUTPUT" | jq -r length)" -ge "1" expect_succ pulp container content -t blob list --digest "$blob_digest" test "$(echo "$OUTPUT" | jq -r length)" -ge "1" + +# Filter tags by repository version and by distribution (resolves to repository version) +repo_ver_href="$(pulp container repository show --name "cli_test_container_content_repository" | jq -r .latest_version_href)" +expect_succ pulp container content -t tag list --repository-version "$repo_ver_href" +test "$(echo "$OUTPUT" | jq -r length)" -ge "1" +expect_succ pulp container content -t tag list --distribution "cli_test_container_content_distro" +test "$(echo "$OUTPUT" | jq -r length)" -ge "1" +expect_fail pulp container content -t tag list \ + --distribution "cli_test_container_content_distro" \ + --repository-version "$repo_ver_href" From e745efa42a5db9931c50217be84a480ee7702504 Mon Sep 17 00:00:00 2001 From: Gerrod Ubben Date: Thu, 9 Jul 2026 18:03:00 -0400 Subject: [PATCH 2/3] Await repository field clears in container distribution update. Switching between repository and repository_version must complete the clearing update before applying the new value to avoid API validation errors under non-blocking updates. Co-authored-by: Cursor --- src/pulpcore/cli/container/distribution.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/pulpcore/cli/container/distribution.py b/src/pulpcore/cli/container/distribution.py index cb983da6b..ecd566398 100644 --- a/src/pulpcore/cli/container/distribution.py +++ b/src/pulpcore/cli/container/distribution.py @@ -127,18 +127,16 @@ def update( repository = t.cast(PulpEntityContext, repository) if version is not None: if distribution["repository"]: - distribution_ctx.update(body={"repository": ""}, non_blocking=non_blocking) + distribution_ctx.update(body={"repository": ""}, non_blocking=False) body["repository_version"] = f"{repository.pulp_href}versions/{version}/" else: if distribution["repository_version"]: - distribution_ctx.update( - body={"repository_version": ""}, non_blocking=non_blocking - ) + distribution_ctx.update(body={"repository_version": ""}, non_blocking=False) body["repository"] = repository.pulp_href elif version is not None: # keep current repository, change version if distribution["repository"]: - distribution_ctx.update(body={"repository": ""}, non_blocking=non_blocking) + distribution_ctx.update(body={"repository": ""}, non_blocking=False) body["repository_version"] = f"{distribution['repository']}versions/{version}/" elif distribution["repository_version"]: # 'dummy' vars are to get us around a mypy/1.2 complaint about '_' From 3637f5b13a4b682df4b966c02b5618f815ddc1ca Mon Sep 17 00:00:00 2001 From: Gerrod Ubben Date: Thu, 9 Jul 2026 18:05:47 -0400 Subject: [PATCH 3/3] Remove unused non_blocking variable from distribution update. Co-authored-by: Cursor --- src/pulpcore/cli/container/distribution.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/pulpcore/cli/container/distribution.py b/src/pulpcore/cli/container/distribution.py index ecd566398..922a58792 100644 --- a/src/pulpcore/cli/container/distribution.py +++ b/src/pulpcore/cli/container/distribution.py @@ -5,7 +5,6 @@ from pulp_glue.common.context import ( EntityDefinition, EntityFieldDefinition, - PluginRequirement, PulpEntityContext, PulpRepositoryContext, ) @@ -104,9 +103,6 @@ def update( distribution: EntityDefinition = distribution_ctx.entity body: EntityDefinition = {} - non_blocking = base_path is None and distribution_ctx.pulp_ctx.has_plugin( - PluginRequirement("core", specifier=">=3.24.0") - ) if private is not None: body["private"] = private