diff --git a/awx/main/models/notifications.py b/awx/main/models/notifications.py index 75fab26fd1..9585a404ec 100644 --- a/awx/main/models/notifications.py +++ b/awx/main/models/notifications.py @@ -150,12 +150,12 @@ class NotificationTemplate(CommonModelNameNotUnique): def recipients(self): return self.notification_configuration[self.notification_class.recipient_parameter] - def generate_notification(self, subject, message): + def generate_notification(self, msg, body): notification = Notification(notification_template=self, notification_type=self.notification_type, recipients=smart_str(self.recipients), - subject=subject, - body=message) + subject=msg, + body=body) notification.save() return notification @@ -415,32 +415,32 @@ class JobNotificationMixin(object): context = self.context(job_serialization) msg_template = body_template = None + msg = body = '' + # Use custom template if available if nt.messages: templates = nt.messages.get(self.STATUS_TO_TEMPLATE_TYPE[status], {}) or {} - msg_template = templates.get('message', {}) - body_template = templates.get('body', {}) + msg_template = templates.get('message', None) + body_template = templates.get('body', None) + # If custom template not provided, look up default template + if not msg_template: + msg_template = getattr(nt.notification_class, 'DEFAULT_MSG', None) + if not body_template: + body_template = getattr(nt.notification_class, 'DEFAULT_BODY', None) if msg_template: try: - notification_subject = env.from_string(msg_template).render(**context) + msg = env.from_string(msg_template).render(**context) except (TemplateSyntaxError, UndefinedError, SecurityError): - notification_subject = '' - else: - notification_subject = u"{} #{} '{}' {}: {}".format(self.get_notification_friendly_name(), - self.id, - self.name, - status, - self.get_ui_url()) - notification_body = self.notification_data() - notification_body['friendly_name'] = self.get_notification_friendly_name() + msg = '' + if body_template: try: - notification_body['body'] = env.from_string(body_template).render(**context) + body = env.from_string(body_template).render(**context) except (TemplateSyntaxError, UndefinedError, SecurityError): - notification_body['body'] = '' + body = '' - return (notification_subject, notification_body) + return (msg, body) def send_notification_templates(self, status): from awx.main.tasks import send_notifications # avoid circular import @@ -456,16 +456,13 @@ class JobNotificationMixin(object): return for nt in set(notification_templates.get(self.STATUS_TO_TEMPLATE_TYPE[status], [])): - try: - (notification_subject, notification_body) = self.build_notification_message(nt, status) - except AttributeError: - raise NotImplementedError("build_notification_message() does not exist" % status) + (msg, body) = self.build_notification_message(nt, status) # Use kwargs to force late-binding # https://stackoverflow.com/a/3431699/10669572 - def send_it(local_nt=nt, local_subject=notification_subject, local_body=notification_body): + def send_it(local_nt=nt, local_msg=msg, local_body=body): def _func(): - send_notifications.delay([local_nt.generate_notification(local_subject, local_body).id], + send_notifications.delay([local_nt.generate_notification(local_msg, local_body).id], job_id=self.id) return _func connection.on_commit(send_it()) diff --git a/awx/main/notifications/base.py b/awx/main/notifications/base.py index bb5ac7a9ca..66ac369cbc 100644 --- a/awx/main/notifications/base.py +++ b/awx/main/notifications/base.py @@ -1,21 +1,10 @@ # Copyright (c) 2016 Ansible, Inc. # All Rights Reserved. -import json - -from django.utils.encoding import smart_text from django.core.mail.backends.base import BaseEmailBackend -from django.utils.translation import ugettext_lazy as _ class AWXBaseEmailBackend(BaseEmailBackend): def format_body(self, body): - if "body" in body: - body_actual = body['body'] - else: - body_actual = smart_text(_("{} #{} had status {}, view details at {}\n\n").format( - body['friendly_name'], body['id'], body['status'], body['url']) - ) - body_actual += json.dumps(body, indent=4) - return body_actual + return body diff --git a/awx/main/notifications/email_backend.py b/awx/main/notifications/email_backend.py index 8abce4fff1..4ae70ef376 100644 --- a/awx/main/notifications/email_backend.py +++ b/awx/main/notifications/email_backend.py @@ -1,8 +1,6 @@ # Copyright (c) 2016 Ansible, Inc. # All Rights Reserved. -import json - from django.utils.encoding import smart_text from django.core.mail.backends.smtp import EmailBackend from django.utils.translation import ugettext_lazy as _ @@ -20,21 +18,15 @@ class CustomEmailBackend(EmailBackend): "recipients": {"label": "Recipient List", "type": "list"}, "timeout": {"label": "Timeout", "type": "int", "default": 30}} - DEFAULT_SUBJECT = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" + DEFAULT_MSG = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" DEFAULT_BODY = smart_text(_("{{ job_friendly_name }} #{{ job.id }} had status {{ job.status }}, view details at {{ url }}\n\n{{ job_summary_dict }}")) - default_messages = {"started": {"message": DEFAULT_SUBJECT, "body": DEFAULT_BODY}, - "success": {"message": DEFAULT_SUBJECT, "body": DEFAULT_BODY}, - "error": {"message": DEFAULT_SUBJECT, "body": DEFAULT_BODY}} + default_messages = {"started": {"message": DEFAULT_MSG, "body": DEFAULT_BODY}, + "success": {"message": DEFAULT_MSG, "body": DEFAULT_BODY}, + "error": {"message": DEFAULT_MSG, "body": DEFAULT_BODY}} recipient_parameter = "recipients" sender_parameter = "sender" def format_body(self, body): - if "body" in body: - body_actual = body['body'] - else: - body_actual = smart_text(_("{} #{} had status {}, view details at {}\n\n").format( - body['friendly_name'], body['id'], body['status'], body['url']) - ) - body_actual += json.dumps(body, indent=4) - return body_actual + # leave body unchanged (expect a string) + return body diff --git a/awx/main/notifications/grafana_backend.py b/awx/main/notifications/grafana_backend.py index ccd176e4f4..5c6759399d 100644 --- a/awx/main/notifications/grafana_backend.py +++ b/awx/main/notifications/grafana_backend.py @@ -21,10 +21,10 @@ class GrafanaBackend(AWXBaseEmailBackend): recipient_parameter = "grafana_url" sender_parameter = None - DEFAULT_SUBJECT = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" - default_messages = {"started": {"message": DEFAULT_SUBJECT}, - "success": {"message": DEFAULT_SUBJECT}, - "error": {"message": DEFAULT_SUBJECT}} + DEFAULT_MSG = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" + default_messages = {"started": {"message": DEFAULT_MSG}, + "success": {"message": DEFAULT_MSG}, + "error": {"message": DEFAULT_MSG}} def __init__(self, grafana_key,dashboardId=None, panelId=None, annotation_tags=None, grafana_no_verify_ssl=False, isRegion=True, fail_silently=False, **kwargs): diff --git a/awx/main/notifications/hipchat_backend.py b/awx/main/notifications/hipchat_backend.py index 0fa53b4471..e187852722 100644 --- a/awx/main/notifications/hipchat_backend.py +++ b/awx/main/notifications/hipchat_backend.py @@ -23,10 +23,10 @@ class HipChatBackend(AWXBaseEmailBackend): recipient_parameter = "rooms" sender_parameter = "message_from" - DEFAULT_SUBJECT = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" - default_messages = {"started": {"message": DEFAULT_SUBJECT}, - "success": {"message": DEFAULT_SUBJECT}, - "error": {"message": DEFAULT_SUBJECT}} + DEFAULT_MSG = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" + default_messages = {"started": {"message": DEFAULT_MSG}, + "success": {"message": DEFAULT_MSG}, + "error": {"message": DEFAULT_MSG}} def __init__(self, token, color, api_url, notify, fail_silently=False, **kwargs): super(HipChatBackend, self).__init__(fail_silently=fail_silently) diff --git a/awx/main/notifications/irc_backend.py b/awx/main/notifications/irc_backend.py index 037293a0d3..bbdaefaf56 100644 --- a/awx/main/notifications/irc_backend.py +++ b/awx/main/notifications/irc_backend.py @@ -25,10 +25,10 @@ class IrcBackend(AWXBaseEmailBackend): recipient_parameter = "targets" sender_parameter = None - DEFAULT_SUBJECT = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" - default_messages = {"started": {"message": DEFAULT_SUBJECT}, - "success": {"message": DEFAULT_SUBJECT}, - "error": {"message": DEFAULT_SUBJECT}} + DEFAULT_MSG = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" + default_messages = {"started": {"message": DEFAULT_MSG}, + "success": {"message": DEFAULT_MSG}, + "error": {"message": DEFAULT_MSG}} def __init__(self, server, port, nickname, password, use_ssl, fail_silently=False, **kwargs): super(IrcBackend, self).__init__(fail_silently=fail_silently) diff --git a/awx/main/notifications/mattermost_backend.py b/awx/main/notifications/mattermost_backend.py index 41b3c4caa4..096bff9428 100644 --- a/awx/main/notifications/mattermost_backend.py +++ b/awx/main/notifications/mattermost_backend.py @@ -19,10 +19,10 @@ class MattermostBackend(AWXBaseEmailBackend): recipient_parameter = "mattermost_url" sender_parameter = None - DEFAULT_SUBJECT = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" - default_messages = {"started": {"message": DEFAULT_SUBJECT}, - "success": {"message": DEFAULT_SUBJECT}, - "error": {"message": DEFAULT_SUBJECT}} + DEFAULT_MSG = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" + default_messages = {"started": {"message": DEFAULT_MSG}, + "success": {"message": DEFAULT_MSG}, + "error": {"message": DEFAULT_MSG}} def __init__(self, mattermost_no_verify_ssl=False, mattermost_channel=None, mattermost_username=None, mattermost_icon_url=None, fail_silently=False, **kwargs): diff --git a/awx/main/notifications/pagerduty_backend.py b/awx/main/notifications/pagerduty_backend.py index 55827a67de..6e76974f3c 100644 --- a/awx/main/notifications/pagerduty_backend.py +++ b/awx/main/notifications/pagerduty_backend.py @@ -1,6 +1,7 @@ # Copyright (c) 2016 Ansible, Inc. # All Rights Reserved. +import json import logging import pygerduty @@ -20,11 +21,11 @@ class PagerDutyBackend(AWXBaseEmailBackend): recipient_parameter = "service_key" sender_parameter = "client_name" - DEFAULT_SUBJECT = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" + DEFAULT_MSG = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" DEFAULT_BODY = "{{ job_summary_dict }}" - default_messages = {"started": { "message": DEFAULT_SUBJECT, "body": DEFAULT_BODY}, - "success": { "message": DEFAULT_SUBJECT, "body": DEFAULT_BODY}, - "error": { "message": DEFAULT_SUBJECT, "body": DEFAULT_BODY}} + default_messages = {"started": { "message": DEFAULT_MSG, "body": DEFAULT_BODY}, + "success": { "message": DEFAULT_MSG, "body": DEFAULT_BODY}, + "error": { "message": DEFAULT_MSG, "body": DEFAULT_BODY}} def __init__(self, subdomain, token, fail_silently=False, **kwargs): super(PagerDutyBackend, self).__init__(fail_silently=fail_silently) @@ -32,6 +33,16 @@ class PagerDutyBackend(AWXBaseEmailBackend): self.token = token def format_body(self, body): + # cast to dict if possible # TODO: is it true that this can be a dict or str? + try: + potential_body = json.loads(body) + if isinstance(potential_body, dict): + body = potential_body + except json.JSONDecodeError: + pass + + # but it's okay if this is also just a string + return body def send_messages(self, messages): diff --git a/awx/main/notifications/rocketchat_backend.py b/awx/main/notifications/rocketchat_backend.py index e211708a8c..e5c6246f11 100644 --- a/awx/main/notifications/rocketchat_backend.py +++ b/awx/main/notifications/rocketchat_backend.py @@ -19,10 +19,10 @@ class RocketChatBackend(AWXBaseEmailBackend): recipient_parameter = "rocketchat_url" sender_parameter = None - DEFAULT_SUBJECT = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" - default_messages = {"started": {"message": DEFAULT_SUBJECT}, - "success": {"message": DEFAULT_SUBJECT}, - "error": {"message": DEFAULT_SUBJECT}} + DEFAULT_MSG = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" + default_messages = {"started": {"message": DEFAULT_MSG}, + "success": {"message": DEFAULT_MSG}, + "error": {"message": DEFAULT_MSG}} def __init__(self, rocketchat_no_verify_ssl=False, rocketchat_username=None, rocketchat_icon_url=None, fail_silently=False, **kwargs): super(RocketChatBackend, self).__init__(fail_silently=fail_silently) diff --git a/awx/main/notifications/slack_backend.py b/awx/main/notifications/slack_backend.py index 24b7876708..7c5ee60628 100644 --- a/awx/main/notifications/slack_backend.py +++ b/awx/main/notifications/slack_backend.py @@ -19,10 +19,10 @@ class SlackBackend(AWXBaseEmailBackend): recipient_parameter = "channels" sender_parameter = None - DEFAULT_SUBJECT = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" - default_messages = {"started": {"message": DEFAULT_SUBJECT}, - "success": {"message": DEFAULT_SUBJECT}, - "error": {"message": DEFAULT_SUBJECT}} + DEFAULT_MSG = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" + default_messages = {"started": {"message": DEFAULT_MSG}, + "success": {"message": DEFAULT_MSG}, + "error": {"message": DEFAULT_MSG}} def __init__(self, token, hex_color="", fail_silently=False, **kwargs): super(SlackBackend, self).__init__(fail_silently=fail_silently) diff --git a/awx/main/notifications/twilio_backend.py b/awx/main/notifications/twilio_backend.py index 21e98e6882..9dc68a0d97 100644 --- a/awx/main/notifications/twilio_backend.py +++ b/awx/main/notifications/twilio_backend.py @@ -21,10 +21,10 @@ class TwilioBackend(AWXBaseEmailBackend): recipient_parameter = "to_numbers" sender_parameter = "from_number" - DEFAULT_SUBJECT = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" - default_messages = {"started": {"message": DEFAULT_SUBJECT}, - "success": {"message": DEFAULT_SUBJECT}, - "error": {"message": DEFAULT_SUBJECT}} + DEFAULT_MSG = "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}" + default_messages = {"started": {"message": DEFAULT_MSG}, + "success": {"message": DEFAULT_MSG}, + "error": {"message": DEFAULT_MSG}} def __init__(self, account_sid, account_token, fail_silently=False, **kwargs): super(TwilioBackend, self).__init__(fail_silently=fail_silently) diff --git a/awx/main/notifications/webhook_backend.py b/awx/main/notifications/webhook_backend.py index 92cddf4b3b..be273b125a 100644 --- a/awx/main/notifications/webhook_backend.py +++ b/awx/main/notifications/webhook_backend.py @@ -38,15 +38,13 @@ class WebhookBackend(AWXBaseEmailBackend): super(WebhookBackend, self).__init__(fail_silently=fail_silently) def format_body(self, body): - # If `body` has body field, attempt to use this as the main body, - # otherwise, leave it as a sub-field - if isinstance(body, dict) and 'body' in body and isinstance(body['body'], str): - try: - potential_body = json.loads(body['body']) - if isinstance(potential_body, dict): - body = potential_body - except json.JSONDecodeError: - pass + # expect body to be a string representing a dict + try: + potential_body = json.loads(body) + if isinstance(potential_body, dict): + body = potential_body + except json.JSONDecodeError: + body = {} return body def send_messages(self, messages): diff --git a/awx/main/tests/functional/models/test_notifications.py b/awx/main/tests/functional/models/test_notifications.py index e835f2d2dd..2ebbcca71a 100644 --- a/awx/main/tests/functional/models/test_notifications.py +++ b/awx/main/tests/functional/models/test_notifications.py @@ -144,5 +144,3 @@ class TestJobNotificationMixin(object): context_stub = JobNotificationMixin.context_stub() check_structure_and_completeness(TestJobNotificationMixin.CONTEXT_STRUCTURE, context_stub) - -