diff --git a/doc/source/command-objects/user.rst b/doc/source/command-objects/user.rst index e54c65673c..a9a98fe18f 100644 --- a/doc/source/command-objects/user.rst +++ b/doc/source/command-objects/user.rst @@ -101,28 +101,26 @@ List users .. code:: bash os user list - [--domain <domain>] [--project <project>] - [--group <group>] + [--domain <domain>] + [--group <group> | --project <project>] [--long] -.. option:: --domain <domain> - - Filter users by `<domain>` (name or ID) - - .. versionadded:: 3 - .. option:: --project <project> Filter users by `<project>` (name or ID) - *Removed in version 3.* +.. option:: --domain <domain> + + Filter users by `<domain>` (name or ID) + + *Identity version 3 only* .. option:: --group <group> Filter users by `<group>` membership (name or ID) - .. versionadded:: 3 + *Identity version 3 only* .. option:: --long diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index a60c8c83fa..4fb7b6d1e0 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -188,11 +188,17 @@ class ListUser(lister.Lister): metavar='<domain>', help='Filter users by <domain> (name or ID)', ) - parser.add_argument( + project_or_group = parser.add_mutually_exclusive_group() + project_or_group.add_argument( '--group', metavar='<group>', help='Filter users by <group> membership (name or ID)', ) + project_or_group.add_argument( + '--project', + metavar='<project>', + help='Filter users by <project> (name or ID)', + ) parser.add_argument( '--long', action='store_true', @@ -219,7 +225,44 @@ class ListUser(lister.Lister): else: group = None - # List users + if parsed_args.project: + if domain is not None: + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + domain_id=domain + ).id + else: + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ).id + + assignments = identity_client.role_assignments.list( + project=project) + + # NOTE(stevemar): If a user has more than one role on a project + # then they will have two entries in the returned data. Since we + # are looking for any role, let's just track unique user IDs. + user_ids = set() + for assignment in assignments: + if hasattr(assignment, 'user'): + user_ids.add(assignment.user['id']) + + # NOTE(stevemar): Call find_resource once we have unique IDs, so + # it's fewer trips to the Identity API, then collect the data. + data = [] + for user_id in user_ids: + user = utils.find_resource(identity_client.users, user_id) + data.append(user) + + else: + data = identity_client.users.list( + domain=domain, + group=group, + ) + + # Column handling if parsed_args.long: columns = ['ID', 'Name', 'Default Project Id', 'Domain Id', 'Description', 'Email', 'Enabled'] @@ -228,11 +271,7 @@ class ListUser(lister.Lister): column_headers[3] = 'Domain' else: columns = ['ID', 'Name'] - column_headers = copy.deepcopy(columns) - data = identity_client.users.list( - domain=domain, - group=group, - ) + column_headers = columns return ( column_headers, diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index dd517e19f2..35dd98ee9e 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -44,6 +44,11 @@ class TestUser(identity_fakes.TestIdentityv3): self.users_mock = self.app.client_manager.identity.users self.users_mock.reset_mock() + # Shortcut for RoleAssignmentManager Mock + self.role_assignments_mock = self.app.client_manager.identity.\ + role_assignments + self.role_assignments_mock.reset_mock() + class TestUserCreate(TestUser): @@ -511,6 +516,21 @@ class TestUserList(TestUser): loaded=True, ) + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + + self.role_assignments_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy( + identity_fakes.ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID), + loaded=True, + ) + ] + # Get the command object to test self.cmd = user.ListUser(self.app, None) @@ -643,6 +663,33 @@ class TestUserList(TestUser): ), ) self.assertEqual(datalist, tuple(data)) + def test_user_list_project(self): + arglist = [ + '--project', identity_fakes.project_name, + ] + verifylist = [ + ('project', identity_fakes.project_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'project': identity_fakes.project_id, + } + + self.role_assignments_mock.list.assert_called_with(**kwargs) + self.users_mock.get.assert_called_with(identity_fakes.user_id) + + collist = ['ID', 'Name'] + self.assertEqual(columns, collist) + datalist = (( + identity_fakes.user_id, + identity_fakes.user_name, + ), ) + self.assertEqual(datalist, tuple(data)) + class TestUserSet(TestUser):