diff --git a/awx/api/serializers.py b/awx/api/serializers.py index f0094a52e2..060ce1cb91 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -2244,6 +2244,8 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt obj = super(InventorySourceSerializer, self).update(obj, validated_data) if deprecated_fields: self._update_deprecated_fields(deprecated_fields, obj) + if obj.source == 'constructed': + raise serializers.ValidationError({'error': _("Cannot edit source of type constructed.")}) return obj # TODO: remove when old 'credential' fields are removed diff --git a/awx/api/views/inventory.py b/awx/api/views/inventory.py index 64550e11c5..4085cf9bff 100644 --- a/awx/api/views/inventory.py +++ b/awx/api/views/inventory.py @@ -14,6 +14,7 @@ from django.utils.translation import gettext_lazy as _ from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response from rest_framework import status +from rest_framework import serializers # AWX from awx.main.models import ActivityStream, Inventory, JobTemplate, Role, User, InstanceGroup, InventoryUpdateEvent, InventoryUpdate @@ -115,6 +116,10 @@ class InventoryInputInventoriesList(SubListAttachDetachAPIView): parent_model = Inventory relationship = 'input_inventories' + def is_valid_relation(self, parent, sub, created=False): + if sub.kind == 'constructed': + raise serializers.ValidationError({'error': 'You cannot add a constructed inventory to another constructed inventory.'}) + class InventoryActivityStreamList(SubListAPIView): model = ActivityStream diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py index 4f8b6bc83c..e1284ce87c 100644 --- a/awx/main/tests/functional/conftest.py +++ b/awx/main/tests/functional/conftest.py @@ -511,6 +511,14 @@ def group(inventory): return inventory.groups.create(name='single-group') +@pytest.fixture +def constructed_inventory(organization): + """ + creates a new constructed inventory source + """ + return Inventory.objects.create(name='dummy1', kind='constructed', organization=organization) + + @pytest.fixture def inventory_source(inventory): # by making it ec2, the credential is not required diff --git a/awx/main/tests/functional/test_inventory_input_constructed.py b/awx/main/tests/functional/test_inventory_input_constructed.py new file mode 100644 index 0000000000..2602cf2947 --- /dev/null +++ b/awx/main/tests/functional/test_inventory_input_constructed.py @@ -0,0 +1,61 @@ +import pytest +from awx.main.models import Inventory +from awx.api.versioning import reverse + + +@pytest.mark.django_db +def test_constructed_inventory_post(post, admin_user, organization): + inv1 = Inventory.objects.create(name='dummy1', kind='constructed', organization=organization) + inv2 = Inventory.objects.create(name='dummy2', kind='constructed', organization=organization) + resp = post( + url=reverse('api:inventory_input_inventories', kwargs={'pk': inv1.pk}), + data={'id': inv2.pk}, + user=admin_user, + expect=400, + ) + assert resp.status_code == 400 + + +@pytest.mark.django_db +def test_add_constructed_inventory_source(post, admin_user, constructed_inventory): + resp = post( + url=reverse('api:inventory_inventory_sources_list', kwargs={'pk': constructed_inventory.pk}), + data={'name': 'dummy1', 'source': 'constructed'}, + user=admin_user, + expect=400, + ) + assert resp.status_code == 400 + + +@pytest.mark.django_db +def test_add_constructed_inventory_host(post, admin_user, constructed_inventory): + resp = post( + url=reverse('api:inventory_hosts_list', kwargs={'pk': constructed_inventory.pk}), + data={'name': 'dummy1'}, + user=admin_user, + expect=400, + ) + assert resp.status_code == 400 + + +@pytest.mark.django_db +def test_add_constructed_inventory_group(post, admin_user, constructed_inventory): + resp = post( + reverse('api:inventory_groups_list', kwargs={'pk': constructed_inventory.pk}), + data={'name': 'group-test'}, + user=admin_user, + expect=400, + ) + assert resp.status_code == 400 + + +@pytest.mark.django_db +def test_edit_constructed_inventory_source(patch, admin_user, inventory_source_factory): + inv_src = inventory_source_factory(name='dummy1', source='constructed') + resp = patch( + reverse('api:inventory_source_detail', kwargs={'pk': inv_src.pk}), + data={'description': inv_src.name}, + user=admin_user, + expect=400, + ) + assert resp.status_code == 400