Skip to content

Commit 4fab606

Browse files
committed
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 <rodrigods@lsd.ufcg.edu.br> Co-Authored-By: Samuel de Medeiros Queiroz <samuel@lsd.ufcg.edu.br> Implements: bp hierarchical-multitenancy Change-Id: I874f6faffc8a2db9d99f12cbe0a69c0a30c0d9df
1 parent 7cf7790 commit 4fab606

5 files changed

Lines changed: 213 additions & 9 deletions

File tree

doc/source/command-objects/project.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,18 @@ Display project details
171171

172172
.. versionadded:: 3
173173

174+
.. option:: --parents
175+
176+
Show the project\'s parents as a list
177+
178+
.. versionadded:: 3
179+
180+
.. option:: --children
181+
182+
Show project\'s subtree (children) as a list
183+
184+
.. versionadded:: 3
185+
174186
.. _project_show-project:
175187
.. describe:: <project>
176188

openstackclient/common/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def find_resource(manager, name_or_id, **kwargs):
5151
# Try to get entity as integer id
5252
try:
5353
if isinstance(name_or_id, int) or name_or_id.isdigit():
54-
return manager.get(int(name_or_id))
54+
return manager.get(int(name_or_id), **kwargs)
5555
# FIXME(dtroyer): The exception to catch here is dependent on which
5656
# client library the manager passed in belongs to.
5757
# Eventually this should be pulled from a common set
@@ -64,7 +64,7 @@ def find_resource(manager, name_or_id, **kwargs):
6464

6565
# Try directly using the passed value
6666
try:
67-
return manager.get(name_or_id)
67+
return manager.get(name_or_id, **kwargs)
6868
except Exception:
6969
pass
7070

openstackclient/identity/v3/project.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,18 @@ def get_parser(self, prog_name):
323323
metavar='<domain>',
324324
help='Domain owning <project> (name or ID)',
325325
)
326+
parser.add_argument(
327+
'--parents',
328+
action='store_true',
329+
default=False,
330+
help='Show the project\'s parents as a list',
331+
)
332+
parser.add_argument(
333+
'--children',
334+
action='store_true',
335+
default=False,
336+
help='Show project\'s subtree (children) as a list',
337+
)
326338
return parser
327339

328340
def take_action(self, parsed_args):
@@ -331,14 +343,25 @@ def take_action(self, parsed_args):
331343

332344
if parsed_args.domain:
333345
domain = common.find_domain(identity_client, parsed_args.domain)
334-
project = utils.find_resource(identity_client.projects,
335-
parsed_args.project,
336-
domain_id=domain.id)
346+
project = utils.find_resource(
347+
identity_client.projects,
348+
parsed_args.project,
349+
domain_id=domain.id,
350+
parents_as_list=parsed_args.parents,
351+
subtree_as_list=parsed_args.children)
337352
else:
338-
project = utils.find_resource(identity_client.projects,
339-
parsed_args.project)
353+
project = utils.find_resource(
354+
identity_client.projects,
355+
parsed_args.project,
356+
parents_as_list=parsed_args.parents,
357+
subtree_as_list=parsed_args.children)
358+
359+
if project._info.get('parents'):
360+
project._info['parents'] = [str(p['project']['id'])
361+
for p in project._info['parents']]
362+
if project._info.get('subtree'):
363+
project._info['subtree'] = [str(p['project']['id'])
364+
for p in project._info['subtree']]
340365

341366
project._info.pop('links')
342-
# TODO(stevemar): Remove the line below when we support multitenancy
343-
project._info.pop('parent_id', None)
344367
return zip(*sorted(six.iteritems(project._info)))

openstackclient/tests/identity/v3/fakes.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,25 @@
145145
'links': base_url + 'projects/' + (project_id + '-with-parent'),
146146
}
147147

148+
PROJECT_WITH_GRANDPARENT = {
149+
'id': project_id + '-with-grandparent',
150+
'name': project_name + ', granny and grandpa',
151+
'description': project_description + ' plus another eight?',
152+
'enabled': True,
153+
'domain_id': domain_id,
154+
'parent_id': PROJECT_WITH_PARENT['id'],
155+
'links': base_url + 'projects/' + (project_id + '-with-grandparent'),
156+
}
157+
158+
parents = [{'project': PROJECT}]
159+
grandparents = [{'project': PROJECT}, {'project': PROJECT_WITH_PARENT}]
160+
ids_for_parents = [PROJECT['id']]
161+
ids_for_parents_and_grandparents = [PROJECT['id'], PROJECT_WITH_PARENT['id']]
162+
163+
children = [{'project': PROJECT_WITH_GRANDPARENT}]
164+
ids_for_children = [PROJECT_WITH_GRANDPARENT['id']]
165+
166+
148167
role_id = 'r1'
149168
role_name = 'roller'
150169

openstackclient/tests/identity/v3/test_project.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,8 @@ def test_project_show(self):
783783
columns, data = self.cmd.take_action(parsed_args)
784784
self.projects_mock.get.assert_called_with(
785785
identity_fakes.project_id,
786+
parents_as_list=False,
787+
subtree_as_list=False,
786788
)
787789

