diff --git a/.gitreview b/.gitreview index 130a080b..f38a02e4 100644 --- a/.gitreview +++ b/.gitreview @@ -2,3 +2,4 @@ host=review.opendev.org port=29418 project=openstack/python-ironicclient.git +defaultbranch=stable/2025.1 diff --git a/ironicclient/common/utils.py b/ironicclient/common/utils.py index 498dfabe..33227771 100644 --- a/ironicclient/common/utils.py +++ b/ironicclient/common/utils.py @@ -25,6 +25,7 @@ import tempfile import time +from cliff import columns from oslo_utils import strutils import yaml @@ -462,3 +463,26 @@ def get_json_data(data): return json.loads(data) except ValueError: return None + + +def format_hash(data): + if data is None: + return None + + output = "" + for s in sorted(data): + key_str = s + if isinstance(data[s], dict): + # NOTE(dtroyer): Only append the separator chars here, quoting + # is completely handled in the terminal case. + output = output + format_hash(data[s], prefix=key_str) + ", " + elif data[s] is not None: + output = output + key_str + "='" + str(data[s]) + "', " + else: + output = output + key_str + "=, " + return output[:-2] + + +class HashColumn(columns.FormattableColumn): + def human_readable(self): + return format_hash(self._value) diff --git a/ironicclient/osc/v1/baremetal_chassis.py b/ironicclient/osc/v1/baremetal_chassis.py index a84fc52f..58dc5bb6 100644 --- a/ironicclient/osc/v1/baremetal_chassis.py +++ b/ironicclient/osc/v1/baremetal_chassis.py @@ -190,7 +190,7 @@ def take_action(self, parsed_args): return (labels, (oscutils.get_item_properties(s, columns, formatters={ - 'Properties': oscutils.format_dict},) for s in data)) + 'Properties': utils.HashColumn},) for s in data)) class SetBaremetalChassis(command.Command): diff --git a/ironicclient/osc/v1/baremetal_conductor.py b/ironicclient/osc/v1/baremetal_conductor.py index 627fd622..2dbb9c8c 100755 --- a/ironicclient/osc/v1/baremetal_conductor.py +++ b/ironicclient/osc/v1/baremetal_conductor.py @@ -21,6 +21,7 @@ from osc_lib import utils as oscutils from ironicclient.common.i18n import _ +from ironicclient.common import utils from ironicclient import exc from ironicclient.v1 import resource_fields as res_fields @@ -106,7 +107,7 @@ def take_action(self, parsed_args): return (labels, (oscutils.get_item_properties(s, columns, formatters={ - 'Properties': oscutils.format_dict},) for s in data)) + 'Properties': utils.HashColumn},) for s in data)) class ShowBaremetalConductor(command.ShowOne): diff --git a/ironicclient/osc/v1/baremetal_node.py b/ironicclient/osc/v1/baremetal_node.py index b32d6643..02d59a83 100755 --- a/ironicclient/osc/v1/baremetal_node.py +++ b/ironicclient/osc/v1/baremetal_node.py @@ -925,7 +925,7 @@ def take_action(self, parsed_args): return (labels, (oscutils.get_item_properties(s, columns, formatters={ - 'Properties': oscutils.format_dict},) for s in data)) + 'Properties': utils.HashColumn},) for s in data)) class MaintenanceSetBaremetalNode(command.Command): diff --git a/ironicclient/osc/v1/baremetal_port.py b/ironicclient/osc/v1/baremetal_port.py index d6fc424a..b5163472 100644 --- a/ironicclient/osc/v1/baremetal_port.py +++ b/ironicclient/osc/v1/baremetal_port.py @@ -547,4 +547,4 @@ def take_action(self, parsed_args): return (labels, (oscutils.get_item_properties(s, columns, formatters={ - 'extra': oscutils.format_dict},) for s in data)) + 'extra': utils.HashColumn},) for s in data)) diff --git a/ironicclient/osc/v1/baremetal_portgroup.py b/ironicclient/osc/v1/baremetal_portgroup.py index bd7f1a2b..a1dc4048 100644 --- a/ironicclient/osc/v1/baremetal_portgroup.py +++ b/ironicclient/osc/v1/baremetal_portgroup.py @@ -252,7 +252,7 @@ def take_action(self, parsed_args): return (labels, (oscutils.get_item_properties(s, columns, formatters={ - 'Properties': oscutils.format_dict},) for s in data)) + 'Properties': utils.HashColumn},) for s in data)) class DeleteBaremetalPortGroup(command.Command): diff --git a/ironicclient/osc/v1/baremetal_volume_connector.py b/ironicclient/osc/v1/baremetal_volume_connector.py index f8b270ad..cb2e9cd4 100644 --- a/ironicclient/osc/v1/baremetal_volume_connector.py +++ b/ironicclient/osc/v1/baremetal_volume_connector.py @@ -218,7 +218,7 @@ def take_action(self, parsed_args): return (labels, (oscutils.get_item_properties(s, columns, formatters={ - 'Properties': oscutils.format_dict},) for s in data)) + 'Properties': utils.HashColumn},) for s in data)) class DeleteBaremetalVolumeConnector(command.Command): diff --git a/ironicclient/osc/v1/baremetal_volume_target.py b/ironicclient/osc/v1/baremetal_volume_target.py index 7940e482..614781aa 100644 --- a/ironicclient/osc/v1/baremetal_volume_target.py +++ b/ironicclient/osc/v1/baremetal_volume_target.py @@ -234,7 +234,7 @@ def take_action(self, parsed_args): return (labels, (oscutils.get_item_properties(s, columns, formatters={ - 'Properties': oscutils.format_dict},) for s in data)) + 'Properties': utils.HashColumn},) for s in data)) class DeleteBaremetalVolumeTarget(command.Command): diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_chassis.py b/ironicclient/tests/unit/osc/v1/test_baremetal_chassis.py index 9af48532..6422ea0d 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_chassis.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_chassis.py @@ -50,14 +50,14 @@ def setUp(self): self.arglist = [] self.verifylist = [] self.collist = ( + 'uuid', 'description', 'extra', - 'uuid', ) self.datalist = ( + baremetal_fakes.baremetal_chassis_uuid, baremetal_fakes.baremetal_chassis_description, baremetal_fakes.baremetal_chassis_extra, - baremetal_fakes.baremetal_chassis_uuid, ) self.actual_kwargs = {} diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_conductor.py b/ironicclient/tests/unit/osc/v1/test_baremetal_conductor.py index e9708a97..4c008340 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_conductor.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_conductor.py @@ -212,17 +212,18 @@ def test_conductor_show(self): *args, fields=None ) - collist = ('alive', - 'conductor_group', - 'drivers', - 'hostname', - ) + collist = ( + 'hostname', + 'conductor_group', + 'alive', + 'drivers', + ) self.assertEqual(collist, columns) datalist = ( - baremetal_fakes.baremetal_alive, + baremetal_fakes.baremetal_hostname, baremetal_fakes.baremetal_conductor_group, + baremetal_fakes.baremetal_alive, baremetal_fakes.baremetal_drivers, - baremetal_fakes.baremetal_hostname, ) self.assertEqual(datalist, tuple(data)) diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py index 31949102..a1fab2b1 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py @@ -759,22 +759,23 @@ def setUp(self): self.cmd = baremetal_node.CreateBaremetalNode(self.app, None) self.arglist = ['--driver', 'fake_driver'] self.verifylist = [('driver', 'fake_driver')] - self.collist = ('chassis_uuid', - 'instance_uuid', - 'maintenance', - 'name', - 'power_state', - 'provision_state', - 'uuid' - ) + self.collist = ( + 'uuid', + 'name', + 'instance_uuid', + 'power_state', + 'provision_state', + 'maintenance', + 'chassis_uuid' + ) self.datalist = ( - baremetal_fakes.baremetal_chassis_uuid_empty, - baremetal_fakes.baremetal_instance_uuid, - baremetal_fakes.baremetal_maintenance, + baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_name, + baremetal_fakes.baremetal_instance_uuid, baremetal_fakes.baremetal_power_state, baremetal_fakes.baremetal_provision_state, - baremetal_fakes.baremetal_uuid, + baremetal_fakes.baremetal_maintenance, + baremetal_fakes.baremetal_chassis_uuid_empty, ) self.actual_kwargs = { 'driver': 'fake_driver' @@ -3704,25 +3705,26 @@ def test_baremetal_show(self): *args, fields=None ) - collist = ('chassis_uuid', - 'instance_uuid', - 'maintenance', - 'name', - 'power_state', - 'provision_state', - 'uuid' - ) + collist = ( + 'uuid', + 'name', + 'instance_uuid', + 'power_state', + 'provision_state', + 'maintenance', + 'chassis_uuid', + ) self.assertEqual(collist, columns) self.assertNotIn('ports', columns) self.assertNotIn('states', columns) datalist = ( - baremetal_fakes.baremetal_chassis_uuid_empty, - baremetal_fakes.baremetal_instance_uuid, - baremetal_fakes.baremetal_maintenance, + baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_name, + baremetal_fakes.baremetal_instance_uuid, baremetal_fakes.baremetal_power_state, baremetal_fakes.baremetal_provision_state, - baremetal_fakes.baremetal_uuid + baremetal_fakes.baremetal_maintenance, + baremetal_fakes.baremetal_chassis_uuid_empty, ) self.assertEqual(datalist, tuple(data)) @@ -4828,10 +4830,10 @@ def test_baremetal_node_history_list(self): columns, data = self.cmd.take_action(parsed_args) self.baremetal_mock.node.get_history_event.assert_called_once_with( 'node_uuid', 'event_uuid') - expected_columns = ('conductor', 'created_at', 'event', 'event_type', - 'severity', 'user', 'uuid') - expected_data = ('lap-conductor', 'time', 'meow', 'purring', 'info', - '0191', 'abcdef1') + expected_columns = ('uuid', 'created_at', 'severity', 'event', + 'event_type', 'conductor', 'user') + expected_data = ('abcdef1', 'time', 'info', 'meow', 'purring', + 'lap-conductor', '0191') self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, tuple(data)) diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_port.py b/ironicclient/tests/unit/osc/v1/test_baremetal_port.py index 01797f72..ef507c34 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_port.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_port.py @@ -18,8 +18,8 @@ from unittest import mock from osc_lib.tests import utils as osctestutils -from osc_lib import utils as oscutils +from ironicclient.common import utils from ironicclient import exc from ironicclient.osc.v1 import baremetal_port from ironicclient.tests.unit.osc.v1 import fakes as baremetal_fakes @@ -813,7 +813,7 @@ def test_baremetal_port_list_long(self): baremetal_fakes.baremetal_port_uuid, baremetal_fakes.baremetal_port_address, '', - oscutils.format_dict(baremetal_fakes.baremetal_port_extra), + utils.HashColumn(baremetal_fakes.baremetal_port_extra), baremetal_fakes.baremetal_uuid, '', '', diff --git a/ironicclient/tests/unit/v1/test_node.py b/ironicclient/tests/unit/v1/test_node.py index 9b23623c..6cb5bcc8 100644 --- a/ironicclient/tests/unit/v1/test_node.py +++ b/ironicclient/tests/unit/v1/test_node.py @@ -2240,7 +2240,7 @@ def side_effect(node_manager, node_ident, *args, **kwargs): mock_get.side_effect = side_effect self.assertRaisesRegex(exc.StateTransitionTimeout, - r'Node\(s\) node2', + r'.*node2.*', self.mgr.wait_for_provision_state, ['node1', 'node2'], 'active', timeout=0.001) diff --git a/tox.ini b/tox.ini index a3687e16..0bbf0339 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ setenv = VIRTUAL_ENV={envdir} TESTS_DIR=./ironicclient/tests/unit usedevelop = True deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.1} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = @@ -19,7 +19,7 @@ commands = [testenv:releasenotes] deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.1} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html @@ -30,7 +30,7 @@ deps = flake8-import-order>=0.17.1 # LGPLv3 pycodestyle>=2.0.0,<3.0.0 # MIT Pygments>=2.2.0 # BSD - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.1} commands = flake8 {posargs} doc8 doc/source CONTRIBUTING.rst README.rst @@ -47,7 +47,7 @@ commands = [testenv:venv] deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.1} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt @@ -55,7 +55,7 @@ commands = {posargs} [testenv:docs] deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.1} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html