Added /api/v1/groups/N/hosts and /api/v1/groups/N/all_hosts/

This commit is contained in:
Michael DeHaan 2013-04-16 17:41:20 -04:00
parent 27ac9a206e
commit 5e6ad5a244
5 changed files with 140 additions and 0 deletions

View File

@ -78,3 +78,9 @@ QUESTIONS
* if creating a project, do we want to have an appliance style path for them, like
/storage/projects/GUID ??? may want to keep somewhere else ?
MISC
----
when associating inventory objects, objects should share common inventory records (user_can_attach method should check)

View File

@ -123,6 +123,11 @@ class BaseSubList(BaseList):
# now make sure we could have already attached the two together. If we could not have, raise an exception
# such that the transaction does not commit.
if main == obj:
# no attaching to yourself
raise PermissionDenied()
if self.__class__.parent_model != User:
if not self.__class__.parent_model.can_user_attach(request.user, main, obj, self.__class__.relationship):
raise PermissionDenied()

View File

@ -212,6 +212,7 @@ class InventoryTest(BaseTest):
got = self.get(url5, expect=200, auth=self.get_other_credentials())
self.assertEquals(got['count'], 3)
##################################################
# GROUPS->inventories POST via subcollection
@ -310,6 +311,38 @@ class InventoryTest(BaseTest):
put = self.put(vdata1_url, data=vars_c, expect=200, auth=self.get_normal_credentials())
self.assertEquals(put, vars_c)
####################################################
# ADDING HOSTS TO GROUPS
groups = Group.objects.all()
hosts = Host.objects.all()
groups[0].hosts.add(Host.objects.get(pk=1))
groups[0].hosts.add(Host.objects.get(pk=3))
groups[0].save()
# access
url1 = '/api/v1/groups/1/hosts/'
data = self.get(url1, expect=200, auth=self.get_normal_credentials())
self.assertEquals(data['count'], 2)
self.assertEquals(data['results'][0]['id'], 1)
self.assertEquals(data['results'][1]['id'], 3)
# addition
got = self.get('/api/v1/hosts/2/', expect=200, auth=self.get_normal_credentials())
self.assertEquals(got['id'], 2)
posted = self.post('/api/v1/groups/1/hosts/', data=got, expect=204, auth=self.get_normal_credentials())
data = self.get(url1, expect=200, auth=self.get_normal_credentials())
self.assertEquals(data['count'], 3)
self.assertEquals(data['results'][1]['id'], 2)
# removal
got['disassociate'] = 1
posted = self.post('/api/v1/groups/1/hosts/', data=got, expect=204, auth=self.get_normal_credentials())
data = self.get(url1, expect=200, auth=self.get_normal_credentials())
self.assertEquals(data['count'], 2)
self.assertEquals(data['results'][1]['id'], 3)
####################################################
# SUBGROUPS
@ -329,6 +362,12 @@ class InventoryTest(BaseTest):
permission_type = PERM_INVENTORY_WRITE
)
# data used for testing listing all hosts that are transitive members of a group
g2 = Group.objects.get(pk=2)
nh = Host.objects.create(name='newhost.example.com', inventory=inv, created_by=User.objects.get(pk=1))
g2.hosts.add(nh)
g2.save()
# a super user can set subgroups
subgroups_url = '/api/v1/groups/1/children/'
child_url = '/api/v1/groups/2/'
@ -355,6 +394,15 @@ class InventoryTest(BaseTest):
self.post(subgroups_url3, data=got, expect=204, auth=self.get_other_credentials())
checked = self.get(subgroups_url3, expect=200, auth=self.get_normal_credentials())
self.assertEqual(checked['count'], 1)
# slight detour
# can see all hosts under a group, even if it has subgroups
# this URL is NOT postable
all_hosts = '/api/v1/groups/1/all_hosts/'
self.assertEqual(Group.objects.get(pk=1).hosts.count(), 2)
data = self.get(all_hosts, expect=200, auth=self.get_normal_credentials())
self.post(all_hosts, data=dict(id=123456, msg='spam'), expect=405, auth=self.get_normal_credentials())
self.assertEquals(data['count'], 3)
# now post it back to remove it, by adding the disassociate bit
result = checked['results'][0]

