1
0
mirror of https://github.com/ansible/awx.git synced 2024-11-01 08:21:15 +03:00

Refactor message generator

* Job object can now control the output and generate K:V output for
  notification types that can support it
* Notifications store the body as json/dict now to encode more
  information
* Notification Type can further compose the message based on what is
  sensible for the notification type
* This will also allow customizing the message template in the future
* All notification types use sane defaults for the level of detail now
This commit is contained in:
Matthew Jones 2016-02-22 17:09:36 -05:00
parent eb3d663d18
commit ab3669efa9
13 changed files with 76 additions and 37 deletions

View File

@ -2118,7 +2118,7 @@ class NotificationSerializer(BaseSerializer):
class Meta:
model = Notification
fields = ('*', '-name', '-description', 'notifier', 'error', 'status', 'notifications_sent',
'notification_type', 'recipients', 'subject', 'body')
'notification_type', 'recipients', 'subject')
def get_related(self, obj):
res = super(NotificationSerializer, self).get_related(obj)

View File

@ -3053,7 +3053,8 @@ class NotifierTest(GenericAPIView):
def post(self, request, *args, **kwargs):
obj = self.get_object()
notification = obj.generate_notification("Tower Notification Test", "Ansible Tower Test Notification")
notification = obj.generate_notification("Tower Notification Test {}".format(obj.id),
{"body": "Ansible Tower Test Notification {}".format(obj.id)})
if not notification:
return Response({}, status=status.HTTP_400_BAD_REQUEST)
else:

View File

@ -496,6 +496,26 @@ class Job(UnifiedJob, JobOptions):
dependencies.append(source.create_inventory_update(launch_type='dependency'))
return dependencies
def notification_data(self):
data = super(Job, self).notification_data()
all_hosts = {}
for h in self.job_host_summaries.all():
all_hosts[h.host.name] = dict(failed=h.failed,
changed=h.changed,
dark=h.dark,
failures=h.failures,
ok=h.ok,
processed=h.processed,
skipped=h.skipped)
data.update(dict(inventory=self.inventory.name,
project=self.project.name,
playbook=self.playbook,
credential=self.credential.name,
limit=self.limit,
extra_vars=self.extra_vars,
hosts=all_hosts))
return data
def handle_extra_data(self, extra_data):
extra_vars = {}
if type(extra_data) == dict:

View File

@ -69,8 +69,8 @@ class Notifier(CommonModel):
for field in filter(lambda x: self.notification_class.init_parameters[x]['type'] == "password",
self.notification_class.init_parameters):
if new_instance:
value = getattr(self.notification_configuration, field, '')
setattr(self, '_saved_{}'.format(field), value)
value = self.notification_configuration[field]
setattr(self, '_saved_{}_{}'.format("config", field), value)
self.notification_configuration[field] = ''
else:
encrypted = encrypt_field(self, 'notification_configuration', subfield=field)
@ -82,8 +82,9 @@ class Notifier(CommonModel):
update_fields = []
for field in filter(lambda x: self.notification_class.init_parameters[x]['type'] == "password",
self.notification_class.init_parameters):
saved_value = getattr(self, '_saved_{}'.format(field), '')
setattr(self.notification_configuration, field, saved_value)
saved_value = getattr(self, '_saved_{}_{}'.format("config", field), '')
self.notification_configuration[field] = saved_value
#setattr(self.notification_configuration, field, saved_value)
if 'notification_configuration' not in update_fields:
update_fields.append('notification_configuration')
self.save(update_fields=update_fields)
@ -112,7 +113,7 @@ class Notifier(CommonModel):
recipients = [recipients]
sender = self.notification_configuration.pop(self.notification_class.sender_parameter, None)
backend_obj = self.notification_class(**self.notification_configuration)
notification_obj = EmailMessage(subject, body, sender, recipients)
notification_obj = EmailMessage(subject, backend_obj.format_body(body), sender, recipients)
return backend_obj.send_messages([notification_obj])
class Notification(CreatedModifiedModel):
@ -165,11 +166,7 @@ class Notification(CreatedModifiedModel):
default='',
editable=False,
)
body = models.TextField(
blank=True,
default='',
editable=False,
)
body = JSONField(blank=True)
def get_absolute_url(self):
return reverse('api:notification_detail', args=(self.pk,))

View File

@ -731,6 +731,16 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
tasks that might preclude creating one'''
return []
def notification_data(self):
return dict(id=self.id,
name=self.name,
url=self.get_absolute_url(), #TODO: Need to replace with UI job view
created_by=str(self.created_by),
started=self.started.isoformat(),
finished=self.finished.isoformat(),
status=self.status,
traceback=self.result_traceback)
def start(self, error_callback, success_callback, **kwargs):
'''
Start the task running via Celery.

View File

@ -18,3 +18,10 @@ class CustomEmailBackend(EmailBackend):
recipient_parameter = "recipients"
sender_parameter = "sender"
def format_body(self, body):
body_actual = "{} #{} had status {} on Ansible Tower, view details at {}\n\n".format(body['friendly_name'],
body['id'],
body['status'],
body['url'])
body_actual += pprint.pformat(body, indent=4)
return body_actual

View File

