mirror of
https://github.com/ansible/awx.git
synced 2026-02-26 07:26:03 -03:30
Merge pull request #3377 from chrismeyersfsu/refactor-notifications
refactor notifications
This commit is contained in:
@@ -23,13 +23,14 @@ from awx.main.models.base import * # noqa
|
|||||||
from awx.main.models.unified_jobs import * # noqa
|
from awx.main.models.unified_jobs import * # noqa
|
||||||
from awx.main.utils import decrypt_field
|
from awx.main.utils import decrypt_field
|
||||||
from awx.main.conf import tower_settings
|
from awx.main.conf import tower_settings
|
||||||
|
from awx.main.models.notifications import JobNotificationMixin
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.models.ad_hoc_commands')
|
logger = logging.getLogger('awx.main.models.ad_hoc_commands')
|
||||||
|
|
||||||
__all__ = ['AdHocCommand', 'AdHocCommandEvent']
|
__all__ = ['AdHocCommand', 'AdHocCommandEvent']
|
||||||
|
|
||||||
|
|
||||||
class AdHocCommand(UnifiedJob):
|
class AdHocCommand(UnifiedJob, JobNotificationMixin):
|
||||||
|
|
||||||
class Meta(object):
|
class Meta(object):
|
||||||
app_label = 'main'
|
app_label = 'main'
|
||||||
@@ -237,6 +238,14 @@ class AdHocCommand(UnifiedJob):
|
|||||||
update_fields.append('name')
|
update_fields.append('name')
|
||||||
super(AdHocCommand, self).save(*args, **kwargs)
|
super(AdHocCommand, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
'''
|
||||||
|
JobNotificationMixin
|
||||||
|
'''
|
||||||
|
def get_notification_templates(self):
|
||||||
|
return self.notification_templates
|
||||||
|
|
||||||
|
def get_notification_friendly_name(self):
|
||||||
|
return "AdHoc Command"
|
||||||
|
|
||||||
class AdHocCommandEvent(CreatedModifiedModel):
|
class AdHocCommandEvent(CreatedModifiedModel):
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ from awx.main.models.base import * # noqa
|
|||||||
from awx.main.models.jobs import Job
|
from awx.main.models.jobs import Job
|
||||||
from awx.main.models.unified_jobs import * # noqa
|
from awx.main.models.unified_jobs import * # noqa
|
||||||
from awx.main.models.mixins import ResourceMixin
|
from awx.main.models.mixins import ResourceMixin
|
||||||
from awx.main.models.notifications import NotificationTemplate
|
from awx.main.models.notifications import (
|
||||||
|
NotificationTemplate,
|
||||||
|
JobNotificationMixin,
|
||||||
|
)
|
||||||
from awx.main.utils import _inventory_updates
|
from awx.main.utils import _inventory_updates
|
||||||
from awx.main.conf import tower_settings
|
from awx.main.conf import tower_settings
|
||||||
|
|
||||||
@@ -1192,7 +1195,7 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
|
|||||||
return source
|
return source
|
||||||
|
|
||||||
|
|
||||||
class InventoryUpdate(UnifiedJob, InventorySourceOptions):
|
class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin):
|
||||||
'''
|
'''
|
||||||
Internal job for tracking inventory updates from external sources.
|
Internal job for tracking inventory updates from external sources.
|
||||||
'''
|
'''
|
||||||
@@ -1268,6 +1271,15 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
'''
|
||||||
|
JobNotificationMixin
|
||||||
|
'''
|
||||||
|
def get_notification_templates(self):
|
||||||
|
return self.inventory_source.notification_templates
|
||||||
|
|
||||||
|
def get_notification_friendly_name(self):
|
||||||
|
return "Inventory Update"
|
||||||
|
|
||||||
|
|
||||||
class CustomInventoryScript(CommonModelNameNotUnique, ResourceMixin):
|
class CustomInventoryScript(CommonModelNameNotUnique, ResourceMixin):
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ from jsonfield import JSONField
|
|||||||
from awx.main.constants import CLOUD_PROVIDERS
|
from awx.main.constants import CLOUD_PROVIDERS
|
||||||
from awx.main.models.base import * # noqa
|
from awx.main.models.base import * # noqa
|
||||||
from awx.main.models.unified_jobs import * # noqa
|
from awx.main.models.unified_jobs import * # noqa
|
||||||
from awx.main.models.notifications import NotificationTemplate
|
from awx.main.models.notifications import (
|
||||||
|
NotificationTemplate,
|
||||||
|
JobNotificationMixin,
|
||||||
|
)
|
||||||
from awx.main.utils import decrypt_field, ignore_inventory_computed_fields
|
from awx.main.utils import decrypt_field, ignore_inventory_computed_fields
|
||||||
from awx.main.utils import emit_websocket_notification
|
from awx.main.utils import emit_websocket_notification
|
||||||
from awx.main.redact import PlainTextCleaner
|
from awx.main.redact import PlainTextCleaner
|
||||||
@@ -499,7 +502,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
|
|||||||
any_notification_templates = set(any_notification_templates + list(base_notification_templates.filter(organization_notification_templates_for_any=self.project.organization)))
|
any_notification_templates = set(any_notification_templates + list(base_notification_templates.filter(organization_notification_templates_for_any=self.project.organization)))
|
||||||
return dict(error=list(error_notification_templates), success=list(success_notification_templates), any=list(any_notification_templates))
|
return dict(error=list(error_notification_templates), success=list(success_notification_templates), any=list(any_notification_templates))
|
||||||
|
|
||||||
class Job(UnifiedJob, JobOptions):
|
class Job(UnifiedJob, JobOptions, JobNotificationMixin):
|
||||||
'''
|
'''
|
||||||
A job applies a project (with playbook) to an inventory source with a given
|
A job applies a project (with playbook) to an inventory source with a given
|
||||||
credential. It represents a single invocation of ansible-playbook with the
|
credential. It represents a single invocation of ansible-playbook with the
|
||||||
@@ -792,6 +795,15 @@ class Job(UnifiedJob, JobOptions):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
'''
|
||||||
|
JobNotificationMixin
|
||||||
|
'''
|
||||||
|
def get_notification_templates(self):
|
||||||
|
return self.job_template.notification_templates
|
||||||
|
|
||||||
|
def get_notification_friendly_name(self):
|
||||||
|
return "Job"
|
||||||
|
|
||||||
class JobHostSummary(CreatedModifiedModel):
|
class JobHostSummary(CreatedModifiedModel):
|
||||||
'''
|
'''
|
||||||
Per-host statistics for each job.
|
Per-host statistics for each job.
|
||||||
@@ -1315,7 +1327,7 @@ class SystemJobTemplate(UnifiedJobTemplate, SystemJobOptions):
|
|||||||
any=list(any_notification_templates))
|
any=list(any_notification_templates))
|
||||||
|
|
||||||
|
|
||||||
class SystemJob(UnifiedJob, SystemJobOptions):
|
class SystemJob(UnifiedJob, SystemJobOptions, JobNotificationMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'main'
|
app_label = 'main'
|
||||||
@@ -1378,3 +1390,13 @@ class SystemJob(UnifiedJob, SystemJobOptions):
|
|||||||
@property
|
@property
|
||||||
def task_impact(self):
|
def task_impact(self):
|
||||||
return 150
|
return 150
|
||||||
|
|
||||||
|
'''
|
||||||
|
JobNotificationMixin
|
||||||
|
'''
|
||||||
|
def get_notification_templates(self):
|
||||||
|
return self.system_job_template.notification_templates
|
||||||
|
|
||||||
|
def get_notification_friendly_name(self):
|
||||||
|
return "System Job"
|
||||||
|
|
||||||
|
|||||||
@@ -171,3 +171,27 @@ class Notification(CreatedModifiedModel):
|
|||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('api:notification_detail', args=(self.pk,))
|
return reverse('api:notification_detail', args=(self.pk,))
|
||||||
|
|
||||||
|
class JobNotificationMixin(object):
|
||||||
|
def get_notification_templates(self):
|
||||||
|
raise RuntimeError("Define me")
|
||||||
|
|
||||||
|
def get_notification_friendly_name(self):
|
||||||
|
raise RuntimeError("Define me")
|
||||||
|
|
||||||
|
def _build_notification_message(self, status_str):
|
||||||
|
notification_body = self.notification_data()
|
||||||
|
notification_subject = "{} #{} '{}' {} on Ansible Tower: {}".format(self.get_notification_friendly_name(),
|
||||||
|
self.id,
|
||||||
|
self.name,
|
||||||
|
status_str,
|
||||||
|
notification_body['url'])
|
||||||
|
notification_body['friendly_name'] = self.get_notification_friendly_name()
|
||||||
|
return (notification_subject, notification_body)
|
||||||
|
|
||||||
|
def build_notification_succeeded_message(self):
|
||||||
|
return self._build_notification_message('succeeded')
|
||||||
|
|
||||||
|
def build_notification_failed_message(self):
|
||||||
|
return self._build_notification_message('failed')
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ from django.utils.timezone import now, make_aware, get_default_timezone
|
|||||||
from awx.lib.compat import slugify
|
from awx.lib.compat import slugify
|
||||||
from awx.main.models.base import * # noqa
|
from awx.main.models.base import * # noqa
|
||||||
from awx.main.models.jobs import Job
|
from awx.main.models.jobs import Job
|
||||||
from awx.main.models.notifications import NotificationTemplate
|
from awx.main.models.notifications import (
|
||||||
|
NotificationTemplate,
|
||||||
|
JobNotificationMixin,
|
||||||
|
)
|
||||||
from awx.main.models.unified_jobs import * # noqa
|
from awx.main.models.unified_jobs import * # noqa
|
||||||
from awx.main.models.mixins import ResourceMixin
|
from awx.main.models.mixins import ResourceMixin
|
||||||
from awx.main.utils import update_scm_url
|
from awx.main.utils import update_scm_url
|
||||||
@@ -372,8 +375,7 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('api:project_detail', args=(self.pk,))
|
return reverse('api:project_detail', args=(self.pk,))
|
||||||
|
|
||||||
|
class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin):
|
||||||
class ProjectUpdate(UnifiedJob, ProjectOptions):
|
|
||||||
'''
|
'''
|
||||||
Internal job for tracking project updates from SCM.
|
Internal job for tracking project updates from SCM.
|
||||||
'''
|
'''
|
||||||
@@ -443,3 +445,12 @@ class ProjectUpdate(UnifiedJob, ProjectOptions):
|
|||||||
if 'scm_delete_on_next_update' not in update_fields:
|
if 'scm_delete_on_next_update' not in update_fields:
|
||||||
update_fields.append('scm_delete_on_next_update')
|
update_fields.append('scm_delete_on_next_update')
|
||||||
parent_instance.save(update_fields=update_fields)
|
parent_instance.save(update_fields=update_fields)
|
||||||
|
|
||||||
|
'''
|
||||||
|
JobNotificationMixin
|
||||||
|
'''
|
||||||
|
def get_notification_templates(self):
|
||||||
|
return self.project.notification_templates
|
||||||
|
|
||||||
|
def get_notification_friendly_name(self):
|
||||||
|
return "Project Update"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from django.core.exceptions import NON_FIELD_ERRORS
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
|
from django.apps import apps
|
||||||
|
|
||||||
# Django-JSONField
|
# Django-JSONField
|
||||||
from jsonfield import JSONField
|
from jsonfield import JSONField
|
||||||
@@ -360,8 +361,30 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
|
|||||||
dest_field.add(*list(src_field_value.all().values_list('id', flat=True)))
|
dest_field.add(*list(src_field_value.all().values_list('id', flat=True)))
|
||||||
return unified_job
|
return unified_job
|
||||||
|
|
||||||
|
class UnifiedJobTypeStringMixin(object):
|
||||||
|
@classmethod
|
||||||
|
def _underscore_to_camel(cls, word):
|
||||||
|
return ''.join(x.capitalize() or '_' for x in word.split('_'))
|
||||||
|
|
||||||
class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique):
|
@classmethod
|
||||||
|
def _model_type(cls, job_type):
|
||||||
|
# Django >= 1.9
|
||||||
|
#app = apps.get_app_config('main')
|
||||||
|
model_str = cls._underscore_to_camel(job_type)
|
||||||
|
try:
|
||||||
|
return apps.get_model('main', model_str)
|
||||||
|
except LookupError:
|
||||||
|
print("Lookup model error")
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_instance_by_type(cls, job_type, job_id):
|
||||||
|
model = cls._model_type(job_type)
|
||||||
|
if not model:
|
||||||
|
return None
|
||||||
|
return model.objects.get(id=job_id)
|
||||||
|
|
||||||
|
class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique, UnifiedJobTypeStringMixin):
|
||||||
'''
|
'''
|
||||||
Concrete base class for unified job run by the task engine.
|
Concrete base class for unified job run by the task engine.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -185,114 +185,61 @@ def notify_task_runner(metadata_dict):
|
|||||||
queue = FifoQueue('tower_task_manager')
|
queue = FifoQueue('tower_task_manager')
|
||||||
queue.push(metadata_dict)
|
queue.push(metadata_dict)
|
||||||
|
|
||||||
|
|
||||||
|
def _send_notification_templates(instance, status_str):
|
||||||
|
if status_str not in ['succeeded', 'failed']:
|
||||||
|
raise ValueError("status_str must be either succeeded or failed")
|
||||||
|
print("Instance has some shit in it %s" % instance)
|
||||||
|
notification_templates = instance.get_notification_templates()
|
||||||
|
if notification_templates:
|
||||||
|
all_notification_templates = set(notification_templates.get('success', []) + notification_templates.get('any', []))
|
||||||
|
if len(all_notification_templates):
|
||||||
|
try:
|
||||||
|
(notification_subject, notification_body) = getattr(instance, 'build_notification_%s_message' % status_str)()
|
||||||
|
except AttributeError:
|
||||||
|
raise NotImplementedError("build_notification_%s_message() does not exist" % status_str)
|
||||||
|
send_notifications.delay([n.generate_notification(notification_subject, notification_body).id
|
||||||
|
for n in all_notification_templates],
|
||||||
|
job_id=instance.id)
|
||||||
|
|
||||||
@task(bind=True)
|
@task(bind=True)
|
||||||
def handle_work_success(self, result, task_actual):
|
def handle_work_success(self, result, task_actual):
|
||||||
if task_actual['type'] == 'project_update':
|
instance = UnifiedJob.get_instance_by_type(task_actual['type'], task_actual['id'])
|
||||||
instance = ProjectUpdate.objects.get(id=task_actual['id'])
|
if not instance:
|
||||||
instance_name = instance.name
|
|
||||||
notification_templates = instance.project.notification_templates
|
|
||||||
friendly_name = "Project Update"
|
|
||||||
elif task_actual['type'] == 'inventory_update':
|
|
||||||
instance = InventoryUpdate.objects.get(id=task_actual['id'])
|
|
||||||
instance_name = instance.name
|
|
||||||
notification_templates = instance.inventory_source.notification_templates
|
|
||||||
friendly_name = "Inventory Update"
|
|
||||||
elif task_actual['type'] == 'job':
|
|
||||||
instance = Job.objects.get(id=task_actual['id'])
|
|
||||||
instance_name = instance.job_template.name
|
|
||||||
notification_templates = instance.job_template.notification_templates
|
|
||||||
friendly_name = "Job"
|
|
||||||
elif task_actual['type'] == 'ad_hoc_command':
|
|
||||||
instance = AdHocCommand.objects.get(id=task_actual['id'])
|
|
||||||
instance_name = instance.module_name
|
|
||||||
notification_templates = instance.notification_templates
|
|
||||||
friendly_name = "AdHoc Command"
|
|
||||||
elif task_actual['type'] == 'system_job':
|
|
||||||
instance = SystemJob.objects.get(id=task_actual['id'])
|
|
||||||
instance_name = instance.system_job_template.name
|
|
||||||
notification_templates = instance.system_job_template.notification_templates
|
|
||||||
friendly_name = "System Job"
|
|
||||||
else:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
all_notification_templates = set(notification_templates.get('success', []) + notification_templates.get('any', []))
|
_send_notification_templates(instance, 'succeeded')
|
||||||
if len(all_notification_templates):
|
|
||||||
notification_body = instance.notification_data()
|
|
||||||
notification_subject = "{} #{} '{}' succeeded on Ansible Tower: {}".format(friendly_name,
|
|
||||||
task_actual['id'],
|
|
||||||
smart_str(instance_name),
|
|
||||||
notification_body['url'])
|
|
||||||
notification_body['friendly_name'] = friendly_name
|
|
||||||
send_notifications.delay([n.generate_notification(notification_subject, notification_body).id
|
|
||||||
for n in all_notification_templates],
|
|
||||||
job_id=task_actual['id'])
|
|
||||||
|
|
||||||
@task(bind=True)
|
@task(bind=True)
|
||||||
def handle_work_error(self, task_id, subtasks=None):
|
def handle_work_error(self, task_id, subtasks=None):
|
||||||
print('Executing error task id %s, subtasks: %s' %
|
print('Executing error task id %s, subtasks: %s' %
|
||||||
(str(self.request.id), str(subtasks)))
|
(str(self.request.id), str(subtasks)))
|
||||||
first_task = None
|
first_instance = None
|
||||||
first_task_id = None
|
first_instance_type = ''
|
||||||
first_task_type = ''
|
|
||||||
first_task_name = ''
|
|
||||||
if subtasks is not None:
|
if subtasks is not None:
|
||||||
for each_task in subtasks:
|
for each_task in subtasks:
|
||||||
instance_name = ''
|
instance = UnifiedJob.get_instance_by_type(each_task['type'], each_task['id'])
|
||||||
if each_task['type'] == 'project_update':
|
if not instance:
|
||||||
instance = ProjectUpdate.objects.get(id=each_task['id'])
|
|
||||||
instance_name = instance.name
|
|
||||||
notification_templates = instance.project.notification_templates
|
|
||||||
friendly_name = "Project Update"
|
|
||||||
elif each_task['type'] == 'inventory_update':
|
|
||||||
instance = InventoryUpdate.objects.get(id=each_task['id'])
|
|
||||||
instance_name = instance.name
|
|
||||||
notification_templates = instance.inventory_source.notification_templates
|
|
||||||
friendly_name = "Inventory Update"
|
|
||||||
elif each_task['type'] == 'job':
|
|
||||||
instance = Job.objects.get(id=each_task['id'])
|
|
||||||
instance_name = instance.job_template.name
|
|
||||||
notification_templates = instance.job_template.notification_templates
|
|
||||||
friendly_name = "Job"
|
|
||||||
elif each_task['type'] == 'ad_hoc_command':
|
|
||||||
instance = AdHocCommand.objects.get(id=each_task['id'])
|
|
||||||
instance_name = instance.module_name
|
|
||||||
notification_templates = instance.notification_templates
|
|
||||||
friendly_name = "AdHoc Command"
|
|
||||||
elif each_task['type'] == 'system_job':
|
|
||||||
instance = SystemJob.objects.get(id=each_task['id'])
|
|
||||||
instance_name = instance.system_job_template.name
|
|
||||||
notification_templates = instance.system_job_template.notification_templates
|
|
||||||
friendly_name = "System Job"
|
|
||||||
else:
|
|
||||||
# Unknown task type
|
# Unknown task type
|
||||||
logger.warn("Unknown task type: {}".format(each_task['type']))
|
logger.warn("Unknown task type: {}".format(each_task['type']))
|
||||||
continue
|
continue
|
||||||
if first_task is None:
|
|
||||||
first_task = instance
|
if first_instance is None:
|
||||||
first_task_id = instance.id
|
first_instance = instance
|
||||||
first_task_type = each_task['type']
|
first_instance_type = each_task['type']
|
||||||
first_task_name = instance_name
|
|
||||||
first_task_friendly_name = friendly_name
|
|
||||||
if instance.celery_task_id != task_id:
|
if instance.celery_task_id != task_id:
|
||||||
instance.status = 'failed'
|
instance.status = 'failed'
|
||||||
instance.failed = True
|
instance.failed = True
|
||||||
instance.job_explanation = 'Previous Task Failed: {"job_type": "%s", "job_name": "%s", "job_id": "%s"}' % \
|
instance.job_explanation = 'Previous Task Failed: {"job_type": "%s", "job_name": "%s", "job_id": "%s"}' % \
|
||||||
(first_task_type, first_task_name, first_task_id)
|
(first_instance_type, first_instance.name, first_instance.id)
|
||||||
instance.save()
|
instance.save()
|
||||||
instance.socketio_emit_status("failed")
|
instance.socketio_emit_status("failed")
|
||||||
|
|
||||||
all_notification_templates = set(notification_templates.get('error', []) + notification_templates.get('any', []))
|
if first_instance:
|
||||||
if len(all_notification_templates):
|
print("Instance type is %s" % first_instance_type)
|
||||||
notification_body = first_task.notification_data()
|
print("Instance passing along %s" % first_instance.name)
|
||||||
notification_subject = "{} #{} '{}' failed on Ansible Tower: {}".format(first_task_friendly_name,
|
_send_notification_templates(first_instance, 'failed')
|
||||||
first_task_id,
|
|
||||||
smart_str(first_task_name),
|
|
||||||
notification_body['url'])
|
|
||||||
notification_body['friendly_name'] = first_task_friendly_name
|
|
||||||
send_notifications.delay([n.generate_notification(notification_subject, notification_body).id
|
|
||||||
for n in all_notification_templates],
|
|
||||||
job_id=first_task_id)
|
|
||||||
|
|
||||||
|
|
||||||
@task()
|
@task()
|
||||||
def update_inventory_computed_fields(inventory_id, should_update_hosts=True):
|
def update_inventory_computed_fields(inventory_id, should_update_hosts=True):
|
||||||
@@ -1710,3 +1657,4 @@ class RunSystemJob(BaseTask):
|
|||||||
|
|
||||||
def build_cwd(self, instance, **kwargs):
|
def build_cwd(self, instance, **kwargs):
|
||||||
return settings.BASE_DIR
|
return settings.BASE_DIR
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user