View File

@ -578,6 +578,83 @@ class GroupsChildrenList(BaseSubList):
).distinct()
return admin_of | has_user_perms | has_team_perms
class GroupsHostsList(BaseSubList):
''' the list of hosts directly below a group '''
model = Host
serializer_class = HostSerializer
permission_classes = (CustomRbac,)
parent_model = Group
relationship = 'hosts'
postable = True
inject_primary_key_on_post_as = 'group'
def _get_queryset(self):
parent = Group.objects.get(pk=self.kwargs['pk'])
# FIXME: verify read permissions on this object are still required at a higher level
base = parent.hosts
if self.request.user.is_superuser:
return base.all()
admin_of = base.filter(inventory__organization__admins__in = [ self.request.user ]).distinct()
has_user_perms = base.filter(
inventory__permissions__user__in = [ self.request.user ],
inventory__permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
).distinct()
has_team_perms = base.filter(
inventory__permissions__team__in = self.request.user.teams.all(),
inventory__permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
).distinct()
return admin_of | has_user_perms | has_team_perms
class GroupsAllHostsList(BaseSubList):
''' the list of all hosts below a group, even including subgroups '''
model = Host
serializer_class = HostSerializer
permission_classes = (CustomRbac,)
parent_model = Group
relationship = 'hosts'
def _child_hosts(self, parent):
# TODO: should probably be a method on the model
result = parent.hosts.distinct()
if parent.children.count() == 0:
return result
else:
for child in parent.children.all():
if child == parent:
# shouldn't happen, but be prepared in case DB is weird
continue
result = result | self._child_hosts(child)
return result
def _get_queryset(self):
parent = Group.objects.get(pk=self.kwargs['pk'])
# FIXME: verify read permissions on this object are still required at a higher level
base = self._child_hosts(parent)
if self.request.user.is_superuser:
return base.all()
admin_of = base.filter(inventory__organization__admins__in = [ self.request.user ]).distinct()
has_user_perms = base.filter(
inventory__permissions__user__in = [ self.request.user ],
inventory__permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
).distinct()
has_team_perms = base.filter(
inventory__permissions__team__in = self.request.user.teams.all(),
inventory__permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
).distinct()
return admin_of | has_user_perms | has_team_perms
class GroupsDetail(BaseDetail):
model = Group

View File

@ -66,6 +66,8 @@ views_GroupsList = views.GroupsList.as_view()
views_GroupsDetail = views.GroupsDetail.as_view()
views_GroupsVariableDetail = views.GroupsVariableDetail.as_view()
views_GroupsChildrenList = views.GroupsChildrenList.as_view()
views_GroupsAllHostsList = views.GroupsAllHostsList.as_view()
views_GroupsHostsList = views.GroupsHostsList.as_view()
# host service
views_HostsList = views.HostsList.as_view()
@ -147,6 +149,8 @@ urlpatterns = patterns('',
url(r'^api/v1/groups/$', views_GroupsList),
url(r'^api/v1/groups/(?P<pk>[0-9]+)/$', views_GroupsDetail),
url(r'^api/v1/groups/(?P<pk>[0-9]+)/children/$', views_GroupsChildrenList),
url(r'^api/v1/groups/(?P<pk>[0-9]+)/hosts/$', views_GroupsHostsList),
url(r'^api/v1/groups/(?P<pk>[0-9]+)/all_hosts/$', views_GroupsAllHostsList),
# variable data
url(r'^api/v1/hosts/(?P<pk>[0-9]+)/variable_data/$', views_HostsVariableDetail),