Merge "Add '--fields' to show more columns for baymodel-list"
This commit is contained in:
@@ -287,3 +287,40 @@ def exit(msg=''):
|
|||||||
if msg:
|
if msg:
|
||||||
print (msg, file=sys.stderr)
|
print (msg, file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def _format_field_name(attr):
|
||||||
|
"""Format an object attribute in a human-friendly way."""
|
||||||
|
# Split at ':' and leave the extension name as-is.
|
||||||
|
parts = attr.rsplit(':', 1)
|
||||||
|
name = parts[-1].replace('_', ' ')
|
||||||
|
# Don't title() on mixed case
|
||||||
|
if name.isupper() or name.islower():
|
||||||
|
name = name.title()
|
||||||
|
parts[-1] = name
|
||||||
|
return ': '.join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def make_field_formatter(attr, filters=None):
|
||||||
|
"""Given an object attribute.
|
||||||
|
|
||||||
|
Return a formatted field name and a formatter suitable for passing to
|
||||||
|
print_list.
|
||||||
|
Optionally pass a dict mapping attribute names to a function. The function
|
||||||
|
will be passed the value of the attribute and should return the string to
|
||||||
|
display.
|
||||||
|
"""
|
||||||
|
|
||||||
|
filter_ = None
|
||||||
|
if filters:
|
||||||
|
filter_ = filters.get(attr)
|
||||||
|
|
||||||
|
def get_field(obj):
|
||||||
|
field = getattr(obj, attr, '')
|
||||||
|
if field and filter_:
|
||||||
|
field = filter_(field)
|
||||||
|
return field
|
||||||
|
|
||||||
|
name = _format_field_name(attr)
|
||||||
|
formatter = get_field
|
||||||
|
return name, formatter
|
||||||
|
|||||||
@@ -70,8 +70,10 @@ class TestCommandLineArgument(utils.TestCase):
|
|||||||
self.addCleanup(loader.stop)
|
self.addCleanup(loader.stop)
|
||||||
self.addCleanup(session.stop)
|
self.addCleanup(session.stop)
|
||||||
|
|
||||||
def _test_arg_success(self, command):
|
def _test_arg_success(self, command, keyword=None):
|
||||||
stdout, stderr = self.shell(command)
|
stdout, stderr = self.shell(command)
|
||||||
|
if keyword:
|
||||||
|
self.assertTrue(keyword in (stdout + stderr))
|
||||||
|
|
||||||
def _test_arg_failure(self, command, error_msg):
|
def _test_arg_failure(self, command, error_msg):
|
||||||
stdout, stderr = self.shell(command, (2,))
|
stdout, stderr = self.shell(command, (2,))
|
||||||
|
|||||||
@@ -14,7 +14,24 @@
|
|||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
|
from magnumclient.common.apiclient import exceptions
|
||||||
from magnumclient.tests.v1 import shell_test_base
|
from magnumclient.tests.v1 import shell_test_base
|
||||||
|
from magnumclient.v1.baymodels import BayModel
|
||||||
|
|
||||||
|
|
||||||
|
class FakeBayModel(BayModel):
|
||||||
|
def __init__(self, manager=None, info={}, **kwargs):
|
||||||
|
BayModel.__init__(self, manager=manager, info=info)
|
||||||
|
self.apiserver_port = kwargs.get('apiserver_port', None)
|
||||||
|
self.uuid = kwargs.get('uuid', 'x')
|
||||||
|
self.links = kwargs.get('links', [])
|
||||||
|
self.server_type = kwargs.get('server_type', 'vm')
|
||||||
|
self.image_id = kwargs.get('image_id', 'x')
|
||||||
|
self.tls_disabled = kwargs.get('tls_disabled', False)
|
||||||
|
self.registry_enabled = kwargs.get('registry_enabled', False)
|
||||||
|
self.coe = kwargs.get('coe', 'x')
|
||||||
|
self.public = kwargs.get('public', False)
|
||||||
|
self.name = kwargs.get('name', 'x')
|
||||||
|
|
||||||
|
|
||||||
class ShellTest(shell_test_base.TestCommandLineArgument):
|
class ShellTest(shell_test_base.TestCommandLineArgument):
|
||||||
@@ -256,6 +273,29 @@ class ShellTest(shell_test_base.TestCommandLineArgument):
|
|||||||
'--sort-key uuid')
|
'--sort-key uuid')
|
||||||
self.assertTrue(mock_list.called)
|
self.assertTrue(mock_list.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.baymodels.BayModelManager.list')
|
||||||
|
def test_baymodel_list_ignored_duplicated_field(self, mock_list):
|
||||||
|
mock_list.return_value = [FakeBayModel()]
|
||||||
|
self._test_arg_success('baymodel-list --fields coe,coe,coe,name,name',
|
||||||
|
keyword='\n| uuid | name | Coe |\n')
|
||||||
|
# Output should be
|
||||||
|
# +------+------+-----+
|
||||||
|
# | uuid | name | Coe |
|
||||||
|
# +------+------+-----+
|
||||||
|
# | x | x | x |
|
||||||
|
# +------+------+-----+
|
||||||
|
self.assertTrue(mock_list.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.baymodels.BayModelManager.list')
|
||||||
|
def test_baymodel_list_failure_with_invalid_field(self, mock_list):
|
||||||
|
mock_list.return_value = [FakeBayModel()]
|
||||||
|
_error_msg = [".*?^Non-existent fields are specified: ['xxx','zzz']"]
|
||||||
|
self.assertRaises(exceptions.CommandError,
|
||||||
|
self._test_arg_failure,
|
||||||
|
'baymodel-list --fields xxx,coe,zzz',
|
||||||
|
_error_msg)
|
||||||
|
self.assertTrue(mock_list.called)
|
||||||
|
|
||||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.list')
|
@mock.patch('magnumclient.v1.baymodels.BayModelManager.list')
|
||||||
def test_baymodel_list_failure_invalid_arg(self, mock_list):
|
def test_baymodel_list_failure_invalid_arg(self, mock_list):
|
||||||
_error_msg = [
|
_error_msg = [
|
||||||
|
|||||||
@@ -14,8 +14,10 @@
|
|||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
|
from magnumclient.common.apiclient import exceptions
|
||||||
from magnumclient.common import cliutils as utils
|
from magnumclient.common import cliutils as utils
|
||||||
from magnumclient.common import utils as magnum_utils
|
from magnumclient.common import utils as magnum_utils
|
||||||
|
from magnumclient.i18n import _
|
||||||
|
|
||||||
|
|
||||||
def _show_baymodel(baymodel):
|
def _show_baymodel(baymodel):
|
||||||
@@ -159,12 +161,23 @@ def do_baymodel_show(cs, args):
|
|||||||
metavar='<sort-dir>',
|
metavar='<sort-dir>',
|
||||||
choices=['desc', 'asc'],
|
choices=['desc', 'asc'],
|
||||||
help='Direction to sort. "asc" or "desc".')
|
help='Direction to sort. "asc" or "desc".')
|
||||||
|
@utils.arg('--fields',
|
||||||
|
default=None,
|
||||||
|
metavar='<fields>',
|
||||||
|
help=_('Comma-separated list of fields to display. '
|
||||||
|
'Available fields: uuid, name, coe, image_id, public, link, '
|
||||||
|
'apiserver_port, server_type, tls_disabled, registry_enabled'
|
||||||
|
)
|
||||||
|
)
|
||||||
def do_baymodel_list(cs, args):
|
def do_baymodel_list(cs, args):
|
||||||
"""Print a list of baymodels."""
|
"""Print a list of baymodels."""
|
||||||
nodes = cs.baymodels.list(limit=args.limit,
|
nodes = cs.baymodels.list(limit=args.limit,
|
||||||
sort_key=args.sort_key,
|
sort_key=args.sort_key,
|
||||||
sort_dir=args.sort_dir)
|
sort_dir=args.sort_dir)
|
||||||
columns = ('uuid', 'name')
|
columns = ['uuid', 'name']
|
||||||
|
columns += _get_list_table_columns_and_formatters(
|
||||||
|
args.fields, nodes,
|
||||||
|
exclude_fields=(c.lower() for c in columns))[0]
|
||||||
utils.print_list(nodes, columns,
|
utils.print_list(nodes, columns,
|
||||||
{'versions': magnum_utils.print_list_field('versions')},
|
{'versions': magnum_utils.print_list_field('versions')},
|
||||||
sortby_index=None)
|
sortby_index=None)
|
||||||
@@ -194,3 +207,60 @@ def do_baymodel_update(cs, args):
|
|||||||
|
|
||||||
baymodel = cs.baymodels.update(args.baymodel, patch)
|
baymodel = cs.baymodels.update(args.baymodel, patch)
|
||||||
_show_baymodel(baymodel)
|
_show_baymodel(baymodel)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_list_table_columns_and_formatters(fields, objs, exclude_fields=(),
|
||||||
|
filters=None):
|
||||||
|
"""Check and add fields to output columns.
|
||||||
|
|
||||||
|
If there is any value in fields that not an attribute of obj,
|
||||||
|
CommandError will be raised.
|
||||||
|
If fields has duplicate values (case sensitive), we will make them unique
|
||||||
|
and ignore duplicate ones.
|
||||||
|
:param fields: A list of string contains the fields to be printed.
|
||||||
|
:param objs: An list of object which will be used to check if field is
|
||||||
|
valid or not. Note, we don't check fields if obj is None or
|
||||||
|
empty.
|
||||||
|
:param exclude_fields: A tuple of string which contains the fields to be
|
||||||
|
excluded.
|
||||||
|
:param filters: A dictionary defines how to get value from fields, this
|
||||||
|
is useful when field's value is a complex object such as
|
||||||
|
dictionary.
|
||||||
|
:return: columns, formatters.
|
||||||
|
columns is a list of string which will be used as table header.
|
||||||
|
formatters is a dictionary specifies how to display the value
|
||||||
|
of the field.
|
||||||
|
They can be [], {}.
|
||||||
|
:raise: magnumclient.common.apiclient.exceptions.CommandError.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if objs and isinstance(objs, list):
|
||||||
|
obj = objs[0]
|
||||||
|
else:
|
||||||
|
obj = None
|
||||||
|
fields = None
|
||||||
|
|
||||||
|
columns = []
|
||||||
|
formatters = {}
|
||||||
|
|
||||||
|
if fields:
|
||||||
|
non_existent_fields = []
|
||||||
|
exclude_fields = set(exclude_fields)
|
||||||
|
|
||||||
|
for field in fields.split(','):
|
||||||
|
if not hasattr(obj, field):
|
||||||
|
non_existent_fields.append(field)
|
||||||
|
continue
|
||||||
|
if field in exclude_fields:
|
||||||
|
continue
|
||||||
|
field_title, formatter = utils.make_field_formatter(field, filters)
|
||||||
|
columns.append(field_title)
|
||||||
|
formatters[field_title] = formatter
|
||||||
|
exclude_fields.add(field)
|
||||||
|
|
||||||
|
if non_existent_fields:
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
_("Non-existent fields are specified: %s") %
|
||||||
|
non_existent_fields
|
||||||
|
)
|
||||||
|
return columns, formatters
|
||||||
|
|||||||
Reference in New Issue
Block a user