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
This commit is contained in:
Bob Haddleton
2018-03-21 10:34:46 -05:00
committed by Bob.Haddleton
parent 7a1c8cc240
commit d53da3629f
24 changed files with 686 additions and 649 deletions

View File

@@ -14,9 +14,12 @@
import copy import copy
import json import json
import six
from keystoneauth1 import exceptions from keystoneauth1 import exceptions
urlparse = six.moves.urllib.parse
class Resource(object): class Resource(object):
resource_name = 'Something' resource_name = 'Something'
@@ -71,7 +74,41 @@ class ResourceManager(object):
self.http_client = http_client self.http_client = http_client
def find(self, **kwargs): 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): def _ensure_not_empty(self, **kwargs):
for name, value in kwargs.items(): for name, value in kwargs.items():

View File

@@ -13,12 +13,9 @@
# limitations under the License. # limitations under the License.
import json import json
import six
from mistralclient.api import base from mistralclient.api import base
urlparse = six.moves.urllib.parse
class ActionExecution(base.Resource): class ActionExecution(base.Resource):
resource_name = 'ActionExecution' resource_name = 'ActionExecution'
@@ -62,7 +59,8 @@ class ActionExecutionManager(base.ResourceManager):
return self._update('/action_executions/%s' % id, data) 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' url = '/action_executions'
if task_execution_id: if task_execution_id:
@@ -70,13 +68,14 @@ class ActionExecutionManager(base.ResourceManager):
url += "%s" url += "%s"
qparams = {} query_string = self._build_query_params(
marker=marker,
if limit and limit > 0: limit=limit,
qparams['limit'] = limit sort_keys=sort_keys,
sort_dirs=sort_dirs,
query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) fields=fields,
if qparams else "") filters=filters
)
return self._list(url % query_string, response_key='action_executions') return self._list(url % query_string, response_key='action_executions')

View File

