From 3a959d1e78bbcbea6487e83fe90b577aa98b65a1 Mon Sep 17 00:00:00 2001 From: Matthias Dellweg Date: Mon, 19 Sep 2022 14:04:09 +0200 Subject: [PATCH] Add filter parameters to task list fixes #543 Co-authored-by: Grant Gainey --- CHANGES/543.feature | 1 + pulpcore/cli/common/context.py | 25 +++++++- pulpcore/cli/core/context.py | 8 ++- pulpcore/cli/core/generic.py | 91 ++++++++++++++++++++++------- pulpcore/cli/core/task.py | 19 ++++-- tests/scripts/pulpcore/test_task.sh | 10 ++++ 6 files changed, 124 insertions(+), 30 deletions(-) create mode 100644 CHANGES/543.feature diff --git a/CHANGES/543.feature b/CHANGES/543.feature new file mode 100644 index 000000000..539f1d141 --- /dev/null +++ b/CHANGES/543.feature @@ -0,0 +1 @@ +Added task filtering options. diff --git a/pulpcore/cli/common/context.py b/pulpcore/cli/common/context.py index b17f6c7eb..83e9bb95b 100644 --- a/pulpcore/cli/common/context.py +++ b/pulpcore/cli/common/context.py @@ -2,6 +2,7 @@ import json import sys import time +from collections.abc import Iterable from typing import Any, ClassVar, Dict, List, NamedTuple, Optional, Set, Type, Union import click @@ -27,6 +28,20 @@ DEFAULT_LIMIT = 25 BATCH_SIZE = 25 +DATETIME_FORMATS = [ + "%Y-%m-%dT%H:%M:%S.%fZ", # Pulp format + "%Y-%m-%d", # intl. format + "%Y-%m-%d %H:%M:%S", # ... with time + "%Y-%m-%dT%H:%M:%S", # ... with time and T as a separator + "%y/%m/%d", # US format + "%y/%m/%d %h:%M:%S %p", # ... with time + "%y/%m/%dT%h:%M:%S %p", # ... with time and T as a separator + "%y/%m/%d %H:%M:%S", # ... with time 24h + "%y/%m/%dT%H:%M:%S", # ... with time 24h and T as a separator + "%x", # local format + "%x %X", # ... with time + "%xT%X", # ... with time and T as a separator +] class PreprocessedEntityDefinition(Dict[str, Any]): @@ -64,9 +79,15 @@ def default(self, obj: Any) -> Any: return super().default(obj) -def _preprocess_value(key: str, value: Any) -> Any: +def _preprocess_value(value: Any) -> Any: + if isinstance(value, str): + return value if isinstance(value, PulpEntityContext): return value.pulp_href + if isinstance(value, datetime.datetime): + return value.strftime(DATETIME_FORMATS[0]) + if isinstance(value, Iterable): + return [_preprocess_value(item) for item in value] return value @@ -75,7 +96,7 @@ def preprocess_payload(payload: EntityDefinition) -> EntityDefinition: return payload return PreprocessedEntityDefinition( - {key: _preprocess_value(key, value) for key, value in payload.items() if value is not None} + {key: _preprocess_value(value) for key, value in payload.items() if value is not None} ) diff --git a/pulpcore/cli/core/context.py b/pulpcore/cli/core/context.py index c2928aeed..8fc1d4769 100644 --- a/pulpcore/cli/core/context.py +++ b/pulpcore/cli/core/context.py @@ -1,3 +1,4 @@ +import datetime import hashlib import os import sys @@ -341,7 +342,11 @@ def scope(self) -> Dict[str, Any]: else: return {} - def purge(self, finished_before: Optional[str], states: Optional[List[str]]) -> Any: + def purge( + self, + finished_before: Optional[datetime.datetime], + states: Optional[List[str]], + ) -> Any: body: Dict[str, Any] = {} if finished_before: body["finished_before"] = finished_before @@ -455,3 +460,4 @@ class PulpWorkerContext(PulpEntityContext): ENTITIES = _("workers") HREF = "worker_href" ID_PREFIX = "workers" + HREF_PATTERN = r"workers/" diff --git a/pulpcore/cli/core/generic.py b/pulpcore/cli/core/generic.py index 57cc5943c..6acac83ab 100644 --- a/pulpcore/cli/core/generic.py +++ b/pulpcore/cli/core/generic.py @@ -4,15 +4,16 @@ import click from pulpcore.cli.common.context import ( + DATETIME_FORMATS, PluginRequirement, PulpContext, PulpEntityContext, pass_entity_context, pass_pulp_context, ) -from pulpcore.cli.common.generic import list_command, pulp_group, pulp_option +from pulpcore.cli.common.generic import list_command, pulp_group, pulp_option, resource_option from pulpcore.cli.common.i18n import get_translation -from pulpcore.cli.core.context import PulpTaskContext +from pulpcore.cli.core.context import PulpTaskContext, PulpWorkerContext translation = get_translation(__name__) _ = translation.gettext @@ -22,27 +23,31 @@ # Generic reusable commands -def _task_group_filter_callback( - ctx: click.Context, param: click.Parameter, value: Optional[str] -) -> Optional[str]: - if value is not None: - pulp_ctx = ctx.find_object(PulpContext) - assert pulp_ctx is not None - - # Example: "/pulp/api/v3/task-groups/a69230d2-506e-44c7-9c46-e64a905f85e7/" - match = re.match( - rf"({pulp_ctx.api_path}task-groups/)?" - r"([0-9a-f]{8})-?([0-9a-f]{4})-?([0-9a-f]{4})-?([0-9a-f]{4})-?([0-9a-f]{12})/?", - value, - ) - if match: - value = "{}task-groups/{}-{}-{}-{}-{}/".format( - pulp_ctx.api_path, *match.group(2, 3, 4, 5, 6) +class HrefOrUuidCallback: + def __init__(self, base_href: str) -> None: + self.base_href = base_href + + def __call__( + self, ctx: click.Context, param: click.Parameter, value: Optional[str] + ) -> Optional[str]: + if value is not None: + pulp_ctx = ctx.find_object(PulpContext) + assert pulp_ctx is not None + + # Example: "/pulp/api/v3/tasks/a69230d2-506e-44c7-9c46-e64a905f85e7/" + href_match = re.match( + rf"({pulp_ctx.api_path}{self.base_href}/)?" + r"([0-9a-f]{8})-?([0-9a-f]{4})-?([0-9a-f]{4})-?([0-9a-f]{4})-?([0-9a-f]{12})/?", + value, ) - else: - raise click.ClickException(_("Either an href or a UUID must be provided.")) + if href_match: + value = "{}{}/{}-{}-{}-{}-{}/".format( + pulp_ctx.api_path, self.base_href, *href_match.group(2, 3, 4, 5, 6) + ) + else: + raise click.ClickException(_("Either an href or a UUID must be provided.")) - return value + return value task_filter = [ @@ -69,7 +74,49 @@ def _task_group_filter_callback( click.option( "--task-group", help=_("List only tasks in this task group. Provide pulp_href or UUID."), - callback=_task_group_filter_callback, + callback=HrefOrUuidCallback("task-groups"), + ), + click.option( + "--parent-task", + help=_("Parent task by uuid or href."), + callback=HrefOrUuidCallback("tasks"), + ), + resource_option( + "--worker", + default_plugin="core", + default_type="none", + context_table={"core:none": PulpWorkerContext}, + href_pattern=PulpWorkerContext.HREF_PATTERN, + help=_("Worker used to execute the task by name or href."), + ), + click.option( + "--created-resource", + "created_resources", + help=_("Href of a resource created in the task."), + ), + click.option( + "--started-after", + "started_at__gte", + help=_("Filter for tasks started at or after this ISO 8601 date"), + type=click.DateTime(formats=DATETIME_FORMATS), + ), + click.option( + "--started-before", + "started_at__lte", + help=_("Filter for tasks started at or before this ISO 8601 date"), + type=click.DateTime(formats=DATETIME_FORMATS), + ), + click.option( + "--finished-after", + "finished_at__gte", + help=_("Filter for tasks finished at or after this ISO 8601 date"), + type=click.DateTime(formats=DATETIME_FORMATS), + ), + click.option( + "--finished-before", + "finished_at__lte", + help=_("Filter for tasks finished at or before this ISO 8601 date"), + type=click.DateTime(formats=DATETIME_FORMATS), ), ] diff --git a/pulpcore/cli/core/task.py b/pulpcore/cli/core/task.py index c0f253f5f..3616ba147 100644 --- a/pulpcore/cli/core/task.py +++ b/pulpcore/cli/core/task.py @@ -6,6 +6,7 @@ import click from pulpcore.cli.common.context import ( + DATETIME_FORMATS, PluginRequirement, PulpContext, PulpEntityContext, @@ -27,8 +28,6 @@ translation = get_translation(__name__) _ = translation.gettext -DATETIME_FORMATS = ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S"] - def _uuid_callback( ctx: click.Context, param: click.Parameter, value: Optional[str] @@ -55,7 +54,18 @@ def task(ctx: click.Context, pulp_ctx: PulpContext) -> None: ctx.obj = PulpTaskContext(pulp_ctx) -task.add_command(list_command(decorators=task_filter)) +task.add_command( + list_command( + decorators=task_filter + + [ + click.option( + "--reserved-resource", + "reserved_resources_record", + help=_("Href of a resource reserved by the task."), + ), + ] + ) +) task.add_command(destroy_command(decorators=[href_option, uuid_option])) task.add_command( role_command( @@ -155,5 +165,4 @@ def purge( ) -> None: pulp_ctx.needs_plugin(PluginRequirement("core", "3.17.0")) state_list = list(state) if state else None - finished_str = finished.strftime(DATETIME_FORMATS[1]) if finished else None - task_ctx.purge(finished_str, state_list) + task_ctx.purge(finished, state_list) diff --git a/tests/scripts/pulpcore/test_task.sh b/tests/scripts/pulpcore/test_task.sh index 0c5a48c46..e8fc79f4a 100755 --- a/tests/scripts/pulpcore/test_task.sh +++ b/tests/scripts/pulpcore/test_task.sh @@ -16,6 +16,9 @@ trap cleanup EXIT sync_task="pulp_file.app.tasks.synchronizing.synchronize" expect_succ pulp task list --name $sync_task --state canceled count="$(echo "$OUTPUT" | jq -r length)" +expect_succ pulp worker list --limit 1 +worker="$(echo "$OUTPUT" | jq -r '.[0].pulp_href')" +worker_name="$(echo "$OUTPUT" | jq -r '.[0].name')" expect_succ pulp file remote create --name "cli_test_file_remote" \ --url "$FILE_REMOTE_URL" @@ -41,9 +44,16 @@ task=$(echo "$ERROUTPUT" | grep -E -o "${PULP_API_ROOT}api/v3/tasks/[-[:xdigit:] task_uuid="${task%/}" task_uuid="${task_uuid##*/}" expect_succ pulp task show --wait --uuid "$task_uuid" +reserved_resource="$(echo "$OUTPUT" | jq -r '.reserved_resources_record[0]')" +created_resource="$(echo "$OUTPUT" | jq -r '.created_resources[0]')" expect_succ test "$(echo "$OUTPUT" | jq -r '.state')" = "completed" expect_succ pulp task list --name-contains file +expect_succ pulp task list --parent-task "$task" --worker "$worker" +expect_succ pulp task list --parent-task "$task_uuid" --worker "$worker_name" +expect_succ pulp task list --started-before "21/01/12" --started-after "22/01/06T00:00:00" +expect_succ pulp task list --finished-before "2021-12-01" --finished-after "2022-06-01 00:00:00" +expect_succ pulp task list --reserved-resource "$reserved_resource" --created-resource "$created_resource" expect_fail pulp task list --state=cannotwork expect_succ pulp task list --state=COmPLetED