diff --git a/awx/main/scheduler/__init__.py b/awx/main/scheduler/__init__.py index e76a7ca17c..8f6e1b9617 100644 --- a/awx/main/scheduler/__init__.py +++ b/awx/main/scheduler/__init__.py @@ -235,7 +235,12 @@ class TaskManager(): dependencies.append(project_task) # Inventory created 2 seconds behind job + inventory_sources_already_updated = task.get_inventory_sources_already_updated() + for inventory_source_task in self.graph.get_inventory_sources(task['inventory_id']): + if inventory_source_task['id'] in inventory_sources_already_updated: + print("Inventory already updated") + continue if self.graph.should_update_related_inventory_source(task, inventory_source_task['id']): inventory_task = self.create_inventory_update(task, inventory_source_task) dependencies.append(inventory_task) diff --git a/awx/main/scheduler/partial.py b/awx/main/scheduler/partial.py index c6b8a2e575..e8355cbcfc 100644 --- a/awx/main/scheduler/partial.py +++ b/awx/main/scheduler/partial.py @@ -1,5 +1,9 @@ +# Python +import json + # AWX +from awx.main.utils import decrypt_field_value from awx.main.models import ( Job, ProjectUpdate, @@ -61,7 +65,7 @@ class JobDict(PartialModelDict): 'id', 'status', 'job_template_id', 'inventory_id', 'project_id', 'launch_type', 'limit', 'allow_simultaneous', 'created', 'job_type', 'celery_task_id', 'project__scm_update_on_launch', - 'forks', + 'forks', 'start_args', ) model = Job @@ -71,6 +75,14 @@ class JobDict(PartialModelDict): def task_impact(self): return (5 if self.data['forks'] == 0 else self.data['forks']) * 10 + def get_inventory_sources_already_updated(self): + try: + start_args = json.loads(decrypt_field_value(self.data['id'], 'start_args', self.data['start_args'])) + except Exception: + return [] + start_args = start_args or {} + return start_args.get('inventory_sources_already_updated', []) + class ProjectUpdateDict(PartialModelDict): FIELDS = ( 'id', 'status', 'project_id', 'created', 'celery_task_id', diff --git a/awx/main/utils.py b/awx/main/utils.py index 3614a99039..4178bea0d2 100644 --- a/awx/main/utils.py +++ b/awx/main/utils.py @@ -156,17 +156,20 @@ def get_awx_version(): return __version__ -def get_encryption_key(instance, field_name): +def get_encryption_key_for_pk(pk, field_name): ''' Generate key for encrypted password based on instance pk and field name. ''' from django.conf import settings h = hashlib.sha1() h.update(settings.SECRET_KEY) - h.update(str(instance.pk)) + h.update(str(pk)) h.update(field_name) return h.digest()[:16] +def get_encryption_key(instance, field_name): + return get_encryption_key_for_pk(instance.pk, field_name) + def encrypt_field(instance, field_name, ask=False, subfield=None): ''' Return content of the given instance and field name encrypted. @@ -185,6 +188,14 @@ def encrypt_field(instance, field_name, ask=False, subfield=None): b64data = base64.b64encode(encrypted) return '$encrypted$%s$%s' % ('AES', b64data) +def decrypt_value(encryption_key, value): + algo, b64data = value[len('$encrypted$'):].split('$', 1) + if algo != 'AES': + raise ValueError('unsupported algorithm: %s' % algo) + encrypted = base64.b64decode(b64data) + cipher = AES.new(encryption_key, AES.MODE_ECB) + value = cipher.decrypt(encrypted) + return value.rstrip('\x00') def decrypt_field(instance, field_name, subfield=None): ''' @@ -195,15 +206,13 @@ def decrypt_field(instance, field_name, subfield=None): value = value[subfield] if not value or not value.startswith('$encrypted$'): return value - algo, b64data = value[len('$encrypted$'):].split('$', 1) - if algo != 'AES': - raise ValueError(_('unsupported algorithm: %s') % algo) - encrypted = base64.b64decode(b64data) key = get_encryption_key(instance, field_name) - cipher = AES.new(key, AES.MODE_ECB) - value = cipher.decrypt(encrypted) - return value.rstrip('\x00') + return decrypt_value(key, value) + +def decrypt_field_value(pk, field_name, value): + key = get_encryption_key_for_pk(pk, field_name) + return decrypt_value(key, value) def update_scm_url(scm_type, url, username=True, password=True, check_special_cases=True, scp_format=False):