From 7385efef351dfa14e61af9438923a1907ca484b4 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Mon, 1 Feb 2016 16:54:34 -0500 Subject: [PATCH] Adding some early Notifications stubs * A basic NotificationTemplate model class with early notification type definitions * Initial implementations of the Email, Slack, and Twilio Notification backends using the Django email backend system * Some dependencies thereof --- awx/main/models/__init__.py | 1 + awx/main/models/notifications.py | 37 +++++++++++++++++++ awx/main/notifications/__init__.py | 0 awx/main/notifications/email_backend.py | 11 ++++++ awx/main/notifications/slack_backend.py | 46 ++++++++++++++++++++++++ awx/main/notifications/twilio_backend.py | 42 ++++++++++++++++++++++ requirements/requirements.txt | 2 ++ 7 files changed, 139 insertions(+) create mode 100644 awx/main/models/notifications.py create mode 100644 awx/main/notifications/__init__.py create mode 100644 awx/main/notifications/email_backend.py create mode 100644 awx/main/notifications/slack_backend.py create mode 100644 awx/main/notifications/twilio_backend.py diff --git a/awx/main/models/__init__.py b/awx/main/models/__init__.py index 23cf591e6b..4e6d45f18f 100644 --- a/awx/main/models/__init__.py +++ b/awx/main/models/__init__.py @@ -17,6 +17,7 @@ from awx.main.models.schedules import * # noqa from awx.main.models.activity_stream import * # noqa from awx.main.models.ha import * # noqa from awx.main.models.configuration import * # noqa +from awx.main.models.notifications import * # noqa # Monkeypatch Django serializer to ignore django-taggit fields (which break # the dumpdata command; see https://github.com/alex/django-taggit/issues/155). diff --git a/awx/main/models/notifications.py b/awx/main/models/notifications.py new file mode 100644 index 0000000000..e2539edc4b --- /dev/null +++ b/awx/main/models/notifications.py @@ -0,0 +1,37 @@ +# Copyright (c) 2016 Ansible, Inc. +# All Rights Reserved. + +import logging + +from django.db import models +from awx.main.models.base import * # noqa +from awx.main.notifications.email_backend import CustomEmailBackend +from awx.main.notifications.slack_backend import SlackBackend +from awx.main.notifications.twilio_backend import TwilioBackend + +# Django-JSONField +from jsonfield import JSONField + +logger = logging.getLogger('awx.main.models.notifications') + +class NotificationTemplate(CommonModel): + + NOTIFICATION_TYPES = [('email', _('Email'), CustomEmailBackend), + ('slack', _('Slack'), SlackBackend), + ('twilio', _('Twilio'), TwilioBackend)] + NOTIFICATION_TYPE_CHOICES = [(x[0], x[1]) for x in NOTIFICATION_TYPES] + CLASS_FOR_NOTIFICATION_TYPE = dict([(x[0], x[2]) for x in NOTIFICATION_TYPES]) + + class Meta: + app_label = 'main' + + notification_type = models.CharField( + max_length = 32, + choices=NOTIFICATION_TYPE_CHOICES, + ) + + notification_configuration = JSONField(blank=False) + + @property + def notification_class(self): + return CLASS_FOR_NOTIFICATION_TYPE[self.notification_type] diff --git a/awx/main/notifications/__init__.py b/awx/main/notifications/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/awx/main/notifications/email_backend.py b/awx/main/notifications/email_backend.py new file mode 100644 index 0000000000..47c6c6dd00 --- /dev/null +++ b/awx/main/notifications/email_backend.py @@ -0,0 +1,11 @@ +# Copyright (c) 2016 Ansible, Inc. +# All Rights Reserved. + +import logging + +from django.core.mail.backends.smtp import EmailBackend + +class CustomEmailBackend(EmailBackend): + + init_parameters = ("host", "port", "username", "password", + "use_tls", "use_ssl") diff --git a/awx/main/notifications/slack_backend.py b/awx/main/notifications/slack_backend.py new file mode 100644 index 0000000000..07c0f2c4fc --- /dev/null +++ b/awx/main/notifications/slack_backend.py @@ -0,0 +1,46 @@ +# Copyright (c) 2016 Ansible, Inc. +# All Rights Reserved. + +import logging +from slackclient import SlackClient + +from django.core.mail.backends.base import BaseEmailBackend + +logger = logging.getLogger('awx.main.notifications.slack_backend') + +class SlackBackend(BaseEmailBackend): + + init_parameters = ('token',) + + def __init__(self, token, fail_silently=False, **kwargs): + super(SlackBackend, self).__init__(fail_silently=fail_silently) + self.token = token + self.connection = None + + def open(self): + if self.connection is not None: + return False + self.connection = SlackClient(self.token) + if not self.connection.rtm_connect(): + if not self.fail_silently: + raise Exception("Slack Notification Token is invalid") + return True + + def close(self): + if self.connection is None: + return + self.connection = None + + def send_messages(self, messages): + if self.connection is None: + self.open() + sent_messages = 0 + for m in messages: + try: + self.connection.rtm_send_message(m.to, m.body) + sent_messages += 1 + except Exception as e: + if not self.fail_silently: + raise + logger.error("Exception sending messages: {}".format(e)) + return sent_messages diff --git a/awx/main/notifications/twilio_backend.py b/awx/main/notifications/twilio_backend.py new file mode 100644 index 0000000000..86d6829c09 --- /dev/null +++ b/awx/main/notifications/twilio_backend.py @@ -0,0 +1,42 @@ +# Copyright (c) 2016 Ansible, Inc. +# All Rights Reserved. + +import logging + +from twilio.rest import TwilioRestClient + +from django.core.mail.backends.base import BaseEmailBackend + +logger = logging.getLogger('awx.main.notifications.twilio_backend') + +class TwilioBackend(BaseEmailBackend): + + init_parameters = ('account_sid', 'account_token', 'from_phone',) + + def __init__(self, account_sid, account_token, from_phone, fail_silently=False, **kwargs): + super(TwilioBackend, self).__init__(fail_silently=fail_silently) + self.account_sid = account_sid + self.account_token = account_token + self.from_phone = from_phone + + def send_messages(self, messages): + sent_messages = 0 + try: + connection = TwilioRestClient(self.account_sid, self.account_token) + except Exception as e: + if not self.fail_silently: + raise + logger.error("Exception connecting to Twilio: {}".format(e)) + + for m in messages: + try: + connection.messages.create( + to=m.to, + from_=self.from_phone, + body=m.body) + sent_messages += 1 + except Exception as e: + if not self.fail_silently: + raise + logger.error("Exception sending messages: {}".format(e)) + return sent_messages diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 48857bc6d2..73942d9eec 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -114,9 +114,11 @@ requests==2.5.1 requests-oauthlib==0.5.0 simplejson==3.6.0 six==1.9.0 +slackclient==0.16 statsd==3.2.1 stevedore==1.3.0 suds==0.4 +twilio==4.9.1 warlock==1.1.0 wheel==0.24.0 wsgiref==0.1.2