diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 5cc40b4e65..69aaad3232 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -45,6 +45,8 @@ from awx.main.fields import ImplicitRoleField from awx.main.utils import ( get_type_for_model, get_model_for_type, timestamp_apiformat, camelcase_to_underscore, getattrd, parse_yaml_or_json) +from awx.main.utils.filters import DynamicFilter + from awx.main.validators import vars_validate_or_raise from awx.conf.license import feature_enabled @@ -1113,7 +1115,7 @@ class InventorySerializer(BaseSerializerWithVariables): class Meta: model = Inventory - fields = ('*', 'organization', 'variables', 'has_active_failures', + fields = ('*', 'organization', 'kind', 'host_filter', 'variables', 'has_active_failures', 'total_hosts', 'hosts_with_active_failures', 'total_groups', 'groups_with_active_failures', 'has_inventory_sources', 'total_inventory_sources', 'inventory_sources_with_failures') @@ -1145,6 +1147,17 @@ class InventorySerializer(BaseSerializerWithVariables): ret['organization'] = None return ret + def validate(self, attrs): + kind = attrs.get('kind', 'standard') + if kind == 'dynamic': + host_filter = attrs.get('host_filter') + if host_filter is not None: + try: + DynamicFilter().query_from_string(host_filter) + except RuntimeError, e: + raise models.base.ValidationError(e) + return super(InventorySerializer, self).validate(attrs) + class InventoryDetailSerializer(InventorySerializer): diff --git a/awx/api/views.py b/awx/api/views.py index 5337b7d3e8..f493a9ae0d 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -72,6 +72,8 @@ from awx.main.utils import * # noqa from awx.main.utils import ( callback_filter_out_ansible_extra_vars ) +from awx.main.utils.filters import DynamicFilter + from awx.api.permissions import * # noqa from awx.api.renderers import * # noqa from awx.api.serializers import * # noqa @@ -79,7 +81,6 @@ from awx.api.metadata import RoleMetadata from awx.main.consumers import emit_channel_notification from awx.main.models.unified_jobs import ACTIVE_STATES from awx.main.scheduler.tasks import run_job_complete -from awx.main.querysets import DynamicFilterQuerySet logger = logging.getLogger('awx.api.views') @@ -1764,10 +1765,10 @@ class HostList(ListCreateAPIView): capabilities_prefetch = ['inventory.admin'] def get_queryset(self): - qs = DynamicFilterQuerySet(HostList, using=self._db) + qs = super(HostList, self).get_queryset() filter_string = self.request.query_params.get('host_filter', None) if filter_string: - filter_q = qs.query_from_string(filter_string) + filter_q = DynamicFilter.query_from_string(filter_string) qs = qs.filter(filter_q) return qs diff --git a/awx/main/managers.py b/awx/main/managers.py index 57d518d270..5884c88938 100644 --- a/awx/main/managers.py +++ b/awx/main/managers.py @@ -27,7 +27,7 @@ class HostManager(models.Manager): set. Use the `host_filter` to generate the queryset for the hosts. """ qs = super(HostManager, self).get_queryset() - if self.instance is not None: + if hasattr(self, 'instance') and self.instance is not None: if hasattr(self.instance, 'kind') and self.instance.kind == 'dynamic': if hasattr(self.instance, 'host_filter') and self.instance.host_filter is not None: q = DynamicFilter.query_from_string(self.instance.host_filter) diff --git a/awx/main/tests/functional/models/test_inventory.py b/awx/main/tests/functional/models/test_inventory.py index 03e216d74c..129c4e0c98 100644 --- a/awx/main/tests/functional/models/test_inventory.py +++ b/awx/main/tests/functional/models/test_inventory.py @@ -37,11 +37,8 @@ class TestSCMUpdateFeatures: assert not mck_update.called -@pytest.mark.django_db -def test_host_objects_manager(organization): - dynamic_inventory = Inventory(organization=organization, name='dynamic', host_filter='inventory_sources__source=ec2') - dynamic_inventory.save() - +@pytest.fixture +def setup_ec2_gce(organization): ec2_inv = Inventory(name='test_ec2', organization=organization) ec2_inv.save() @@ -59,7 +56,35 @@ def test_host_objects_manager(organization): gce_host.inventory_sources.add(gce_source) gce_inv.save() - hosts = dynamic_inventory.hosts.all() - assert len(hosts) == 2 - assert hosts[0].inventory_sources.first() == ec2_source - assert hosts[1].inventory_sources.first() == ec2_source + +@pytest.mark.django_db +class TestHostManager: + def test_host_filter_change(self, setup_ec2_gce, organization): + dynamic_inventory = Inventory(name='dynamic', + kind='dynamic', + organization=organization, + host_filter='inventory_sources__source=ec2') + dynamic_inventory.save() + assert len(dynamic_inventory.hosts.all()) == 2 + + dynamic_inventory.host_filter = 'inventory_sources__source=gce' + dynamic_inventory.save() + assert len(dynamic_inventory.hosts.all()) == 1 + + def test_host_filter_not_dynamic(self, setup_ec2_gce, organization): + dynamic_inventory = Inventory(name='dynamic', + organization=organization, + host_filter='inventory_sources__source=ec2') + assert len(dynamic_inventory.hosts.all()) == 0 + + def test_host_objects_manager(self, setup_ec2_gce, organization): + dynamic_inventory = Inventory(kind='dynamic', + name='dynamic', + organization=organization, + host_filter='inventory_sources__source=ec2') + dynamic_inventory.save() + + hosts = dynamic_inventory.hosts.all() + assert len(hosts) == 2 + assert hosts[0].inventory_sources.first().source == 'ec2' + assert hosts[1].inventory_sources.first().source == 'ec2'