788790
collist = ('description', 'domain_id', 'enabled', 'id', 'name')
@@ -795,3 +797,151 @@ def test_project_show(self):
795797
identity_fakes.project_name,
796798
)
797799
self.assertEqual(datalist, data)
800+
801+
def test_project_show_parents(self):
802+
project = copy.deepcopy(identity_fakes.PROJECT_WITH_GRANDPARENT)
803+
project['parents'] = identity_fakes.grandparents
804+
self.projects_mock.get.return_value = fakes.FakeResource(
805+
None,
806+
project,
807+
loaded=True,
808+
)
809+
810+
arglist = [
811+
identity_fakes.PROJECT_WITH_GRANDPARENT['id'],
812+
'--parents',
813+
]
814+
verifylist = [
815+
('project', identity_fakes.PROJECT_WITH_GRANDPARENT['id']),
816+
('parents', True),
817+
('children', False),
818+
]
819+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
820+
821+
columns, data = self.cmd.take_action(parsed_args)
822+
self.projects_mock.get.assert_called_with(
823+
identity_fakes.PROJECT_WITH_GRANDPARENT['id'],
824+
parents_as_list=True,
825+
subtree_as_list=False,
826+
)
827+
828+
collist = (
829+
'description',
830+
'domain_id',
831+
'enabled',
832+
'id',
833+
'name',
834+
'parent_id',
835+
'parents',
836+
)
837+
self.assertEqual(columns, collist)
838+
datalist = (
839+
identity_fakes.PROJECT_WITH_GRANDPARENT['description'],
840+
identity_fakes.PROJECT_WITH_GRANDPARENT['domain_id'],
841+
identity_fakes.PROJECT_WITH_GRANDPARENT['enabled'],
842+
identity_fakes.PROJECT_WITH_GRANDPARENT['id'],
843+
identity_fakes.PROJECT_WITH_GRANDPARENT['name'],
844+
identity_fakes.PROJECT_WITH_GRANDPARENT['parent_id'],
845+
identity_fakes.ids_for_parents_and_grandparents,
846+
)
847+
self.assertEqual(data, datalist)
848+
849+
def test_project_show_subtree(self):
850+
project = copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT)
851+
project['subtree'] = identity_fakes.children
852+
self.projects_mock.get.return_value = fakes.FakeResource(
853+
None,
854+
project,
855+
loaded=True,
856+
)
857+
858+
arglist = [
859+
identity_fakes.PROJECT_WITH_PARENT['id'],
860+
'--children',
861+
]
862+
verifylist = [
863+
('project', identity_fakes.PROJECT_WITH_PARENT['id']),
864+
('parents', False),
865+
('children', True),
866+
]
867+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
868+
869+
columns, data = self.cmd.take_action(parsed_args)
870+
self.projects_mock.get.assert_called_with(
871+
identity_fakes.PROJECT_WITH_PARENT['id'],
872+
parents_as_list=False,
873+
subtree_as_list=True,
874+
)
875+
876+
collist = (
877+
'description',
878+
'domain_id',
879+
'enabled',
880+
'id',
881+
'name',
882+
'parent_id',
883+
'subtree',
884+
)
885+
self.assertEqual(columns, collist)
886+
datalist = (
887+
identity_fakes.PROJECT_WITH_PARENT['description'],
888+
identity_fakes.PROJECT_WITH_PARENT['domain_id'],
889+
identity_fakes.PROJECT_WITH_PARENT['enabled'],
890+
identity_fakes.PROJECT_WITH_PARENT['id'],
891+
identity_fakes.PROJECT_WITH_PARENT['name'],
892+
identity_fakes.PROJECT_WITH_PARENT['parent_id'],
893+
identity_fakes.ids_for_children,
894+
)
895+
self.assertEqual(data, datalist)
896+
897+
def test_project_show_parents_and_children(self):
898+
project = copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT)
899+
project['subtree'] = identity_fakes.children
900+
project['parents'] = identity_fakes.parents
901+
self.projects_mock.get.return_value = fakes.FakeResource(
902+
None,
903+
project,
904+
loaded=True,
905+
)
906+
907+
arglist = [
908+
identity_fakes.PROJECT_WITH_PARENT['id'],
909+
'--parents',
910+
'--children',
911+
]
912+
verifylist = [
913+
('project', identity_fakes.PROJECT_WITH_PARENT['id']),
914+
('parents', True),
915+
('children', True),
916+
]
917+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
918+
919+
columns, data = self.cmd.take_action(parsed_args)
920+
self.projects_mock.get.assert_called_with(
921+
identity_fakes.PROJECT_WITH_PARENT['id'],
922+
parents_as_list=True,
923+
subtree_as_list=True,
924+
)
925+
926+
collist = (
927+
'description',
928+
'domain_id',
929+
'enabled',
930+
'id',
931+
'name',
932+
'parent_id',
933+
'parents',
934+
'subtree',
935+
)
936+
self.assertEqual(columns, collist)
937+
datalist = (
938+
identity_fakes.PROJECT_WITH_PARENT['description'],
939+
identity_fakes.PROJECT_WITH_PARENT['domain_id'],
940+
identity_fakes.PROJECT_WITH_PARENT['enabled'],
941+
identity_fakes.PROJECT_WITH_PARENT['id'],
942+
identity_fakes.PROJECT_WITH_PARENT['name'],
943+
identity_fakes.PROJECT_WITH_PARENT['parent_id'],
944+
identity_fakes.ids_for_parents,
945+
identity_fakes.ids_for_children,
946+
)
947+
self.assertEqual(data, datalist)

0 commit comments

Comments
 (0)