diff --git a/ironicclient/common/http.py b/ironicclient/common/http.py index f2a1aefaa..b44959996 100644 --- a/ironicclient/common/http.py +++ b/ironicclient/common/http.py @@ -37,7 +37,7 @@ from ironicclient import exc # http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa # for full details. DEFAULT_VER = '1.9' -LAST_KNOWN_API_VERSION = 76 +LAST_KNOWN_API_VERSION = 77 LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION) LOG = logging.getLogger(__name__) diff --git a/ironicclient/osc/v1/baremetal_driver.py b/ironicclient/osc/v1/baremetal_driver.py index 9c3323c12..21c092925 100644 --- a/ironicclient/osc/v1/baremetal_driver.py +++ b/ironicclient/osc/v1/baremetal_driver.py @@ -12,8 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. # - - +import itertools import logging from osc_lib.command import command @@ -39,11 +38,23 @@ class ListBaremetalDriver(command.Lister): help='Type of driver ("classic" or "dynamic"). ' 'The default is to list all of them.' ) - parser.add_argument( + display_group = parser.add_mutually_exclusive_group() + display_group.add_argument( '--long', action='store_true', default=None, help="Show detailed information about the drivers.") + display_group.add_argument( + '--fields', + nargs='+', + dest='fields', + metavar='', + action='append', + default=[], + choices=res_fields.DRIVER_DETAILED_RESOURCE.fields, + help=_("One or more node fields. Only these fields will be " + "fetched from the server. Can not be used when '--long' " + "is specified.")) return parser def take_action(self, parsed_args): @@ -55,6 +66,12 @@ class ListBaremetalDriver(command.Lister): if parsed_args.long: labels = res_fields.DRIVER_DETAILED_RESOURCE.labels columns = res_fields.DRIVER_DETAILED_RESOURCE.fields + elif parsed_args.fields: + fields = itertools.chain.from_iterable(parsed_args.fields) + resource = res_fields.Resource(list(fields)) + columns = resource.fields + labels = resource.labels + params['fields'] = columns else: labels = res_fields.DRIVER_RESOURCE.labels columns = res_fields.DRIVER_RESOURCE.fields @@ -213,13 +230,26 @@ class ShowBaremetalDriver(command.ShowOne): 'driver', metavar='', help=_('Name of the driver.')) + parser.add_argument( + '--fields', + nargs='+', + dest='fields', + metavar='', + action='append', + default=[], + choices=res_fields.DRIVER_DETAILED_RESOURCE.fields, + help=_("One or more node fields. Only these fields will be " + "fetched from the server.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal - driver = baremetal_client.driver.get(parsed_args.driver)._info + fields = list(itertools.chain.from_iterable(parsed_args.fields)) + fields = fields if fields else None + driver = baremetal_client.driver.get(parsed_args.driver, + fields=fields)._info driver.pop("links", None) driver.pop("properties", None) # For list-type properties, show the values as comma separated diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_driver.py b/ironicclient/tests/unit/osc/v1/test_baremetal_driver.py index c6c935f56..15ef919c4 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_driver.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_driver.py @@ -146,6 +146,69 @@ class TestListBaremetalDriver(TestBaremetalDriver): ),) self.assertEqual(datalist, tuple(data)) + def test_baremetal_driver_list_fields(self): + arglist = [ + '--fields', 'name', 'hosts' + ] + verifylist = [ + ('fields', [['name', 'hosts']]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + kwargs = { + 'driver_type': None, + 'detail': None, + 'fields': ('name', 'hosts') + } + + self.baremetal_mock.driver.list.assert_called_with(**kwargs) + + def test_baremetal_driver_list_fields_multiple(self): + arglist = [ + '--fields', 'name', + '--fields', 'hosts', 'type' + ] + verifylist = [ + ('fields', [['name'], ['hosts', 'type']]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + kwargs = { + 'driver_type': None, + 'detail': None, + 'fields': ('name', 'hosts', 'type') + } + + self.baremetal_mock.driver.list.assert_called_with(**kwargs) + + def test_baremetal_driver_list_invalid_fields(self): + arglist = [ + '--fields', 'name', 'invalid' + ] + verifylist = [ + ('fields', [['name', 'invalid']]) + ] + self.assertRaises(oscutils.ParserException, + self.check_parser, + self.cmd, arglist, verifylist) + + def test_baremetal_driver_list_fields_with_long(self): + arglist = [ + '--fields', 'name', 'hosts', + '--long' + ] + verifylist = [ + ('fields', [['name', 'invalid']]), + ('long', True) + ] + self.assertRaises(oscutils.ParserException, + self.check_parser, + self.cmd, arglist, verifylist) + class TestListBaremetalDriverProperty(TestBaremetalDriver): @@ -362,7 +425,7 @@ class TestShowBaremetalDriver(TestBaremetalDriver): columns, data = self.cmd.take_action(parsed_args) args = ['fakedrivername'] - self.baremetal_mock.driver.get.assert_called_with(*args) + self.baremetal_mock.driver.get.assert_called_with(*args, fields=None) self.assertFalse(self.baremetal_mock.driver.properties.called) collist = ('default_bios_interface', 'default_boot_interface', @@ -420,3 +483,54 @@ class TestShowBaremetalDriver(TestBaremetalDriver): self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) + + def test_baremetal_driver_show_fields(self): + arglist = [ + 'fakedrivername', + '--fields', 'name', 'hosts' + ] + verifylist = [ + ('driver', 'fakedrivername'), + ('fields', [['name', 'hosts']]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + args = ['fakedrivername'] + fields = ['name', 'hosts'] + self.baremetal_mock.driver.get.assert_called_with(*args, fields=fields) + self.assertFalse(self.baremetal_mock.driver.properties.called) + + def test_baremetal_driver_show_fields_multiple(self): + arglist = [ + 'fakedrivername', + '--fields', 'name', + '--fields', 'hosts', 'type' + ] + verifylist = [ + ('driver', 'fakedrivername'), + ('fields', [['name'], ['hosts', 'type']]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + args = ['fakedrivername'] + fields = ['name', 'hosts', 'type'] + self.baremetal_mock.driver.get.assert_called_with(*args, fields=fields) + self.assertFalse(self.baremetal_mock.driver.properties.called) + + def test_baremetal_driver_show_invalid_fields(self): + arglist = [ + 'fakedrivername', + '--fields', 'name', 'invalid' + ] + verifylist = [ + ('driver', 'fakedrivername'), + ('fields', [['name', 'invalid']]) + ] + + self.assertRaises(oscutils.ParserException, + self.check_parser, + self.cmd, arglist, verifylist) diff --git a/ironicclient/tests/unit/v1/test_driver.py b/ironicclient/tests/unit/v1/test_driver.py index 0d1d5628d..9a32d0136 100644 --- a/ironicclient/tests/unit/v1/test_driver.py +++ b/ironicclient/tests/unit/v1/test_driver.py @@ -77,6 +77,13 @@ fake_responses = { {'drivers': [DRIVER1]}, ), }, + '/v1/drivers/?fields=name,hosts': + { + 'GET': ( + {}, + {'drivers': [DRIVER1]} + ) + }, '/v1/drivers/%s' % DRIVER1['name']: { 'GET': ( @@ -84,6 +91,13 @@ fake_responses = { DRIVER1 ), }, + '/v1/drivers/%s?fields=name,hosts' % DRIVER1['name']: + { + 'GET': ( + {}, + DRIVER1, + ), + }, '/v1/drivers/%s/properties' % DRIVER2['name']: { 'GET': ( @@ -136,6 +150,24 @@ class DriverManagerTest(testtools.TestCase): self.assertEqual(DRIVER1, driver_attr) + def test_driver_list_fields(self): + drivers = self.mgr.list(fields=['name', 'hosts']) + expect = [ + ('GET', '/v1/drivers/?fields=name,hosts', {}, None), + ] + self.assertEqual(expect, self.api.calls) + self.assertThat(drivers, matchers.HasLength(1)) + + def test_driver_show_fields(self): + driver_ = self.mgr.get(DRIVER1['name'], fields=['name', 'hosts']) + expect = [ + ('GET', '/v1/drivers/%s?fields=name,hosts' % + DRIVER1['name'], {}, None) + ] + self.assertEqual(expect, self.api.calls) + self.assertEqual(DRIVER1['name'], driver_.name) + self.assertEqual(DRIVER1['hosts'], driver_.hosts) + def test_driver_properties(self): properties = self.mgr.properties(DRIVER2['name']) expect = [ diff --git a/ironicclient/v1/driver.py b/ironicclient/v1/driver.py index 289ee05a9..6b41be673 100644 --- a/ironicclient/v1/driver.py +++ b/ironicclient/v1/driver.py @@ -28,7 +28,7 @@ class DriverManager(base.Manager): _resource_name = 'drivers' def list(self, driver_type=None, detail=None, os_ironic_api_version=None, - global_request_id=None): + global_request_id=None, fields=None): """Retrieve a list of drivers. :param driver_type: Optional, string to filter the drivers by type. @@ -41,13 +41,22 @@ class DriverManager(base.Manager): the request. If not specified, the client's default is used. :param global_request_id: String containing global request ID header value (in form "req-") to use for the request. + :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 drivers. """ + filters = [] + if detail and fields: + raise exc.InvalidAttribute(_("Can't fetch a subset of fields " + "with 'detail' set")) if driver_type is not None: filters.append('type=%s' % driver_type) if detail is not None: filters.append('detail=%s' % detail) + if fields is not None: + filters.append('fields=%s' % ','.join(fields)) path = '' if filters: @@ -57,8 +66,8 @@ class DriverManager(base.Manager): global_request_id=global_request_id) def get(self, driver_name, os_ironic_api_version=None, - global_request_id=None): - return self._get(resource_id=driver_name, + global_request_id=None, fields=None): + return self._get(resource_id=driver_name, fields=fields, os_ironic_api_version=os_ironic_api_version, global_request_id=global_request_id) diff --git a/releasenotes/notes/add-driver-cli-fields-selector-b0f527eb5f6fb2a9.yaml b/releasenotes/notes/add-driver-cli-fields-selector-b0f527eb5f6fb2a9.yaml new file mode 100644 index 000000000..4c86f5771 --- /dev/null +++ b/releasenotes/notes/add-driver-cli-fields-selector-b0f527eb5f6fb2a9.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Adds support for fields selector in driver cli. + See `story 1674775 + `_. + + * ``openstack baremetal driver list --fields [ ...]`` + * ``openstack baremetal driver show --fields [ ...]``