@@ -12,14 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import six
from keystoneauth1 import exceptions from keystoneauth1 import exceptions
from mistralclient.api import base from mistralclient.api import base
from mistralclient import utils from mistralclient import utils
urlparse = six.moves.urllib.parse
class Action(base.Resource): class Action(base.Resource):
resource_name = 'Action' resource_name = 'Action'
@@ -75,26 +71,16 @@ class ActionManager(base.ResourceManager):
for resource_data in base.extract_json(resp, 'actions')] for resource_data in base.extract_json(resp, 'actions')]
def list(self, marker='', limit=None, sort_keys='', sort_dirs='', def list(self, marker='', limit=None, sort_keys='', sort_dirs='',
**filters): fields='', **filters):
qparams = {}
if marker: query_string = self._build_query_params(
qparams['marker'] = marker marker=marker,
limit=limit,
if limit and limit > 0: sort_keys=sort_keys,
qparams['limit'] = limit sort_dirs=sort_dirs,
fields=fields,
if sort_keys: filters=filters
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 "")
return self._list( return self._list(
'/actions%s' % query_string, '/actions%s' % query_string,

View File

@@ -54,8 +54,19 @@ class CronTriggerManager(base.ResourceManager):
return self._create('/cron_triggers', data) return self._create('/cron_triggers', data)
def list(self): def list(self, marker='', limit=None, sort_keys='', fields='',
return self._list('/cron_triggers', response_key='cron_triggers') 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): def get(self, name):
self._ensure_not_empty(name=name) self._ensure_not_empty(name=name)

View File

@@ -13,7 +13,6 @@
# limitations under the License. # limitations under the License.
import json import json
import six import six
from mistralclient.api import base from mistralclient.api import base
@@ -71,8 +70,19 @@ class EnvironmentManager(base.ResourceManager):
return self._update('/environments', kwargs) return self._update('/environments', kwargs)
def list(self): def list(self, marker='', limit=None, sort_keys='', sort_dirs='',
return self._list('/environments', response_key='environments') 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): def get(self, name):
self._ensure_not_empty(name=name) self._ensure_not_empty(name=name)

View File

@@ -47,8 +47,19 @@ class EventTriggerManager(base.ResourceManager):
return self._create('/event_triggers', data) return self._create('/event_triggers', data)
def list(self): def list(self, marker='', limit=None, sort_keys='', sort_dirs='',
return self._list('/event_triggers', response_key='event_triggers') 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): def get(self, id):
self._ensure_not_empty(id=id) self._ensure_not_empty(id=id)

View File

@@ -14,16 +14,13 @@
# limitations under the License. # limitations under the License.
import json import json
from oslo_utils import uuidutils
import six import six
from oslo_utils import uuidutils
from mistralclient.api import base from mistralclient.api import base
urlparse = six.moves.urllib.parse
class Execution(base.Resource): class Execution(base.Resource):
resource_name = 'Execution' resource_name = 'Execution'
@@ -79,29 +76,18 @@ class ExecutionManager(base.ResourceManager):
return self._update('/executions/%s' % id, data) return self._update('/executions/%s' % id, data)
def list(self, task=None, marker='', limit=None, sort_keys='', def list(self, task=None, marker='', limit=None, sort_keys='',
sort_dirs='', **filters): sort_dirs='', fields='', **filters):
qparams = {}
if task: if task:
qparams['task_execution_id'] = task filters['task_execution_id'] = task
if marker: query_string = self._build_query_params(
qparams['marker'] = marker marker=marker,
limit=limit,
if limit and limit > 0: sort_keys=sort_keys,
qparams['limit'] = limit sort_dirs=sort_dirs,
fields=fields,
if sort_keys: filters=filters
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 "")
return self._list( return self._list(
'/executions%s' % query_string, '/executions%s' % query_string,
@@ -116,11 +102,9 @@ class ExecutionManager(base.ResourceManager):
def delete(self, id, force=None): def delete(self, id, force=None):
self._ensure_not_empty(id=id) self._ensure_not_empty(id=id)
qparams = {} qparams = {}
if force: if force:
qparams['force'] = True qparams['force'] = True
query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) query_string = self._build_query_params(filters=qparams)
if qparams else "")
self._delete('/executions/%s%s' % (id, query_string)) self._delete('/executions/%s%s' % (id, query_string))

View File

@@ -14,12 +14,9 @@
# limitations under the License. # limitations under the License.
import json import json
import six
from mistralclient.api import base from mistralclient.api import base
urlparse = six.moves.urllib.parse
class Task(base.Resource): class Task(base.Resource):
resource_name = 'Task' resource_name = 'Task'
@@ -29,7 +26,7 @@ class TaskManager(base.ResourceManager):
resource_class = Task resource_class = Task
def list(self, workflow_execution_id=None, marker='', limit=None, 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' url = '/tasks'
if workflow_execution_id: if workflow_execution_id:
@@ -37,28 +34,14 @@ class TaskManager(base.ResourceManager):
url += '%s' url += '%s'
qparams = {} query_string = self._build_query_params(
marker=marker,
if marker: limit=limit,
qparams['marker'] = marker sort_keys=sort_keys,
sort_dirs=sort_dirs,
if limit and limit > 0: fields=fields,
qparams['limit'] = limit filters=filters
)
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 "")
return self._list(url % query_string, response_key='tasks') return self._list(url % query_string, response_key='tasks')

View File

@@ -82,12 +82,20 @@ class WorkbookManager(base.ResourceManager):
return self.resource_class(self, base.extract_json(resp, None)) return self.resource_class(self, base.extract_json(resp, None))
def list(self, namespace=''): def list(self, namespace='', marker='', limit=None, sort_keys='',
return self._list( sort_dirs='', fields='', **filters):
self._get_workbooks_url(None, namespace), query_string = self._build_query_params(
response_key='workbooks' 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=''): def get(self, name, namespace=''):
self._ensure_not_empty(name=name) self._ensure_not_empty(name=name)

View File

@@ -13,16 +13,11 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import six
from keystoneauth1 import exceptions from keystoneauth1 import exceptions
from mistralclient.api import base from mistralclient.api import base
from mistralclient import utils from mistralclient import utils
urlparse = six.moves.urllib.parse
class Workflow(base.Resource): class Workflow(base.Resource):
resource_name = 'Workflow' resource_name = 'Workflow'
@@ -80,29 +75,18 @@ class WorkflowManager(base.ResourceManager):
for resource_data in base.extract_json(resp, 'workflows')] for resource_data in base.extract_json(resp, 'workflows')]
def list(self, namespace='', marker='', limit=None, sort_keys='', def list(self, namespace='', marker='', limit=None, sort_keys='',
sort_dirs='', **filters): sort_dirs='', fields='', **filters):
qparams = {}
if namespace: if namespace:
qparams['namespace'] = namespace filters['namespace'] = namespace
if marker: query_string = self._build_query_params(
qparams['marker'] = marker marker=marker,
limit=limit,
if limit and limit > 0: sort_keys=sort_keys,
qparams['limit'] = limit sort_dirs=sort_dirs,
fields=fields,
if sort_keys: filters=filters
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 "")
return self._list( return self._list(
'/workflows%s' % query_string, '/workflows%s' % query_string,

View File

@@ -25,64 +25,46 @@ from mistralclient import utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
def format_list(action_ex=None): class ActionExecutionFormatter(base.MistralFormatter):
columns = ( COLUMNS = [
'ID', ('id', 'ID'),
'Name', ('name', 'Name'),
'Workflow name', ('workflow_name', 'Workflow name'),
'Workflow namespace', ('workflow_namespace', 'Workflow namespace'),
'Task name', ('task_name', 'Task name'),
'Task ID', ('task_execution_id', 'Task ID'),
'State', ('state', 'State'),
'Accepted', ('state_info', 'State info'),
'Created at', ('accepted', 'Accepted'),
'Updated at' ('created_at', 'Created at'),
) ('updated_at', 'Updated at'),
]
if action_ex: LIST_COLUMN_FIELD_NAMES = [c[0] for c in COLUMNS if c[0] != 'state_info']
data = ( LIST_COLUMN_HEADING_NAMES = [c[1] for c in COLUMNS if c[0] != 'state_info']
action_ex.id,
action_ex.name, @staticmethod
action_ex.workflow_name, def format(action_ex=None, lister=False):
action_ex.workflow_namespace, if lister:
action_ex.task_name if hasattr(action_ex, 'task_name') else None, columns = ActionExecutionFormatter.LIST_COLUMN_HEADING_NAMES
action_ex.task_execution_id,
action_ex.state,
action_ex.accepted,
action_ex.created_at,
action_ex.updated_at or '<none>'
)
else: else:
data = (tuple('' for _ in range(len(columns))),) columns = ActionExecutionFormatter.headings()
return columns, data
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: if action_ex:
if hasattr(action_ex, 'task_name'):
task_name = action_ex.task_name
else:
task_name = None
data = ( data = (
action_ex.id, action_ex.id,
action_ex.name, action_ex.name,
action_ex.workflow_name, action_ex.workflow_name,
action_ex.workflow_namespace, action_ex.workflow_namespace,
action_ex.task_name if hasattr(action_ex, 'task_name') else None, task_name,
action_ex.task_execution_id, action_ex.task_execution_id,
action_ex.state, action_ex.state,)
action_ex.state_info, if not lister:
data += (action_ex.state_info,)
data += (
action_ex.accepted, action_ex.accepted,
action_ex.created_at, action_ex.created_at,
action_ex.updated_at or '<none>' action_ex.updated_at or '<none>'
@@ -166,18 +148,18 @@ class Create(command.ShowOne):
) )
if not parsed_args.run_sync and parsed_args.save_result: if not parsed_args.run_sync and parsed_args.save_result:
return format(action_ex) return ActionExecutionFormatter.format(action_ex)
else: else:
self.app.stdout.write("%s\n" % action_ex.output) self.app.stdout.write("%s\n" % action_ex.output)
return None, None return None, None
class List(base.MistralLister): class List(base.MistralExecutionLister):
"""List all Action executions.""" """List all Action executions."""
def _get_format_function(self): def _get_format_function(self):
return format_list return ActionExecutionFormatter.format_list
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(List, self).get_parser(prog_name) parser = super(List, self).get_parser(prog_name)
@@ -187,33 +169,21 @@ class List(base.MistralLister):
nargs='?', nargs='?',
help='Task execution ID.' 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 return parser
def _get_resources(self, parsed_args): 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 mistral_client = self.app.client_manager.workflow_engine
return mistral_client.action_executions.list( return mistral_client.action_executions.list(
parsed_args.task_execution_id, parsed_args.task_execution_id,
marker=parsed_args.marker,
limit=parsed_args.limit, 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 parsed_args.action_execution
) )
return format(execution) return ActionExecutionFormatter.format(execution)
class Update(command.ShowOne): class Update(command.ShowOne):
@@ -270,7 +240,7 @@ class Update(command.ShowOne):
output output
) )
return format(execution) return ActionExecutionFormatter.format(execution)
class GetOutput(command.Command): class GetOutput(command.Command):

View File

@@ -22,25 +22,23 @@ from mistralclient.commands.v2 import base
from mistralclient import utils from mistralclient import utils
def format_list(action=None): class ActionFormatter(base.MistralFormatter):
return format(action, lister=True) 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): def format(action=None, lister=False):
columns = (
'ID',
'Name',
'Is system',
'Input',
'Description',
'Tags',
'Created at',
'Updated at'
)
if action: if action:
tags = getattr(action, 'tags', None) or [] tags = getattr(action, 'tags', None) or []
input = action.input if not lister else base.cut(action.input) input_ = action.input if not lister else base.cut(action.input)
desc = (action.description if not lister desc = (action.description if not lister
else base.cut(action.description)) else base.cut(action.description))
@@ -48,44 +46,42 @@ def format(action=None, lister=False):
action.id, action.id,
action.name, action.name,
action.is_system, action.is_system,
input, input_,
desc, desc,
base.wrap(', '.join(tags)) or '<none>', base.wrap(', '.join(tags)) or '<none>',
action.created_at, action.created_at,
) )
if hasattr(action, 'updated_at'): if hasattr(action, 'updated_at'):
data += (action.updated_at,) data += (action.updated_at,)
else: else:
data += (None,) data += (None,)
else:
data = (tuple('' for _ in range(len(columns))),)
return columns, data else:
data = (tuple('' for _ in range(len(ActionFormatter.COLUMNS))),)
return ActionFormatter.headings(), data
class List(base.MistralLister): class List(base.MistralLister):
"""List all actions.""" """List all actions."""
def _get_format_function(self): def _get_format_function(self):
return format_list return ActionFormatter.format_list
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(List, self).get_parser(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 return parser
def _get_resources(self, parsed_args): def _get_resources(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine mistral_client = self.app.client_manager.workflow_engine
return mistral_client.actions.list( 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) **base.get_filters(parsed_args)
) )
@@ -104,7 +100,7 @@ class Get(command.ShowOne):
mistral_client = self.app.client_manager.workflow_engine mistral_client = self.app.client_manager.workflow_engine
action = mistral_client.actions.get(parsed_args.action) action = mistral_client.actions.get(parsed_args.action)
return format(action) return ActionFormatter.format(action)
class Create(base.MistralLister): class Create(base.MistralLister):
@@ -131,7 +127,7 @@ class Create(base.MistralLister):
raise RuntimeError("Provide action definition file.") raise RuntimeError("Provide action definition file.")
def _get_format_function(self): def _get_format_function(self):
return format_list return ActionFormatter.format_list
def _get_resources(self, parsed_args): def _get_resources(self, parsed_args):
scope = 'public' if parsed_args.public else 'private' scope = 'public' if parsed_args.public else 'private'
@@ -190,7 +186,7 @@ class Update(base.MistralLister):
return parser return parser
def _get_format_function(self): def _get_format_function(self):
return format_list return ActionFormatter.format_list
def _get_resources(self, parsed_args): def _get_resources(self, parsed_args):
scope = 'public' if parsed_args.public else 'private' scope = 'public' if parsed_args.public else 'private'
@@ -223,8 +219,8 @@ class GetDefinition(command.Command):
class Validate(command.ShowOne): class Validate(command.ShowOne):
"""Validate action.""" """Validate action."""
@staticmethod
def _format(self, result=None): def _format(result=None):
columns = ('Valid', 'Error') columns = ('Valid', 'Error')
if result: if result:

