diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index ae653e76ee..8c365cf0fe 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -166,7 +166,8 @@ class ListKeypair(command.Lister): def get_parser(self, prog_name): parser = super().get_parser(prog_name) - parser.add_argument( + user_group = parser.add_mutually_exclusive_group() + user_group.add_argument( '--user', metavar='', help=_( @@ -175,15 +176,44 @@ class ListKeypair(command.Lister): ), ) identity_common.add_user_domain_option_to_parser(parser) + user_group.add_argument( + '--project', + metavar='', + help=_( + 'Show keypairs for all users associated with project ' + '(admin only) (name or ID). ' + 'Requires ``--os-compute-api-version`` 2.10 or greater.' + ), + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute identity_client = self.app.client_manager.identity - kwargs = {} + if parsed_args.project: + if compute_client.api_version < api_versions.APIVersion('2.10'): + msg = _( + '--os-compute-api-version 2.10 or greater is required to ' + 'support the --project option' + ) + raise exceptions.CommandError(msg) - if parsed_args.user: + # NOTE(stephenfin): This is done client side because nova doesn't + # currently support doing so server-side. If this is slow, we can + # think about spinning up a threadpool or similar. + project = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + users = identity_client.users.list(tenant_id=project) + + data = [] + for user in users: + data.extend(compute_client.keypairs.list(user_id=user.id)) + elif parsed_args.user: if compute_client.api_version < api_versions.APIVersion('2.10'): msg = _( '--os-compute-api-version 2.10 or greater is required to ' @@ -191,13 +221,15 @@ class ListKeypair(command.Lister): ) raise exceptions.CommandError(msg) - kwargs['user_id'] = identity_common.find_user( + user = identity_common.find_user( identity_client, parsed_args.user, parsed_args.user_domain, - ).id + ) - data = compute_client.keypairs.list(**kwargs) + data = compute_client.keypairs.list(user_id=user.id) + else: + data = compute_client.keypairs.list() columns = ( "Name", diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py index 5e6a47414f..286cbb0929 100644 --- a/openstackclient/tests/unit/compute/v2/test_keypair.py +++ b/openstackclient/tests/unit/compute/v2/test_keypair.py @@ -310,14 +310,6 @@ class TestKeypairList(TestKeypair): def setUp(self): super(TestKeypairList, self).setUp() - self.users_mock = self.app.client_manager.identity.users - self.users_mock.reset_mock() - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) - self.keypairs_mock.list.return_value = self.keypairs # Get the command object to test @@ -378,6 +370,14 @@ class TestKeypairList(TestKeypair): self.app.client_manager.compute.api_version = \ api_versions.APIVersion('2.10') + users_mock = self.app.client_manager.identity.users + users_mock.reset_mock() + users_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ) + arglist = [ '--user', identity_fakes.user_name, ] @@ -388,7 +388,7 @@ class TestKeypairList(TestKeypair): columns, data = self.cmd.take_action(parsed_args) - self.users_mock.get.assert_called_with(identity_fakes.user_name) + users_mock.get.assert_called_with(identity_fakes.user_name) self.keypairs_mock.list.assert_called_with( user_id=identity_fakes.user_id, ) @@ -423,6 +423,83 @@ class TestKeypairList(TestKeypair): self.assertIn( '--os-compute-api-version 2.10 or greater is required', str(ex)) + def test_keypair_list_with_project(self): + + # Filtering by user is support for nova api 2.10 or above + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.10') + + projects_mock = self.app.client_manager.identity.tenants + projects_mock.reset_mock() + projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + + users_mock = self.app.client_manager.identity.users + users_mock.reset_mock() + users_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ), + ] + + arglist = ['--project', identity_fakes.project_name] + verifylist = [('project', identity_fakes.project_name)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + projects_mock.get.assert_called_with(identity_fakes.project_name) + users_mock.list.assert_called_with(tenant_id=identity_fakes.project_id) + self.keypairs_mock.list.assert_called_with( + user_id=identity_fakes.user_id, + ) + + self.assertEqual(('Name', 'Fingerprint', 'Type'), columns) + self.assertEqual( + (( + self.keypairs[0].name, + self.keypairs[0].fingerprint, + self.keypairs[0].type, + ), ), + tuple(data) + ) + + def test_keypair_list_with_project_pre_v210(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.9') + + arglist = ['--project', identity_fakes.project_name] + verifylist = [('project', identity_fakes.project_name)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.10 or greater is required', str(ex)) + + def test_keypair_list_conflicting_user_options(self): + + # Filtering by user is support for nova api 2.10 or above + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.10') + + arglist = [ + '--user', identity_fakes.user_name, + '--project', identity_fakes.project_name, + ] + + self.assertRaises( + tests_utils.ParserException, + self.check_parser, self.cmd, arglist, None) + class TestKeypairShow(TestKeypair): diff --git a/releasenotes/notes/add-keypairs-project-filter-99cb6938f247927f.yaml b/releasenotes/notes/add-keypairs-project-filter-99cb6938f247927f.yaml new file mode 100644 index 0000000000..77e7afe6ba --- /dev/null +++ b/releasenotes/notes/add-keypairs-project-filter-99cb6938f247927f.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + It is now possible to list the keypairs for all users in a project using + the ``--project`` parameter. This is an admin-only action by default and + requires Compute API microversion 2.10 or later.