mirror of
https://github.com/ansible/awx.git
synced 2026-05-14 12:57:40 -02:30
More updates for InventorySource changes
This commit is contained in:
@@ -1238,7 +1238,7 @@ class HostSerializer(BaseSerializerWithVariables):
|
|||||||
|
|
||||||
|
|
||||||
class GroupSerializer(BaseSerializerWithVariables):
|
class GroupSerializer(BaseSerializerWithVariables):
|
||||||
show_capabilities = ['start', 'copy', 'schedule', 'edit', 'delete']
|
show_capabilities = ['copy', 'edit', 'delete']
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Group
|
model = Group
|
||||||
@@ -1427,13 +1427,12 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
|
|||||||
status = serializers.ChoiceField(choices=InventorySource.INVENTORY_SOURCE_STATUS_CHOICES, read_only=True)
|
status = serializers.ChoiceField(choices=InventorySource.INVENTORY_SOURCE_STATUS_CHOICES, read_only=True)
|
||||||
last_update_failed = serializers.BooleanField(read_only=True)
|
last_update_failed = serializers.BooleanField(read_only=True)
|
||||||
last_updated = serializers.DateTimeField(read_only=True)
|
last_updated = serializers.DateTimeField(read_only=True)
|
||||||
|
show_capabilities = ['start', 'schedule', 'edit', 'delete']
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InventorySource
|
model = InventorySource
|
||||||
fields = ('*', 'inventory', 'update_on_launch',
|
fields = ('*', 'name', 'inventory', 'update_on_launch', 'update_cache_timeout') + \
|
||||||
'update_cache_timeout') + \
|
|
||||||
('last_update_failed', 'last_updated') # Backwards compatibility.
|
('last_update_failed', 'last_updated') # Backwards compatibility.
|
||||||
read_only_fields = ('*', 'name', 'inventory')
|
|
||||||
|
|
||||||
def get_related(self, obj):
|
def get_related(self, obj):
|
||||||
res = super(InventorySourceSerializer, self).get_related(obj)
|
res = super(InventorySourceSerializer, self).get_related(obj)
|
||||||
@@ -1450,8 +1449,6 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
|
|||||||
))
|
))
|
||||||
if obj.inventory:
|
if obj.inventory:
|
||||||
res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk})
|
res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk})
|
||||||
if obj.group:
|
|
||||||
res['group'] = self.reverse('api:group_detail', kwargs={'pk': obj.group.pk})
|
|
||||||
# Backwards compatibility.
|
# Backwards compatibility.
|
||||||
if obj.current_update:
|
if obj.current_update:
|
||||||
res['current_update'] = self.reverse('api:inventory_update_detail',
|
res['current_update'] = self.reverse('api:inventory_update_detail',
|
||||||
@@ -1467,8 +1464,6 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
|
|||||||
return ret
|
return ret
|
||||||
if 'inventory' in ret and not obj.inventory:
|
if 'inventory' in ret and not obj.inventory:
|
||||||
ret['inventory'] = None
|
ret['inventory'] = None
|
||||||
if 'group' in ret and not obj.group:
|
|
||||||
ret['group'] = None
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2154,7 +2154,7 @@ class InventoryTreeView(RetrieveAPIView):
|
|||||||
root_group_pks = inventory.root_groups.order_by('name').values_list('pk', flat=True)
|
root_group_pks = inventory.root_groups.order_by('name').values_list('pk', flat=True)
|
||||||
groups_qs = inventory.groups
|
groups_qs = inventory.groups
|
||||||
groups_qs = groups_qs.select_related('inventory')
|
groups_qs = groups_qs.select_related('inventory')
|
||||||
groups_qs = groups_qs.prefetch_related('inventory_source')
|
groups_qs = groups_qs.prefetch_related('inventory_sources')
|
||||||
all_group_data = GroupSerializer(groups_qs, many=True).data
|
all_group_data = GroupSerializer(groups_qs, many=True).data
|
||||||
all_group_data_map = dict((x['id'], x) for x in all_group_data)
|
all_group_data_map = dict((x['id'], x) for x in all_group_data)
|
||||||
tree_data = [all_group_data_map[x] for x in root_group_pks]
|
tree_data = [all_group_data_map[x] for x in root_group_pks]
|
||||||
@@ -2164,22 +2164,17 @@ class InventoryTreeView(RetrieveAPIView):
|
|||||||
return Response(tree_data)
|
return Response(tree_data)
|
||||||
|
|
||||||
|
|
||||||
class InventoryInventorySourcesList(SubListAPIView):
|
class InventoryInventorySourcesList(SubListCreateAPIView):
|
||||||
|
|
||||||
|
view_name = _('Inventory Source List')
|
||||||
|
|
||||||
model = InventorySource
|
model = InventorySource
|
||||||
serializer_class = InventorySourceSerializer
|
serializer_class = InventorySourceSerializer
|
||||||
parent_model = Inventory
|
parent_model = Inventory
|
||||||
relationship = None # Not defined since using get_queryset().
|
relationship = 'inventory_sources'
|
||||||
view_name = _('Inventory Source List')
|
parent_key = 'inventory'
|
||||||
new_in_14 = True
|
new_in_14 = True
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
parent = self.get_parent_object()
|
|
||||||
self.check_parent_access(parent)
|
|
||||||
qs = self.request.user.get_queryset(self.model)
|
|
||||||
return qs.filter(Q(inventory__pk=parent.pk) |
|
|
||||||
Q(group__inventory__pk=parent.pk))
|
|
||||||
|
|
||||||
|
|
||||||
class InventorySourceList(ListAPIView):
|
class InventorySourceList(ListAPIView):
|
||||||
|
|
||||||
|
|||||||
@@ -670,7 +670,7 @@ class GroupAccess(BaseAccess):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = Group.objects.filter(inventory__in=Inventory.accessible_objects(self.user, 'read_role'))
|
qs = Group.objects.filter(inventory__in=Inventory.accessible_objects(self.user, 'read_role'))
|
||||||
qs = qs.select_related('created_by', 'modified_by', 'inventory')
|
qs = qs.select_related('created_by', 'modified_by', 'inventory')
|
||||||
return qs.prefetch_related('parents', 'children', 'inventory_source').all()
|
return qs.prefetch_related('parents', 'children').all()
|
||||||
|
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
return obj and self.user in obj.inventory.read_role
|
return obj and self.user in obj.inventory.read_role
|
||||||
@@ -720,11 +720,6 @@ class GroupAccess(BaseAccess):
|
|||||||
"active_jobs": active_jobs})
|
"active_jobs": active_jobs})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def can_start(self, obj, validate_license=True):
|
|
||||||
if obj and obj.inventory:
|
|
||||||
return self.user.can_access(Inventory, 'start', obj.inventory, validate_license=validate_license)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class InventorySourceAccess(BaseAccess):
|
class InventorySourceAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
@@ -736,10 +731,9 @@ class InventorySourceAccess(BaseAccess):
|
|||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = self.model.objects.all()
|
qs = self.model.objects.all()
|
||||||
qs = qs.select_related('created_by', 'modified_by', 'group', 'inventory')
|
qs = qs.select_related('created_by', 'modified_by', 'inventory')
|
||||||
inventory_ids = self.user.get_queryset(Inventory)
|
inventory_ids = self.user.get_queryset(Inventory)
|
||||||
return qs.filter(Q(inventory_id__in=inventory_ids) |
|
return qs.filter(Q(inventory_id__in=inventory_ids))
|
||||||
Q(group__inventory_id__in=inventory_ids))
|
|
||||||
|
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
if obj and obj.inventory:
|
if obj and obj.inventory:
|
||||||
@@ -748,8 +742,10 @@ class InventorySourceAccess(BaseAccess):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
# Automatically created from group or management command.
|
if not data or 'inventory' not in data:
|
||||||
return False
|
return False
|
||||||
|
# Checks for admin or change permission on inventory.
|
||||||
|
return self.check_related('inventory', Inventory, data)
|
||||||
|
|
||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
# Checks for admin or change permission on group.
|
# Checks for admin or change permission on group.
|
||||||
@@ -780,8 +776,7 @@ class InventoryUpdateAccess(BaseAccess):
|
|||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = InventoryUpdate.objects.distinct()
|
qs = InventoryUpdate.objects.distinct()
|
||||||
qs = qs.select_related('created_by', 'modified_by', 'inventory_source__group',
|
qs = qs.select_related('created_by', 'modified_by', 'inventory_source__inventory')
|
||||||
'inventory_source__inventory')
|
|
||||||
inventory_sources_qs = self.user.get_queryset(InventorySource)
|
inventory_sources_qs = self.user.get_queryset(InventorySource)
|
||||||
return qs.filter(inventory_source__in=inventory_sources_qs)
|
return qs.filter(inventory_source__in=inventory_sources_qs)
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ class Migration(migrations.Migration):
|
|||||||
model_name='inventorysource',
|
model_name='inventorysource',
|
||||||
name='group',
|
name='group',
|
||||||
),
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='inventorysource',
|
||||||
|
name='inventory',
|
||||||
|
field=models.ForeignKey(related_name='inventory_sources', default=None, to='main.Inventory', null=True),
|
||||||
|
),
|
||||||
|
|
||||||
# Facts Latest
|
# Facts Latest
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
|
|||||||
@@ -1060,7 +1060,6 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
|
|||||||
related_name='inventory_sources',
|
related_name='inventory_sources',
|
||||||
null=True,
|
null=True,
|
||||||
default=None,
|
default=None,
|
||||||
editable=False,
|
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
)
|
)
|
||||||
update_on_launch = models.BooleanField(
|
update_on_launch = models.BooleanField(
|
||||||
@@ -1169,16 +1168,6 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
|
|||||||
success=list(success_notification_templates),
|
success=list(success_notification_templates),
|
||||||
any=list(any_notification_templates))
|
any=list(any_notification_templates))
|
||||||
|
|
||||||
def clean_source(self):
|
|
||||||
source = self.source
|
|
||||||
if source and self.group:
|
|
||||||
qs = self.group.inventory_sources.filter(source__in=CLOUD_INVENTORY_SOURCES)
|
|
||||||
existing_sources = qs.exclude(pk=self.pk)
|
|
||||||
if existing_sources.count():
|
|
||||||
s = u', '.join([x.group.name for x in existing_sources])
|
|
||||||
raise ValidationError(_('Unable to configure this item for cloud sync. It is already managed by %s.') % s)
|
|
||||||
return source
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin):
|
class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin):
|
||||||
'''
|
'''
|
||||||
@@ -1213,8 +1202,6 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin):
|
|||||||
|
|
||||||
def websocket_emit_data(self):
|
def websocket_emit_data(self):
|
||||||
websocket_data = super(InventoryUpdate, self).websocket_emit_data()
|
websocket_data = super(InventoryUpdate, self).websocket_emit_data()
|
||||||
if self.inventory_source.group is not None:
|
|
||||||
websocket_data.update(dict(group_id=self.inventory_source.group.id))
|
|
||||||
return websocket_data
|
return websocket_data
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -97,6 +97,21 @@ def test_edit_inventory_group(put, group, alice, role_field, expected_status_cod
|
|||||||
put(reverse('api:group_detail', kwargs={'pk': group.id}), data, alice, expect=expected_status_code)
|
put(reverse('api:group_detail', kwargs={'pk': group.id}), data, alice, expect=expected_status_code)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("role_field,expected_status_code", [
|
||||||
|
(None, 403),
|
||||||
|
('admin_role', 201),
|
||||||
|
('update_role', 403),
|
||||||
|
('adhoc_role', 403),
|
||||||
|
('use_role', 403)
|
||||||
|
])
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_create_inventory_inventory_source(post, inventory, alice, role_field, expected_status_code):
|
||||||
|
data = { 'source': 'ec2', 'name': 'ec2-inv-source'}
|
||||||
|
if role_field:
|
||||||
|
getattr(inventory, role_field).members.add(alice)
|
||||||
|
post(reverse('api:inventory_inventory_sources_list', kwargs={'pk': inventory.id}), data, alice, expect=expected_status_code)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("role_field,expected_status_code", [
|
@pytest.mark.parametrize("role_field,expected_status_code", [
|
||||||
(None, 403),
|
(None, 403),
|
||||||
('admin_role', 204),
|
('admin_role', 204),
|
||||||
|
|||||||
@@ -341,28 +341,6 @@ def test_manual_projects_no_update(project, get, admin_user):
|
|||||||
assert not response.data['summary_fields']['user_capabilities']['schedule']
|
assert not response.data['summary_fields']['user_capabilities']['schedule']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_group_update_capabilities_possible(group, inventory_source, admin_user):
|
|
||||||
group.inventory_source = inventory_source
|
|
||||||
group.save()
|
|
||||||
|
|
||||||
capabilities = get_user_capabilities(admin_user, group.inventory, method_list=['start'])
|
|
||||||
assert capabilities['start']
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_group_update_capabilities_impossible(group, inventory_source, admin_user):
|
|
||||||
"Manual groups can not be updated or scheduled"
|
|
||||||
inventory_source.source = ""
|
|
||||||
inventory_source.save()
|
|
||||||
group.inventory_source = inventory_source
|
|
||||||
group.save()
|
|
||||||
|
|
||||||
capabilities = get_user_capabilities(admin_user, group, method_list=['edit', 'start', 'schedule'])
|
|
||||||
assert not capabilities['start']
|
|
||||||
assert not capabilities['schedule']
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_license_check_not_called(mocker, job_template, project, org_admin, get):
|
def test_license_check_not_called(mocker, job_template, project, org_admin, get):
|
||||||
job_template.project = project
|
job_template.project = project
|
||||||
|
|||||||
Reference in New Issue
Block a user