View File

@@ -24,12 +24,75 @@ import six
DEFAULT_LIMIT = 100 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) @six.add_metaclass(abc.ABCMeta)
class MistralLister(command.Lister): class MistralLister(command.Lister):
@abc.abstractmethod @abc.abstractmethod
def _get_format_function(self): def _get_format_function(self):
raise NotImplementedError 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 @abc.abstractmethod
def _get_resources(self, parsed_args): def _get_resources(self, parsed_args):
"""Gets a list of API resources (e.g. using client).""" """Gets a list of API resources (e.g. using client)."""
@@ -56,6 +119,48 @@ class MistralLister(command.Lister):
return f() 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): def cut(string, length=25):
if string and len(string) > length: if string and len(string) > length:
return "%s..." % string[:length] return "%s..." % string[:length]

View File

@@ -22,24 +22,22 @@ from mistralclient.commands.v2 import base
from mistralclient import utils from mistralclient import utils
def format_list(trigger=None): class CronTriggerFormatter(base.MistralFormatter):
return format(trigger, lister=True) COLUMNS = [
('name', 'Name'),
('workflow_name', 'Workflow'),
def format(trigger=None, lister=False): ('workflow_params', 'Params'),
columns = ( ('pattern', 'Pattern'),
'Name',
'Workflow',
'Params',
'Pattern',
# TODO(rakhmerov): Uncomment when passwords are handled properly. # TODO(rakhmerov): Uncomment when passwords are handled properly.
# TODO(rakhmerov): Add 'Workflow input' column. # TODO(rakhmerov): Add 'Workflow input' column.
'Next execution time', ('next_execution_time', 'Next execution time'),
'Remaining executions', ('remaining_executions', 'Remaining executions'),
'Created at', ('created_at', 'Created at'),
'Updated at' ('updated_at', 'Updated at')
) ]
@staticmethod
def format(trigger=None, lister=False):
if trigger: if trigger:
# TODO(rakhmerov): Add following here: # TODO(rakhmerov): Add following here:
# TODO(rakhmerov): wf_input = trigger.workflow_input if not lister # TODO(rakhmerov): wf_input = trigger.workflow_input if not lister
@@ -50,7 +48,8 @@ def format(trigger=None, lister=False):
trigger.workflow_name, trigger.workflow_name,
trigger.workflow_params, trigger.workflow_params,
trigger.pattern, trigger.pattern,
# TODO(rakhmerov): Uncomment when passwords are handled properly. # TODO(rakhmerov): Uncomment when passwords are handled
# properly.
# TODo(rakhmerov): Add 'wf_input' here. # TODo(rakhmerov): Add 'wf_input' here.
trigger.next_execution_time, trigger.next_execution_time,
trigger.remaining_executions, trigger.remaining_executions,
@@ -62,20 +61,28 @@ def format(trigger=None, lister=False):
else: else:
data += (None,) data += (None,)
else: 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): class List(base.MistralLister):
"""List all cron triggers.""" """List all cron triggers."""
def _get_format_function(self): def _get_format_function(self):
return format_list return CronTriggerFormatter.format_list
def _get_resources(self, parsed_args): def _get_resources(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine 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): class Get(command.ShowOne):
@@ -91,7 +98,7 @@ class Get(command.ShowOne):
def take_action(self, parsed_args): def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine 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 parsed_args.cron_trigger
)) ))
@@ -189,7 +196,7 @@ class Create(command.ShowOne):
parsed_args.count parsed_args.count
) )
return format(trigger) return CronTriggerFormatter.format(trigger)
class Delete(command.Command): class Delete(command.Command):

