From 4fab60634983f9327295cc68edf89824557728f9 Mon Sep 17 00:00:00 2001 From: Rodrigo Duarte Sousa Date: Fri, 20 Mar 2015 17:51:02 -0300 Subject: [PATCH] Enables retrieval of project's parents and subtree Adds the possibility to retrieve a project and list its parents and subtree in the hierarchy. Co-Authored-By: Rodrigo Duarte Co-Authored-By: Samuel de Medeiros Queiroz Implements: bp hierarchical-multitenancy Change-Id: I874f6faffc8a2db9d99f12cbe0a69c0a30c0d9df --- doc/source/command-objects/project.rst | 12 ++ openstackclient/common/utils.py | 4 +- openstackclient/identity/v3/project.py | 37 ++++- openstackclient/tests/identity/v3/fakes.py | 19 +++ .../tests/identity/v3/test_project.py | 150 ++++++++++++++++++ 5 files changed, 213 insertions(+), 9 deletions(-) diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst index 63796da84a..637a44986f 100644 --- a/doc/source/command-objects/project.rst +++ b/doc/source/command-objects/project.rst @@ -171,6 +171,18 @@ Display project details .. versionadded:: 3 +.. option:: --parents + + Show the project\'s parents as a list + + .. versionadded:: 3 + +.. option:: --children + + Show project\'s subtree (children) as a list + + .. versionadded:: 3 + .. _project_show-project: .. describe:: diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index aad0519c1b..c824678e3b 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -51,7 +51,7 @@ def find_resource(manager, name_or_id, **kwargs): # Try to get entity as integer id try: if isinstance(name_or_id, int) or name_or_id.isdigit(): - return manager.get(int(name_or_id)) + return manager.get(int(name_or_id), **kwargs) # FIXME(dtroyer): The exception to catch here is dependent on which # client library the manager passed in belongs to. # Eventually this should be pulled from a common set @@ -64,7 +64,7 @@ def find_resource(manager, name_or_id, **kwargs): # Try directly using the passed value try: - return manager.get(name_or_id) + return manager.get(name_or_id, **kwargs) except Exception: pass diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 48f547f3ed..8185d65af8 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -323,6 +323,18 @@ class ShowProject(show.ShowOne): metavar='', help='Domain owning (name or ID)', ) + parser.add_argument( + '--parents', + action='store_true', + default=False, + help='Show the project\'s parents as a list', + ) + parser.add_argument( + '--children', + action='store_true', + default=False, + help='Show project\'s subtree (children) as a list', + ) return parser def take_action(self, parsed_args): @@ -331,14 +343,25 @@ class ShowProject(show.ShowOne): if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) - project = utils.find_resource(identity_client.projects, - parsed_args.project, - domain_id=domain.id) + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + domain_id=domain.id, + parents_as_list=parsed_args.parents, + subtree_as_list=parsed_args.children) else: - project = utils.find_resource(identity_client.projects, - parsed_args.project) + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + parents_as_list=parsed_args.parents, + subtree_as_list=parsed_args.children) + + if project._info.get('parents'): + project._info['parents'] = [str(p['project']['id']) + for p in project._info['parents']] + if project._info.get('subtree'): + project._info['subtree'] = [str(p['project']['id']) + for p in project._info['subtree']] project._info.pop('links') - # TODO(stevemar): Remove the line below when we support multitenancy - project._info.pop('parent_id', None) return zip(*sorted(six.iteritems(project._info))) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index dfbcf44f60..ae7a684cf0 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -145,6 +145,25 @@ PROJECT_WITH_PARENT = { 'links': base_url + 'projects/' + (project_id + '-with-parent'), } +PROJECT_WITH_GRANDPARENT = { + 'id': project_id + '-with-grandparent', + 'name': project_name + ', granny and grandpa', + 'description': project_description + ' plus another eight?', + 'enabled': True, + 'domain_id': domain_id, + 'parent_id': PROJECT_WITH_PARENT['id'], + 'links': base_url + 'projects/' + (project_id + '-with-grandparent'), +} + +parents = [{'project': PROJECT}] +grandparents = [{'project': PROJECT}, {'project': PROJECT_WITH_PARENT}] +ids_for_parents = [PROJECT['id']] +ids_for_parents_and_grandparents = [PROJECT['id'], PROJECT_WITH_PARENT['id']] + +children = [{'project': PROJECT_WITH_GRANDPARENT}] +ids_for_children = [PROJECT_WITH_GRANDPARENT['id']] + + role_id = 'r1' role_name = 'roller' diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index ebf612ccd3..946bbdcd89 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -783,6 +783,8 @@ class TestProjectShow(TestProject): columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( identity_fakes.project_id, + parents_as_list=False, + subtree_as_list=False, ) collist = ('description', 'domain_id', 'enabled', 'id', 'name') @@ -795,3 +797,151 @@ class TestProjectShow(TestProject): identity_fakes.project_name, ) self.assertEqual(datalist, data) + + def test_project_show_parents(self): + project = copy.deepcopy(identity_fakes.PROJECT_WITH_GRANDPARENT) + project['parents'] = identity_fakes.grandparents + self.projects_mock.get.return_value = fakes.FakeResource( + None, + project, + loaded=True, + ) + + arglist = [ + identity_fakes.PROJECT_WITH_GRANDPARENT['id'], + '--parents', + ] + verifylist = [ + ('project', identity_fakes.PROJECT_WITH_GRANDPARENT['id']), + ('parents', True), + ('children', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.projects_mock.get.assert_called_with( + identity_fakes.PROJECT_WITH_GRANDPARENT['id'], + parents_as_list=True, + subtree_as_list=False, + ) + + collist = ( + 'description', + 'domain_id', + 'enabled', + 'id', + 'name', + 'parent_id', + 'parents', + ) + self.assertEqual(columns, collist) + datalist = ( + identity_fakes.PROJECT_WITH_GRANDPARENT['description'], + identity_fakes.PROJECT_WITH_GRANDPARENT['domain_id'], + identity_fakes.PROJECT_WITH_GRANDPARENT['enabled'], + identity_fakes.PROJECT_WITH_GRANDPARENT['id'], + identity_fakes.PROJECT_WITH_GRANDPARENT['name'], + identity_fakes.PROJECT_WITH_GRANDPARENT['parent_id'], + identity_fakes.ids_for_parents_and_grandparents, + ) + self.assertEqual(data, datalist) + + def test_project_show_subtree(self): + project = copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT) + project['subtree'] = identity_fakes.children + self.projects_mock.get.return_value = fakes.FakeResource( + None, + project, + loaded=True, + ) + + arglist = [ + identity_fakes.PROJECT_WITH_PARENT['id'], + '--children', + ] + verifylist = [ + ('project', identity_fakes.PROJECT_WITH_PARENT['id']), + ('parents', False), + ('children', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.projects_mock.get.assert_called_with( + identity_fakes.PROJECT_WITH_PARENT['id'], + parents_as_list=False, + subtree_as_list=True, + ) + + collist = ( + 'description', + 'domain_id', + 'enabled', + 'id', + 'name', + 'parent_id', + 'subtree', + ) + self.assertEqual(columns, collist) + datalist = ( + identity_fakes.PROJECT_WITH_PARENT['description'], + identity_fakes.PROJECT_WITH_PARENT['domain_id'], + identity_fakes.PROJECT_WITH_PARENT['enabled'], + identity_fakes.PROJECT_WITH_PARENT['id'], + identity_fakes.PROJECT_WITH_PARENT['name'], + identity_fakes.PROJECT_WITH_PARENT['parent_id'], + identity_fakes.ids_for_children, + ) + self.assertEqual(data, datalist) + + def test_project_show_parents_and_children(self): + project = copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT) + project['subtree'] = identity_fakes.children + project['parents'] = identity_fakes.parents + self.projects_mock.get.return_value = fakes.FakeResource( + None, + project, + loaded=True, + ) + + arglist = [ + identity_fakes.PROJECT_WITH_PARENT['id'], + '--parents', + '--children', + ] + verifylist = [ + ('project', identity_fakes.PROJECT_WITH_PARENT['id']), + ('parents', True), + ('children', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.projects_mock.get.assert_called_with( + identity_fakes.PROJECT_WITH_PARENT['id'], + parents_as_list=True, + subtree_as_list=True, + ) + + collist = ( + 'description', + 'domain_id', + 'enabled', + 'id', + 'name', + 'parent_id', + 'parents', + 'subtree', + ) + self.assertEqual(columns, collist) + datalist = ( + identity_fakes.PROJECT_WITH_PARENT['description'], + identity_fakes.PROJECT_WITH_PARENT['domain_id'], + identity_fakes.PROJECT_WITH_PARENT['enabled'], + identity_fakes.PROJECT_WITH_PARENT['id'], + identity_fakes.PROJECT_WITH_PARENT['name'], + identity_fakes.PROJECT_WITH_PARENT['parent_id'], + identity_fakes.ids_for_parents, + identity_fakes.ids_for_children, + ) + self.assertEqual(data, datalist)