From d53da3629f95a0319d496b66dc2ae1c322aa52b9 Mon Sep 17 00:00:00 2001 From: Bob Haddleton Date: Wed, 21 Mar 2018 10:34:46 -0500 Subject: [PATCH] Provide consistent options and return latest execution entries This patchset updates the *-list commands to have consistent definitions of the sort_keys/sort_dirs/limit/filter/marker options. It also modified the execution-list, action-execution-list and task-list commands to return the MOST RECENT entries by default, when no other sort_key, sort_dir or marker options are provided, rather than the oldest entries. There is a new --oldest option for these three commands to allow the user to access the oldest entries instead of the newest. A release note has also been created. Change-Id: I002edd1b10ab281072cfa7501cfa763073a7781c --- mistralclient/api/base.py | 39 ++++- mistralclient/api/v2/action_executions.py | 21 ++- mistralclient/api/v2/actions.py | 32 ++-- mistralclient/api/v2/cron_triggers.py | 15 +- mistralclient/api/v2/environments.py | 16 +- mistralclient/api/v2/event_triggers.py | 15 +- mistralclient/api/v2/executions.py | 42 ++---- mistralclient/api/v2/tasks.py | 35 ++--- mistralclient/api/v2/workbooks.py | 16 +- mistralclient/api/v2/workflows.py | 36 ++--- .../commands/v2/action_executions.py | 142 +++++++----------- mistralclient/commands/v2/actions.py | 92 ++++++------ mistralclient/commands/v2/base.py | 105 +++++++++++++ mistralclient/commands/v2/cron_triggers.py | 87 ++++++----- mistralclient/commands/v2/environments.py | 107 ++++++------- mistralclient/commands/v2/event_triggers.py | 83 +++++----- mistralclient/commands/v2/executions.py | 141 ++++++----------- mistralclient/commands/v2/members.py | 64 ++++---- mistralclient/commands/v2/tasks.py | 43 +----- mistralclient/commands/v2/workbooks.py | 71 +++++---- mistralclient/commands/v2/workflows.py | 90 ++++++----- .../tests/unit/v2/test_cli_executions.py | 30 +++- mistralclient/tests/unit/v2/test_cli_tasks.py | 2 +- ...ault-behavior-change-225010204e32bc89.yaml | 11 ++ 24 files changed, 686 insertions(+), 649 deletions(-) create mode 100644 releasenotes/notes/execution-list-default-behavior-change-225010204e32bc89.yaml diff --git a/mistralclient/api/base.py b/mistralclient/api/base.py index c2b71b53..fd6fbbcd 100644 --- a/mistralclient/api/base.py +++ b/mistralclient/api/base.py @@ -14,9 +14,12 @@ import copy import json +import six from keystoneauth1 import exceptions +urlparse = six.moves.urllib.parse + class Resource(object): resource_name = 'Something' @@ -71,7 +74,41 @@ class ResourceManager(object): self.http_client = http_client def find(self, **kwargs): - return [i for i in self.list() if _check_items(i, kwargs.items())] + return [i for i in self._list() if _check_items(i, kwargs.items())] + + @staticmethod + def _build_query_params(marker=None, limit=None, sort_keys=None, + sort_dirs=None, fields=None, filters=None, + scope=None, namespace=None): + qparams = {} + + if marker: + qparams['marker'] = marker + + if limit and limit > 0: + qparams['limit'] = limit + + if sort_keys: + qparams['sort_keys'] = sort_keys + + if sort_dirs: + qparams['sort_dirs'] = sort_dirs + + if fields: + qparams['fields'] = ",".join(fields) + + if filters: + for name, val in filters.items(): + qparams[name] = val + + if scope: + qparams['scope'] = scope + + if namespace: + qparams['namespace'] = namespace + + return ("?%s" % urlparse.urlencode(list(qparams.items())) + if qparams else "") def _ensure_not_empty(self, **kwargs): for name, value in kwargs.items(): diff --git a/mistralclient/api/v2/action_executions.py b/mistralclient/api/v2/action_executions.py index 7115925e..628bdc13 100644 --- a/mistralclient/api/v2/action_executions.py +++ b/mistralclient/api/v2/action_executions.py @@ -13,12 +13,9 @@ # limitations under the License. import json -import six from mistralclient.api import base -urlparse = six.moves.urllib.parse - class ActionExecution(base.Resource): resource_name = 'ActionExecution' @@ -62,7 +59,8 @@ class ActionExecutionManager(base.ResourceManager): return self._update('/action_executions/%s' % id, data) - def list(self, task_execution_id=None, limit=None): + def list(self, task_execution_id=None, limit=None, marker='', fields=None, + sort_keys='', sort_dirs='', **filters): url = '/action_executions' if task_execution_id: @@ -70,13 +68,14 @@ class ActionExecutionManager(base.ResourceManager): url += "%s" - qparams = {} - - if limit and limit > 0: - qparams['limit'] = limit - - query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) - if qparams else "") + query_string = self._build_query_params( + marker=marker, + limit=limit, + sort_keys=sort_keys, + sort_dirs=sort_dirs, + fields=fields, + filters=filters + ) return self._list(url % query_string, response_key='action_executions') diff --git a/mistralclient/api/v2/actions.py b/mistralclient/api/v2/actions.py index c5acc347..498de452 100644 --- a/mistralclient/api/v2/actions.py +++ b/mistralclient/api/v2/actions.py @@ -12,14 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import six - from keystoneauth1 import exceptions from mistralclient.api import base from mistralclient import utils -urlparse = six.moves.urllib.parse - class Action(base.Resource): resource_name = 'Action' @@ -75,26 +71,16 @@ class ActionManager(base.ResourceManager): for resource_data in base.extract_json(resp, 'actions')] def list(self, marker='', limit=None, sort_keys='', sort_dirs='', - **filters): - qparams = {} + fields='', **filters): - if marker: - qparams['marker'] = marker - - if limit and limit > 0: - qparams['limit'] = limit - - if sort_keys: - qparams['sort_keys'] = sort_keys - - if sort_dirs: - qparams['sort_dirs'] = sort_dirs - - for name, val in filters.items(): - qparams[name] = val - - query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) - if qparams else "") + query_string = self._build_query_params( + marker=marker, + limit=limit, + sort_keys=sort_keys, + sort_dirs=sort_dirs, + fields=fields, + filters=filters + ) return self._list( '/actions%s' % query_string, diff --git a/mistralclient/api/v2/cron_triggers.py b/mistralclient/api/v2/cron_triggers.py index 036b1d17..13e8e135 100644 --- a/mistralclient/api/v2/cron_triggers.py +++ b/mistralclient/api/v2/cron_triggers.py @@ -54,8 +54,19 @@ class CronTriggerManager(base.ResourceManager): return self._create('/cron_triggers', data) - def list(self): - return self._list('/cron_triggers', response_key='cron_triggers') + def list(self, marker='', limit=None, sort_keys='', fields='', + sort_dirs='', **filters): + query_string = self._build_query_params( + marker=marker, + limit=limit, + sort_keys=sort_keys, + sort_dirs=sort_dirs, + fields=fields, + filters=filters + ) + + return self._list('/cron_triggers%s' % query_string, + response_key='cron_triggers') def get(self, name): self._ensure_not_empty(name=name) diff --git a/mistralclient/api/v2/environments.py b/mistralclient/api/v2/environments.py index da53d4e7..8e7a756f 100644 --- a/mistralclient/api/v2/environments.py +++ b/mistralclient/api/v2/environments.py @@ -13,7 +13,6 @@ # limitations under the License. import json - import six from mistralclient.api import base @@ -71,8 +70,19 @@ class EnvironmentManager(base.ResourceManager): return self._update('/environments', kwargs) - def list(self): - return self._list('/environments', response_key='environments') + def list(self, marker='', limit=None, sort_keys='', sort_dirs='', + fields='', **filters): + query_string = self._build_query_params( + marker=marker, + limit=limit, + sort_keys=sort_keys, + sort_dirs=sort_dirs, + fields=fields, + filters=filters + ) + + return self._list('/environments%s' % query_string, + response_key='environments') def get(self, name): self._ensure_not_empty(name=name) diff --git a/mistralclient/api/v2/event_triggers.py b/mistralclient/api/v2/event_triggers.py index 0eef9f37..e20df26c 100644 --- a/mistralclient/api/v2/event_triggers.py +++ b/mistralclient/api/v2/event_triggers.py @@ -47,8 +47,19 @@ class EventTriggerManager(base.ResourceManager): return self._create('/event_triggers', data) - def list(self): - return self._list('/event_triggers', response_key='event_triggers') + def list(self, marker='', limit=None, sort_keys='', sort_dirs='', + fields='', **filters): + query_string = self._build_query_params( + marker=marker, + limit=limit, + sort_keys=sort_keys, + sort_dirs=sort_dirs, + fields=fields, + filters=filters + ) + + return self._list('/event_triggers%s' % query_string, + response_key='event_triggers') def get(self, id): self._ensure_not_empty(id=id) diff --git a/mistralclient/api/v2/executions.py b/mistralclient/api/v2/executions.py index 7db0aec6..b4421d0e 100644 --- a/mistralclient/api/v2/executions.py +++ b/mistralclient/api/v2/executions.py @@ -14,16 +14,13 @@ # limitations under the License. import json - -from oslo_utils import uuidutils import six +from oslo_utils import uuidutils + from mistralclient.api import base -urlparse = six.moves.urllib.parse - - class Execution(base.Resource): resource_name = 'Execution' @@ -79,29 +76,18 @@ class ExecutionManager(base.ResourceManager): return self._update('/executions/%s' % id, data) def list(self, task=None, marker='', limit=None, sort_keys='', - sort_dirs='', **filters): - qparams = {} - + sort_dirs='', fields='', **filters): if task: - qparams['task_execution_id'] = task + filters['task_execution_id'] = task - if marker: - qparams['marker'] = marker - - if limit and limit > 0: - qparams['limit'] = limit - - if sort_keys: - qparams['sort_keys'] = sort_keys - - if sort_dirs: - qparams['sort_dirs'] = sort_dirs - - for name, val in filters.items(): - qparams[name] = val - - query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) - if qparams else "") + query_string = self._build_query_params( + marker=marker, + limit=limit, + sort_keys=sort_keys, + sort_dirs=sort_dirs, + fields=fields, + filters=filters + ) return self._list( '/executions%s' % query_string, @@ -116,11 +102,9 @@ class ExecutionManager(base.ResourceManager): def delete(self, id, force=None): self._ensure_not_empty(id=id) qparams = {} - if force: qparams['force'] = True - query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) - if qparams else "") + query_string = self._build_query_params(filters=qparams) self._delete('/executions/%s%s' % (id, query_string)) diff --git a/mistralclient/api/v2/tasks.py b/mistralclient/api/v2/tasks.py index 17450a0e..ae590ce0 100644 --- a/mistralclient/api/v2/tasks.py +++ b/mistralclient/api/v2/tasks.py @@ -14,12 +14,9 @@ # limitations under the License. import json -import six from mistralclient.api import base -urlparse = six.moves.urllib.parse - class Task(base.Resource): resource_name = 'Task' @@ -29,7 +26,7 @@ class TaskManager(base.ResourceManager): resource_class = Task def list(self, workflow_execution_id=None, marker='', limit=None, - sort_keys='', sort_dirs='', fields=[], **filters): + sort_keys='', sort_dirs='', fields=None, **filters): url = '/tasks' if workflow_execution_id: @@ -37,28 +34,14 @@ class TaskManager(base.ResourceManager): url += '%s' - qparams = {} - - if marker: - qparams['marker'] = marker - - if limit and limit > 0: - qparams['limit'] = limit - - if sort_keys: - qparams['sort_keys'] = sort_keys - - if sort_dirs: - qparams['sort_dirs'] = sort_dirs - - if fields: - qparams['fields'] = ",".join(fields) - - for name, val in filters.items(): - qparams[name] = val - - query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) - if qparams else "") + query_string = self._build_query_params( + marker=marker, + limit=limit, + sort_keys=sort_keys, + sort_dirs=sort_dirs, + fields=fields, + filters=filters + ) return self._list(url % query_string, response_key='tasks') diff --git a/mistralclient/api/v2/workbooks.py b/mistralclient/api/v2/workbooks.py index c890d777..f5d578af 100644 --- a/mistralclient/api/v2/workbooks.py +++ b/mistralclient/api/v2/workbooks.py @@ -82,12 +82,20 @@ class WorkbookManager(base.ResourceManager): return self.resource_class(self, base.extract_json(resp, None)) - def list(self, namespace=''): - return self._list( - self._get_workbooks_url(None, namespace), - response_key='workbooks' + def list(self, namespace='', marker='', limit=None, sort_keys='', + sort_dirs='', fields='', **filters): + query_string = self._build_query_params( + marker=marker, + limit=limit, + sort_keys=sort_keys, + sort_dirs=sort_dirs, + filters=filters, + namespace=namespace ) + return self._list('/workbooks{}'.format(query_string), + response_key='workbooks') + def get(self, name, namespace=''): self._ensure_not_empty(name=name) diff --git a/mistralclient/api/v2/workflows.py b/mistralclient/api/v2/workflows.py index b72e1e92..237a4f9d 100644 --- a/mistralclient/api/v2/workflows.py +++ b/mistralclient/api/v2/workflows.py @@ -13,16 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import six - from keystoneauth1 import exceptions from mistralclient.api import base from mistralclient import utils -urlparse = six.moves.urllib.parse - - class Workflow(base.Resource): resource_name = 'Workflow' @@ -80,29 +75,18 @@ class WorkflowManager(base.ResourceManager): for resource_data in base.extract_json(resp, 'workflows')] def list(self, namespace='', marker='', limit=None, sort_keys='', - sort_dirs='', **filters): - qparams = {} - + sort_dirs='', fields='', **filters): if namespace: - qparams['namespace'] = namespace + filters['namespace'] = namespace - if marker: - qparams['marker'] = marker - - if limit and limit > 0: - qparams['limit'] = limit - - if sort_keys: - qparams['sort_keys'] = sort_keys - - if sort_dirs: - qparams['sort_dirs'] = sort_dirs - - for name, val in filters.items(): - qparams[name] = val - - query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) - if qparams else "") + query_string = self._build_query_params( + marker=marker, + limit=limit, + sort_keys=sort_keys, + sort_dirs=sort_dirs, + fields=fields, + filters=filters + ) return self._list( '/workflows%s' % query_string, diff --git a/mistralclient/commands/v2/action_executions.py b/mistralclient/commands/v2/action_executions.py index ea754bc9..8ae9ba02 100644 --- a/mistralclient/commands/v2/action_executions.py +++ b/mistralclient/commands/v2/action_executions.py @@ -25,72 +25,54 @@ from mistralclient import utils LOG = logging.getLogger(__name__) -def format_list(action_ex=None): - columns = ( - 'ID', - 'Name', - 'Workflow name', - 'Workflow namespace', - 'Task name', - 'Task ID', - 'State', - 'Accepted', - 'Created at', - 'Updated at' - ) +class ActionExecutionFormatter(base.MistralFormatter): + COLUMNS = [ + ('id', 'ID'), + ('name', 'Name'), + ('workflow_name', 'Workflow name'), + ('workflow_namespace', 'Workflow namespace'), + ('task_name', 'Task name'), + ('task_execution_id', 'Task ID'), + ('state', 'State'), + ('state_info', 'State info'), + ('accepted', 'Accepted'), + ('created_at', 'Created at'), + ('updated_at', 'Updated at'), + ] - if action_ex: - data = ( - action_ex.id, - action_ex.name, - action_ex.workflow_name, - action_ex.workflow_namespace, - action_ex.task_name if hasattr(action_ex, 'task_name') else None, - action_ex.task_execution_id, - action_ex.state, - action_ex.accepted, - action_ex.created_at, - action_ex.updated_at or '' - ) - else: - data = (tuple('' for _ in range(len(columns))),) + LIST_COLUMN_FIELD_NAMES = [c[0] for c in COLUMNS if c[0] != 'state_info'] + LIST_COLUMN_HEADING_NAMES = [c[1] for c in COLUMNS if c[0] != 'state_info'] - return columns, data + @staticmethod + def format(action_ex=None, lister=False): + if lister: + columns = ActionExecutionFormatter.LIST_COLUMN_HEADING_NAMES + else: + columns = ActionExecutionFormatter.headings() + if action_ex: + if hasattr(action_ex, 'task_name'): + task_name = action_ex.task_name + else: + task_name = None + data = ( + action_ex.id, + action_ex.name, + action_ex.workflow_name, + action_ex.workflow_namespace, + task_name, + action_ex.task_execution_id, + action_ex.state,) + if not lister: + data += (action_ex.state_info,) + data += ( + action_ex.accepted, + action_ex.created_at, + action_ex.updated_at or '' + ) + else: + data = (tuple('' for _ in range(len(columns))),) - -def format(action_ex=None): - columns = ( - 'ID', - 'Name', - 'Workflow name', - 'Workflow namespace', - 'Task name', - 'Task ID', - 'State', - 'State info', - 'Accepted', - 'Created at', - 'Updated at', - ) - - if action_ex: - data = ( - action_ex.id, - action_ex.name, - action_ex.workflow_name, - action_ex.workflow_namespace, - action_ex.task_name if hasattr(action_ex, 'task_name') else None, - action_ex.task_execution_id, - action_ex.state, - action_ex.state_info, - action_ex.accepted, - action_ex.created_at, - action_ex.updated_at or '' - ) - else: - data = (tuple('' for _ in range(len(columns))),) - - return columns, data + return columns, data class Create(command.ShowOne): @@ -166,18 +148,18 @@ class Create(command.ShowOne): ) if not parsed_args.run_sync and parsed_args.save_result: - return format(action_ex) + return ActionExecutionFormatter.format(action_ex) else: self.app.stdout.write("%s\n" % action_ex.output) return None, None -class List(base.MistralLister): +class List(base.MistralExecutionLister): """List all Action executions.""" def _get_format_function(self): - return format_list + return ActionExecutionFormatter.format_list def get_parser(self, prog_name): parser = super(List, self).get_parser(prog_name) @@ -187,33 +169,21 @@ class List(base.MistralLister): nargs='?', help='Task execution ID.' ) - parser.add_argument( - '--limit', - type=int, - help='Maximum number of action-executions to return in a single ' - 'result. limit is set to %s by default. Use --limit -1 to ' - 'fetch the full result set.' % base.DEFAULT_LIMIT, - nargs='?' - ) return parser def _get_resources(self, parsed_args): - if parsed_args.limit is None: - parsed_args.limit = base.DEFAULT_LIMIT - - LOG.info( - "limit is set to %s by default. Set " - "the limit explicitly using \'--limit\', if required. " - "Use \'--limit\' -1 to fetch the full result set.", - base.DEFAULT_LIMIT - ) - mistral_client = self.app.client_manager.workflow_engine return mistral_client.action_executions.list( parsed_args.task_execution_id, + marker=parsed_args.marker, limit=parsed_args.limit, + sort_keys=parsed_args.sort_keys, + sort_dirs=parsed_args.sort_dirs, + # TODO(bobh) - Uncomment when the fix for bug 1800322 merges + # fields=ActionExecutionFormatter.LIST_COLUMN_FIELD_NAMES, + **base.get_filters(parsed_args) ) @@ -234,7 +204,7 @@ class Get(command.ShowOne): parsed_args.action_execution ) - return format(execution) + return ActionExecutionFormatter.format(execution) class Update(command.ShowOne): @@ -270,7 +240,7 @@ class Update(command.ShowOne): output ) - return format(execution) + return ActionExecutionFormatter.format(execution) class GetOutput(command.Command): diff --git a/mistralclient/commands/v2/actions.py b/mistralclient/commands/v2/actions.py index 0b0b8036..d0583b64 100644 --- a/mistralclient/commands/v2/actions.py +++ b/mistralclient/commands/v2/actions.py @@ -22,70 +22,66 @@ from mistralclient.commands.v2 import base from mistralclient import utils -def format_list(action=None): - return format(action, lister=True) +class ActionFormatter(base.MistralFormatter): + COLUMNS = [ + ('id', 'ID'), + ('name', 'Name'), + ('is_system', 'Is system'), + ('input', 'Input'), + ('description', 'Description'), + ('tags', 'Tags'), + ('created_at', 'Created at'), + ('updated_at', 'Updated at') + ] + @staticmethod + def format(action=None, lister=False): + if action: + tags = getattr(action, 'tags', None) or [] + input_ = action.input if not lister else base.cut(action.input) + desc = (action.description if not lister + else base.cut(action.description)) -def format(action=None, lister=False): - columns = ( - 'ID', - 'Name', - 'Is system', - 'Input', - 'Description', - 'Tags', - 'Created at', - 'Updated at' - ) + data = ( + action.id, + action.name, + action.is_system, + input_, + desc, + base.wrap(', '.join(tags)) or '', + action.created_at, + ) + if hasattr(action, 'updated_at'): + data += (action.updated_at,) + else: + data += (None,) - if action: - tags = getattr(action, 'tags', None) or [] - input = action.input if not lister else base.cut(action.input) - desc = (action.description if not lister - else base.cut(action.description)) - - data = ( - action.id, - action.name, - action.is_system, - input, - desc, - base.wrap(', '.join(tags)) or '', - action.created_at, - ) - - if hasattr(action, 'updated_at'): - data += (action.updated_at,) else: - data += (None,) - else: - data = (tuple('' for _ in range(len(columns))),) + data = (tuple('' for _ in range(len(ActionFormatter.COLUMNS))),) - return columns, data + return ActionFormatter.headings(), data class List(base.MistralLister): """List all actions.""" def _get_format_function(self): - return format_list + return ActionFormatter.format_list def get_parser(self, prog_name): parser = super(List, self).get_parser(prog_name) - parser.add_argument( - '--filter', - dest='filters', - action='append', - help='Filters. Can be repeated.' - ) - return parser def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.actions.list( + marker=parsed_args.marker, + limit=parsed_args.limit, + sort_keys=parsed_args.sort_keys, + sort_dirs=parsed_args.sort_dirs, + fields=ActionFormatter.fields(), **base.get_filters(parsed_args) ) @@ -104,7 +100,7 @@ class Get(command.ShowOne): mistral_client = self.app.client_manager.workflow_engine action = mistral_client.actions.get(parsed_args.action) - return format(action) + return ActionFormatter.format(action) class Create(base.MistralLister): @@ -131,7 +127,7 @@ class Create(base.MistralLister): raise RuntimeError("Provide action definition file.") def _get_format_function(self): - return format_list + return ActionFormatter.format_list def _get_resources(self, parsed_args): scope = 'public' if parsed_args.public else 'private' @@ -190,7 +186,7 @@ class Update(base.MistralLister): return parser def _get_format_function(self): - return format_list + return ActionFormatter.format_list def _get_resources(self, parsed_args): scope = 'public' if parsed_args.public else 'private' @@ -223,8 +219,8 @@ class GetDefinition(command.Command): class Validate(command.ShowOne): """Validate action.""" - - def _format(self, result=None): + @staticmethod + def _format(result=None): columns = ('Valid', 'Error') if result: diff --git a/mistralclient/commands/v2/base.py b/mistralclient/commands/v2/base.py index cbfe6da8..48428375 100644 --- a/mistralclient/commands/v2/base.py +++ b/mistralclient/commands/v2/base.py @@ -24,12 +24,75 @@ import six DEFAULT_LIMIT = 100 +@six.add_metaclass(abc.ABCMeta) +class MistralFormatter(object): + COLUMNS = [] + + @classmethod + def fields(cls): + return [c[0] for c in cls.COLUMNS] + + @classmethod + def headings(cls): + return [c[1] for c in cls.COLUMNS] + + @classmethod + def format_list(cls, instance=None): + return cls.format(instance, lister=True) + + @staticmethod + def format(instance=None, lister=False): + pass + + @six.add_metaclass(abc.ABCMeta) class MistralLister(command.Lister): @abc.abstractmethod def _get_format_function(self): raise NotImplementedError + def get_parser(self, parsed_args): + parser = super(MistralLister, self).get_parser(parsed_args) + + parser.add_argument( + '--marker', + type=str, + help='The last execution uuid of the previous page, displays list ' + 'of executions after "marker".', + default='', + nargs='?' + ) + parser.add_argument( + '--limit', + type=int, + help='Maximum number of entries to return in a single result. ', + nargs='?' + ) + parser.add_argument( + '--sort_keys', + help='Comma-separated list of sort keys to sort results by. ' + 'Default: created_at. ' + 'Example: mistral execution-list --sort_keys=id,description', + default='created_at', + nargs='?' + ) + parser.add_argument( + '--sort_dirs', + help='Comma-separated list of sort directions. Default: asc. ' + 'Example: mistral execution-list --sort_keys=id,description ' + '--sort_dirs=asc,desc', + default='asc', + nargs='?' + ) + parser.add_argument( + '--filter', + dest='filters', + action='append', + help='Filters. Can be repeated.' + ) + + return parser + @abc.abstractmethod def _get_resources(self, parsed_args): """Gets a list of API resources (e.g. using client).""" @@ -56,6 +119,48 @@ class MistralLister(command.Lister): return f() +@six.add_metaclass(abc.ABCMeta) +class MistralExecutionLister(MistralLister): + def get_parser(self, parsed_args): + parser = super(MistralExecutionLister, self).get_parser(parsed_args) + parser.set_defaults(limit=DEFAULT_LIMIT) + parser.add_argument( + '--oldest', + help='Display the executions starting from the oldest entries ' + 'instead of the newest', + default=False, + action='store_true' + ) + + return parser + + def take_action(self, parsed_args): + self._validate_parsed_args(parsed_args) + + f = self._get_format_function() + + reverse_results = False + if (parsed_args.marker == '' and parsed_args.sort_dirs == 'asc' and + parsed_args.sort_keys == 'created_at' and + not parsed_args.oldest): + reverse_results = True + parsed_args.sort_dirs = 'desc' + + ret = self._get_resources(parsed_args) + if not isinstance(ret, list): + ret = [ret] + + if reverse_results: + ret.reverse() + + data = [f(r)[1] for r in ret] + + if data: + return f()[0], data + else: + return f() + + def cut(string, length=25): if string and len(string) > length: return "%s..." % string[:length] diff --git a/mistralclient/commands/v2/cron_triggers.py b/mistralclient/commands/v2/cron_triggers.py index df18300d..a0ebce4a 100644 --- a/mistralclient/commands/v2/cron_triggers.py +++ b/mistralclient/commands/v2/cron_triggers.py @@ -22,60 +22,67 @@ from mistralclient.commands.v2 import base from mistralclient import utils -def format_list(trigger=None): - return format(trigger, lister=True) - - -def format(trigger=None, lister=False): - columns = ( - 'Name', - 'Workflow', - 'Params', - 'Pattern', +class CronTriggerFormatter(base.MistralFormatter): + COLUMNS = [ + ('name', 'Name'), + ('workflow_name', 'Workflow'), + ('workflow_params', 'Params'), + ('pattern', 'Pattern'), # TODO(rakhmerov): Uncomment when passwords are handled properly. # TODO(rakhmerov): Add 'Workflow input' column. - 'Next execution time', - 'Remaining executions', - 'Created at', - 'Updated at' - ) + ('next_execution_time', 'Next execution time'), + ('remaining_executions', 'Remaining executions'), + ('created_at', 'Created at'), + ('updated_at', 'Updated at') + ] - if trigger: - # TODO(rakhmerov): Add following here: - # TODO(rakhmerov): wf_input = trigger.workflow_input if not lister - # TODO(rakhmerov:): else base.cut(trigger.workflow_input) + @staticmethod + def format(trigger=None, lister=False): + if trigger: + # TODO(rakhmerov): Add following here: + # TODO(rakhmerov): wf_input = trigger.workflow_input if not lister + # TODO(rakhmerov:): else base.cut(trigger.workflow_input) - data = ( - trigger.name, - trigger.workflow_name, - trigger.workflow_params, - trigger.pattern, - # TODO(rakhmerov): Uncomment when passwords are handled properly. - # TODo(rakhmerov): Add 'wf_input' here. - trigger.next_execution_time, - trigger.remaining_executions, - trigger.created_at, - ) + data = ( + trigger.name, + trigger.workflow_name, + trigger.workflow_params, + trigger.pattern, + # TODO(rakhmerov): Uncomment when passwords are handled + # properly. + # TODo(rakhmerov): Add 'wf_input' here. + trigger.next_execution_time, + trigger.remaining_executions, + trigger.created_at, + ) - if hasattr(trigger, 'updated_at'): - data += (trigger.updated_at,) + if hasattr(trigger, 'updated_at'): + data += (trigger.updated_at,) + else: + data += (None,) else: - data += (None,) - else: - data = (tuple('' for _ in range(len(columns))),) + data = (tuple('' for _ in + range(len(CronTriggerFormatter.COLUMNS))),) - return columns, data + return CronTriggerFormatter.headings(), data class List(base.MistralLister): """List all cron triggers.""" def _get_format_function(self): - return format_list + return CronTriggerFormatter.format_list def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine - return mistral_client.cron_triggers.list() + return mistral_client.cron_triggers.list( + marker=parsed_args.marker, + limit=parsed_args.limit, + sort_keys=parsed_args.sort_keys, + sort_dirs=parsed_args.sort_dirs, + fields=CronTriggerFormatter.fields(), + **base.get_filters(parsed_args) + ) class Get(command.ShowOne): @@ -91,7 +98,7 @@ class Get(command.ShowOne): def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine - return format(mistral_client.cron_triggers.get( + return CronTriggerFormatter.format(mistral_client.cron_triggers.get( parsed_args.cron_trigger )) @@ -189,7 +196,7 @@ class Create(command.ShowOne): parsed_args.count ) - return format(trigger) + return CronTriggerFormatter.format(trigger) class Delete(command.Command): diff --git a/mistralclient/commands/v2/environments.py b/mistralclient/commands/v2/environments.py index 956efd4e..8ae8039e 100644 --- a/mistralclient/commands/v2/environments.py +++ b/mistralclient/commands/v2/environments.py @@ -21,78 +21,63 @@ from mistralclient.commands.v2 import base from mistralclient import utils -def format_list(environment=None): - columns = ( - 'Name', - 'Description', - 'Scope', - 'Created at', - 'Updated at' - ) +class EnvironmentFormatter(base.MistralFormatter): + COLUMNS = [ + ('name', 'Name'), + ('description', 'Description'), + ('variables', 'Variables'), + ('scope', 'Scope'), + ('created_at', 'Created at'), + ('updated_at', 'Updated at'), + ] + LIST_COLUMN_FIELD_NAMES = [c[0] for c in COLUMNS if c[0] != 'variables'] + LIST_COLUMN_HEADING_NAMES = [c[1] for c in COLUMNS if c[0] != 'variables'] - if environment: - data = ( - environment.name, - environment.description, - environment.scope, - environment.created_at, - ) - - if hasattr(environment, 'updated_at'): - data += (environment.updated_at or '',) + @staticmethod + def format(environment=None, lister=False): + if lister: + columns = EnvironmentFormatter.LIST_COLUMN_HEADING_NAMES else: - data += (None,) + columns = EnvironmentFormatter.headings() - else: - data = (tuple('' for _ in range(len(columns))),) - - return columns, data - - -def format(environment=None): - columns = ( - 'Name', - 'Description', - 'Variables', - 'Scope', - 'Created at', - 'Updated at' - ) - - if environment: - data = (environment.name,) - - if hasattr(environment, 'description'): - data += (environment.description or '',) + if environment: + data = ( + environment.name,) + if hasattr(environment, 'description'): + data += (environment.description or '',) + else: + data += (None,) + if not lister: + data += (json.dumps(environment.variables, indent=4),) + data += ( + environment.scope, + environment.created_at,) + if hasattr(environment, 'updated_at'): + data += (environment.updated_at or '',) + else: + data += (None,) else: - data += (None,) + data = (tuple('' for _ in range(len(columns))),) - data += ( - json.dumps(environment.variables, indent=4), - environment.scope, - environment.created_at, - ) - - if hasattr(environment, 'updated_at'): - data += (environment.updated_at or '',) - else: - data += (None,) - - else: - data = (tuple('' for _ in range(len(columns))),) - - return columns, data + return columns, data class List(base.MistralLister): """List all environments.""" def _get_format_function(self): - return format_list + return EnvironmentFormatter.format_list def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine - return mistral_client.environments.list() + return mistral_client.environments.list( + marker=parsed_args.marker, + limit=parsed_args.limit, + sort_keys=parsed_args.sort_keys, + sort_dirs=parsed_args.sort_dirs, + fields=EnvironmentFormatter.fields(), + **base.get_filters(parsed_args) + ) class Get(command.ShowOne): @@ -132,7 +117,7 @@ class Get(command.ShowOne): return columns, data - return format(environment) + return EnvironmentFormatter.format(environment) class Create(command.ShowOne): @@ -155,7 +140,7 @@ class Create(command.ShowOne): mistral_client = self.app.client_manager.workflow_engine environment = mistral_client.environments.create(**data) - return format(environment) + return EnvironmentFormatter.format(environment) class Delete(command.Command): @@ -203,4 +188,4 @@ class Update(command.ShowOne): mistral_client = self.app.client_manager.workflow_engine environment = mistral_client.environments.update(**data) - return format(environment) + return EnvironmentFormatter.format(environment) diff --git a/mistralclient/commands/v2/event_triggers.py b/mistralclient/commands/v2/event_triggers.py index a2d845fc..9f09aee8 100644 --- a/mistralclient/commands/v2/event_triggers.py +++ b/mistralclient/commands/v2/event_triggers.py @@ -19,54 +19,60 @@ from mistralclient.commands.v2 import base from mistralclient import utils -def format_list(trigger=None): - return format(trigger, lister=True) +class EventTriggerFormatter(base.MistralFormatter): + COLUMNS = [ + ('id', 'ID'), + ('name', 'Name'), + ('workflow_id', 'Workflow ID'), + ('workflow_params', 'Params'), + ('exchange', 'Exchange'), + ('topic', 'Topic'), + ('event', 'Event'), + ('created_at', 'Created at'), + ('updated_at', 'Updated at') + ] + @staticmethod + def format(trigger=None, lister=False): + if trigger: + data = ( + trigger.id, + trigger.name, + trigger.workflow_id, + trigger.workflow_params, + trigger.exchange, + trigger.topic, + trigger.event, + trigger.created_at, + ) -def format(trigger=None, lister=False): - columns = ( - 'ID', - 'Name', - 'Workflow ID', - 'Params', - 'Exchange', - 'Topic', - 'Event', - 'Created at', - 'Updated at' - ) - - if trigger: - data = ( - trigger.id, - trigger.name, - trigger.workflow_id, - trigger.workflow_params, - trigger.exchange, - trigger.topic, - trigger.event, - trigger.created_at, - ) - - if hasattr(trigger, 'updated_at'): - data += (trigger.updated_at,) + if hasattr(trigger, 'updated_at'): + data += (trigger.updated_at,) + else: + data += (None,) else: - data += (None,) - else: - data = (tuple('' for _ in range(len(columns))),) + data = (tuple('' for _ in + range(len(EventTriggerFormatter.COLUMNS))),) - return columns, data + return EventTriggerFormatter.headings(), data class List(base.MistralLister): """List all event triggers.""" def _get_format_function(self): - return format_list + return EventTriggerFormatter.format_list def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine - return mistral_client.event_triggers.list() + return mistral_client.event_triggers.list( + marker=parsed_args.marker, + limit=parsed_args.limit, + sort_keys=parsed_args.sort_keys, + sort_dirs=parsed_args.sort_dirs, + fields=EventTriggerFormatter.fields(), + **base.get_filters(parsed_args) + ) class Get(command.ShowOne): @@ -82,9 +88,8 @@ class Get(command.ShowOne): def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine - return format(mistral_client.event_triggers.get( - parsed_args.event_trigger - )) + return EventTriggerFormatter.format(mistral_client.event_triggers.get( + parsed_args.event_trigger)) class Create(command.ShowOne): @@ -140,7 +145,7 @@ class Create(command.ShowOne): wf_params, ) - return format(trigger) + return EventTriggerFormatter.format(trigger) class Delete(command.Command): diff --git a/mistralclient/commands/v2/executions.py b/mistralclient/commands/v2/executions.py index d4533a47..9e4b4e27 100644 --- a/mistralclient/commands/v2/executions.py +++ b/mistralclient/commands/v2/executions.py @@ -29,54 +29,54 @@ from mistralclient import utils LOG = logging.getLogger(__name__) -def format_list(execution=None): - return format(execution, lister=True) +class ExecutionFormatter(base.MistralFormatter): + COLUMNS = [ + ('id', 'ID'), + ('workflow_id', 'Workflow ID'), + ('workflow_name', 'Workflow name'), + ('workflow_namespace', 'Workflow namespace'), + ('description', 'Description'), + ('task_execution_id', 'Task Execution ID'), + ('root_execution_id', 'Root Execution ID'), + ('state', 'State'), + ('state_info', 'State info'), + ('created_at', 'Created at'), + ('updated_at', 'Updated at'), + ] + + @staticmethod + def format(execution=None, lister=False): + # TODO(nmakhotkin) Add parent task id when it's implemented in API. + + if execution: + state_info = (execution.state_info if not lister + else base.cut(execution.state_info)) + + data = ( + execution.id, + execution.workflow_id, + execution.workflow_name, + execution.workflow_namespace, + execution.description, + execution.task_execution_id or '', + execution.root_execution_id or '', + execution.state, + state_info, + execution.created_at, + execution.updated_at or '' + ) + else: + data = (tuple('' for _ in + range(len(ExecutionFormatter.COLUMNS))),) + + return ExecutionFormatter.headings(), data -def format(execution=None, lister=False): - columns = ( - 'ID', - 'Workflow ID', - 'Workflow name', - 'Workflow namespace', - 'Description', - 'Task Execution ID', - 'Root Execution ID', - 'State', - 'State info', - 'Created at', - 'Updated at' - ) - # TODO(nmakhotkin) Add parent task id when it's implemented in API. - - if execution: - state_info = (execution.state_info if not lister - else base.cut(execution.state_info)) - - data = ( - execution.id, - execution.workflow_id, - execution.workflow_name, - execution.workflow_namespace, - execution.description, - execution.task_execution_id or '', - execution.root_execution_id or '', - execution.state, - state_info, - execution.created_at, - execution.updated_at or '' - ) - else: - data = (tuple('' for _ in range(len(columns))),) - - return columns, data - - -class List(base.MistralLister): +class List(base.MistralExecutionLister): """List all executions.""" def _get_format_function(self): - return format_list + return ExecutionFormatter.format_list def get_parser(self, parsed_args): parser = super(List, self).get_parser(parsed_args) @@ -86,58 +86,10 @@ class List(base.MistralLister): help="Parent task execution ID associated with workflow " "execution list.", ) - parser.add_argument( - '--marker', - type=str, - help='The last execution uuid of the previous page, displays list ' - 'of executions after "marker".', - default='', - nargs='?' - ) - parser.add_argument( - '--limit', - type=int, - help='Maximum number of executions to return in a single result. ' - 'limit is set to %s by default. Use --limit -1 to fetch the ' - 'full result set.' % base.DEFAULT_LIMIT, - nargs='?' - ) - parser.add_argument( - '--sort_keys', - help='Comma-separated list of sort keys to sort results by. ' - 'Default: created_at. ' - 'Example: mistral execution-list --sort_keys=id,description', - default='created_at', - nargs='?' - ) - parser.add_argument( - '--sort_dirs', - help='Comma-separated list of sort directions. Default: asc. ' - 'Example: mistral execution-list --sort_keys=id,description ' - '--sort_dirs=asc,desc', - default='asc', - nargs='?' - ) - parser.add_argument( - '--filter', - dest='filters', - action='append', - help='Filters. Can be repeated.' - ) return parser def _get_resources(self, parsed_args): - if parsed_args.limit is None: - parsed_args.limit = base.DEFAULT_LIMIT - - LOG.info( - "limit is set to %s by default. Set " - "the limit explicitly using \'--limit\', if required. " - "Use \'--limit\' -1 to fetch the full result set.", - base.DEFAULT_LIMIT - ) - mistral_client = self.app.client_manager.workflow_engine return mistral_client.executions.list( @@ -146,6 +98,7 @@ class List(base.MistralLister): limit=parsed_args.limit, sort_keys=parsed_args.sort_keys, sort_dirs=parsed_args.sort_dirs, + fields=ExecutionFormatter.fields(), **base.get_filters(parsed_args) ) @@ -164,7 +117,7 @@ class Get(command.ShowOne): mistral_client = self.app.client_manager.workflow_engine execution = mistral_client.executions.get(parsed_args.execution) - return format(execution) + return ExecutionFormatter.format(execution) class Create(command.ShowOne): @@ -235,7 +188,7 @@ class Create(command.ShowOne): **params ) - return format(execution) + return ExecutionFormatter.format(execution) class Delete(command.Command): @@ -322,7 +275,7 @@ class Update(command.ShowOne): env=env ) - return format(execution) + return ExecutionFormatter.format(execution) class GetInput(command.Command): diff --git a/mistralclient/commands/v2/members.py b/mistralclient/commands/v2/members.py index a1460658..842c986c 100644 --- a/mistralclient/commands/v2/members.py +++ b/mistralclient/commands/v2/members.py @@ -19,46 +19,44 @@ from mistralclient.commands.v2 import base from mistralclient import exceptions -def format_list(member=None): - return format(member, lister=True) +class MemberFormatter(base.MistralFormatter): + COLUMNS = [ + ('resource_id', 'Resource ID'), + ('resource_type', 'Resource Type'), + ('project_id', 'Resource Owner'), + ('member_id', 'Member ID'), + ('status', 'Status'), + ('created_at', 'Created at'), + ('updated_at', 'Updated at') + ] + @staticmethod + def format(member=None, lister=False): + if member: + data = ( + member.resource_id, + member.resource_type, + member.project_id, + member.member_id, + member.status, + member.created_at, + ) -def format(member=None, lister=False): - columns = ( - 'Resource ID', - 'Resource Type', - 'Resource Owner', - 'Member ID', - 'Status', - 'Created at', - 'Updated at' - ) - - if member: - data = ( - member.resource_id, - member.resource_type, - member.project_id, - member.member_id, - member.status, - member.created_at, - ) - - if hasattr(member, 'updated_at'): - data += (member.updated_at,) + if hasattr(member, 'updated_at'): + data += (member.updated_at,) + else: + data += (None,) else: - data += (None,) - else: - data = (tuple('' for _ in range(len(columns))),) + data = (tuple('' for _ in range(len(MemberFormatter.COLUMNS))),) - return columns, data + return MemberFormatter.headings(), data class List(base.MistralLister): """List all members.""" def _get_format_function(self): - return format_list + return MemberFormatter.format_list def get_parser(self, parsed_args): parser = super(List, self).get_parser(parsed_args) @@ -114,7 +112,7 @@ class Get(command.ShowOne): parsed_args.member_id, ) - return format(member) + return MemberFormatter.format(member) class Create(command.ShowOne): @@ -146,7 +144,7 @@ class Create(command.ShowOne): parsed_args.member_id, ) - return format(member) + return MemberFormatter.format(member) class Delete(command.Command): @@ -232,4 +230,4 @@ class Update(command.ShowOne): status=parsed_args.status ) - return format(member) + return MemberFormatter.format(member) diff --git a/mistralclient/commands/v2/tasks.py b/mistralclient/commands/v2/tasks.py index bb48e345..e7f608e5 100644 --- a/mistralclient/commands/v2/tasks.py +++ b/mistralclient/commands/v2/tasks.py @@ -27,7 +27,7 @@ from mistralclient import utils LOG = logging.getLogger(__name__) -class TaskFormatter(object): +class TaskFormatter(base.MistralFormatter): COLUMNS = [ ('id', 'ID'), ('name', 'Name'), @@ -40,13 +40,6 @@ class TaskFormatter(object): ('updated_at', 'Updated at'), ] - COLUMN_FIELD_NAMES = list(zip(*COLUMNS))[0] - COLUMN_HEADING_NAMES = list(zip(*COLUMNS))[1] - - @staticmethod - def format_list(task=None): - return TaskFormatter.format(task, lister=True) - @staticmethod def format(task=None, lister=False): if task: @@ -67,10 +60,10 @@ class TaskFormatter(object): else: data = (tuple('' for _ in range(len(TaskFormatter.COLUMNS))),) - return TaskFormatter.COLUMN_HEADING_NAMES, data + return TaskFormatter.headings(), data -class List(base.MistralLister): +class List(base.MistralExecutionLister): """List all tasks.""" def get_parser(self, prog_name): @@ -81,43 +74,21 @@ class List(base.MistralLister): nargs='?', help='Workflow execution ID associated with list of Tasks.' ) - parser.add_argument( - '--filter', - dest='filters', - action='append', - help='Filters. Can be repeated.' - ) - parser.add_argument( - '--limit', - type=int, - help='Maximum number of tasks to return in a single result. ' - 'limit is set to %s by default. Use --limit -1 to fetch the ' - 'full result set.' % base.DEFAULT_LIMIT, - nargs='?' - ) - return parser def _get_format_function(self): return TaskFormatter.format_list def _get_resources(self, parsed_args): - if parsed_args.limit is None: - parsed_args.limit = base.DEFAULT_LIMIT - - LOG.info( - "limit is set to %s by default. Set " - "the limit explicitly using \'--limit\', if required. " - "Use \'--limit\' -1 to fetch the full result set.", - base.DEFAULT_LIMIT - ) - mistral_client = self.app.client_manager.workflow_engine return mistral_client.tasks.list( parsed_args.workflow_execution, + marker=parsed_args.marker, limit=parsed_args.limit, - fields=TaskFormatter.COLUMN_FIELD_NAMES, + sort_keys=parsed_args.sort_keys, + sort_dirs=parsed_args.sort_dirs, + fields=TaskFormatter.fields(), **base.get_filters(parsed_args) ) diff --git a/mistralclient/commands/v2/workbooks.py b/mistralclient/commands/v2/workbooks.py index 3c015373..faa5e4d1 100644 --- a/mistralclient/commands/v2/workbooks.py +++ b/mistralclient/commands/v2/workbooks.py @@ -21,45 +21,54 @@ from mistralclient.commands.v2 import base from mistralclient import utils -def format(workbook=None): - columns = ( - 'Name', - 'Namespace', - 'Tags', - 'Scope', - 'Created at', - 'Updated at' - ) +class WorkbookFormatter(base.MistralFormatter): + COLUMNS = [ + ('name', 'Name'), + ('namespace', 'Namespace'), + ('tags', 'Tags'), + ('scope', 'Scope'), + ('created_at', 'Created at'), + ('updated_at', 'Updated at') + ] - if workbook: - data = ( - workbook.name, - workbook.namespace, - base.wrap(', '.join(workbook.tags or '')) or '', - workbook.scope, - workbook.created_at, - ) + @staticmethod + def format(workbook=None, lister=False): + if workbook: + data = ( + workbook.name, + workbook.namespace, + base.wrap(', '.join(workbook.tags or '')) or '', + workbook.scope, + workbook.created_at, + ) + + if hasattr(workbook, 'updated_at'): + data += (workbook.updated_at,) + else: + data += (None,) - if hasattr(workbook, 'updated_at'): - data += (workbook.updated_at,) else: - data += (None,) + data = (tuple('' for _ in range(len(WorkbookFormatter.COLUMNS))),) - else: - data = (tuple('' for _ in range(len(columns))),) - - return columns, data + return WorkbookFormatter.headings(), data class List(base.MistralLister): """List all workbooks.""" def _get_format_function(self): - return format + return WorkbookFormatter.format def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine - return mistral_client.workbooks.list() + return mistral_client.workbooks.list( + marker=parsed_args.marker, + limit=parsed_args.limit, + sort_keys=parsed_args.sort_keys, + sort_dirs=parsed_args.sort_dirs, + fields=WorkbookFormatter.fields(), + **base.get_filters(parsed_args) + ) class Get(command.ShowOne): @@ -86,7 +95,7 @@ class Get(command.ShowOne): parsed_args.namespace ) - return format(workbook) + return WorkbookFormatter.format(workbook) class Create(command.ShowOne): @@ -125,7 +134,7 @@ class Create(command.ShowOne): scope=scope ) - return format(workbook) + return WorkbookFormatter.format(workbook) class Delete(command.Command): @@ -190,7 +199,7 @@ class Update(command.ShowOne): scope=scope ) - return format(workbook) + return WorkbookFormatter.format(workbook) class GetDefinition(command.Command): @@ -212,8 +221,8 @@ class GetDefinition(command.Command): class Validate(command.ShowOne): """Validate workbook.""" - - def _format(self, result=None): + @staticmethod + def _format(result=None): columns = ('Valid', 'Error') if result: diff --git a/mistralclient/commands/v2/workflows.py b/mistralclient/commands/v2/workflows.py index 66851d35..a94da0c7 100644 --- a/mistralclient/commands/v2/workflows.py +++ b/mistralclient/commands/v2/workflows.py @@ -22,69 +22,65 @@ from mistralclient.commands.v2 import base from mistralclient import utils -def format_list(workflow=None): - return format(workflow, lister=True) +class WorkflowFormatter(base.MistralFormatter): + COLUMNS = [ + ('id', 'ID'), + ('name', 'Name'), + ('namespace', 'Namespace'), + ('project_id', 'Project ID'), + ('tags', 'Tags'), + ('input', 'Input'), + ('scope', 'Scope'), + ('created_at', 'Created at'), + ('updated_at', 'Updated at') + ] + @staticmethod + def format(workflow=None, lister=False): + if workflow: + tags = getattr(workflow, 'tags', None) or [] -def format(workflow=None, lister=False): - columns = ( - 'ID', - 'Name', - 'Namespace', - 'Project ID', - 'Tags', - 'Input', - 'Scope', - 'Created at', - 'Updated at' - ) + data = ( + workflow.id, + workflow.name, + workflow.namespace, + workflow.project_id, + base.wrap(', '.join(tags)) or '', + workflow.input if not lister else base.cut(workflow.input), + workflow.scope, + workflow.created_at + ) - if workflow: - tags = getattr(workflow, 'tags', None) or [] - - data = ( - workflow.id, - workflow.name, - workflow.namespace, - workflow.project_id, - base.wrap(', '.join(tags)) or '', - workflow.input if not lister else base.cut(workflow.input), - workflow.scope, - workflow.created_at - ) - - if hasattr(workflow, 'updated_at'): - data += (workflow.updated_at,) + if hasattr(workflow, 'updated_at'): + data += (workflow.updated_at,) + else: + data += (None,) else: - data += (None,) - else: - data = (tuple('' for _ in range(len(columns))),) + data = (tuple('' for _ in range(len(WorkflowFormatter.COLUMNS))),) - return columns, data + return WorkflowFormatter.headings(), data class List(base.MistralLister): """List all workflows.""" def _get_format_function(self): - return format_list + return WorkflowFormatter.format_list def get_parser(self, prog_name): parser = super(List, self).get_parser(prog_name) - parser.add_argument( - '--filter', - dest='filters', - action='append', - help='Filters. Can be repeated.' - ) - return parser def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.workflows.list( + marker=parsed_args.marker, + limit=parsed_args.limit, + sort_keys=parsed_args.sort_keys, + sort_dirs=parsed_args.sort_dirs, + fields=WorkflowFormatter.fields(), **base.get_filters(parsed_args) ) @@ -113,7 +109,7 @@ class Get(show.ShowOne): parsed_args.namespace ) - return format(wf) + return WorkflowFormatter.format(wf) class Create(base.MistralLister): @@ -142,7 +138,7 @@ class Create(base.MistralLister): return parser def _get_format_function(self): - return format_list + return WorkflowFormatter.format_list def _validate_parsed_args(self, parsed_args): if not parsed_args.definition: @@ -223,7 +219,7 @@ class Update(base.MistralLister): return parser def _get_format_function(self): - return format_list + return WorkflowFormatter.format_list def _get_resources(self, parsed_args): scope = 'public' if parsed_args.public else 'private' @@ -267,8 +263,8 @@ class GetDefinition(command.Command): class Validate(show.ShowOne): """Validate workflow.""" - - def _format(self, result=None): + @staticmethod + def _format(result=None): columns = ('Valid', 'Error') if result: diff --git a/mistralclient/tests/unit/v2/test_cli_executions.py b/mistralclient/tests/unit/v2/test_cli_executions.py index 080e2df6..00fa05d9 100644 --- a/mistralclient/tests/unit/v2/test_cli_executions.py +++ b/mistralclient/tests/unit/v2/test_cli_executions.py @@ -225,7 +225,7 @@ class TestCLIExecutionsV2(base.BaseCommandTest): ) def test_list(self): - self.client.executions.list.return_value = [EXEC, SUB_WF_EXEC] + self.client.executions.list.return_value = [SUB_WF_EXEC, EXEC] result = self.call(execution_cmd.List) @@ -239,28 +239,46 @@ class TestCLIExecutionsV2(base.BaseCommandTest): self.call(execution_cmd.List) self.client.executions.list.assert_called_once_with( + fields=execution_cmd.ExecutionFormatter.fields(), limit=100, marker='', - sort_dirs='asc', + sort_dirs='desc', sort_keys='created_at', task=None ) + self.call( + execution_cmd.List, + app_args=[ + '--oldest' + ] + ) + + self.client.executions.list.assert_called_with( + fields=execution_cmd.ExecutionFormatter.fields(), + limit=100, + marker='', + sort_keys='created_at', + sort_dirs='asc', + task=None + ) + self.call( execution_cmd.List, app_args=[ '--limit', '5', - '--sort_dirs', 'id, Workflow', - '--sort_keys', 'desc', + '--sort_keys', 'id, Workflow', + '--sort_dirs', 'desc', '--marker', 'abc' ] ) self.client.executions.list.assert_called_with( + fields=execution_cmd.ExecutionFormatter.fields(), limit=5, marker='abc', - sort_dirs='id, Workflow', - sort_keys='desc', + sort_keys='id, Workflow', + sort_dirs='desc', task=None ) diff --git a/mistralclient/tests/unit/v2/test_cli_tasks.py b/mistralclient/tests/unit/v2/test_cli_tasks.py index f3bea89f..08cdda1a 100644 --- a/mistralclient/tests/unit/v2/test_cli_tasks.py +++ b/mistralclient/tests/unit/v2/test_cli_tasks.py @@ -61,7 +61,7 @@ class TestCLITasksV2(base.BaseCommandTest): self.assertEqual([EXPECTED_TASK_RESULT], result[1]) self.assertEqual( self.client.tasks.list.call_args[1]["fields"], - task_cmd.TaskFormatter.COLUMN_FIELD_NAMES + task_cmd.TaskFormatter.fields() ) def test_list_with_workflow_execution(self): diff --git a/releasenotes/notes/execution-list-default-behavior-change-225010204e32bc89.yaml b/releasenotes/notes/execution-list-default-behavior-change-225010204e32bc89.yaml new file mode 100644 index 00000000..b32dedf3 --- /dev/null +++ b/releasenotes/notes/execution-list-default-behavior-change-225010204e32bc89.yaml @@ -0,0 +1,11 @@ +--- +critical: + - | + The default behavior of the action-execution-list, execution-list and + task-list commands has been changed. Instead of returning the oldest + N records (default 100 or --limit specified value) by default, + they now return the most recent N records, when no other sort_dir, + sort_key or marker values are provided. If the user specifies --oldest + or any of the --marker, --sort_key or --sort_dir options, the new + behavior is disabled and the commands will work according to the + user-supplied options.