View File

@@ -21,63 +21,41 @@ from mistralclient.commands.v2 import base
from mistralclient import utils from mistralclient import utils
def format_list(environment=None): class EnvironmentFormatter(base.MistralFormatter):
columns = ( COLUMNS = [
'Name', ('name', 'Name'),
'Description', ('description', 'Description'),
'Scope', ('variables', 'Variables'),
'Created at', ('scope', 'Scope'),
'Updated at' ('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']
@staticmethod
def format(environment=None, lister=False):
if lister:
columns = EnvironmentFormatter.LIST_COLUMN_HEADING_NAMES
else:
columns = EnvironmentFormatter.headings()
if environment: if environment:
data = ( data = (
environment.name, environment.name,)
environment.description,
environment.scope,
environment.created_at,
)
if hasattr(environment, 'updated_at'):
data += (environment.updated_at or '<none>',)
else:
data += (None,)
else:
data = (tuple('<none>' 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'): if hasattr(environment, 'description'):
data += (environment.description or '<none>',) data += (environment.description or '<none>',)
else: else:
data += (None,) data += (None,)
if not lister:
data += (json.dumps(environment.variables, indent=4),)
data += ( data += (
json.dumps(environment.variables, indent=4),
environment.scope, environment.scope,
environment.created_at, environment.created_at,)
)
if hasattr(environment, 'updated_at'): if hasattr(environment, 'updated_at'):
data += (environment.updated_at or '<none>',) data += (environment.updated_at or '<none>',)
else: else:
data += (None,) data += (None,)
else: else:
data = (tuple('' for _ in range(len(columns))),) data = (tuple('' for _ in range(len(columns))),)
@@ -88,11 +66,18 @@ class List(base.MistralLister):
"""List all environments.""" """List all environments."""
def _get_format_function(self): def _get_format_function(self):
return format_list return EnvironmentFormatter.format_list
def _get_resources(self, parsed_args): def _get_resources(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine 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): class Get(command.ShowOne):
@@ -132,7 +117,7 @@ class Get(command.ShowOne):
return columns, data return columns, data
return format(environment) return EnvironmentFormatter.format(environment)
class Create(command.ShowOne): class Create(command.ShowOne):
@@ -155,7 +140,7 @@ class Create(command.ShowOne):
mistral_client = self.app.client_manager.workflow_engine mistral_client = self.app.client_manager.workflow_engine
environment = mistral_client.environments.create(**data) environment = mistral_client.environments.create(**data)
return format(environment) return EnvironmentFormatter.format(environment)
class Delete(command.Command): class Delete(command.Command):
@@ -203,4 +188,4 @@ class Update(command.ShowOne):
mistral_client = self.app.client_manager.workflow_engine mistral_client = self.app.client_manager.workflow_engine
environment = mistral_client.environments.update(**data) environment = mistral_client.environments.update(**data)
return format(environment) return EnvironmentFormatter.format(environment)

View File

@@ -19,23 +19,21 @@ from mistralclient.commands.v2 import base
from mistralclient import utils from mistralclient import utils
def format_list(trigger=None): class EventTriggerFormatter(base.MistralFormatter):
return format(trigger, lister=True) 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): def format(trigger=None, lister=False):
columns = (
'ID',
'Name',
'Workflow ID',
'Params',
'Exchange',
'Topic',
'Event',
'Created at',
'Updated at'
)
if trigger: if trigger:
data = ( data = (
trigger.id, trigger.id,
@@ -53,20 +51,28 @@ def format(trigger=None, lister=False):
else: else:
data += (None,) data += (None,)
else: 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): class List(base.MistralLister):
"""List all event triggers.""" """List all event triggers."""
def _get_format_function(self): def _get_format_function(self):
return format_list return EventTriggerFormatter.format_list
def _get_resources(self, parsed_args): def _get_resources(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine 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): class Get(command.ShowOne):
@@ -82,9 +88,8 @@ class Get(command.ShowOne):
def take_action(self, parsed_args): def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine mistral_client = self.app.client_manager.workflow_engine
return format(mistral_client.event_triggers.get( return EventTriggerFormatter.format(mistral_client.event_triggers.get(
parsed_args.event_trigger parsed_args.event_trigger))
))
class Create(command.ShowOne): class Create(command.ShowOne):
@@ -140,7 +145,7 @@ class Create(command.ShowOne):
wf_params, wf_params,
) )
return format(trigger) return EventTriggerFormatter.format(trigger)
class Delete(command.Command): class Delete(command.Command):

View File

@@ -29,24 +29,23 @@ from mistralclient import utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
def format_list(execution=None): class ExecutionFormatter(base.MistralFormatter):
return format(execution, lister=True) 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): 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. # TODO(nmakhotkin) Add parent task id when it's implemented in API.
if execution: if execution:
@@ -67,16 +66,17 @@ def format(execution=None, lister=False):
execution.updated_at or '<none>' execution.updated_at or '<none>'
) )
else: else:
data = (tuple('' for _ in range(len(columns))),) data = (tuple('' for _ in
range(len(ExecutionFormatter.COLUMNS))),)
return columns, data return ExecutionFormatter.headings(), data
class List(base.MistralLister): class List(base.MistralExecutionLister):
"""List all executions.""" """List all executions."""
def _get_format_function(self): def _get_format_function(self):
return format_list return ExecutionFormatter.format_list
def get_parser(self, parsed_args): def get_parser(self, parsed_args):
parser = super(List, self).get_parser(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 " help="Parent task execution ID associated with workflow "
"execution list.", "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 return parser
def _get_resources(self, parsed_args): 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 mistral_client = self.app.client_manager.workflow_engine
return mistral_client.executions.list( return mistral_client.executions.list(
@@ -146,6 +98,7 @@ class List(base.MistralLister):
limit=parsed_args.limit, limit=parsed_args.limit,
sort_keys=parsed_args.sort_keys, sort_keys=parsed_args.sort_keys,
sort_dirs=parsed_args.sort_dirs, sort_dirs=parsed_args.sort_dirs,
fields=ExecutionFormatter.fields(),
**base.get_filters(parsed_args) **base.get_filters(parsed_args)
) )
@@ -164,7 +117,7 @@ class Get(command.ShowOne):
mistral_client = self.app.client_manager.workflow_engine mistral_client = self.app.client_manager.workflow_engine
execution = mistral_client.executions.get(parsed_args.execution) execution = mistral_client.executions.get(parsed_args.execution)
return format(execution) return ExecutionFormatter.format(execution)
class Create(command.ShowOne): class Create(command.ShowOne):
@@ -235,7 +188,7 @@ class Create(command.ShowOne):
**params **params
) )
return format(execution) return ExecutionFormatter.format(execution)
class Delete(command.Command): class Delete(command.Command):
@@ -322,7 +275,7 @@ class Update(command.ShowOne):
env=env env=env
) )
return format(execution) return ExecutionFormatter.format(execution)
class GetInput(command.Command): class GetInput(command.Command):

