From 8bfa180430354d1db11b11a3443486fe04415443 Mon Sep 17 00:00:00 2001 From: Lance Bragstad Date: Thu, 30 Nov 2017 22:42:55 +0000 Subject: [PATCH] Add system role functionality This commit adds the necessary bits to expose system role assignments to openstackclient via python-keystoneclient. bp system-scope Depends-On: Iecbcbf020a15f2bec777334c648d4477f89f3b2c Change-Id: I261e84700b51e8715eaebdc3f8f8bc46b68542c2 --- lower-constraints.txt | 2 +- openstackclient/identity/v3/role.py | 33 +++++++++++---- .../identity/v3/role_assignment.py | 35 +++++++++++++--- openstackclient/identity/v3/token.py | 6 +++ .../unit/identity/v3/test_role_assignment.py | 42 ++++++++++++++++++- ...plement-system-scope-4c3c47996f98deac.yaml | 8 ++++ requirements.txt | 2 +- 7 files changed, 112 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/implement-system-scope-4c3c47996f98deac.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index f8a6cde90d..88c75cf47b 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -95,7 +95,7 @@ python-heatclient==1.10.0 python-ironic-inspector-client==1.5.0 python-ironicclient==2.3.0 python-karborclient==0.6.0 -python-keystoneclient==3.8.0 +python-keystoneclient==3.15.0 python-mimeparse==1.6.0 python-mistralclient==3.1.0 python-muranoclient==0.8.2 diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 2828a34928..58a76f8a65 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -31,13 +31,18 @@ LOG = logging.getLogger(__name__) def _add_identity_and_resource_options_to_parser(parser): - domain_or_project = parser.add_mutually_exclusive_group() - domain_or_project.add_argument( + system_or_domain_or_project = parser.add_mutually_exclusive_group() + system_or_domain_or_project.add_argument( + '--system', + metavar='', + help=_('Include (all)'), + ) + system_or_domain_or_project.add_argument( '--domain', metavar='', help=_('Include (name or ID)'), ) - domain_or_project.add_argument( + system_or_domain_or_project.add_argument( '--project', metavar='', help=_('Include (name or ID)'), @@ -62,7 +67,14 @@ def _add_identity_and_resource_options_to_parser(parser): def _process_identity_and_resource_options(parsed_args, identity_client_manager): kwargs = {} - if parsed_args.user and parsed_args.domain: + if parsed_args.user and parsed_args.system: + kwargs['user'] = common.find_user( + identity_client_manager, + parsed_args.user, + parsed_args.user_domain, + ).id + kwargs['system'] = parsed_args.system + elif parsed_args.user and parsed_args.domain: kwargs['user'] = common.find_user( identity_client_manager, parsed_args.user, @@ -83,6 +95,13 @@ def _process_identity_and_resource_options(parsed_args, parsed_args.project, parsed_args.project_domain, ).id + elif parsed_args.group and parsed_args.system: + kwargs['group'] = common.find_group( + identity_client_manager, + parsed_args.group, + parsed_args.group_domain, + ).id + kwargs['system'] = parsed_args.system elif parsed_args.group and parsed_args.domain: kwargs['group'] = common.find_group( identity_client_manager, @@ -109,8 +128,8 @@ def _process_identity_and_resource_options(parsed_args, class AddRole(command.Command): - _description = _("Adds a role assignment to a user or group on a domain " - "or project") + _description = _("Adds a role assignment to a user or group on the " + "system, a domain, or a project") def get_parser(self, prog_name): parser = super(AddRole, self).get_parser(prog_name) @@ -381,7 +400,7 @@ class ListRole(command.Lister): class RemoveRole(command.Command): - _description = _("Removes a role assignment from domain/project : " + _description = _("Removes a role assignment from system/domain/project : " "user/group") def get_parser(self, prog_name): diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index a362adb07d..9c2f3d249e 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -55,17 +55,22 @@ class ListRoleAssignment(command.Lister): help=_('Group to filter (name or ID)'), ) common.add_group_domain_option_to_parser(parser) - domain_or_project = parser.add_mutually_exclusive_group() - domain_or_project.add_argument( + system_or_domain_or_project = parser.add_mutually_exclusive_group() + system_or_domain_or_project.add_argument( '--domain', metavar='', help=_('Domain to filter (name or ID)'), ) - domain_or_project.add_argument( + system_or_domain_or_project.add_argument( '--project', metavar='', help=_('Project to filter (name or ID)'), ) + system_or_domain_or_project.add_argument( + '--system', + metavar='', + help=_('Filter based on system role assignments'), + ) common.add_project_domain_option_to_parser(parser) common.add_inherited_option_to_parser(parser) parser.add_argument( @@ -85,7 +90,8 @@ class ListRoleAssignment(command.Lister): def _as_tuple(self, assignment): return (assignment.role, assignment.user, assignment.group, - assignment.project, assignment.domain, assignment.inherited) + assignment.project, assignment.domain, assignment.system, + assignment.inherited) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -117,6 +123,10 @@ class ListRoleAssignment(command.Lister): auth_ref.user_id ) + system = None + if parsed_args.system: + system = parsed_args.system + domain = None if parsed_args.domain: domain = common.find_domain( @@ -149,7 +159,9 @@ class ListRoleAssignment(command.Lister): include_names = True if parsed_args.names else False effective = True if parsed_args.effective else False - columns = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + columns = ( + 'Role', 'User', 'Group', 'Project', 'Domain', 'System', 'Inherited' + ) inherited_to = 'projects' if parsed_args.inherited else None data = identity_client.role_assignments.list( @@ -157,6 +169,7 @@ class ListRoleAssignment(command.Lister): user=user, group=group, project=project, + system=system, role=role, effective=effective, os_inherit_extension_inherited_to=inherited_to, @@ -174,14 +187,24 @@ class ListRoleAssignment(command.Lister): else: setattr(assignment, 'project', scope['project']['id']) assignment.domain = '' + assignment.system = '' elif 'domain' in scope: if include_names: setattr(assignment, 'domain', scope['domain']['name']) else: setattr(assignment, 'domain', scope['domain']['id']) assignment.project = '' - + assignment.system = '' + elif 'system' in scope: + # NOTE(lbragstad): If, or when, keystone supports role + # assignments on subsets of a system, this will have to evolve + # to handle that case instead of hardcoding to the entire + # system. + setattr(assignment, 'system', 'all') + assignment.domain = '' + assignment.project = '' else: + assignment.system = '' assignment.domain = '' assignment.project = '' diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index effb9e3525..1933ecad65 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -192,6 +192,12 @@ class IssueToken(command.ShowOne): data['user_id'] = auth_ref.user_id if auth_ref.domain_id: data['domain_id'] = auth_ref.domain_id + if auth_ref.system_scoped: + # NOTE(lbragstad): This could change in the future when, or if, + # keystone supports the ability to scope to a subset of the entire + # deployment system. When that happens, this will have to relay + # scope information and IDs like we do for projects and domains. + data['system'] = 'all' return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/tests/unit/identity/v3/test_role_assignment.py b/openstackclient/tests/unit/identity/v3/test_role_assignment.py index 835837e608..bff6c56df1 100644 --- a/openstackclient/tests/unit/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/unit/identity/v3/test_role_assignment.py @@ -34,6 +34,7 @@ class TestRoleAssignmentList(TestRoleAssignment): 'Group', 'Project', 'Domain', + 'System', 'Inherited', ) @@ -95,6 +96,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=None, effective=False, role=None, @@ -110,12 +112,14 @@ class TestRoleAssignmentList(TestRoleAssignment): '', identity_fakes.project_id, '', + '', False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, '', + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -143,6 +147,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', identity_fakes.user_name), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -159,6 +164,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, user=self.users_mock.get(), group=None, project=None, @@ -174,12 +180,14 @@ class TestRoleAssignmentList(TestRoleAssignment): '', '', identity_fakes.domain_id, + '', False ), (identity_fakes.role_id, identity_fakes.user_id, '', identity_fakes.project_id, '', + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -207,6 +215,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', identity_fakes.group_name), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -223,6 +232,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=self.groups_mock.get(), effective=False, project=None, @@ -238,12 +248,14 @@ class TestRoleAssignmentList(TestRoleAssignment): identity_fakes.group_id, '', identity_fakes.domain_id, + '', False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, '', + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -271,6 +283,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', identity_fakes.domain_name), ('project', None), ('role', None), @@ -287,6 +300,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=self.domains_mock.get(), + system=None, group=None, effective=False, project=None, @@ -302,12 +316,14 @@ class TestRoleAssignmentList(TestRoleAssignment): '', '', identity_fakes.domain_id, + '', False ), (identity_fakes.role_id, '', identity_fakes.group_id, '', identity_fakes.domain_id, + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -335,6 +351,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', identity_fakes.project_name), ('role', None), @@ -351,6 +368,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=None, effective=False, project=self.projects_mock.get(), @@ -366,12 +384,14 @@ class TestRoleAssignmentList(TestRoleAssignment): '', identity_fakes.project_id, '', + '', False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, '', + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -398,6 +418,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -416,6 +437,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, user=self.users_mock.get(), group=None, project=self.projects_mock.get(), @@ -431,6 +453,7 @@ class TestRoleAssignmentList(TestRoleAssignment): '', identity_fakes.project_id, '', + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -456,6 +479,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -472,6 +496,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=None, effective=True, project=None, @@ -487,12 +512,14 @@ class TestRoleAssignmentList(TestRoleAssignment): '', identity_fakes.project_id, '', + '', False ), (identity_fakes.role_id, identity_fakes.user_id, '', '', identity_fakes.domain_id, + '', False ),) self.assertEqual(tuple(data), datalist) @@ -520,6 +547,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -536,6 +564,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=None, effective=False, project=None, @@ -551,12 +580,14 @@ class TestRoleAssignmentList(TestRoleAssignment): '', identity_fakes.project_id, '', + '', True ), (identity_fakes.role_id, identity_fakes.user_id, '', '', identity_fakes.domain_id, + '', True ),) self.assertEqual(datalist, tuple(data)) @@ -584,6 +615,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -602,6 +634,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=None, effective=False, project=None, @@ -610,7 +643,9 @@ class TestRoleAssignmentList(TestRoleAssignment): os_inherit_extension_inherited_to=None, include_names=True) - collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + collist = ( + 'Role', 'User', 'Group', 'Project', 'Domain', 'System', 'Inherited' + ) self.assertEqual(columns, collist) datalist1 = (( @@ -620,12 +655,14 @@ class TestRoleAssignmentList(TestRoleAssignment): '@'.join([identity_fakes.project_name, identity_fakes.domain_name]), '', + '', False ), (identity_fakes.role_name, '@'.join([identity_fakes.user_name, identity_fakes.domain_name]), '', '', identity_fakes.domain_name, + '', False ),) self.assertEqual(tuple(data), datalist1) @@ -648,6 +685,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', identity_fakes.ROLE_2['name']), @@ -664,6 +702,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, user=None, group=None, project=None, @@ -679,6 +718,7 @@ class TestRoleAssignmentList(TestRoleAssignment): '', '', identity_fakes.domain_id, + '', False ),) self.assertEqual(datalist, tuple(data)) diff --git a/releasenotes/notes/implement-system-scope-4c3c47996f98deac.yaml b/releasenotes/notes/implement-system-scope-4c3c47996f98deac.yaml new file mode 100644 index 0000000000..6ad3d8244d --- /dev/null +++ b/releasenotes/notes/implement-system-scope-4c3c47996f98deac.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Added support for system-scope. This includes support for the ability to + generate system-scoped tokens using ``system_scope: all`` in ``cloud.yaml`` + or ``OS_SYSTEM_SCOPE=all`` in an environment variable. Support is also + included for managing role assignments on the system using ``--system`` + when adding and removing roles. diff --git a/requirements.txt b/requirements.txt index bfee36b687..c5795fd5ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,6 @@ osc-lib>=1.8.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 -python-keystoneclient>=3.8.0 # Apache-2.0 +python-keystoneclient>=3.15.0 # Apache-2.0 python-novaclient>=9.1.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0