From f1cd38aabfeddfdf4c2b4eb7cf17a9a62255baa8 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 15 May 2025 15:48:34 +0100 Subject: [PATCH] identity: Normalise output of application credentials commands cliff is now smarter (I9155763eee15e19eab23b48989dfcc19ea2c5d34), so we can effectively revert change I6b4f1b793dc383856bfdf9a01514381be3cd2bf1. We bump the dependencies to ensure this. Change-Id: I2af19043fd66b5be0826a774baeabeac7110a4aa Signed-off-by: Stephen Finucane --- .../identity/v3/application_credential.py | 171 +++++++++--------- .../identity/v3/test_access_rule.py | 2 +- .../v3/test_application_credential.py | 14 +- .../v3/test_application_credential.py | 88 ++++----- requirements.txt | 2 +- 5 files changed, 142 insertions(+), 135 deletions(-) diff --git a/openstackclient/identity/v3/application_credential.py b/openstackclient/identity/v3/application_credential.py index f0dd5a58fd..4c4cd3f5a5 100644 --- a/openstackclient/identity/v3/application_credential.py +++ b/openstackclient/identity/v3/application_credential.py @@ -20,6 +20,7 @@ import json import logging import uuid +from cliff import columns as cliff_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -27,10 +28,84 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common - LOG = logging.getLogger(__name__) +class RolesColumn(cliff_columns.FormattableColumn): + """Generate a formatted string of role names.""" + + def human_readable(self): + return utils.format_list(r['name'] for r in self._value) + + +def _format_application_credential( + application_credential, *, include_secret=False +): + column_headers: tuple[str, ...] = ( + 'ID', + 'Name', + 'Description', + 'Project ID', + 'Roles', + 'Unrestricted', + 'Access Rules', + 'Expires At', + ) + columns: tuple[str, ...] = ( + 'id', + 'name', + 'description', + 'project_id', + 'roles', + 'unrestricted', + 'access_rules', + 'expires_at', + ) + if include_secret: + column_headers += ('Secret',) + columns += ('secret',) + + return ( + column_headers, + utils.get_item_properties( + application_credential, columns, formatters={'roles': RolesColumn} + ), + ) + + +def _format_application_credentials(application_credentials): + column_headers = ( + 'ID', + 'Name', + 'Description', + 'Project ID', + 'Roles', + 'Unrestricted', + 'Access Rules', + 'Expires At', + ) + columns = ( + 'id', + 'name', + 'description', + 'project_id', + 'roles', + 'unrestricted', + 'access_rules', + 'expires_at', + ) + + return ( + column_headers, + ( + utils.get_item_properties( + x, columns, formatters={'roles': RolesColumn} + ) + for x in application_credentials + ), + ) + + # TODO(stephenfin): Move this to osc_lib since it's useful elsewhere def is_uuid_like(value) -> bool: """Returns validation of a value as a UUID. @@ -38,9 +113,6 @@ def is_uuid_like(value) -> bool: :param val: Value to verify :type val: string :returns: bool - - .. versionchanged:: 1.1.1 - Support non-lowercase UUIDs. """ try: formatted_value = ( @@ -179,31 +251,8 @@ class CreateApplicationCredential(command.ShowOne): access_rules=access_rules, ) - # Format roles into something sensible - if application_credential['roles']: - roles = application_credential['roles'] - msg = ' '.join(r['name'] for r in roles) - application_credential['roles'] = msg - - columns = ( - 'id', - 'name', - 'description', - 'project_id', - 'roles', - 'unrestricted', - 'access_rules', - 'expires_at', - 'secret', - ) - return ( - columns, - ( - utils.get_dict_properties( - application_credential, - columns, - ) - ), + return _format_application_credential( + application_credential, include_secret=True ) @@ -252,6 +301,8 @@ class DeleteApplicationCredential(command.Command): ) % {'errors': errors, 'total': total} raise exceptions.CommandError(msg) + return None + class ListApplicationCredential(command.Lister): _description = _("List application credentials") @@ -276,39 +327,12 @@ class ListApplicationCredential(command.Lister): conn = self.app.client_manager.sdk_connection user_id = conn.config.get_auth().get_user_id(conn.identity) - data = identity_client.application_credentials(user=user_id) - - data_formatted = [] - for ac in data: - # Format roles into something sensible - roles = ac['roles'] - msg = ' '.join(r['name'] for r in roles) - ac['roles'] = msg - - data_formatted.append(ac) - - columns = ( - 'ID', - 'Name', - 'Description', - 'Project ID', - 'Roles', - 'Unrestricted', - 'Access Rules', - 'Expires At', - ) - return ( - columns, - ( - utils.get_item_properties( - s, - columns, - formatters={}, - ) - for s in data_formatted - ), + application_credentials = identity_client.application_credentials( + user=user_id ) + return _format_application_credentials(application_credentials) + class ShowApplicationCredential(command.ShowOne): _description = _("Display application credential details") @@ -327,31 +351,8 @@ class ShowApplicationCredential(command.ShowOne): conn = self.app.client_manager.sdk_connection user_id = conn.config.get_auth().get_user_id(conn.identity) - app_cred = identity_client.find_application_credential( + application_credential = identity_client.find_application_credential( user_id, parsed_args.application_credential ) - # Format roles into something sensible - roles = app_cred['roles'] - msg = ' '.join(r['name'] for r in roles) - app_cred['roles'] = msg - - columns = ( - 'id', - 'name', - 'description', - 'project_id', - 'roles', - 'unrestricted', - 'access_rules', - 'expires_at', - ) - return ( - columns, - ( - utils.get_dict_properties( - app_cred, - columns, - ) - ), - ) + return _format_application_credential(application_credential) diff --git a/openstackclient/tests/functional/identity/v3/test_access_rule.py b/openstackclient/tests/functional/identity/v3/test_access_rule.py index c8436c834d..4f7225801f 100644 --- a/openstackclient/tests/functional/identity/v3/test_access_rule.py +++ b/openstackclient/tests/functional/identity/v3/test_access_rule.py @@ -62,7 +62,7 @@ class AccessRuleTests(common.IdentityTests): items = self.parse_show_as_object(raw_output) self.access_rule_ids = [ - x['id'] for x in ast.literal_eval(items['access_rules']) + x['id'] for x in ast.literal_eval(items['Access Rules']) ] self.addCleanup( self.openstack, diff --git a/openstackclient/tests/functional/identity/v3/test_application_credential.py b/openstackclient/tests/functional/identity/v3/test_application_credential.py index a9c2286d57..20315c4e76 100644 --- a/openstackclient/tests/functional/identity/v3/test_application_credential.py +++ b/openstackclient/tests/functional/identity/v3/test_application_credential.py @@ -21,13 +21,13 @@ from openstackclient.tests.functional.identity.v3 import common class ApplicationCredentialTests(common.IdentityTests): APPLICATION_CREDENTIAL_FIELDS = [ - 'id', - 'name', - 'project_id', - 'description', - 'roles', - 'expires_at', - 'unrestricted', + 'ID', + 'Name', + 'Project ID', + 'Description', + 'Roles', + 'Expires At', + 'Unrestricted', ] APPLICATION_CREDENTIAL_LIST_HEADERS = [ 'ID', diff --git a/openstackclient/tests/unit/identity/v3/test_application_credential.py b/openstackclient/tests/unit/identity/v3/test_application_credential.py index a4ecec5dc4..a5fda7c4f9 100644 --- a/openstackclient/tests/unit/identity/v3/test_application_credential.py +++ b/openstackclient/tests/unit/identity/v3/test_application_credential.py @@ -31,18 +31,6 @@ from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestApplicationCredentialCreate(identity_fakes.TestIdentityv3): - columns = ( - 'id', - 'name', - 'description', - 'project_id', - 'roles', - 'unrestricted', - 'access_rules', - 'expires_at', - 'secret', - ) - def setUp(self): super().setUp() @@ -52,12 +40,25 @@ class TestApplicationCredentialCreate(identity_fakes.TestIdentityv3): roles=[], ) - self.datalist = ( + self.columns = ( + 'ID', + 'Name', + 'Description', + 'Project ID', + 'Roles', + 'Unrestricted', + 'Access Rules', + 'Expires At', + 'Secret', + ) + self.data = ( self.application_credential.id, self.application_credential.name, self.application_credential.description, self.application_credential.project_id, - self.application_credential.roles, + application_credential.RolesColumn( + self.application_credential.roles + ), self.application_credential.unrestricted, self.application_credential.access_rules, self.application_credential.expires_at, @@ -101,7 +102,7 @@ class TestApplicationCredentialCreate(identity_fakes.TestIdentityv3): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertEqual(self.data, data) def test_application_credential_create_with_options(self): name = self.application_credential.name @@ -147,7 +148,7 @@ class TestApplicationCredentialCreate(identity_fakes.TestIdentityv3): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertEqual(self.data, data) def test_application_credential_create_with_access_rules_string(self): name = self.application_credential.name @@ -191,7 +192,7 @@ class TestApplicationCredentialCreate(identity_fakes.TestIdentityv3): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertEqual(self.data, data) @mock.patch('openstackclient.identity.v3.application_credential.json.load') @mock.patch('openstackclient.identity.v3.application_credential.open') @@ -231,7 +232,7 @@ class TestApplicationCredentialCreate(identity_fakes.TestIdentityv3): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertEqual(self.data, data) class TestApplicationCredentialDelete(identity_fakes.TestIdentityv3): @@ -345,7 +346,9 @@ class TestApplicationCredentialList(identity_fakes.TestIdentityv3): self.application_credential.name, self.application_credential.description, self.application_credential.project_id, - '', + application_credential.RolesColumn( + self.application_credential.roles + ), self.application_credential.unrestricted, self.application_credential.access_rules, self.application_credential.expires_at, @@ -408,6 +411,29 @@ class TestApplicationCredentialShow(identity_fakes.TestIdentityv3): self.application_credential ) + self.columns = ( + 'ID', + 'Name', + 'Description', + 'Project ID', + 'Roles', + 'Unrestricted', + 'Access Rules', + 'Expires At', + ) + self.data = ( + self.application_credential.id, + self.application_credential.name, + self.application_credential.description, + self.application_credential.project_id, + application_credential.RolesColumn( + self.application_credential.roles + ), + self.application_credential.unrestricted, + self.application_credential.access_rules, + self.application_credential.expires_at, + ) + # Get the command object to test self.cmd = application_credential.ShowApplicationCredential( self.app, None @@ -434,25 +460,5 @@ class TestApplicationCredentialShow(identity_fakes.TestIdentityv3): user_id, self.application_credential.id ) - collist = ( - 'id', - 'name', - 'description', - 'project_id', - 'roles', - 'unrestricted', - 'access_rules', - 'expires_at', - ) - self.assertEqual(collist, columns) - datalist = ( - self.application_credential.id, - self.application_credential.name, - self.application_credential.description, - self.application_credential.project_id, - self.application_credential.roles, - self.application_credential.unrestricted, - self.application_credential.access_rules, - self.application_credential.expires_at, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/requirements.txt b/requirements.txt index 5e9b33d275..2fe51f5f0f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 cryptography>=2.7 # BSD/Apache-2.0 -cliff>=3.5.0 # Apache-2.0 +cliff>=4.8.0 # Apache-2.0 iso8601>=0.1.11 # MIT openstacksdk>=4.5.0 # Apache-2.0 osc-lib>=2.3.0 # Apache-2.0