mirror of
https://github.com/ansible/awx.git
synced 2026-03-01 00:38:45 -03:30
Switch disallowed object delete to 409
In the case of running job conflicts
This commit is contained in:
@@ -2184,14 +2184,6 @@ class JobTemplateDetail(RetrieveUpdateDestroyAPIView):
|
|||||||
serializer_class = JobTemplateSerializer
|
serializer_class = JobTemplateSerializer
|
||||||
always_allow_superuser = False
|
always_allow_superuser = False
|
||||||
|
|
||||||
def destroy(self, request, *args, **kwargs):
|
|
||||||
obj = self.get_object()
|
|
||||||
can_delete = request.user.can_access(JobTemplate, 'delete', obj)
|
|
||||||
if not can_delete:
|
|
||||||
raise PermissionDenied("Cannot delete job template.")
|
|
||||||
return super(JobTemplateDetail, self).destroy(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
|
class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
|
||||||
|
|
||||||
model = JobTemplate
|
model = JobTemplate
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from django.contrib.auth.models import User
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework.exceptions import ParseError, PermissionDenied, ValidationError
|
from rest_framework.exceptions import ParseError, PermissionDenied, APIException
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.utils import * # noqa
|
from awx.main.utils import * # noqa
|
||||||
@@ -25,7 +25,7 @@ from awx.main.conf import tower_settings
|
|||||||
|
|
||||||
__all__ = ['get_user_queryset', 'check_user_access',
|
__all__ = ['get_user_queryset', 'check_user_access',
|
||||||
'user_accessible_objects',
|
'user_accessible_objects',
|
||||||
'user_admin_role',]
|
'user_admin_role', 'StateConflict',]
|
||||||
|
|
||||||
PERMISSION_TYPES = [
|
PERMISSION_TYPES = [
|
||||||
PERM_INVENTORY_ADMIN,
|
PERM_INVENTORY_ADMIN,
|
||||||
@@ -57,6 +57,9 @@ access_registry = {
|
|||||||
# ...
|
# ...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class StateConflict(APIException):
|
||||||
|
status_code = 409
|
||||||
|
default_detail = 'Object state is not correct'
|
||||||
|
|
||||||
def register_access(model_class, access_class):
|
def register_access(model_class, access_class):
|
||||||
access_classes = access_registry.setdefault(model_class, [])
|
access_classes = access_registry.setdefault(model_class, [])
|
||||||
@@ -315,11 +318,15 @@ class OrganizationAccess(BaseAccess):
|
|||||||
if not is_change_possible:
|
if not is_change_possible:
|
||||||
return False
|
return False
|
||||||
active_jobs = []
|
active_jobs = []
|
||||||
active_jobs.extend(Job.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES))
|
active_jobs.extend([dict(type="job", id=o.id)
|
||||||
active_jobs.extend(ProjectUpdate.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES))
|
for o in Job.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES)])
|
||||||
active_jobs.extend(InventoryUpdate.objects.filter(inventory_source__inventory__organization=obj, status__in=ACTIVE_STATES))
|
active_jobs.extend([dict(type="project_update", id=o.id)
|
||||||
|
for o in ProjectUpdate.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES)])
|
||||||
|
active_jobs.extend([dict(type="inventory_update", id=o.id)
|
||||||
|
for o in InventoryUpdate.objects.filter(inventory_source__inventory__organization=obj, status__in=ACTIVE_STATES)])
|
||||||
if len(active_jobs) > 0:
|
if len(active_jobs) > 0:
|
||||||
raise ValidationError("Delete not allowed while there are jobs running. Number of jobs {}".format(len(active_jobs)))
|
raise StateConflict({"conflict": "Resource is being used by running jobs",
|
||||||
|
"active_jobs": active_jobs})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
class InventoryAccess(BaseAccess):
|
class InventoryAccess(BaseAccess):
|
||||||
@@ -387,10 +394,13 @@ class InventoryAccess(BaseAccess):
|
|||||||
if not is_can_admin:
|
if not is_can_admin:
|
||||||
return False
|
return False
|
||||||
active_jobs = []
|
active_jobs = []
|
||||||
active_jobs.extend(Job.objects.filter(inventory=obj, status__in=ACTIVE_STATES))
|
active_jobs.extend([dict(type="job", id=o.id)
|
||||||
active_jobs.extend(InventoryUpdate.objects.filter(inventory_source__inventory=obj, status__in=ACTIVE_STATES))
|
for o in Job.objects.filter(inventory=obj, status__in=ACTIVE_STATES)])
|
||||||
|
active_jobs.extend([dict(type="inventory_update", id=o.id)
|
||||||
|
for o in InventoryUpdate.objects.filter(inventory_source__inventory=obj, status__in=ACTIVE_STATES)])
|
||||||
if len(active_jobs) > 0:
|
if len(active_jobs) > 0:
|
||||||
raise ValidationError("Delete not allowed while there are jobs running. Number of jobs {}".format(len(active_jobs)))
|
raise StateConflict({"conflict": "Resource is being used by running jobs",
|
||||||
|
"active_jobs": active_jobs})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def can_run_ad_hoc_commands(self, obj):
|
def can_run_ad_hoc_commands(self, obj):
|
||||||
@@ -508,9 +518,11 @@ class GroupAccess(BaseAccess):
|
|||||||
if not is_delete_allowed:
|
if not is_delete_allowed:
|
||||||
return False
|
return False
|
||||||
active_jobs = []
|
active_jobs = []
|
||||||
active_jobs.extend(InventoryUpdate.objects.filter(inventory_source__in=obj.inventory_sources.all(), status__in=ACTIVE_STATES))
|
active_jobs.extend([dict(type="inventory_update", id=o.id)
|
||||||
|
for o in InventoryUpdate.objects.filter(inventory_source__in=obj.inventory_sources.all(), status__in=ACTIVE_STATES)])
|
||||||
if len(active_jobs) > 0:
|
if len(active_jobs) > 0:
|
||||||
raise ValidationError("Delete not allowed while there are jobs running. Number of jobs {}".format(len(active_jobs)))
|
raise StateConflict({"conflict": "Resource is being used by running jobs",
|
||||||
|
"active_jobs": active_jobs})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
class InventorySourceAccess(BaseAccess):
|
class InventorySourceAccess(BaseAccess):
|
||||||
@@ -765,10 +777,13 @@ class ProjectAccess(BaseAccess):
|
|||||||
if not is_change_allowed:
|
if not is_change_allowed:
|
||||||
return False
|
return False
|
||||||
active_jobs = []
|
active_jobs = []
|
||||||
active_jobs.extend(Job.objects.filter(project=obj, status__in=ACTIVE_STATES))
|
active_jobs.extend([dict(type="job", id=o.id)
|
||||||
active_jobs.extend(ProjectUpdate.objects.filter(project=obj, status__in=ACTIVE_STATES))
|
for o in Job.objects.filter(project=obj, status__in=ACTIVE_STATES)])
|
||||||
|
active_jobs.extend([dict(type="project_update", id=o.id)
|
||||||
|
for o in ProjectUpdate.objects.filter(project=obj, status__in=ACTIVE_STATES)])
|
||||||
if len(active_jobs) > 0:
|
if len(active_jobs) > 0:
|
||||||
raise ValidationError("Delete not allowed while there are jobs running. Number of jobs {}".format(len(active_jobs)))
|
raise StateConflict({"conflict": "Resource is being used by running jobs",
|
||||||
|
"active_jobs": active_jobs})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@check_superuser
|
@check_superuser
|
||||||
@@ -989,14 +1004,15 @@ class JobTemplateAccess(BaseAccess):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@check_superuser
|
|
||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
is_delete_allowed = self.user in obj.admin_role
|
is_delete_allowed = self.user in obj.admin_role
|
||||||
if not is_delete_allowed:
|
if not is_delete_allowed:
|
||||||
return False
|
return False
|
||||||
active_jobs = obj.jobs.filter(status__in=ACTIVE_STATES)
|
active_jobs = [dict(type="job", id=o.id)
|
||||||
|
for o in obj.jobs.filter(status__in=ACTIVE_STATES)]
|
||||||
if len(active_jobs) > 0:
|
if len(active_jobs) > 0:
|
||||||
raise ValidationError("Delete not allowed while there are jobs running. Number of jobs {}".format(len(active_jobs)))
|
raise StateConflict({"conflict": "Resource is being used by running jobs",
|
||||||
|
"active_jobs": active_jobs})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
class JobAccess(BaseAccess):
|
class JobAccess(BaseAccess):
|
||||||
|
|||||||
@@ -335,3 +335,15 @@ def test_jt_without_project(inventory):
|
|||||||
data["job_type"] = "scan"
|
data["job_type"] = "scan"
|
||||||
serializer = JobTemplateSerializer(data=data)
|
serializer = JobTemplateSerializer(data=data)
|
||||||
assert serializer.is_valid()
|
assert serializer.is_valid()
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_disallow_template_delete_on_running_job(job_template_factory, delete, admin_user):
|
||||||
|
objects = job_template_factory('jt',
|
||||||
|
credential='c',
|
||||||
|
job_type="run",
|
||||||
|
project='p',
|
||||||
|
inventory='i',
|
||||||
|
organization='o')
|
||||||
|
j = objects.job_template.create_unified_job()
|
||||||
|
delete_response = delete(reverse('api:job_template_detail', args=[objects.job_template.pk]), user=admin_user)
|
||||||
|
assert delete_response.status_code == 409
|
||||||
|
|||||||
Reference in New Issue
Block a user