diff --git a/awx/api/views.py b/awx/api/views.py index dd2174f7da..5e080ccea9 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -671,6 +671,14 @@ class InstanceGroupDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAP serializer_class = InstanceGroupSerializer permission_classes = (InstanceGroupTowerPermission,) + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + if instance.controller is not None: + raise PermissionDenied(detail=_("Isolated Groups can not be removed from the API")) + if instance.controlled_groups.count(): + raise PermissionDenied(detail=_("Instance Groups acting as a controller for an Isolated Group can not be removed from the API")) + return super(InstanceGroupDetail, self).destroy(request, *args, **kwargs) + class InstanceGroupUnifiedJobsList(SubListAPIView): diff --git a/awx/main/tests/functional/api/test_instance_group.py b/awx/main/tests/functional/api/test_instance_group.py index 0488453fcc..3dfd554f11 100644 --- a/awx/main/tests/functional/api/test_instance_group.py +++ b/awx/main/tests/functional/api/test_instance_group.py @@ -21,6 +21,13 @@ def instance_group(job_factory): return ig +@pytest.fixture +def isolated_instance_group(instance_group): + ig = InstanceGroup(name="iso", controller=instance_group) + ig.save() + return ig + + @pytest.fixture def create_job_factory(job_factory, instance_group): def fn(status='running'): @@ -91,3 +98,11 @@ def test_modify_delete_tower_instance_group_prevented(delete, options, tower_ins assert 'DELETE' not in resp.data['actions'] assert 'GET' in resp.data['actions'] assert 'PUT' in resp.data['actions'] + + +@pytest.mark.django_db +def test_prevent_delete_iso_and_control_groups(delete, isolated_instance_group, admin): + iso_url = reverse("api:instance_group_detail", kwargs={'pk': isolated_instance_group.pk}) + controller_url = reverse("api:instance_group_detail", kwargs={'pk': isolated_instance_group.controller.pk}) + delete(iso_url, None, admin, expect=403) + delete(controller_url, None, admin, expect=403)