diff --git a/awx/main/models/notifications.py b/awx/main/models/notifications.py index b636b7f8d7..a803ffe6e9 100644 --- a/awx/main/models/notifications.py +++ b/awx/main/models/notifications.py @@ -20,6 +20,7 @@ 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.mattermost_backend import MattermostBackend +from awx.main.notifications.rocketchat_backend import RocketChatBackend from awx.main.notifications.irc_backend import IrcBackend from awx.main.fields import JSONField @@ -38,6 +39,7 @@ class NotificationTemplate(CommonModelNameNotUnique): ('hipchat', _('HipChat'), HipChatBackend), ('webhook', _('Webhook'), WebhookBackend), ('mattermost', _('Mattermost'), MattermostBackend), + ('rocketchat', _('Rocket.Chat'), RocketChatBackend), ('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/rocketchat_backend.py b/awx/main/notifications/rocketchat_backend.py new file mode 100644 index 0000000000..f316bf41c9 --- /dev/null +++ b/awx/main/notifications/rocketchat_backend.py @@ -0,0 +1,51 @@ +# Copyright (c) 2016 Ansible, Inc. +# All Rights Reserved. + +import logging +import requests +import json + +from django.utils.encoding import smart_text +from django.utils.translation import ugettext_lazy as _ +from awx.main.notifications.base import AWXBaseEmailBackend + +logger = logging.getLogger('awx.main.notifications.rocketchat_backend') + + +class RocketChatBackend(AWXBaseEmailBackend): + + init_parameters = {"rocketchat_url": {"label": "Target URL", "type": "string"}, + "rocketchat_no_verify_ssl": {"label": "Verify SSL", "type": "bool"}} + recipient_parameter = "rocketchat_url" + sender_parameter = None + + 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) + self.rocketchat_no_verify_ssl = rocketchat_no_verify_ssl + self.rocketchat_username = rocketchat_username + self.rocketchat_icon_url = rocketchat_icon_url + + def format_body(self, body): + return body + + def send_messages(self, messages): + sent_messages = 0 + for m in messages: + payload = {"text": m.subject} + for opt, optval in {'rocketchat_icon_url': 'icon_url', + 'rocketchat_username': 'username'}.iteritems(): + optvalue = getattr(self, opt) + if optvalue is not None: + payload[optval] = optvalue.strip() + + r = requests.post("{}".format(m.recipients()[0]), + data=json.dumps(payload), verify=(not self.rocketchat_no_verify_ssl)) + + if r.status_code >= 400: + logger.error(smart_text( + _("Error sending notification rocket.chat: {}").format(r.text))) + if not self.fail_silently: + raise Exception(smart_text( + _("Error sending notification rocket.chat: {}").format(r.text))) + sent_messages += 1 + return sent_messages diff --git a/awx/main/tests/unit/notifications/test_rocketchat.py b/awx/main/tests/unit/notifications/test_rocketchat.py new file mode 100644 index 0000000000..d621f74ba2 --- /dev/null +++ b/awx/main/tests/unit/notifications/test_rocketchat.py @@ -0,0 +1,40 @@ +import mock +from django.core.mail.message import EmailMessage + +import awx.main.notifications.rocketchat_backend as rocketchat_backend + + +def test_send_messages(): + with mock.patch('awx.main.notifications.rocketchat_backend.requests') as requests_mock: + backend = rocketchat_backend.RocketChatBackend() + message = EmailMessage('test subject', 'test body', [], ['http://example.com', ]) + sent_messages = backend.send_messages([message, ]) + requests_mock.post.assert_called_once_with('http://example.com', data='{"text": "test subject"}', verify=True) + assert sent_messages == 1 + + +def test_send_messages_with_username(): + with mock.patch('awx.main.notifications.rocketchat_backend.requests') as requests_mock: + backend = rocketchat_backend.RocketChatBackend(rocketchat_username='testuser') + message = EmailMessage('test subject', 'test body', [], ['http://example.com', ]) + sent_messages = backend.send_messages([message, ]) + requests_mock.post.assert_called_once_with('http://example.com', data='{"username": "testuser", "text": "test subject"}', verify=True) + assert sent_messages == 1 + + +def test_send_messages_with_icon_url(): + with mock.patch('awx.main.notifications.rocketchat_backend.requests') as requests_mock: + backend = rocketchat_backend.RocketChatBackend(rocketchat_icon_url='http://example.com') + message = EmailMessage('test subject', 'test body', [], ['http://example.com', ]) + sent_messages = backend.send_messages([message, ]) + requests_mock.post.assert_called_once_with('http://example.com', data='{"text": "test subject", "icon_url": "http://example.com"}', verify=True) + assert sent_messages == 1 + + +def test_send_messages_with_no_verify_ssl(): + with mock.patch('awx.main.notifications.rocketchat_backend.requests') as requests_mock: + backend = rocketchat_backend.RocketChatBackend(rocketchat_no_verify_ssl=True) + message = EmailMessage('test subject', 'test body', [], ['http://example.com', ]) + sent_messages = backend.send_messages([message, ]) + requests_mock.post.assert_called_once_with('http://example.com', data='{"text": "test subject"}', verify=False) + assert sent_messages == 1 diff --git a/awx/ui/client/src/notifications/notificationTemplates.form.js b/awx/ui/client/src/notifications/notificationTemplates.form.js index 6a9237d061..e2b15ef77b 100644 --- a/awx/ui/client/src/notifications/notificationTemplates.form.js +++ b/awx/ui/client/src/notifications/notificationTemplates.form.js @@ -372,6 +372,38 @@ export default ['i18n', function(i18n) { subForm: 'typeSubForm', ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' }, + rocketchat_url: { + label: i18n._('Target URL'), + type: 'text', + awRequiredWhen: { + reqExpression: "rocketchat_required", + init: "false" + }, + ngShow: "notification_type.value == 'rocketchat' ", + subForm: 'typeSubForm', + ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' + }, + rocketchat_username: { + label: i18n._('Username'), + type: 'text', + ngShow: "notification_type.value == 'rocketchat' ", + subForm: 'typeSubForm', + ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' + }, + rocketchat_icon_url: { + label: i18n._('Icon URL'), + type: 'text', + ngShow: "notification_type.value == 'rocketchat' ", + subForm: 'typeSubForm', + ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' + }, + rocketchat_no_verify_ssl: { + label: i18n._('Disable SSL Verification'), + type: 'checkbox', + ngShow: "notification_type.value == 'rocketchat' ", + subForm: 'typeSubForm', + ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' + }, server: { label: i18n._('IRC Server Address'), type: 'text', diff --git a/awx/ui/client/src/notifications/shared/type-change.service.js b/awx/ui/client/src/notifications/shared/type-change.service.js index dad2cf44db..997902959a 100644 --- a/awx/ui/client/src/notifications/shared/type-change.service.js +++ b/awx/ui/client/src/notifications/shared/type-change.service.js @@ -18,6 +18,7 @@ function (i18n) { obj.twilio_required = false; obj.webhook_required = false; obj.mattermost_required = false; + obj.rocketchat_required = false; obj.token_required = false; obj.port_required = false; obj.password_required = false; @@ -52,6 +53,9 @@ function (i18n) { case 'mattermost': obj.mattermost_required = true; break; + case 'rocketchat': + obj.rocketchat_required = true; + break; case 'pagerduty': obj.tokenLabel = ' ' + i18n._('API Token'); obj.pagerduty_required = true; diff --git a/docs/notification_system.md b/docs/notification_system.md index 43942db548..b2fcd0d662 100644 --- a/docs/notification_system.md +++ b/docs/notification_system.md @@ -36,6 +36,7 @@ The currently defined Notification Types are: * Slack * Hipchat * Mattermost +* Rocket.Chat * Pagerduty * Twilio * IRC @@ -126,6 +127,26 @@ In order to enable these settings in Mattermost: * Utilize an existing Mattermost installation or use their docker container here: `docker run --name mattermost-preview -d --publish 8065:8065 mattermost/mattermost-preview` * Turn on Incoming Webhooks and optionally allow Integrations to override usernames and icons in the System Console. +## Rocket.Chat + +The Rocket.Chat notification integration uses Incoming Webhooks. A password is not required because the webhook URL itself is the secret. An integration must be created in the Administration section of the Rocket.Chat settings. + +The following fields are available for the Rocket.Chat notification type: +* `url`: The incoming webhook URL that was configured in Rocket.Chat. Notifications will use this URL to POST. +* `username`: Optional. Change the displayed username from Rocket Cat to specified username +* `icon_url`: Optional. A URL pointing to an icon to use for the notification. + +### Testing considerations + +* Make sure that all options behave as expected +* Test that all notification options are obeyed + +### Test Service + +* Utilize an existing Rocket.Chat installation or use their docker containers from https://rocket.chat/docs/installation/docker-containers/ +* Create an Incoming Webhook in the Integrations section of the Administration settings + + ## Pagerduty Pager duty is a fairly straightforward integration. The user will create an API Key in the pagerduty system (this will be the token that is given to Tower) and then create a "Service" which will provide an "Integration Key" that will be given to Tower also. The other options of note are: