diff --git a/awx/api/serializers.py b/awx/api/serializers.py index d954f532af..65c4f1de8f 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -1270,8 +1270,6 @@ class GroupSerializer(BaseSerializerWithVariables): )) if obj.inventory: res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk}) - if obj.inventory_source: - res['inventory_source'] = self.reverse('api:inventory_source_detail', kwargs={'pk': obj.inventory_source.pk}) return res def validate_name(self, value): @@ -1432,10 +1430,10 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt class Meta: model = InventorySource - fields = ('*', 'inventory', 'group', 'update_on_launch', + fields = ('*', 'inventory', 'update_on_launch', 'update_cache_timeout') + \ ('last_update_failed', 'last_updated') # Backwards compatibility. - read_only_fields = ('*', 'name', 'inventory', 'group') + read_only_fields = ('*', 'name', 'inventory') def get_related(self, obj): res = super(InventorySourceSerializer, self).get_related(obj) diff --git a/awx/main/access.py b/awx/main/access.py index f4c4486faf..a781b062ab 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -346,10 +346,6 @@ class BaseAccess(object): elif display_method == 'copy' and isinstance(obj, WorkflowJobTemplate) and obj.organization_id is None: user_capabilities[display_method] = self.user.is_superuser continue - elif display_method in ['start', 'schedule'] and isinstance(obj, Group): - if obj.inventory_source and not obj.inventory_source._can_update(): - user_capabilities[display_method] = False - continue elif display_method in ['start', 'schedule'] and isinstance(obj, (Project)): if obj.scm_type == '': user_capabilities[display_method] = False @@ -725,9 +721,8 @@ class GroupAccess(BaseAccess): return True def can_start(self, obj, validate_license=True): - # Used as another alias to inventory_source start access for user_capabilities - if obj and obj.inventory_source: - return self.user.can_access(InventorySource, 'start', obj.inventory_source, validate_license=validate_license) + if obj and obj.inventory: + return self.user.can_access(Inventory, 'start', obj.inventory, validate_license=validate_license) return False @@ -747,9 +742,7 @@ class InventorySourceAccess(BaseAccess): Q(group__inventory_id__in=inventory_ids)) def can_read(self, obj): - if obj and obj.group: - return self.user.can_access(Group, 'read', obj.group) - elif obj and obj.inventory: + if obj and obj.inventory: return self.user.can_access(Inventory, 'read', obj.inventory) else: return False @@ -760,9 +753,9 @@ class InventorySourceAccess(BaseAccess): def can_change(self, obj, data): # Checks for admin or change permission on group. - if obj and obj.group: + if obj and obj.inventory: return ( - self.user.can_access(Group, 'change', obj.group, None) and + self.user.can_access(Inventory, 'change', obj.inventory, None) and self.check_related('credential', Credential, data, obj=obj, role_field='use_role') ) # Can't change inventory sources attached to only the inventory, since @@ -771,9 +764,7 @@ class InventorySourceAccess(BaseAccess): return False def can_start(self, obj, validate_license=True): - if obj and obj.group: - return obj.can_update and self.user in obj.group.inventory.update_role - elif obj and obj.inventory: + if obj and obj.inventory: return obj.can_update and self.user in obj.inventory.update_role return False diff --git a/awx/main/migrations/0037_v320_fact_recent.py b/awx/main/migrations/0037_v320_fact_recent.py deleted file mode 100644 index d7ca92f154..0000000000 --- a/awx/main/migrations/0037_v320_fact_recent.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -# Python -from __future__ import unicode_literals - -# Django -from django.db import migrations, models - -from psycopg2.extensions import AsIs - -# AWX -import awx.main.fields -from awx.main.models import FactLatest - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0036_v311_insights'), - ] - - operations = [ - migrations.CreateModel( - name='FactLatest', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('timestamp', models.DateTimeField(default=None, help_text='Date and time of the corresponding fact scan gathering time.', editable=False)), - ('module', models.CharField(max_length=128)), - ('facts', awx.main.fields.JSONBField(default={}, help_text='Arbitrary JSON structure of module facts captured at timestamp for a single host.', blank=True)), - ('host', models.ForeignKey(related_name='facts_latest', to='main.Host', help_text='Host for the facts that the fact scan captured.')), - ], - ), - migrations.AlterField( - model_name='fact', - name='facts', - field=awx.main.fields.JSONBField(default={}, help_text='Arbitrary JSON structure of module facts captured at timestamp for a single host.', blank=True), - ), - migrations.AlterIndexTogether( - name='factlatest', - index_together=set([('timestamp', 'module', 'host')]), - ), - migrations.RunSQL([("CREATE INDEX fact_latest_facts_default_gin ON %s USING gin" - "(facts jsonb_path_ops);", [AsIs(FactLatest._meta.db_table)])], - [('DROP INDEX fact_latest_facts_default_gin;', None)]), - ] diff --git a/awx/main/migrations/0037_v320_release.py b/awx/main/migrations/0037_v320_release.py new file mode 100644 index 0000000000..579783e16d --- /dev/null +++ b/awx/main/migrations/0037_v320_release.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0036_v311_insights'), + ] + + operations = [ + migrations.RemoveField( + model_name='inventorysource', + name='group', + ), + ] diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index e91e67f134..18025ced50 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -19,7 +19,7 @@ from django.utils.timezone import now # AWX from awx.api.versioning import reverse from awx.main.constants import CLOUD_PROVIDERS -from awx.main.fields import AutoOneToOneField, ImplicitRoleField +from awx.main.fields import ImplicitRoleField from awx.main.managers import HostManager from awx.main.models.base import * # noqa from awx.main.models.unified_jobs import * # noqa @@ -1063,14 +1063,6 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions): editable=False, on_delete=models.CASCADE, ) - group = AutoOneToOneField( - 'Group', - related_name='inventory_source', - null=True, - default=None, - editable=False, - on_delete=models.CASCADE, - ) update_on_launch = models.BooleanField( default=False, ) @@ -1092,20 +1084,12 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions): # If update_fields has been specified, add our field names to it, # if it hasn't been specified, then we're just doing a normal save. update_fields = kwargs.get('update_fields', []) - # Update inventory from group (if available). - if self.group and not self.inventory: - self.inventory = self.group.inventory - if 'inventory' not in update_fields: - update_fields.append('inventory') + # Set name automatically. Include PK (or placeholder) to make sure the names are always unique. replace_text = '__replace_%s__' % now() old_name_re = re.compile(r'^inventory_source \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.*?$') if not self.name or old_name_re.match(self.name) or '__replace_' in self.name: - if self.inventory and self.group and self.pk: - self.name = '%s (%s - %s)' % (self.group.name, self.inventory.name, self.pk) - elif self.inventory and self.group: - self.name = '%s (%s - %s)' % (self.group.name, self.inventory.name, replace_text) - elif self.inventory and self.pk: + if self.inventory and self.pk: self.name = '%s (%s)' % (self.inventory.name, self.pk) elif self.inventory: self.name = '%s (%s)' % (self.inventory.name, replace_text) @@ -1237,10 +1221,7 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin): update_fields = kwargs.get('update_fields', []) inventory_source = self.inventory_source if inventory_source.inventory and self.name == inventory_source.name: - if inventory_source.group: - self.name = '%s (%s)' % (inventory_source.group.name, inventory_source.inventory.name) - else: - self.name = inventory_source.inventory.name + self.name = inventory_source.inventory.name if 'name' not in update_fields: update_fields.append('name') super(InventoryUpdate, self).save(*args, **kwargs) diff --git a/awx/main/signals.py b/awx/main/signals.py index 9e5fa95692..6ad65254a9 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -372,9 +372,6 @@ model_serializer_mapping = { def activity_stream_create(sender, instance, created, **kwargs): if created and activity_stream_enabled: - # Skip recording any inventory source directly associated with a group. - if isinstance(instance, InventorySource) and instance.group: - return object1 = camelcase_to_underscore(instance.__class__.__name__) changes = model_to_dict(instance, model_serializer_mapping) # Special case where Job survey password variables need to be hidden diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py index 3d79ca4c4c..fac0b39e2b 100644 --- a/awx/main/tests/functional/conftest.py +++ b/awx/main/tests/functional/conftest.py @@ -343,7 +343,8 @@ def group(inventory): @pytest.fixture def inventory_source(group, inventory): - return InventorySource.objects.create(name=group.name, group=group, + group.inventory = inventory + return InventorySource.objects.create(name=group.name, inventory=inventory, source='gce') diff --git a/awx/main/tests/functional/test_notifications.py b/awx/main/tests/functional/test_notifications.py index f6edd42247..2f9787d9f0 100644 --- a/awx/main/tests/functional/test_notifications.py +++ b/awx/main/tests/functional/test_notifications.py @@ -3,7 +3,7 @@ import pytest from awx.api.versioning import reverse from awx.main.models.notifications import NotificationTemplate, Notification -from awx.main.models.inventory import Inventory, Group +from awx.main.models.inventory import Inventory, InventorySource from awx.main.models.jobs import JobTemplate @@ -84,8 +84,8 @@ def test_inherited_notification_templates(get, post, user, organization, project notification_templates.append(response.data['id']) i = Inventory.objects.create(name='test', organization=organization) i.save() - g = Group.objects.create(name='test', inventory=i) - g.save() + isrc = InventorySource.objects.create(name='test', inventory=i) + isrc.save() jt = JobTemplate.objects.create(name='test', inventory=i, project=project, playbook='debug.yml') jt.save() url = reverse('api:organization_notification_templates_any_list', kwargs={'pk': organization.id}) @@ -99,7 +99,7 @@ def test_inherited_notification_templates(get, post, user, organization, project assert response.status_code == 204 assert len(jt.notification_templates['any']) == 3 assert len(project.notification_templates['any']) == 2 - assert len(g.inventory_source.notification_templates['any']) == 1 + assert len(isrc.notification_templates['any']) == 1 @pytest.mark.django_db