diff --git a/CHANGES/423.feature b/CHANGES/423.feature new file mode 100644 index 000000000..a89167330 --- /dev/null +++ b/CHANGES/423.feature @@ -0,0 +1 @@ +Added tag/untag commands to add and remove tags from images in container repositories. diff --git a/pulpcore/cli/container/context.py b/pulpcore/cli/container/context.py index 7db1c2e01..ba4c07f67 100644 --- a/pulpcore/cli/container/context.py +++ b/pulpcore/cli/container/context.py @@ -83,6 +83,7 @@ class PulpContainerRepositoryContext(PulpRepositoryContext): CAPABILITIES = { "sync": [PluginRequirement("container")], "pulpexport": [PluginRequirement("container", "2.8.0.dev")], + "tag": [PluginRequirement("container", "2.3.0")], } @@ -90,6 +91,7 @@ class PulpContainerPushRepositoryContext(PulpRepositoryContext): HREF = "container_container_push_repository_href" ID_PREFIX = "repositories_container_container_push" VERSION_CONTEXT = PulpContainerPushRepositoryVersionContext + CAPABILITIES = {"tag": [PluginRequirement("container", "2.3.0")]} registered_repository_contexts["container:container"] = PulpContainerRepositoryContext diff --git a/pulpcore/cli/container/repository.py b/pulpcore/cli/container/repository.py index 66f2fc64e..4b2e89f64 100644 --- a/pulpcore/cli/container/repository.py +++ b/pulpcore/cli/container/repository.py @@ -1,3 +1,4 @@ +import re from typing import Any, Dict import click @@ -37,6 +38,16 @@ translation = get_translation(__name__) _ = translation.gettext +VALID_TAG_REGEX = r"^[A-Za-z0-9][A-Za-z0-9._-]*$" + + +def _tag_callback(ctx: click.Context, param: click.Parameter, value: str) -> str: + if len(value) == 0: + raise click.ClickException("Please pass a non empty tag name.") + if re.match(VALID_TAG_REGEX, value) is None: + raise click.ClickException("Please pass a valid tag.") + + return value remote_option = resource_option( @@ -122,3 +133,46 @@ def sync( href=repository_href, body=body, ) + + +@repository.command(name="tag") +@name_option +@href_option +@click.option("--tag", help=_("Name to tag an image with"), required=True, callback=_tag_callback) +@click.option("--digest", help=_("SHA256 digest of the Manifest file"), required=True) +@pass_repository_context +def add_tag( + repository_ctx: PulpRepositoryContext, + 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.command(name="untag") +@name_option +@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}, + ) diff --git a/tests/scripts/pulp_container/test_tag.sh b/tests/scripts/pulp_container/test_tag.sh new file mode 100755 index 000000000..ddf9b3952 --- /dev/null +++ b/tests/scripts/pulp_container/test_tag.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# shellcheck source=tests/scripts/config.source +. "$(dirname "$(dirname "$(realpath "$0")")")"/config.source + +pulp debug has-plugin --name "container" || exit 3 + +cleanup() { + pulp container repository destroy --name "cli_test_container_repository" || true + pulp container remote destroy --name "cli_test_container_remote" || true + pulp orphan cleanup || true +} +trap cleanup EXIT + +# Prepare +pulp container remote create --name "cli_test_container_remote" --url "$CONTAINER_REMOTE_URL" --upstream-name "$CONTAINER_IMAGE" +pulp container repository create --name "cli_test_container_repository" +pulp container repository sync --name "cli_test_container_repository" --remote "cli_test_container_remote" +manifest_digest="$(pulp container content -t manifest list | tr '\r\n' ' ' | jq -r .[0].digest)" + +expect_succ pulp container repository tag --name "cli_test_container_repository" --tag "test_tag" --digest "$manifest_digest" +expect_succ pulp container repository version show --repository "cli_test_container_repository" --version "2" +test "$(echo "$OUTPUT" | jq -r '.content_summary.added["container.tag"].count')" -eq "1" + +expect_succ pulp container repository untag --name "cli_test_container_repository" --tag "test_tag" +expect_succ pulp container repository version show --repository "cli_test_container_repository" --version "3" +test "$(echo "$OUTPUT" | jq -r '.content_summary.removed["container.tag"].count')" -eq "1"