From 9d6739045ab23e7a966faea3caf1e653f273d70b Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Thu, 11 Feb 2016 15:34:27 -0500 Subject: [PATCH] Implement irc notification backend --- awx/main/models/notifications.py | 4 +- awx/main/notifications/irc_backend.py | 93 +++++++++++++++++++++++++ awx/main/notifications/slack_backend.py | 2 +- requirements/requirements.txt | 1 + 4 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 awx/main/notifications/irc_backend.py diff --git a/awx/main/models/notifications.py b/awx/main/models/notifications.py index ede9e42795..4fc005256b 100644 --- a/awx/main/models/notifications.py +++ b/awx/main/models/notifications.py @@ -16,6 +16,7 @@ from awx.main.notifications.twilio_backend import TwilioBackend from awx.main.notifications.pagerduty_backend import PagerDutyBackend from awx.main.notifications.hipchat_backend import HipChatBackend from awx.main.notifications.webhook_backend import WebhookBackend +from awx.main.notifications.irc_backend import IrcBackend # Django-JSONField from jsonfield import JSONField @@ -31,7 +32,8 @@ class NotificationTemplate(CommonModel): ('twilio', _('Twilio'), TwilioBackend), ('pagerduty', _('Pagerduty'), PagerDutyBackend), ('hipchat', _('HipChat'), HipChatBackend), - ('webhook', _('Webhook'), WebhookBackend)] + ('webhook', _('Webhook'), WebhookBackend), + ('irc', _('IRC'), IrcBackend)] 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]) diff --git a/awx/main/notifications/irc_backend.py b/awx/main/notifications/irc_backend.py new file mode 100644 index 0000000000..2b0944b74a --- /dev/null +++ b/awx/main/notifications/irc_backend.py @@ -0,0 +1,93 @@ +# Copyright (c) 2016 Ansible, Inc. +# All Rights Reserved. + +import time +import ssl +import logging + +import irc.client + +from django.core.mail.backends.base import BaseEmailBackend + +logger = logging.getLogger('awx.main.notifications.irc_backend') + +class IrcBackend(BaseEmailBackend): + + init_parameters = {"server": {"label": "IRC Server Address", "type": "string"}, + "port": {"label": "IRC Server Port", "type": "int"}, + "nickname": {"label": "IRC Nick", "type": "string"}, + "password": {"label": "IRC Server Password", "type": "password"}, + "use_ssl": {"label": "SSL Connection", "type": "bool"}, + "targets": {"label": "Destination Channels or Users", "type": "list"}} + recipient_parameter = "targets" + sender_parameter = None + + def __init__(self, server, port, nickname, password, use_ssl, fail_silently=False, **kwargs): + super(IrcBackend, self).__init__(fail_silently=fail_silently) + self.server = server + self.port = port + self.nickname = nickname + self.password = password if password != "" else None + self.use_ssl = use_ssl + self.connection = None + + def open(self): + if self.connection is not None: + return False + if self.use_ssl: + connection_factory = irc.connection.Factory(wrapper=ssl.wrap_socket) + else: + connection_factory = irc.connection.Factory() + try: + self.reactor = irc.client.Reactor() + self.connection = self.reactor.server().connect( + self.server, + self.port, + self.nickname, + password=self.password, + connect_factory=connection_factory, + ) + except irc.client.ServerConnectionError as e: + logger.error("Exception connecting to irc server: {}".format(e)) + if not self.fail_silently: + raise + return True + + def close(self): + if self.connection is None: + return + self.connection = None + + def on_connect(self, connection, event): + for c in self.channels: + if irc.client.is_channel(c): + connection.join(c) + else: + for m in self.channels[c]: + connection.privmsg(c, m.subject) + self.channels_sent += 1 + + def on_join(self, connection, event): + for m in self.channels[event.target]: + connection.privmsg(event.target, m.subject) + self.channels_sent += 1 + + def send_messages(self, messages): + if self.connection is None: + self.open() + self.channels = {} + self.channels_sent = 0 + for m in messages: + for r in m.recipients(): + if r not in self.channels: + self.channels[r] = [] + self.channels[r].append(m) + self.connection.add_global_handler("welcome", self.on_connect) + self.connection.add_global_handler("join", self.on_join) + start_time = time.time() + process_time = time.time() + while self.channels_sent < len(self.channels) and (process_time-start_time) < 60: + self.reactor.process_once(0.1) + process_time = time.time() + self.reactor.disconnect_all() + return self.channels_sent diff --git a/awx/main/notifications/slack_backend.py b/awx/main/notifications/slack_backend.py index 950d5c2c6e..3bf4f32114 100644 --- a/awx/main/notifications/slack_backend.py +++ b/awx/main/notifications/slack_backend.py @@ -44,7 +44,7 @@ class SlackBackend(BaseEmailBackend): self.connection.rtm_send_message(r, m.body) sent_messages += 1 except Exception as e: + logger.error("Exception sending messages: {}".format(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 0f42f235a3..e8c62488f5 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -47,6 +47,7 @@ httplib2==0.9 idna==2.0 importlib==1.0.3 ipaddress==1.0.14 +irc==13.3.1 iso8601==0.1.10 isodate==0.5.1 jsonpatch==1.11