View File

@@ -19,21 +19,19 @@ from mistralclient.commands.v2 import base
from mistralclient import exceptions from mistralclient import exceptions
def format_list(member=None): class MemberFormatter(base.MistralFormatter):
return format(member, lister=True) 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): def format(member=None, lister=False):
columns = (
'Resource ID',
'Resource Type',
'Resource Owner',
'Member ID',
'Status',
'Created at',
'Updated at'
)
if member: if member:
data = ( data = (
member.resource_id, member.resource_id,
@@ -49,16 +47,16 @@ def format(member=None, lister=False):
else: else:
data += (None,) data += (None,)
else: 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): class List(base.MistralLister):
"""List all members.""" """List all members."""
def _get_format_function(self): def _get_format_function(self):
return format_list return MemberFormatter.format_list
def get_parser(self, parsed_args): def get_parser(self, parsed_args):
parser = super(List, self).get_parser(parsed_args) parser = super(List, self).get_parser(parsed_args)
@@ -114,7 +112,7 @@ class Get(command.ShowOne):
parsed_args.member_id, parsed_args.member_id,
) )
return format(member) return MemberFormatter.format(member)
class Create(command.ShowOne): class Create(command.ShowOne):
@@ -146,7 +144,7 @@ class Create(command.ShowOne):
parsed_args.member_id, parsed_args.member_id,
) )
return format(member) return MemberFormatter.format(member)
class Delete(command.Command): class Delete(command.Command):
@@ -232,4 +230,4 @@ class Update(command.ShowOne):
status=parsed_args.status status=parsed_args.status
) )
return format(member) return MemberFormatter.format(member)

