From 2e75eb4c21578a47dd153542375618b52e256a4b Mon Sep 17 00:00:00 2001 From: Gerrod Ubben Date: Fri, 12 Nov 2021 17:16:03 -0500 Subject: [PATCH] Add tag/untag commands for container repositories fixes: #423 --- CHANGES/423.feature | 1 + pulpcore/cli/container/context.py | 7 ++++ pulpcore/cli/container/repository.py | 49 ++++++++++++++++++++++++ tests/scripts/pulp_container/test_tag.sh | 28 ++++++++++++++ 4 files changed, 85 insertions(+) create mode 100644 CHANGES/423.feature create mode 100644 tests/scripts/pulp_container/test_tag.sh 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 31727c46a..d6d10503f 100644 --- a/pulpcore/cli/container/context.py +++ b/pulpcore/cli/container/context.py @@ -1,3 +1,5 @@ +from typing import ClassVar + from pulpcore.cli.common.context import ( EntityDefinition, PluginRequirement, @@ -101,10 +103,13 @@ class PulpContainerRepositoryContext(PulpRepositoryContext): UPDATE_ID = "repositories_container_container_partial_update" DELETE_ID = "repositories_container_container_delete" SYNC_ID = "repositories_container_container_sync" + TAG_ID: ClassVar[str] = "repositories_container_container_tag" + UNTAG_ID: ClassVar[str] = "repositories_container_container_untag" VERSION_CONTEXT = PulpContainerRepositoryVersionContext CAPABILITIES = { "sync": [PluginRequirement("container")], "pulpexport": [PluginRequirement("container", "2.8.0.dev")], + "tag": [PluginRequirement("container", "2.3.0")] } @@ -115,6 +120,8 @@ class PulpContainerPushRepositoryContext(PulpRepositoryContext): # CREATE_ID = "repositories_container_container_push_create" # UPDATE_ID = "repositories_container_container_push_update" # DELETE_ID = "repositories_container_container_push_delete" + TAG_ID: ClassVar[str] = "repositories_container_push_tag" + UNTAG_ID: ClassVar[str] = "repositories_container_push_untag" VERSION_CONTEXT = PulpContainerPushRepositoryVersionContext diff --git a/pulpcore/cli/container/repository.py b/pulpcore/cli/container/repository.py index 66f2fc64e..3b45a147f 100644 --- a/pulpcore/cli/container/repository.py +++ b/pulpcore/cli/container/repository.py @@ -1,6 +1,7 @@ from typing import Any, Dict import click +import re from pulpcore.cli.common.context import ( EntityFieldDefinition, @@ -37,8 +38,17 @@ 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: Any) -> None: + 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( "--remote", default_plugin="container", @@ -122,3 +132,42 @@ 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.pulp_ctx.call( + repository_ctx.TAG_ID, + 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.pulp_ctx.call( + repository_ctx.UNTAG_ID, + 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 100644 index 000000000..e1b852201 --- /dev/null +++ b/tests/scripts/pulp_container/test_tag.sh @@ -0,0 +1,28 @@ +#!/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_repo" || 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" +pulp container content -t "manifest" list +manifest_digest="$(echo "$OUTPUT" | 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"