From 894699d2b4adfd3f38aba08a2042372ce6622f17 Mon Sep 17 00:00:00 2001 From: Lucas Alvares Gomes Date: Fri, 3 Jul 2015 11:06:18 +0100 Subject: [PATCH] Allow specifying a set of fields of the Port and Chasis resources This patch add a "--fields" parameter to the "port-list", "port-show", "chassis-list", "chassis-show" and "chassis-node-list" commands which the user can specify a subset of fields that will be returned by the server. This patch also enhances the error message for check_for_invalid_fileds() method to show what are the valid fields. Change-Id: Iaf90d615c9fa68f24e54c23d708ff9c5bee7c46c --- ironicclient/common/utils.py | 17 +++--- ironicclient/tests/unit/test_utils.py | 3 +- ironicclient/tests/unit/v1/test_chassis.py | 58 +++++++++++++++++++ .../tests/unit/v1/test_chassis_shell.py | 53 ++++++++++++++++- ironicclient/tests/unit/v1/test_port.py | 37 ++++++++++++ ironicclient/tests/unit/v1/test_port_shell.py | 46 ++++++++++++++- ironicclient/v1/chassis.py | 33 +++++++++-- ironicclient/v1/chassis_shell.py | 54 +++++++++++++++-- ironicclient/v1/port.py | 29 ++++++++-- ironicclient/v1/port_shell.py | 42 ++++++++++++-- 10 files changed, 339 insertions(+), 33 deletions(-) diff --git a/ironicclient/common/utils.py b/ironicclient/common/utils.py index ae2a150d8..3656eb709 100644 --- a/ironicclient/common/utils.py +++ b/ironicclient/common/utils.py @@ -167,10 +167,9 @@ def common_params_for_list(args, fields, field_labels): params['detail'] = args.detail - if hasattr(args, 'fields'): - requested_fields = args.fields[0] if args.fields else None - if requested_fields is not None: - params['fields'] = requested_fields + requested_fields = args.fields[0] if args.fields else None + if requested_fields is not None: + params['fields'] = requested_fields return params @@ -299,7 +298,9 @@ def check_for_invalid_fields(fields, valid_fields): if not fields: return - invalid_attr = set(fields) - set(valid_fields) - if invalid_attr: - raise exc.CommandError(_('Invalid field(s): %s') % - ', '.join(invalid_attr)) + invalid_fields = set(fields) - set(valid_fields) + if invalid_fields: + raise exc.CommandError( + _('Invalid field(s) requested: %(invalid)s. Valid fields ' + 'are: %(valid)s.') % {'invalid': ', '.join(invalid_fields), + 'valid': ', '.join(valid_fields)}) diff --git a/ironicclient/tests/unit/test_utils.py b/ironicclient/tests/unit/test_utils.py index f4a021b43..fc4f4534c 100644 --- a/ironicclient/tests/unit/test_utils.py +++ b/ironicclient/tests/unit/test_utils.py @@ -126,7 +126,8 @@ class CommonParamsForListTest(test_utils.BaseTestCase): def setUp(self): super(CommonParamsForListTest, self).setUp() self.args = mock.Mock(marker=None, limit=None, sort_key=None, - sort_dir=None, detail=False, spec=True) + sort_dir=None, detail=False, fields=None, + spec=True) self.expected_params = {'detail': False} def test_nothing_set(self): diff --git a/ironicclient/tests/unit/v1/test_chassis.py b/ironicclient/tests/unit/v1/test_chassis.py index 535c800cf..eed9f154f 100644 --- a/ironicclient/tests/unit/v1/test_chassis.py +++ b/ironicclient/tests/unit/v1/test_chassis.py @@ -18,6 +18,7 @@ import copy import testtools from testtools.matchers import HasLength +from ironicclient import exc from ironicclient.tests.unit import utils import ironicclient.v1.chassis @@ -67,6 +68,13 @@ fake_responses = { {"chassis": [CHASSIS]}, ), }, + '/v1/chassis/?fields=uuid,extra': + { + 'GET': ( + {}, + {"chassis": [CHASSIS]}, + ), + }, '/v1/chassis/%s' % CHASSIS['uuid']: { 'GET': ( @@ -82,6 +90,13 @@ fake_responses = { UPDATED_CHASSIS, ), }, + '/v1/chassis/%s?fields=uuid,description' % CHASSIS['uuid']: + { + 'GET': ( + {}, + CHASSIS, + ), + }, '/v1/chassis/%s/nodes' % CHASSIS['uuid']: { 'GET': ( @@ -96,6 +111,13 @@ fake_responses = { {"nodes": [NODE]}, ), }, + '/v1/chassis/%s/nodes?fields=uuid,extra' % CHASSIS['uuid']: + { + 'GET': ( + {}, + {"nodes": [NODE]}, + ), + }, } fake_responses_pagination = { @@ -243,6 +265,18 @@ class ChassisManagerTest(testtools.TestCase): self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(chassis)) + def test_chassis_list_fields(self): + nodes = self.mgr.list(fields=['uuid', 'extra']) + expect = [ + ('GET', '/v1/chassis/?fields=uuid,extra', {}, None), + ] + self.assertEqual(expect, self.api.calls) + self.assertEqual(1, len(nodes)) + + def test_chassis_list_detail_and_fields_fail(self): + self.assertRaises(exc.InvalidAttribute, self.mgr.list, + detail=True, fields=['uuid', 'extra']) + def test_chassis_show(self): chassis = self.mgr.get(CHASSIS['uuid']) expect = [ @@ -252,6 +286,16 @@ class ChassisManagerTest(testtools.TestCase): self.assertEqual(CHASSIS['uuid'], chassis.uuid) self.assertEqual(CHASSIS['description'], chassis.description) + def test_chassis_show_fields(self): + chassis = self.mgr.get(CHASSIS['uuid'], fields=['uuid', 'description']) + expect = [ + ('GET', '/v1/chassis/%s?fields=uuid,description' % + CHASSIS['uuid'], {}, None), + ] + self.assertEqual(expect, self.api.calls) + self.assertEqual(CHASSIS['uuid'], chassis.uuid) + self.assertEqual(CHASSIS['description'], chassis.description) + def test_create(self): chassis = self.mgr.create(**CREATE_CHASSIS) expect = [ @@ -297,6 +341,20 @@ class ChassisManagerTest(testtools.TestCase): self.assertEqual(1, len(nodes)) self.assertEqual(NODE['uuid'], nodes[0].uuid) + def test_chassis_node_list_fields(self): + nodes = self.mgr.list_nodes(CHASSIS['uuid'], fields=['uuid', 'extra']) + expect = [ + ('GET', '/v1/chassis/%s/nodes?fields=uuid,extra' % + CHASSIS['uuid'], {}, None), + ] + self.assertEqual(expect, self.api.calls) + self.assertEqual(1, len(nodes)) + + def test_chassis_node_list_detail_and_fields_fail(self): + self.assertRaises(exc.InvalidAttribute, self.mgr.list_nodes, + CHASSIS['uuid'], detail=True, + fields=['uuid', 'extra']) + def test_chassis_node_list_limit(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = ironicclient.v1.chassis.ChassisManager(self.api) diff --git a/ironicclient/tests/unit/v1/test_chassis_shell.py b/ironicclient/tests/unit/v1/test_chassis_shell.py index dd2e0fb17..a1c3b1f3f 100644 --- a/ironicclient/tests/unit/v1/test_chassis_shell.py +++ b/ironicclient/tests/unit/v1/test_chassis_shell.py @@ -22,7 +22,8 @@ import ironicclient.v1.chassis_shell as c_shell class ChassisShellTest(utils.BaseTestCase): def _get_client_mock_args(self, chassis=None, marker=None, limit=None, - sort_dir=None, sort_key=None, detail=False): + sort_dir=None, sort_key=None, detail=False, + fields=None): args = mock.MagicMock(spec=True) args.chassis = chassis args.marker = marker @@ -30,6 +31,7 @@ class ChassisShellTest(utils.BaseTestCase): args.sort_dir = sort_dir args.sort_key = sort_key args.detail = detail + args.fields = fields return args @@ -59,6 +61,24 @@ class ChassisShellTest(utils.BaseTestCase): c_shell.do_chassis_show, client_mock, args) + def test_do_chassis_show_fields(self): + client_mock = mock.MagicMock() + args = mock.MagicMock() + args.chassis = 'chassis_uuid' + args.fields = [['uuid', 'description']] + c_shell.do_chassis_show(client_mock, args) + client_mock.chassis.get.assert_called_once_with( + 'chassis_uuid', fields=['uuid', 'description']) + + def test_do_chassis_show_invalid_fields(self): + client_mock = mock.MagicMock() + args = mock.MagicMock() + args.chassis = 'chassis_uuid' + args.fields = [['foo', 'bar']] + self.assertRaises(exceptions.CommandError, + c_shell.do_chassis_show, + client_mock, args) + def test_do_chassis_list(self): client_mock = mock.MagicMock() args = self._get_client_mock_args() @@ -111,6 +131,20 @@ class ChassisShellTest(utils.BaseTestCase): client_mock, args) self.assertFalse(client_mock.chassis.list.called) + def test_do_chassis_list_fields(self): + client_mock = mock.MagicMock() + args = self._get_client_mock_args(fields=[['uuid', 'description']]) + c_shell.do_chassis_list(client_mock, args) + client_mock.chassis.list.assert_called_once_with( + fields=['uuid', 'description'], detail=False) + + def test_do_chassis_list_invalid_fields(self): + client_mock = mock.MagicMock() + args = self._get_client_mock_args(fields=[['foo', 'bar']]) + self.assertRaises(exceptions.CommandError, + c_shell.do_chassis_list, + client_mock, args) + def test_do_chassis_node_list(self): client_mock = mock.MagicMock() chassis_mock = mock.MagicMock(spec_set=[]) @@ -128,3 +162,20 @@ class ChassisShellTest(utils.BaseTestCase): c_shell.do_chassis_node_list(client_mock, args) client_mock.chassis.list_nodes.assert_called_once_with( chassis_mock, detail=True) + + def test_do_chassis_node_list_fields(self): + client_mock = mock.MagicMock() + chassis_mock = mock.MagicMock(spec_set=[]) + args = self._get_client_mock_args(chassis=chassis_mock, + fields=[['uuid', 'power_state']]) + c_shell.do_chassis_node_list(client_mock, args) + client_mock.chassis.list_nodes.assert_called_once_with( + chassis_mock, fields=['uuid', 'power_state'], detail=False) + + def test_do_chassis_node_list_invalid_fields(self): + client_mock = mock.MagicMock() + chassis_mock = mock.MagicMock(spec_set=[]) + args = self._get_client_mock_args(chassis=chassis_mock, + fields=[['foo', 'bar']]) + self.assertRaises(exceptions.CommandError, + c_shell.do_chassis_node_list, client_mock, args) diff --git a/ironicclient/tests/unit/v1/test_port.py b/ironicclient/tests/unit/v1/test_port.py index ba92d651a..dd2529b1c 100644 --- a/ironicclient/tests/unit/v1/test_port.py +++ b/ironicclient/tests/unit/v1/test_port.py @@ -18,6 +18,7 @@ import copy import testtools from testtools.matchers import HasLength +from ironicclient import exc from ironicclient.tests.unit import utils import ironicclient.v1.port @@ -60,6 +61,13 @@ fake_responses = { {"ports": [PORT]}, ), }, + '/v1/ports/?fields=uuid,address': + { + 'GET': ( + {}, + {"ports": [PORT]}, + ), + }, '/v1/ports/%s' % PORT['uuid']: { 'GET': ( @@ -75,6 +83,13 @@ fake_responses = { UPDATED_PORT, ), }, + '/v1/ports/%s?fields=uuid,address' % PORT['uuid']: + { + 'GET': ( + {}, + PORT, + ), + }, '/v1/ports/detail?address=%s' % PORT['address']: { 'GET': ( @@ -173,6 +188,18 @@ class PortManagerTest(testtools.TestCase): self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(ports)) + def test_port_list_fields(self): + ports = self.mgr.list(fields=['uuid', 'address']) + expect = [ + ('GET', '/v1/ports/?fields=uuid,address', {}, None), + ] + self.assertEqual(expect, self.api.calls) + self.assertEqual(1, len(ports)) + + def test_port_list_detail_and_fields_fail(self): + self.assertRaises(exc.InvalidAttribute, self.mgr.list, + detail=True, fields=['uuid', 'address']) + def test_ports_list_limit(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = ironicclient.v1.port.PortManager(self.api) @@ -245,6 +272,16 @@ class PortManagerTest(testtools.TestCase): self.assertEqual(PORT['address'], port.address) self.assertEqual(PORT['node_uuid'], port.node_uuid) + def test_port_show_fields(self): + port = self.mgr.get(PORT['uuid'], fields=['uuid', 'address']) + expect = [ + ('GET', '/v1/ports/%s?fields=uuid,address' % + PORT['uuid'], {}, None), + ] + self.assertEqual(expect, self.api.calls) + self.assertEqual(PORT['uuid'], port.uuid) + self.assertEqual(PORT['address'], port.address) + def test_create(self): port = self.mgr.create(**CREATE_PORT) expect = [ diff --git a/ironicclient/tests/unit/v1/test_port_shell.py b/ironicclient/tests/unit/v1/test_port_shell.py index 0359b697c..0e60d6eb2 100644 --- a/ironicclient/tests/unit/v1/test_port_shell.py +++ b/ironicclient/tests/unit/v1/test_port_shell.py @@ -39,9 +39,10 @@ class PortShellTest(utils.BaseTestCase): args = mock.MagicMock() args.port = 'port_uuid' args.address = False + args.fields = None p_shell.do_port_show(client_mock, args) - client_mock.port.get.assert_called_once_with('port_uuid') + client_mock.port.get.assert_called_once_with('port_uuid', fields=None) # assert get_by_address() wasn't called self.assertFalse(client_mock.port.get_by_address.called) @@ -68,12 +69,34 @@ class PortShellTest(utils.BaseTestCase): args = mock.MagicMock() args.port = 'port_address' args.address = True + args.fields = None p_shell.do_port_show(client_mock, args) - client_mock.port.get_by_address.assert_called_once_with('port_address') + client_mock.port.get_by_address.assert_called_once_with('port_address', + fields=None) # assert get() wasn't called self.assertFalse(client_mock.port.get.called) + def test_do_port_show_fields(self): + client_mock = mock.MagicMock() + args = mock.MagicMock() + args.port = 'port_uuid' + args.address = False + args.fields = [['uuid', 'address']] + p_shell.do_port_show(client_mock, args) + client_mock.port.get.assert_called_once_with( + 'port_uuid', fields=['uuid', 'address']) + + def test_do_port_show_invalid_fields(self): + client_mock = mock.MagicMock() + args = mock.MagicMock() + args.port = 'port_uuid' + args.address = False + args.fields = [['foo', 'bar']] + self.assertRaises(exceptions.CommandError, + p_shell.do_port_show, + client_mock, args) + def test_do_port_update(self): client_mock = mock.MagicMock() args = mock.MagicMock() @@ -86,7 +109,8 @@ class PortShellTest(utils.BaseTestCase): client_mock.port.update.assert_called_once_with('port_uuid', patch) def _get_client_mock_args(self, address=None, marker=None, limit=None, - sort_dir=None, sort_key=None, detail=False): + sort_dir=None, sort_key=None, detail=False, + fields=None): args = mock.MagicMock(spec=True) args.address = address args.marker = marker @@ -94,6 +118,7 @@ class PortShellTest(utils.BaseTestCase): args.sort_dir = sort_dir args.sort_key = sort_key args.detail = detail + args.fields = fields return args @@ -148,3 +173,18 @@ class PortShellTest(utils.BaseTestCase): p_shell.do_port_list, client_mock, args) self.assertFalse(client_mock.port.list.called) + + def test_do_port_list_fields(self): + client_mock = mock.MagicMock() + args = self._get_client_mock_args(fields=[['uuid', 'address']]) + + p_shell.do_port_list(client_mock, args) + client_mock.port.list.assert_called_once_with( + fields=['uuid', 'address'], detail=False) + + def test_do_port_list_invalid_fields(self): + client_mock = mock.MagicMock() + args = self._get_client_mock_args(fields=[['foo', 'bar']]) + self.assertRaises(exceptions.CommandError, + p_shell.do_port_list, + client_mock, args) diff --git a/ironicclient/v1/chassis.py b/ironicclient/v1/chassis.py index de6c7f29c..666d0518f 100644 --- a/ironicclient/v1/chassis.py +++ b/ironicclient/v1/chassis.py @@ -15,6 +15,7 @@ # under the License. from ironicclient.common import base +from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc @@ -35,7 +36,7 @@ class ChassisManager(base.Manager): return '/v1/chassis/%s' % id if id else '/v1/chassis' def list(self, marker=None, limit=None, sort_key=None, - sort_dir=None, detail=False): + sort_dir=None, detail=False, fields=None): """Retrieve a list of chassis. :param marker: Optional, the UUID of a chassis, eg the last @@ -58,13 +59,22 @@ class ChassisManager(base.Manager): :param detail: Optional, boolean whether to return detailed information about chassis. + :param fields: Optional, a list with a specified set of fields + of the resource to be returned. Can not be used + when 'detail' is set. + :returns: A list of chassis. """ if limit is not None: limit = int(limit) - filters = utils.common_filters(marker, limit, sort_key, sort_dir) + if detail and fields: + raise exc.InvalidAttribute(_("Can't fetch a subset of fields " + "with 'detail' set")) + + filters = utils.common_filters(marker, limit, sort_key, sort_dir, + fields) path = '' if detail: @@ -79,7 +89,7 @@ class ChassisManager(base.Manager): limit=limit) def list_nodes(self, chassis_id, marker=None, limit=None, - sort_key=None, sort_dir=None, detail=False): + sort_key=None, sort_dir=None, detail=False, fields=None): """List all the nodes for a given chassis. :param chassis_id: The UUID of the chassis. @@ -103,13 +113,22 @@ class ChassisManager(base.Manager): :param detail: Optional, boolean whether to return detailed information about nodes. + :param fields: Optional, a list with a specified set of fields + of the resource to be returned. Can not be used + when 'detail' is set. + :returns: A list of nodes. """ if limit is not None: limit = int(limit) - filters = utils.common_filters(marker, limit, sort_key, sort_dir) + if detail and fields: + raise exc.InvalidAttribute(_("Can't fetch a subset of fields " + "with 'detail' set")) + + filters = utils.common_filters(marker, limit, sort_key, sort_dir, + fields) path = "%s/nodes" % chassis_id if detail: @@ -124,7 +143,11 @@ class ChassisManager(base.Manager): return self._list_pagination(self._path(path), "nodes", limit=limit) - def get(self, chassis_id): + def get(self, chassis_id, fields=None): + if fields is not None: + chassis_id = '%s?fields=' % chassis_id + chassis_id += ','.join(fields) + try: return self._list(self._path(chassis_id))[0] except IndexError: diff --git a/ironicclient/v1/chassis_shell.py b/ironicclient/v1/chassis_shell.py index 74ac81387..8d6ee3596 100644 --- a/ironicclient/v1/chassis_shell.py +++ b/ironicclient/v1/chassis_shell.py @@ -18,18 +18,32 @@ from ironicclient.openstack.common import cliutils from ironicclient.v1 import resource_fields as res_fields -def _print_chassis_show(chassis): - fields = ['uuid', 'description', 'created_at', 'updated_at', 'extra'] +def _print_chassis_show(chassis, fields=None): + if fields is None: + fields = res_fields.CHASSIS_DETAILED_RESOURCE.fields + data = dict([(f, getattr(chassis, f, '')) for f in fields]) cliutils.print_dict(data, wrap=72) @cliutils.arg('chassis', metavar='', help="UUID of the chassis.") +@cliutils.arg( + '--fields', + nargs='+', + dest='fields', + metavar='', + action='append', + default=[], + help="One or more chassis fields. Only these fields will be fetched from " + "the server.") def do_chassis_show(cc, args): """Show detailed information about a chassis.""" utils.check_empty_arg(args.chassis, '') - chassis = cc.chassis.get(args.chassis) - _print_chassis_show(chassis) + fields = args.fields[0] if args.fields else None + utils.check_for_invalid_fields( + fields, res_fields.CHASSIS_DETAILED_RESOURCE.fields) + chassis = cc.chassis.get(args.chassis, fields=fields) + _print_chassis_show(chassis, fields=fields) @cliutils.arg( @@ -60,6 +74,15 @@ def do_chassis_show(cc, args): metavar='', choices=['asc', 'desc'], help='Sort direction: "asc" (the default) or "desc".') +@cliutils.arg( + '--fields', + nargs='+', + dest='fields', + metavar='', + action='append', + default=[], + help="One or more chassis fields. Only these fields will be fetched from " + "the server. Can not be used when '--detail' is specified.") def do_chassis_list(cc, args): """List the chassis.""" if args.detail: @@ -67,6 +90,14 @@ def do_chassis_list(cc, args): field_labels = res_fields.CHASSIS_DETAILED_RESOURCE.labels sort_fields = res_fields.CHASSIS_DETAILED_RESOURCE.sort_fields sort_field_labels = res_fields.CHASSIS_DETAILED_RESOURCE.sort_labels + elif args.fields: + utils.check_for_invalid_fields( + args.fields[0], res_fields.CHASSIS_DETAILED_RESOURCE.fields) + resource = res_fields.Resource(args.fields[0]) + fields = resource.fields + field_labels = resource.labels + sort_fields = res_fields.CHASSIS_DETAILED_RESOURCE.sort_fields + sort_field_labels = res_fields.CHASSIS_DETAILED_RESOURCE.sort_labels else: fields = res_fields.CHASSIS_RESOURCE.fields field_labels = res_fields.CHASSIS_RESOURCE.labels @@ -166,11 +197,26 @@ def do_chassis_update(cc, args): choices=['asc', 'desc'], help='Sort direction: "asc" (the default) or "desc".') @cliutils.arg('chassis', metavar='', help="UUID of the chassis.") +@cliutils.arg( + '--fields', + nargs='+', + dest='fields', + metavar='', + action='append', + default=[], + help="One or more node fields. Only these fields will be fetched from " + "the server. Can not be used when '--detail' is specified.") def do_chassis_node_list(cc, args): """List the nodes contained in a chassis.""" if args.detail: fields = res_fields.NODE_DETAILED_RESOURCE.fields field_labels = res_fields.NODE_DETAILED_RESOURCE.labels + elif args.fields: + utils.check_for_invalid_fields( + args.fields[0], res_fields.NODE_DETAILED_RESOURCE.fields) + resource = res_fields.Resource(args.fields[0]) + fields = resource.fields + field_labels = resource.labels else: fields = res_fields.NODE_RESOURCE.fields field_labels = res_fields.NODE_RESOURCE.labels diff --git a/ironicclient/v1/port.py b/ironicclient/v1/port.py index c7371fcd9..1473a1325 100644 --- a/ironicclient/v1/port.py +++ b/ironicclient/v1/port.py @@ -15,6 +15,7 @@ # under the License. from ironicclient.common import base +from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc @@ -34,7 +35,7 @@ class PortManager(base.Manager): return '/v1/ports/%s' % id if id else '/v1/ports' def list(self, address=None, limit=None, marker=None, sort_key=None, - sort_dir=None, detail=False): + sort_dir=None, detail=False, fields=None): """Retrieve a list of port. :param address: Optional, MAC address of a port, to get @@ -59,13 +60,22 @@ class PortManager(base.Manager): :param detail: Optional, boolean whether to return detailed information about ports. + :param fields: Optional, a list with a specified set of fields + of the resource to be returned. Can not be used + when 'detail' is set. + :returns: A list of ports. """ if limit is not None: limit = int(limit) - filters = utils.common_filters(marker, limit, sort_key, sort_dir) + if detail and fields: + raise exc.InvalidAttribute(_("Can't fetch a subset of fields " + "with 'detail' set")) + + filters = utils.common_filters(marker, limit, sort_key, sort_dir, + fields) if address is not None: filters.append('address=%s' % address) @@ -81,14 +91,23 @@ class PortManager(base.Manager): return self._list_pagination(self._path(path), "ports", limit=limit) - def get(self, port_id): + def get(self, port_id, fields=None): + if fields is not None: + port_id = '%s?fields=' % port_id + port_id += ','.join(fields) + try: return self._list(self._path(port_id))[0] except IndexError: return None - def get_by_address(self, address): - path = "detail?address=%s" % address + def get_by_address(self, address, fields=None): + path = '?address=%s' % address + if fields is not None: + path += '&fields=' + ','.join(fields) + else: + path = 'detail' + path + ports = self._list(self._path(path), 'ports') # get all the details of the port assuming that filtering by # address returns a collection of one port if successful. diff --git a/ironicclient/v1/port_shell.py b/ironicclient/v1/port_shell.py index b7c2349a3..43a883bd8 100644 --- a/ironicclient/v1/port_shell.py +++ b/ironicclient/v1/port_shell.py @@ -18,9 +18,10 @@ from ironicclient.openstack.common import cliutils from ironicclient.v1 import resource_fields as res_fields -def _print_port_show(port): - fields = ['address', 'created_at', 'extra', 'node_uuid', 'updated_at', - 'uuid'] +def _print_port_show(port, fields=None): + if fields is None: + fields = res_fields.PORT_DETAILED_RESOURCE.fields + data = dict([(f, getattr(port, f, '')) for f in fields]) cliutils.print_dict(data, wrap=72) @@ -35,14 +36,26 @@ def _print_port_show(port): action='store_true', default=False, help=' is the MAC address (instead of the UUID) of the port.') +@cliutils.arg( + '--fields', + nargs='+', + dest='fields', + metavar='', + action='append', + default=[], + help="One or more port fields. Only these fields will be fetched from " + "the server.") def do_port_show(cc, args): """Show detailed information about a port.""" + fields = args.fields[0] if args.fields else None + utils.check_for_invalid_fields( + fields, res_fields.PORT_DETAILED_RESOURCE.fields) if args.address: - port = cc.port.get_by_address(args.port) + port = cc.port.get_by_address(args.port, fields=fields) else: utils.check_empty_arg(args.port, '') - port = cc.port.get(args.port) - _print_port_show(port) + port = cc.port.get(args.port, fields=fields) + _print_port_show(port, fields=fields) @cliutils.arg( @@ -76,6 +89,15 @@ def do_port_show(cc, args): metavar='', choices=['asc', 'desc'], help='Sort direction: "asc" (the default) or "desc".') +@cliutils.arg( + '--fields', + nargs='+', + dest='fields', + metavar='', + action='append', + default=[], + help="One or more port fields. Only these fields will be fetched from " + "the server. Can not be used when '--detail' is specified.") def do_port_list(cc, args): """List the ports.""" params = {} @@ -88,6 +110,14 @@ def do_port_list(cc, args): field_labels = res_fields.PORT_DETAILED_RESOURCE.labels sort_fields = res_fields.PORT_DETAILED_RESOURCE.sort_fields sort_field_labels = res_fields.PORT_DETAILED_RESOURCE.sort_labels + elif args.fields: + utils.check_for_invalid_fields( + args.fields[0], res_fields.PORT_DETAILED_RESOURCE.fields) + resource = res_fields.Resource(args.fields[0]) + fields = resource.fields + field_labels = resource.labels + sort_fields = res_fields.PORT_DETAILED_RESOURCE.sort_fields + sort_field_labels = res_fields.PORT_DETAILED_RESOURCE.sort_labels else: fields = res_fields.PORT_RESOURCE.fields field_labels = res_fields.PORT_RESOURCE.labels