View File

@@ -27,7 +27,7 @@ from mistralclient import utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class TaskFormatter(object): class TaskFormatter(base.MistralFormatter):
COLUMNS = [ COLUMNS = [
('id', 'ID'), ('id', 'ID'),
('name', 'Name'), ('name', 'Name'),
@@ -40,13 +40,6 @@ class TaskFormatter(object):
('updated_at', 'Updated at'), ('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 @staticmethod
def format(task=None, lister=False): def format(task=None, lister=False):
if task: if task:
@@ -67,10 +60,10 @@ class TaskFormatter(object):
else: else:
data = (tuple('' for _ in range(len(TaskFormatter.COLUMNS))),) 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.""" """List all tasks."""
def get_parser(self, prog_name): def get_parser(self, prog_name):
@@ -81,43 +74,21 @@ class List(base.MistralLister):
nargs='?', nargs='?',
help='Workflow execution ID associated with list of Tasks.' 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 return parser
def _get_format_function(self): def _get_format_function(self):
return TaskFormatter.format_list return TaskFormatter.format_list
def _get_resources(self, parsed_args): 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 mistral_client = self.app.client_manager.workflow_engine
return mistral_client.tasks.list( return mistral_client.tasks.list(
parsed_args.workflow_execution, parsed_args.workflow_execution,
marker=parsed_args.marker,
limit=parsed_args.limit, 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) **base.get_filters(parsed_args)
) )

View File

@@ -21,16 +21,18 @@ from mistralclient.commands.v2 import base
from mistralclient import utils from mistralclient import utils
def format(workbook=None): class WorkbookFormatter(base.MistralFormatter):
columns = ( COLUMNS = [
'Name', ('name', 'Name'),
'Namespace', ('namespace', 'Namespace'),
'Tags', ('tags', 'Tags'),
'Scope', ('scope', 'Scope'),
'Created at', ('created_at', 'Created at'),
'Updated at' ('updated_at', 'Updated at')
) ]
@staticmethod
def format(workbook=None, lister=False):
if workbook: if workbook:
data = ( data = (
workbook.name, workbook.name,
@@ -46,20 +48,27 @@ def format(workbook=None):
data += (None,) data += (None,)
else: else:
data = (tuple('' for _ in range(len(columns))),) data = (tuple('' for _ in range(len(WorkbookFormatter.COLUMNS))),)
return columns, data return WorkbookFormatter.headings(), data
class List(base.MistralLister): class List(base.MistralLister):
"""List all workbooks.""" """List all workbooks."""
def _get_format_function(self): def _get_format_function(self):
return format return WorkbookFormatter.format
def _get_resources(self, parsed_args): def _get_resources(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine 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): class Get(command.ShowOne):
@@ -86,7 +95,7 @@ class Get(command.ShowOne):
parsed_args.namespace parsed_args.namespace
) )
return format(workbook) return WorkbookFormatter.format(workbook)
class Create(command.ShowOne): class Create(command.ShowOne):
@@ -125,7 +134,7 @@ class Create(command.ShowOne):
scope=scope scope=scope
) )
return format(workbook) return WorkbookFormatter.format(workbook)
class Delete(command.Command): class Delete(command.Command):
@@ -190,7 +199,7 @@ class Update(command.ShowOne):
scope=scope scope=scope
) )
return format(workbook) return WorkbookFormatter.format(workbook)
class GetDefinition(command.Command): class GetDefinition(command.Command):
@@ -212,8 +221,8 @@ class GetDefinition(command.Command):
class Validate(command.ShowOne): class Validate(command.ShowOne):
"""Validate workbook.""" """Validate workbook."""
@staticmethod
def _format(self, result=None): def _format(result=None):
columns = ('Valid', 'Error') columns = ('Valid', 'Error')
if result: if result:

View File

@@ -22,23 +22,21 @@ from mistralclient.commands.v2 import base
from mistralclient import utils from mistralclient import utils
def format_list(workflow=None): class WorkflowFormatter(base.MistralFormatter):
return format(workflow, lister=True) 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): def format(workflow=None, lister=False):
columns = (
'ID',
'Name',
'Namespace',
'Project ID',
'Tags',
'Input',
'Scope',
'Created at',
'Updated at'
)
if workflow: if workflow:
tags = getattr(workflow, 'tags', None) or [] tags = getattr(workflow, 'tags', None) or []
@@ -58,33 +56,31 @@ def format(workflow=None, lister=False):
else: else:
data += (None,) data += (None,)
else: 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): class List(base.MistralLister):
"""List all workflows.""" """List all workflows."""
def _get_format_function(self): def _get_format_function(self):
return format_list return WorkflowFormatter.format_list
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(List, self).get_parser(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 return parser
def _get_resources(self, parsed_args): def _get_resources(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine mistral_client = self.app.client_manager.workflow_engine
return mistral_client.workflows.list( 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) **base.get_filters(parsed_args)
) )
@@ -113,7 +109,7 @@ class Get(show.ShowOne):
parsed_args.namespace parsed_args.namespace
) )
return format(wf) return WorkflowFormatter.format(wf)
class Create(base.MistralLister): class Create(base.MistralLister):
@@ -142,7 +138,7 @@ class Create(base.MistralLister):
return parser return parser
def _get_format_function(self): def _get_format_function(self):
return format_list return WorkflowFormatter.format_list
def _validate_parsed_args(self, parsed_args): def _validate_parsed_args(self, parsed_args):
if not parsed_args.definition: if not parsed_args.definition:
@@ -223,7 +219,7 @@ class Update(base.MistralLister):
return parser return parser
def _get_format_function(self): def _get_format_function(self):
return format_list return WorkflowFormatter.format_list
def _get_resources(self, parsed_args): def _get_resources(self, parsed_args):
scope = 'public' if parsed_args.public else 'private' scope = 'public' if parsed_args.public else 'private'
@@ -267,8 +263,8 @@ class GetDefinition(command.Command):
class Validate(show.ShowOne): class Validate(show.ShowOne):
"""Validate workflow.""" """Validate workflow."""
@staticmethod
def _format(self, result=None): def _format(result=None):
columns = ('Valid', 'Error') columns = ('Valid', 'Error')
if result: if result:

View File

@@ -225,7 +225,7 @@ class TestCLIExecutionsV2(base.BaseCommandTest):
) )
def test_list(self): 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) result = self.call(execution_cmd.List)
@@ -239,28 +239,46 @@ class TestCLIExecutionsV2(base.BaseCommandTest):
self.call(execution_cmd.List) self.call(execution_cmd.List)
self.client.executions.list.assert_called_once_with( self.client.executions.list.assert_called_once_with(
fields=execution_cmd.ExecutionFormatter.fields(),
limit=100, limit=100,
marker='', marker='',
sort_dirs='asc', sort_dirs='desc',
sort_keys='created_at', sort_keys='created_at',
task=None 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( self.call(
execution_cmd.List, execution_cmd.List,
app_args=[ app_args=[
'--limit', '5', '--limit', '5',
'--sort_dirs', 'id, Workflow', '--sort_keys', 'id, Workflow',
'--sort_keys', 'desc', '--sort_dirs', 'desc',
'--marker', 'abc' '--marker', 'abc'
] ]
) )
self.client.executions.list.assert_called_with( self.client.executions.list.assert_called_with(
fields=execution_cmd.ExecutionFormatter.fields(),
limit=5, limit=5,
marker='abc', marker='abc',
sort_dirs='id, Workflow', sort_keys='id, Workflow',
sort_keys='desc', sort_dirs='desc',
task=None task=None
) )

View File

@@ -61,7 +61,7 @@ class TestCLITasksV2(base.BaseCommandTest):
self.assertEqual([EXPECTED_TASK_RESULT], result[1]) self.assertEqual([EXPECTED_TASK_RESULT], result[1])
self.assertEqual( self.assertEqual(
self.client.tasks.list.call_args[1]["fields"], 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): def test_list_with_workflow_execution(self):

View File

@@ -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.