@ -5,11 +5,11 @@ import logging
import requests
from django.core.mail.backends.base import BaseEmailBackend
from awx.main.notifications.base import TowerBaseEmailBackend
logger = logging.getLogger('awx.main.notifications.hipchat_backend')
class HipChatBackend(BaseEmailBackend):
class HipChatBackend(TowerBaseEmailBackend):
init_parameters = {"token": {"label": "Token", "type": "password"},
"channels": {"label": "Destination Channels", "type": "list"},
@ -35,7 +35,7 @@ class HipChatBackend(BaseEmailBackend):
r = requests.post("{}/v2/room/{}/notification".format(self.api_url, rcp),
params={"auth_token": self.token},
json={"color": self.color,
"message": m.body,
"message": m.subject,
"notify": self.notify,
"from": m.from_email,
"message_format": "text"})

View File

@ -7,11 +7,11 @@ import logging
import irc.client
from django.core.mail.backends.base import BaseEmailBackend
from awx.main.notifications.base import TowerBaseEmailBackend
logger = logging.getLogger('awx.main.notifications.irc_backend')
class IrcBackend(BaseEmailBackend):
class IrcBackend(TowerBaseEmailBackend):
init_parameters = {"server": {"label": "IRC Server Address", "type": "string"},
"port": {"label": "IRC Server Port", "type": "int"},

View File

@ -4,11 +4,11 @@
import logging
import pygerduty
from django.core.mail.backends.base import BaseEmailBackend
from awx.main.notifications.base import TowerBaseEmailBackend
logger = logging.getLogger('awx.main.notifications.pagerduty_backend')
class PagerDutyBackend(BaseEmailBackend):
class PagerDutyBackend(TowerBaseEmailBackend):
init_parameters = {"subdomain": {"label": "Pagerduty subdomain", "type": "string"},
"token": {"label": "API Token", "type": "password"},
@ -22,6 +22,9 @@ class PagerDutyBackend(BaseEmailBackend):
self.subdomain = subdomain
self.token = token
def format_body(self, body):
return body
def send_messages(self, messages):
sent_messages = 0

View File

@ -4,11 +4,11 @@
import logging
from slackclient import SlackClient
from django.core.mail.backends.base import BaseEmailBackend
from awx.main.notifications.base import TowerBaseEmailBackend
logger = logging.getLogger('awx.main.notifications.slack_backend')
class SlackBackend(BaseEmailBackend):
class SlackBackend(TowerBaseEmailBackend):
init_parameters = {"token": {"label": "Token", "type": "password"},
"channels": {"label": "Destination Channels", "type": "list"}}
@ -41,7 +41,7 @@ class SlackBackend(BaseEmailBackend):
for m in messages:
try:
for r in m.recipients():
self.connection.rtm_send_message(r, m.body)
self.connection.rtm_send_message(r, m.subject)
sent_messages += 1
except Exception as e:
logger.error("Exception sending messages: {}".format(e))

View File

@ -5,11 +5,11 @@ import logging
from twilio.rest import TwilioRestClient
from django.core.mail.backends.base import BaseEmailBackend
from awx.main.notifications.base import TowerBaseEmailBackend
logger = logging.getLogger('awx.main.notifications.twilio_backend')
class TwilioBackend(BaseEmailBackend):
class TwilioBackend(TowerBaseEmailBackend):
init_parameters = {"account_sid": {"label": "Account SID", "type": "string"},
"account_token": {"label": "Account Token", "type": "password"},
@ -38,7 +38,7 @@ class TwilioBackend(BaseEmailBackend):
connection.messages.create(
to=m.to,
from_=m.from_email,
body=m.body)
body=m.subject)
sent_messages += 1
except Exception as e:
logger.error("Exception sending messages: {}".format(e))

View File

@ -4,12 +4,12 @@
import logging
import requests
from django.core.mail.backends.base import BaseEmailBackend
import json
from awx.main.notifications.base import TowerBaseEmailBackend
logger = logging.getLogger('awx.main.notifications.webhook_backend')
class WebhookBackend(BaseEmailBackend):
class WebhookBackend(TowerBaseEmailBackend):
init_parameters = {"url": {"label": "Target URL", "type": "string"},
"headers": {"label": "HTTP Headers", "type": "object"}}
@ -20,11 +20,16 @@ class WebhookBackend(BaseEmailBackend):
self.headers = headers
super(WebhookBackend, self).__init__(fail_silently=fail_silently)
def format_body(self, body):
logger.error("Generating body from {}".format(str(body)))
return body
def send_messages(self, messages):
sent_messages = 0
for m in messages:
logger.error("BODY: " + str(m.body))
r = requests.post("{}".format(m.recipients()[0]),
data=m.body,
data=json.dumps(m.body),
headers=self.headers)
if r.status_code >= 400:
logger.error("Error sending notification webhook: {}".format(r.text))

View File

@ -207,10 +207,8 @@ def handle_work_success(self, result, task_actual):
notification_subject = "{} #{} '{}' succeeded on Ansible Tower".format(friendly_name,
task_actual['id'],
instance_name)
notification_body = "{} #{} '{}' succeeded on Ansible Tower\nTo view the output: {}".format(friendly_name,
task_actual['id'],
instance_name,
instance.get_absolute_url())
notification_body = instance.notification_data()
notification_body['friendly_name'] = friendly_name
send_notifications.delay([n.generate_notification(notification_subject, notification_body)
for n in notifiers.get('success', []) + notifiers.get('any', [])],
job_id=task_actual['id'])
@ -265,10 +263,8 @@ def handle_work_error(self, task_id, subtasks=None):
notification_subject = "{} #{} '{}' failed on Ansible Tower".format(first_task_friendly_name,
first_task_id,
first_task_name)
notification_body = "{} #{} '{}' failed on Ansible Tower\nTo view the output: {}".format(first_task_friendly_name,
first_task_id,
first_task_name,
first_task.get_absolute_url())
notification_body = first_task.notification_data()
notification_body['friendly_name'] = first_task_friendly_name
send_notifications.delay([n.generate_notification(notification_subject, notification_body).id
for n in notifiers.get('error', []) + notifiers.get('any', [])],
job_id=first_task_id)