diff --git a/endpoints/notificationmethod.py b/endpoints/notificationmethod.py
index a6d958037..9650e79f6 100644
--- a/endpoints/notificationmethod.py
+++ b/endpoints/notificationmethod.py
@@ -5,6 +5,7 @@ import tarfile
import base64
import json
import requests
+import re
from flask.ext.mail import Message
from app import mail, app, get_app_url
@@ -302,3 +303,80 @@ class HipchatMethod(NotificationMethod):
return False
return True
+
+
+class SlackMethod(NotificationMethod):
+ """ Method for sending notifications to Slack via the API:
+ https://api.slack.com/docs/attachments
+ """
+ @classmethod
+ def method_name(cls):
+ return 'slack'
+
+ def validate(self, repository, config_data):
+ if not config_data.get('token', ''):
+ raise CannotValidateNotificationMethodException('Missing Slack Token')
+
+ if not config_data.get('subdomain', '').isalnum():
+ raise CannotValidateNotificationMethodException('Missing Slack Subdomain Name')
+
+ def formatForSlack(self, message):
+ message = message.replace('\n', '')
+ message = re.sub(r'\s+', ' ', message)
+ message = message.replace('
', '\n')
+ message = re.sub(r'(.+)', '<\\1|\\2>', message)
+ return message
+
+ def perform(self, notification, event_handler, notification_data):
+ config_data = json.loads(notification.config_json)
+
+ token = config_data.get('token', '')
+ subdomain = config_data.get('subdomain', '')
+
+ if not token or not subdomain:
+ return False
+
+ owner = model.get_user(notification.repository.namespace)
+ if not owner:
+ # Something went wrong.
+ return False
+
+ url = 'https://%s.slack.com/services/hooks/incoming-webhook?token=%s' % (subdomain, token)
+
+ level = event_handler.get_level(notification_data['event_data'], notification_data)
+ color = {
+ 'info': '#ffffff',
+ 'warning': 'warning',
+ 'error': 'danger',
+ 'primary': 'good'
+ }.get(level, '#ffffff')
+
+ summary = event_handler.get_summary(notification_data['event_data'], notification_data)
+ message = event_handler.get_message(notification_data['event_data'], notification_data)
+
+ headers = {'Content-type': 'application/json'}
+ payload = {
+ 'text': summary,
+ 'username': 'quayiobot',
+ 'attachments': [
+ {
+ 'fallback': summary,
+ 'text': self.formatForSlack(message),
+ 'color': color
+ }
+ ]
+ }
+
+ try:
+ resp = requests.post(url, data=json.dumps(payload), headers=headers)
+ if resp.status_code/100 != 2:
+ logger.error('%s response for Slack to url: %s' % (resp.status_code,
+ url))
+ logger.error(resp.content)
+ return False
+
+ except requests.exceptions.RequestException as ex:
+ logger.exception('Slack method was unable to be sent: %s' % ex.message)
+ return False
+
+ return True
diff --git a/initdb.py b/initdb.py
index 6b68cee85..7cadbee87 100644
--- a/initdb.py
+++ b/initdb.py
@@ -253,6 +253,7 @@ def initialize_database():
ExternalNotificationMethod.create(name='flowdock')
ExternalNotificationMethod.create(name='hipchat')
+ ExternalNotificationMethod.create(name='slack')
NotificationKind.create(name='repo_push')
NotificationKind.create(name='build_queued')
diff --git a/static/css/quay.css b/static/css/quay.css
index bd8f571e8..2a4696551 100644
--- a/static/css/quay.css
+++ b/static/css/quay.css
@@ -4573,6 +4573,13 @@ i.hipchat-icon {
height: 16px;
}
+i.slack-icon {
+ background-image: url(/static/img/slack.ico);
+ background-size: 16px;
+ width: 16px;
+ height: 16px;
+}
+
.external-notification-view-element {
margin: 10px;
padding: 6px;
diff --git a/static/img/slack.ico b/static/img/slack.ico
new file mode 100644
index 000000000..d32c25280
Binary files /dev/null and b/static/img/slack.ico differ
diff --git a/static/js/app.js b/static/js/app.js
index 3394543e7..c272b7662 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -1141,6 +1141,24 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
'title': 'Notification Token'
}
]
+ },
+ {
+ 'id': 'slack',
+ 'title': 'Slack Room Notification',
+ 'icon': 'slack-icon',
+ 'fields': [
+ {
+ 'name': 'subdomain',
+ 'type': 'string',
+ 'title': 'Slack Subdomain'
+ },
+ {
+ 'name': 'token',
+ 'type': 'string',
+ 'title': 'Token',
+ 'help_url': 'https://{subdomain}.slack.com/services/new/outgoing-webhook'
+ }
+ ]
}
];
diff --git a/test/data/test.db b/test/data/test.db
index a947e5964..ddfe7166b 100644
Binary files a/test/data/test.db and b/test/data/test.db differ