mirror of
https://github.com/ansible/awx.git
synced 2026-02-27 07:56:06 -03:30
Render default notifications using Jinja templates
This commit is contained in:
@@ -150,12 +150,12 @@ class NotificationTemplate(CommonModelNameNotUnique):
|
|||||||
def recipients(self):
|
def recipients(self):
|
||||||
return self.notification_configuration[self.notification_class.recipient_parameter]
|
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 = Notification(notification_template=self,
|
||||||
notification_type=self.notification_type,
|
notification_type=self.notification_type,
|
||||||
recipients=smart_str(self.recipients),
|
recipients=smart_str(self.recipients),
|
||||||
subject=subject,
|
subject=msg,
|
||||||
body=message)
|
body=body)
|
||||||
notification.save()
|
notification.save()
|
||||||
return notification
|
return notification
|
||||||
|
|
||||||
@@ -415,32 +415,32 @@ class JobNotificationMixin(object):
|
|||||||
context = self.context(job_serialization)
|
context = self.context(job_serialization)
|
||||||
|
|
||||||
msg_template = body_template = None
|
msg_template = body_template = None
|
||||||
|
msg = body = ''
|
||||||
|
|
||||||
|
# Use custom template if available
|
||||||
if nt.messages:
|
if nt.messages:
|
||||||
templates = nt.messages.get(self.STATUS_TO_TEMPLATE_TYPE[status], {}) or {}
|
templates = nt.messages.get(self.STATUS_TO_TEMPLATE_TYPE[status], {}) or {}
|
||||||
msg_template = templates.get('message', {})
|
msg_template = templates.get('message', None)
|
||||||
body_template = templates.get('body', {})
|
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:
|
if msg_template:
|
||||||
try:
|
try:
|
||||||
notification_subject = env.from_string(msg_template).render(**context)
|
msg = env.from_string(msg_template).render(**context)
|
||||||
except (TemplateSyntaxError, UndefinedError, SecurityError):
|
except (TemplateSyntaxError, UndefinedError, SecurityError):
|
||||||
notification_subject = ''
|
msg = ''
|
||||||
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()
|
|
||||||
if body_template:
|
if body_template:
|
||||||
try:
|
try:
|
||||||
notification_body['body'] = env.from_string(body_template).render(**context)
|
body = env.from_string(body_template).render(**context)
|
||||||
except (TemplateSyntaxError, UndefinedError, SecurityError):
|
except (TemplateSyntaxError, UndefinedError, SecurityError):
|
||||||
notification_body['body'] = ''
|
body = ''
|
||||||
|
|
||||||
return (notification_subject, notification_body)
|
return (msg, body)
|
||||||
|
|
||||||
def send_notification_templates(self, status):
|
def send_notification_templates(self, status):
|
||||||
from awx.main.tasks import send_notifications # avoid circular import
|
from awx.main.tasks import send_notifications # avoid circular import
|
||||||
@@ -456,16 +456,13 @@ class JobNotificationMixin(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
for nt in set(notification_templates.get(self.STATUS_TO_TEMPLATE_TYPE[status], [])):
|
for nt in set(notification_templates.get(self.STATUS_TO_TEMPLATE_TYPE[status], [])):
|
||||||
try:
|
(msg, body) = self.build_notification_message(nt, status)
|
||||||
(notification_subject, notification_body) = self.build_notification_message(nt, status)
|
|
||||||
except AttributeError:
|
|
||||||
raise NotImplementedError("build_notification_message() does not exist" % status)
|
|
||||||
|
|
||||||
# Use kwargs to force late-binding
|
# Use kwargs to force late-binding
|
||||||
# https://stackoverflow.com/a/3431699/10669572
|
# 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():
|
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)
|
job_id=self.id)
|
||||||
return _func
|
return _func
|
||||||
connection.on_commit(send_it())
|
connection.on_commit(send_it())
|
||||||
|
|||||||
@@ -1,21 +1,10 @@
|
|||||||
# Copyright (c) 2016 Ansible, Inc.
|
# Copyright (c) 2016 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from django.utils.encoding import smart_text
|
|
||||||
from django.core.mail.backends.base import BaseEmailBackend
|
from django.core.mail.backends.base import BaseEmailBackend
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class AWXBaseEmailBackend(BaseEmailBackend):
|
class AWXBaseEmailBackend(BaseEmailBackend):
|
||||||
|
|
||||||
def format_body(self, body):
|
def format_body(self, body):
|
||||||
if "body" in body:
|
return 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
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# Copyright (c) 2016 Ansible, Inc.
|
# Copyright (c) 2016 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
from django.core.mail.backends.smtp import EmailBackend
|
from django.core.mail.backends.smtp import EmailBackend
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
@@ -20,21 +18,15 @@ class CustomEmailBackend(EmailBackend):
|
|||||||
"recipients": {"label": "Recipient List", "type": "list"},
|
"recipients": {"label": "Recipient List", "type": "list"},
|
||||||
"timeout": {"label": "Timeout", "type": "int", "default": 30}}
|
"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_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},
|
default_messages = {"started": {"message": DEFAULT_MSG, "body": DEFAULT_BODY},
|
||||||
"success": {"message": DEFAULT_SUBJECT, "body": DEFAULT_BODY},
|
"success": {"message": DEFAULT_MSG, "body": DEFAULT_BODY},
|
||||||
"error": {"message": DEFAULT_SUBJECT, "body": DEFAULT_BODY}}
|
"error": {"message": DEFAULT_MSG, "body": DEFAULT_BODY}}
|
||||||
recipient_parameter = "recipients"
|
recipient_parameter = "recipients"
|
||||||
sender_parameter = "sender"
|
sender_parameter = "sender"
|
||||||
|
|
||||||
|
|
||||||
def format_body(self, body):
|
def format_body(self, body):
|
||||||
if "body" in body:
|
# leave body unchanged (expect a string)
|
||||||
body_actual = body['body']
|
return 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
|
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ class GrafanaBackend(AWXBaseEmailBackend):
|
|||||||
recipient_parameter = "grafana_url"
|
recipient_parameter = "grafana_url"
|
||||||
sender_parameter = None
|
sender_parameter = None
|
||||||
|
|
||||||
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_messages = {"started": {"message": DEFAULT_SUBJECT},
|
default_messages = {"started": {"message": DEFAULT_MSG},
|
||||||
"success": {"message": DEFAULT_SUBJECT},
|
"success": {"message": DEFAULT_MSG},
|
||||||
"error": {"message": DEFAULT_SUBJECT}}
|
"error": {"message": DEFAULT_MSG}}
|
||||||
|
|
||||||
def __init__(self, grafana_key,dashboardId=None, panelId=None, annotation_tags=None, grafana_no_verify_ssl=False, isRegion=True,
|
def __init__(self, grafana_key,dashboardId=None, panelId=None, annotation_tags=None, grafana_no_verify_ssl=False, isRegion=True,
|
||||||
fail_silently=False, **kwargs):
|
fail_silently=False, **kwargs):
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ class HipChatBackend(AWXBaseEmailBackend):
|
|||||||
recipient_parameter = "rooms"
|
recipient_parameter = "rooms"
|
||||||
sender_parameter = "message_from"
|
sender_parameter = "message_from"
|
||||||
|
|
||||||
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_messages = {"started": {"message": DEFAULT_SUBJECT},
|
default_messages = {"started": {"message": DEFAULT_MSG},
|
||||||
"success": {"message": DEFAULT_SUBJECT},
|
"success": {"message": DEFAULT_MSG},
|
||||||
"error": {"message": DEFAULT_SUBJECT}}
|
"error": {"message": DEFAULT_MSG}}
|
||||||
|
|
||||||
def __init__(self, token, color, api_url, notify, fail_silently=False, **kwargs):
|
def __init__(self, token, color, api_url, notify, fail_silently=False, **kwargs):
|
||||||
super(HipChatBackend, self).__init__(fail_silently=fail_silently)
|
super(HipChatBackend, self).__init__(fail_silently=fail_silently)
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ class IrcBackend(AWXBaseEmailBackend):
|
|||||||
recipient_parameter = "targets"
|
recipient_parameter = "targets"
|
||||||
sender_parameter = None
|
sender_parameter = None
|
||||||
|
|
||||||
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_messages = {"started": {"message": DEFAULT_SUBJECT},
|
default_messages = {"started": {"message": DEFAULT_MSG},
|
||||||
"success": {"message": DEFAULT_SUBJECT},
|
"success": {"message": DEFAULT_MSG},
|
||||||
"error": {"message": DEFAULT_SUBJECT}}
|
"error": {"message": DEFAULT_MSG}}
|
||||||
|
|
||||||
def __init__(self, server, port, nickname, password, use_ssl, fail_silently=False, **kwargs):
|
def __init__(self, server, port, nickname, password, use_ssl, fail_silently=False, **kwargs):
|
||||||
super(IrcBackend, self).__init__(fail_silently=fail_silently)
|
super(IrcBackend, self).__init__(fail_silently=fail_silently)
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ class MattermostBackend(AWXBaseEmailBackend):
|
|||||||
recipient_parameter = "mattermost_url"
|
recipient_parameter = "mattermost_url"
|
||||||
sender_parameter = None
|
sender_parameter = None
|
||||||
|
|
||||||
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_messages = {"started": {"message": DEFAULT_SUBJECT},
|
default_messages = {"started": {"message": DEFAULT_MSG},
|
||||||
"success": {"message": DEFAULT_SUBJECT},
|
"success": {"message": DEFAULT_MSG},
|
||||||
"error": {"message": DEFAULT_SUBJECT}}
|
"error": {"message": DEFAULT_MSG}}
|
||||||
|
|
||||||
def __init__(self, mattermost_no_verify_ssl=False, mattermost_channel=None, mattermost_username=None,
|
def __init__(self, mattermost_no_verify_ssl=False, mattermost_channel=None, mattermost_username=None,
|
||||||
mattermost_icon_url=None, fail_silently=False, **kwargs):
|
mattermost_icon_url=None, fail_silently=False, **kwargs):
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Copyright (c) 2016 Ansible, Inc.
|
# Copyright (c) 2016 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import pygerduty
|
import pygerduty
|
||||||
|
|
||||||
@@ -20,11 +21,11 @@ class PagerDutyBackend(AWXBaseEmailBackend):
|
|||||||
recipient_parameter = "service_key"
|
recipient_parameter = "service_key"
|
||||||
sender_parameter = "client_name"
|
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_BODY = "{{ job_summary_dict }}"
|
||||||
default_messages = {"started": { "message": DEFAULT_SUBJECT, "body": DEFAULT_BODY},
|
default_messages = {"started": { "message": DEFAULT_MSG, "body": DEFAULT_BODY},
|
||||||
"success": { "message": DEFAULT_SUBJECT, "body": DEFAULT_BODY},
|
"success": { "message": DEFAULT_MSG, "body": DEFAULT_BODY},
|
||||||
"error": { "message": DEFAULT_SUBJECT, "body": DEFAULT_BODY}}
|
"error": { "message": DEFAULT_MSG, "body": DEFAULT_BODY}}
|
||||||
|
|
||||||
def __init__(self, subdomain, token, fail_silently=False, **kwargs):
|
def __init__(self, subdomain, token, fail_silently=False, **kwargs):
|
||||||
super(PagerDutyBackend, self).__init__(fail_silently=fail_silently)
|
super(PagerDutyBackend, self).__init__(fail_silently=fail_silently)
|
||||||
@@ -32,6 +33,16 @@ class PagerDutyBackend(AWXBaseEmailBackend):
|
|||||||
self.token = token
|
self.token = token
|
||||||
|
|
||||||
def format_body(self, body):
|
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
|
return body
|
||||||
|
|
||||||
def send_messages(self, messages):
|
def send_messages(self, messages):
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ class RocketChatBackend(AWXBaseEmailBackend):
|
|||||||
recipient_parameter = "rocketchat_url"
|
recipient_parameter = "rocketchat_url"
|
||||||
sender_parameter = None
|
sender_parameter = None
|
||||||
|
|
||||||
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_messages = {"started": {"message": DEFAULT_SUBJECT},
|
default_messages = {"started": {"message": DEFAULT_MSG},
|
||||||
"success": {"message": DEFAULT_SUBJECT},
|
"success": {"message": DEFAULT_MSG},
|
||||||
"error": {"message": DEFAULT_SUBJECT}}
|
"error": {"message": DEFAULT_MSG}}
|
||||||
|
|
||||||
def __init__(self, rocketchat_no_verify_ssl=False, rocketchat_username=None, rocketchat_icon_url=None, fail_silently=False, **kwargs):
|
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)
|
super(RocketChatBackend, self).__init__(fail_silently=fail_silently)
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ class SlackBackend(AWXBaseEmailBackend):
|
|||||||
recipient_parameter = "channels"
|
recipient_parameter = "channels"
|
||||||
sender_parameter = None
|
sender_parameter = None
|
||||||
|
|
||||||
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_messages = {"started": {"message": DEFAULT_SUBJECT},
|
default_messages = {"started": {"message": DEFAULT_MSG},
|
||||||
"success": {"message": DEFAULT_SUBJECT},
|
"success": {"message": DEFAULT_MSG},
|
||||||
"error": {"message": DEFAULT_SUBJECT}}
|
"error": {"message": DEFAULT_MSG}}
|
||||||
|
|
||||||
def __init__(self, token, hex_color="", fail_silently=False, **kwargs):
|
def __init__(self, token, hex_color="", fail_silently=False, **kwargs):
|
||||||
super(SlackBackend, self).__init__(fail_silently=fail_silently)
|
super(SlackBackend, self).__init__(fail_silently=fail_silently)
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ class TwilioBackend(AWXBaseEmailBackend):
|
|||||||
recipient_parameter = "to_numbers"
|
recipient_parameter = "to_numbers"
|
||||||
sender_parameter = "from_number"
|
sender_parameter = "from_number"
|
||||||
|
|
||||||
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_messages = {"started": {"message": DEFAULT_SUBJECT},
|
default_messages = {"started": {"message": DEFAULT_MSG},
|
||||||
"success": {"message": DEFAULT_SUBJECT},
|
"success": {"message": DEFAULT_MSG},
|
||||||
"error": {"message": DEFAULT_SUBJECT}}
|
"error": {"message": DEFAULT_MSG}}
|
||||||
|
|
||||||
def __init__(self, account_sid, account_token, fail_silently=False, **kwargs):
|
def __init__(self, account_sid, account_token, fail_silently=False, **kwargs):
|
||||||
super(TwilioBackend, self).__init__(fail_silently=fail_silently)
|
super(TwilioBackend, self).__init__(fail_silently=fail_silently)
|
||||||
|
|||||||
@@ -38,15 +38,13 @@ class WebhookBackend(AWXBaseEmailBackend):
|
|||||||
super(WebhookBackend, self).__init__(fail_silently=fail_silently)
|
super(WebhookBackend, self).__init__(fail_silently=fail_silently)
|
||||||
|
|
||||||
def format_body(self, body):
|
def format_body(self, body):
|
||||||
# If `body` has body field, attempt to use this as the main body,
|
# expect body to be a string representing a dict
|
||||||
# otherwise, leave it as a sub-field
|
try:
|
||||||
if isinstance(body, dict) and 'body' in body and isinstance(body['body'], str):
|
potential_body = json.loads(body)
|
||||||
try:
|
if isinstance(potential_body, dict):
|
||||||
potential_body = json.loads(body['body'])
|
body = potential_body
|
||||||
if isinstance(potential_body, dict):
|
except json.JSONDecodeError:
|
||||||
body = potential_body
|
body = {}
|
||||||
except json.JSONDecodeError:
|
|
||||||
pass
|
|
||||||
return body
|
return body
|
||||||
|
|
||||||
def send_messages(self, messages):
|
def send_messages(self, messages):
|
||||||
|
|||||||
@@ -144,5 +144,3 @@ class TestJobNotificationMixin(object):
|
|||||||
|
|
||||||
context_stub = JobNotificationMixin.context_stub()
|
context_stub = JobNotificationMixin.context_stub()
|
||||||
check_structure_and_completeness(TestJobNotificationMixin.CONTEXT_STRUCTURE, context_stub)
|
check_structure_and_completeness(TestJobNotificationMixin.CONTEXT_STRUCTURE, context_stub)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user