From e3ec72ffe3745eb90338943d836fd2311c7ea999 Mon Sep 17 00:00:00 2001 From: David Davis Date: Wed, 13 Jan 2021 15:57:58 -0500 Subject: [PATCH] Add pulp label commands fixes #100 --- CHANGES/100.feature | 1 + pulpcore/cli/common/context.py | 17 ++++++++ pulpcore/cli/common/generic.py | 67 +++++++++++++++++++++++++++++++ pulpcore/cli/file/distribution.py | 5 ++- pulpcore/cli/file/remote.py | 5 ++- pulpcore/cli/file/repository.py | 5 ++- tests/scripts/config.source | 23 +++++++++-- tests/scripts/test_label.sh | 50 +++++++++++++++++++++++ tests/test_scripts.py | 2 + 9 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 CHANGES/100.feature create mode 100755 tests/scripts/test_label.sh diff --git a/CHANGES/100.feature b/CHANGES/100.feature new file mode 100644 index 000000000..c4cf88eed --- /dev/null +++ b/CHANGES/100.feature @@ -0,0 +1 @@ +Added label subcommands (set and unset). diff --git a/pulpcore/cli/common/context.py b/pulpcore/cli/common/context.py index 413a8693e..15d2bb164 100644 --- a/pulpcore/cli/common/context.py +++ b/pulpcore/cli/common/context.py @@ -330,6 +330,23 @@ def update( def delete(self, href: str) -> Any: return self.pulp_ctx.call(self.DELETE_ID, parameters={self.HREF: href}) + def set_label(self, href: str, key: str, value: str) -> Any: + entity = self.show(href) + entity["pulp_labels"][key] = value + return self.update(href, body=entity) + + def unset_label(self, href: str, key: str) -> Any: + entity = self.show(href) + entity["pulp_labels"].pop(key) + return self.update(href, body=entity) + + def show_label(self, href: str, key: str) -> Any: + entity = self.show(href) + try: + return entity["pulp_labels"][key] + except KeyError: + raise click.ClickException(f"Could not find label with key '{key}'.") + def find_repository(self, definition: RepositoryDefinition) -> Any: name, repo_type = definition if repo_type in self.REPOSITORY_FIND_IDS: diff --git a/pulpcore/cli/common/generic.py b/pulpcore/cli/common/generic.py index 29f226844..e1c74c892 100644 --- a/pulpcore/cli/common/generic.py +++ b/pulpcore/cli/common/generic.py @@ -107,6 +107,14 @@ def _version_callback(ctx: click.Context, param: click.Parameter, value: int) -> expose_value=False, ) +label_select_option = click.option( + "--label-select", + "pulp_label_select", + help="Filter {entities} by a label search query.", + type=str, + cls=PulpOption, +) + ############################################################################## # Generic reusable commands @@ -233,6 +241,65 @@ def repair( return callback +def label_command(**kwargs: Any) -> click.Command: + """A factory that creates a label command group.""" + + if "name" not in kwargs: + kwargs["name"] = "label" + decorators = kwargs.pop("decorators", []) + lookup_decorators = kwargs.pop("lookup_decorators", [name_option, href_option]) + + @click.group(**kwargs) + @pass_entity_context + @pass_pulp_context + @click.pass_context + def label_group( + ctx: click.Context, entity_ctx: PulpEntityContext, pulp_ctx: PulpContext + ) -> None: + pass + + for option in decorators: + # Decorate callback + label_group = option(label_group) + + @click.command(name="set") + @click.option("--key", required=True, help="Key of the label") + @click.option("--value", required=True, help="Value of the label") + @pass_entity_context + @pass_pulp_context + def label_set( + pulp_ctx: PulpContext, entity_ctx: PulpEntityContext, key: str, value: str + ) -> None: + """Add or update a label""" + href = entity_ctx.entity["pulp_href"] + entity_ctx.set_label(href, key, value) + + @click.command(name="unset") + @click.option("--key", required=True, help="Key of the label") + @pass_entity_context + @pass_pulp_context + def label_unset(pulp_ctx: PulpContext, entity_ctx: PulpEntityContext, key: str) -> None: + """Remove a label with a given key""" + href = entity_ctx.entity["pulp_href"] + entity_ctx.unset_label(href, key) + + @click.command(name="show") + @click.option("--key", required=True, help="Key of the label") + @pass_entity_context + @pass_pulp_context + def label_show(pulp_ctx: PulpContext, entity_ctx: PulpEntityContext, key: str) -> None: + """Show the value for a particular label key""" + href = entity_ctx.entity["pulp_href"] + click.echo(entity_ctx.show_label(href, key)) + + for subcmd in [label_set, label_unset, label_show]: + for decorator in lookup_decorators: + subcmd = decorator(subcmd) + label_group.add_command(subcmd) + + return label_group + + @click.command(name="list") @limit_option @offset_option diff --git a/pulpcore/cli/file/distribution.py b/pulpcore/cli/file/distribution.py index 56c4c4eb1..d4d8d0169 100644 --- a/pulpcore/cli/file/distribution.py +++ b/pulpcore/cli/file/distribution.py @@ -6,6 +6,8 @@ from pulpcore.cli.common.generic import ( destroy_command, href_option, + label_command, + label_select_option, list_command, name_option, show_command, @@ -32,9 +34,10 @@ def distribution(ctx: click.Context, pulp_ctx: PulpContext, distribution_type: s lookup_options = [href_option, name_option] -distribution.add_command(list_command()) +distribution.add_command(list_command(decorators=[label_select_option])) distribution.add_command(show_command(decorators=lookup_options)) distribution.add_command(destroy_command(decorators=lookup_options)) +distribution.add_command(label_command()) @distribution.command() diff --git a/pulpcore/cli/file/remote.py b/pulpcore/cli/file/remote.py index 138eb2c3a..c1b95ee53 100644 --- a/pulpcore/cli/file/remote.py +++ b/pulpcore/cli/file/remote.py @@ -4,6 +4,8 @@ from pulpcore.cli.common.generic import ( destroy_command, href_option, + label_command, + label_select_option, list_command, name_option, show_command, @@ -30,9 +32,10 @@ def remote(ctx: click.Context, pulp_ctx: PulpContext, remote_type: str) -> None: lookup_options = [href_option, name_option] -remote.add_command(list_command()) +remote.add_command(list_command(decorators=[label_select_option])) remote.add_command(show_command(decorators=lookup_options)) remote.add_command(destroy_command(decorators=lookup_options)) +remote.add_command(label_command()) @remote.command() diff --git a/pulpcore/cli/file/repository.py b/pulpcore/cli/file/repository.py index eadef659d..2fa4ac1ef 100644 --- a/pulpcore/cli/file/repository.py +++ b/pulpcore/cli/file/repository.py @@ -12,6 +12,8 @@ from pulpcore.cli.common.generic import ( destroy_command, href_option, + label_command, + label_select_option, list_command, name_option, show_command, @@ -43,10 +45,11 @@ def repository(ctx: click.Context, pulp_ctx: PulpContext, repo_type: str) -> Non lookup_options = [href_option, name_option] -repository.add_command(list_command()) +repository.add_command(list_command(decorators=[label_select_option])) repository.add_command(show_command(decorators=lookup_options)) repository.add_command(destroy_command(decorators=lookup_options)) repository.add_command(version_command()) +repository.add_command(label_command()) @repository.command() diff --git a/tests/scripts/config.source b/tests/scripts/config.source index a010150a9..43c3149cb 100644 --- a/tests/scripts/config.source +++ b/tests/scripts/config.source @@ -3,8 +3,11 @@ settings="$(dirname "$(realpath "$0")")/config/pulp/settings.toml" PULP_BASE_URL="$(sed -n -e 's/^base_url\s*=\s*"\(\S*\)"\s*$/\1/p' "$settings")" VERIFY_SSL="$(sed -n -e 's/^verify_ssl\s*=\s*\(\S*\)\s*$/\1/p' "$settings")" +# Configure path to config +export XDG_CONFIG_HOME="$(dirname "$(realpath "$0")")/config" + # Constants used in tests -PULPCORE_VERSION=$(http "$PULP_BASE_URL/pulp/api/v3/status/" | jq -r '.versions | .[] | select(.component=="pulpcore").version') +PULPCORE_VERSION=$(pulp status | jq -r '.versions | .[] | select(.component=="pulpcore").version') PULP_FIXTURES_URL="${PULP_FIXTURES_URL:-https://fixtures.pulpproject.org}" FILE_REMOTE_URL="${PULP_FIXTURES_URL}/file/PULP_MANIFEST" CONTAINER_REMOTE_URL="https://registry-1.docker.io" @@ -13,9 +16,6 @@ RPM_REMOTE_URL="${PULP_FIXTURES_URL}/rpm-unsigned" ANSIBLE_COLLECTION_REMOTE_URL="https://galaxy.ansible.com/" ANSIBLE_ROLE_REMOTE_URL="https://galaxy.ansible.com/api/v1/roles/?namespace__name=elastic" -# Configure path to config -export XDG_CONFIG_HOME="$(dirname "$(realpath "$0")")/config" - # Library for test helper functions TMP="$(mktemp -d)" @@ -67,4 +67,19 @@ expect_fail () { fi } +# Returns true if $PULPCORE_VERSION >= $1, false otherwise +require_min_pulp () { + if [ -z "${PULPCORE_VERSION+x}" ] + then + echo "Error: require_min_pulp called without \$PULPCORE_VERSION defined." + exit 1 + fi + + if [ "$1" != "`printf "$PULPCORE_VERSION\n$1" | sort -V | head -n1`" ] + then + echo "Skipping $0" + exit 3 # skip exit code + fi +} + set -eu diff --git a/tests/scripts/test_label.sh b/tests/scripts/test_label.sh new file mode 100755 index 000000000..a1eaa5e35 --- /dev/null +++ b/tests/scripts/test_label.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +# shellcheck source=tests/scripts/config.source +. "$(dirname "$(realpath "$0")")/config.source" + +require_min_pulp "3.10.0" + +cleanup() { + pulp file repository destroy --name "cli_test_file_repo" || true +} +trap cleanup EXIT + +name="cli_test_file_repo" +expect_succ pulp file repository create --name "$name" +expect_succ pulp file repository label set --name "$name" --key "atani" --value "hurin" +expect_succ pulp file repository label set --name "$name" --key "ainur" --value "ulmo" + +expect_succ pulp file repository show --name "$name" +test "$(echo "$OUTPUT" | jq -r -c .pulp_labels)" = '{"atani":"hurin","ainur":"ulmo"}' + +# update a label +expect_succ pulp file repository label set --name "$name" --key "atani" --value "beor" +expect_succ pulp file repository show --name "$name" +test "$(echo "$OUTPUT" | jq -r -c .pulp_labels)" = '{"ainur":"ulmo","atani":"beor"}' +expect_succ pulp file repository label show --name "$name" --key "atani" +test "$OUTPUT" = "beor" + +# remove a label +expect_succ pulp file repository label unset --name "$name" --key "atani" +expect_succ pulp file repository show --name "$name" +test "$(echo "$OUTPUT" | jq -r -c .pulp_labels)" = '{"ainur":"ulmo"}' +expect_fail pulp file repository label show --name "$name" + +# filtering +expect_succ pulp file repository list --label-select "ainur" +test "$(echo "$OUTPUT" | jq length)" -eq 1 +expect_succ pulp file repository list --label-select "!ainur" +test "$(echo "$OUTPUT" | jq length)" -eq 0 +expect_succ pulp file repository list --label-select "ainur=ulmo" +test "$(echo "$OUTPUT" | jq length)" -eq 1 +expect_succ pulp file repository list --label-select "ainur!=ulmo" +test "$(echo "$OUTPUT" | jq length)" -eq 0 +expect_succ pulp file repository list --label-select "ainur~lm" +test "$(echo "$OUTPUT" | jq length)" -eq 1 + +expect_succ pulp file repository label set --name "$name" --key "istar" --value "olorin" +expect_succ pulp file repository list --label-select "ainur=ulmo,istar!=curumo" +test "$(echo "$OUTPUT" | jq length)" -eq 1 +expect_succ pulp file repository list --label-select "ainur=ulmo,istar=olorin" +test "$(echo "$OUTPUT" | jq length)" -eq 1 diff --git a/tests/test_scripts.py b/tests/test_scripts.py index 86da9b642..37ce74d0b 100644 --- a/tests/test_scripts.py +++ b/tests/test_scripts.py @@ -13,4 +13,6 @@ @pytest.mark.parametrize("test_name", TEST_NAMES) def test_script(test_name): run = subprocess.run([os.path.join("tests", "scripts", "test_" + test_name + ".sh")]) + if run.returncode == 3: + pytest.skip("Skipped as requested by the script.") assert run.returncode == 0