Add pagerduty webhook

(imported from commit 06219066b423f53d08094f3f97a98016e145b2a7)
This commit is contained in:
Jason Michalski
2015-02-07 14:29:36 -08:00
parent 53b02a694a
commit f4239d60ca
8 changed files with 552 additions and 0 deletions

View File

@@ -0,0 +1,162 @@
{
"messages": [
{
"created_on": "2015-02-07T21:09:49Z",
"data": {
"incident": {
"acknowledgers": [
{
"at": "2015-02-07T21:09:49Z",
"object": {
"email": "armooo@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/POBCFRJ",
"id": "POBCFRJ",
"name": "Jason MIchalski",
"type": "user"
}
}
],
"assigned_to": [
{
"at": "2015-02-07T21:08:36Z",
"object": {
"email": "armooo+2@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/PJ9ODL1",
"id": "PJ9ODL1",
"name": "Armooo2",
"type": "user"
}
},
{
"at": "2015-02-07T21:09:49Z",
"object": {
"email": "armooo@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/POBCFRJ",
"id": "POBCFRJ",
"name": "Jason MIchalski",
"type": "user"
}
}
],
"assigned_to_user": {
"email": "armooo@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/POBCFRJ",
"id": "POBCFRJ",
"name": "Jason MIchalski"
},
"created_on": "2015-02-07T21:05:47Z",
"escalation_policy": {
"deleted_at": null,
"id": "PUIGL4T",
"name": "Default"
},
"html_url": "https://zulip-test.pagerduty.com/incidents/PO1XIJ5",
"id": "PO1XIJ5",
"incident_key": "It is on fire",
"incident_number": 1,
"last_status_change_by": {
"email": "armooo@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/POBCFRJ",
"id": "POBCFRJ",
"name": "Jason MIchalski"
},
"last_status_change_on": "2015-02-07T21:09:49Z",
"number_of_escalations": 0,
"service": {
"deleted_at": null,
"html_url": "https://zulip-test.pagerduty.com/services/PIL5CUQ",
"id": "PIL5CUQ",
"name": "Test service"
},
"status": "acknowledged",
"trigger_details_html_url": "https://zulip-test.pagerduty.com/incidents/PO1XIJ5/log_entries/Q01ZH27LNRIAUB",
"trigger_summary_data": {
"subject": "It is on fire"
},
"trigger_type": "email_trigger"
}
},
"id": "a710cfd0-af0d-11e4-8aca-f23c91739642",
"type": "incident.assign"
},
{
"created_on": "2015-02-07T21:09:49Z",
"data": {
"incident": {
"acknowledgers": [
{
"at": "2015-02-07T21:09:49Z",
"object": {
"email": "armooo@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/POBCFRJ",
"id": "POBCFRJ",
"name": "Jason MIchalski",
"type": "user"
}
}
],
"assigned_to": [
{
"at": "2015-02-07T21:08:36Z",
"object": {
"email": "armooo+2@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/PJ9ODL1",
"id": "PJ9ODL1",
"name": "Armooo2",
"type": "user"
}
},
{
"at": "2015-02-07T21:09:49Z",
"object": {
"email": "armooo@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/POBCFRJ",
"id": "POBCFRJ",
"name": "Jason MIchalski",
"type": "user"
}
}
],
"assigned_to_user": {
"email": "armooo@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/POBCFRJ",
"id": "POBCFRJ",
"name": "Jason MIchalski"
},
"created_on": "2015-02-07T21:05:47Z",
"escalation_policy": {
"deleted_at": null,
"id": "PUIGL4T",
"name": "Default"
},
"html_url": "https://zulip-test.pagerduty.com/incidents/PO1XIJ5",
"id": "PO1XIJ5",
"incident_key": "It is on fire",
"incident_number": 1,
"last_status_change_by": {
"email": "armooo@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/POBCFRJ",
"id": "POBCFRJ",
"name": "Jason MIchalski"
},
"last_status_change_on": "2015-02-07T21:09:49Z",
"number_of_escalations": 0,
"service": {
"deleted_at": null,
"html_url": "https://zulip-test.pagerduty.com/services/PIL5CUQ",
"id": "PIL5CUQ",
"name": "Test service"
},
"status": "acknowledged",
"trigger_details_html_url": "https://zulip-test.pagerduty.com/incidents/PO1XIJ5/log_entries/Q01ZH27LNRIAUB",
"trigger_summary_data": {
"subject": "It is on fire"
},
"trigger_type": "email_trigger"
}
},
"id": "a7169c30-af0d-11e4-8aca-f23c91739642",
"type": "incident.acknowledge"
}
]
}

View File

@@ -0,0 +1,41 @@
{
"messages": [
{
"created_on": "2015-02-07T21:29:42Z",
"data": {
"incident": {
"assigned_to": [],
"assigned_to_user": null,
"created_on": "2015-02-07T21:19:42Z",
"escalation_policy": {
"deleted_at": null,
"id": "PUIGL4T",
"name": "Default"
},
"html_url": "https://zulip-test.pagerduty.com/incidents/PX7K9J2",
"id": "PX7K9J2",
"incident_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"incident_number": 2,
"last_status_change_by": null,
"last_status_change_on": "2015-02-07T21:29:42Z",
"number_of_escalations": 0,
"resolved_by_user": null,
"service": {
"deleted_at": null,
"html_url": "https://zulip-test.pagerduty.com/services/PIL5CUQ",
"id": "PIL5CUQ",
"name": "Test service"
},
"status": "resolved",
"trigger_details_html_url": "https://zulip-test.pagerduty.com/incidents/PX7K9J2/log_entries/Q2LOVDWN1FGDPC",
"trigger_summary_data": {
"subject": "new"
},
"trigger_type": "web_trigger"
}
},
"id": "6e0d4b20-af10-11e4-8aca-f23c91739642",
"type": "incident.resolve"
}
]
}

View File

@@ -0,0 +1,51 @@
{
"messages": [
{
"created_on": "2015-02-07T21:31:53Z",
"data": {
"incident": {
"assigned_to": [],
"assigned_to_user": null,
"created_on": "2015-02-07T21:05:47Z",
"escalation_policy": {
"deleted_at": null,
"id": "PUIGL4T",
"name": "Default"
},
"html_url": "https://zulip-test.pagerduty.com/incidents/PO1XIJ5",
"id": "PO1XIJ5",
"incident_key": "It is on fire",
"incident_number": 1,
"last_status_change_by": {
"email": "armooo@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/POBCFRJ",
"id": "POBCFRJ",
"name": "Jason MIchalski"
},
"last_status_change_on": "2015-02-07T21:31:53Z",
"number_of_escalations": 0,
"resolved_by_user": {
"email": "armooo@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/POBCFRJ",
"id": "POBCFRJ",
"name": "Jason MIchalski"
},
"service": {
"deleted_at": null,
"html_url": "https://zulip-test.pagerduty.com/services/PIL5CUQ",
"id": "PIL5CUQ",
"name": "Test service"
},
"status": "resolved",
"trigger_details_html_url": "https://zulip-test.pagerduty.com/incidents/PO1XIJ5/log_entries/Q01ZH27LNRIAUB",
"trigger_summary_data": {
"subject": "It is on fire"
},
"trigger_type": "email_trigger"
}
},
"id": "bc3d9ed0-af10-11e4-947f-22000ad9bf74",
"type": "incident.resolve"
}
]
}

View File

@@ -0,0 +1,56 @@
{
"messages": [
{
"created_on": "2015-02-07T21:42:52Z",
"data": {
"incident": {
"assigned_to": [
{
"at": "2015-02-07T21:42:52Z",
"object": {
"email": "armooo@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/POBCFRJ",
"id": "POBCFRJ",
"name": "Jason Michalski",
"type": "user"
}
}
],
"assigned_to_user": {
"email": "armooo@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/POBCFRJ",
"id": "POBCFRJ",
"name": "Jason Michalski"
},
"created_on": "2015-02-07T21:42:52Z",
"escalation_policy": {
"deleted_at": null,
"id": "PUIGL4T",
"name": "Default"
},
"html_url": "https://zulip-test.pagerduty.com/incidents/P140S4Y",
"id": "P140S4Y",
"incident_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"incident_number": 3,
"last_status_change_by": null,
"last_status_change_on": "2015-02-07T21:42:52Z",
"number_of_escalations": 0,
"service": {
"deleted_at": null,
"html_url": "https://zulip-test.pagerduty.com/services/PIL5CUQ",
"id": "PIL5CUQ",
"name": "Test service"
},
"status": "triggered",
"trigger_details_html_url": "https://zulip-test.pagerduty.com/incidents/P140S4Y/log_entries/Q3P8OPKZDJWLKB",
"trigger_summary_data": {
"subject": "foo"
},
"trigger_type": "web_trigger"
}
},
"id": "44df6240-af12-11e4-8e1e-22000ae31361",
"type": "incident.trigger"
}
]
}

View File

@@ -0,0 +1,56 @@
{
"messages": [
{
"created_on": "2015-02-07T21:53:09Z",
"data": {
"incident": {
"assigned_to": [
{
"at": "2015-02-07T21:42:52Z",
"object": {
"email": "armooo@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/POBCFRJ",
"id": "POBCFRJ",
"name": "Jason MIchalski",
"type": "user"
}
}
],
"assigned_to_user": {
"email": "armooo@zulip.com",
"html_url": "https://zulip-test.pagerduty.com/users/POBCFRJ",
"id": "POBCFRJ",
"name": "Jason MIchalski"
},
"created_on": "2015-02-07T21:42:52Z",
"escalation_policy": {
"deleted_at": null,
"id": "PUIGL4T",
"name": "Default"
},
"html_url": "https://zulip-test.pagerduty.com/incidents/P140S4Y",
"id": "P140S4Y",
"incident_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"incident_number": 3,
"last_status_change_by": null,
"last_status_change_on": "2015-02-07T21:53:09Z",
"number_of_escalations": 0,
"service": {
"deleted_at": null,
"html_url": "https://zulip-test.pagerduty.com/services/PIL5CUQ",
"id": "PIL5CUQ",
"name": "Test service"
},
"status": "triggered",
"trigger_details_html_url": "https://zulip-test.pagerduty.com/incidents/P140S4Y/log_entries/Q3P8OPKZDJWLKB",
"trigger_summary_data": {
"subject": "foo"
},
"trigger_type": "web_trigger"
}
},
"id": "b4695980-af13-11e4-8a3a-12313f0a2181",
"type": "incident.unacknowledge"
}
]
}

View File

@@ -734,3 +734,82 @@ class ZenDeskHookTests(AuthedTestCase):
msg = self.generate_webhook_response(message='New comment:\n> It is better\n* here') msg = self.generate_webhook_response(message='New comment:\n> It is better\n* here')
self.assertEqual(msg.content, 'New comment:\n> It is better\n* here') self.assertEqual(msg.content, 'New comment:\n> It is better\n* here')
class PagerDutyHookTests(AuthedTestCase):
def send_webhook(self, data, stream_name):
email = 'hamlet@zulip.com'
self.subscribe_to_stream(email, stream_name)
api_key = self.get_api_key(email)
url = '/api/v1/external/pagerduty?api_key=%s&stream=%s' % (api_key, stream_name)
result = self.client.post(url, ujson.dumps(data), content_type="application/json")
self.assert_json_success(result)
# Check the correct message was sent
msg = Message.objects.filter().order_by('-id')[0]
self.assertEqual(msg.sender.email, email)
return msg
def test_trigger(self):
data = ujson.loads(self.fixture_data('pagerduty', 'trigger'))
msg = self.send_webhook(data, 'pagerduty')
self.assertEqual(msg.subject, 'incident 3')
self.assertEqual(
msg.content,
':unhealthy_heart: Incident [3](https://zulip-test.pagerduty.com/incidents/P140S4Y) triggered by [Test service](https://zulip-test.pagerduty.com/services/PIL5CUQ) and assigned to [armooo@](https://zulip-test.pagerduty.com/users/POBCFRJ)\n\n>foo'
)
def test_unacknowledge(self):
data = ujson.loads(self.fixture_data('pagerduty', 'unacknowledge'))
msg = self.send_webhook(data, 'pagerduty')
self.assertEqual(msg.subject, 'incident 3')
self.assertEqual(
msg.content,
':unhealthy_heart: Incident [3](https://zulip-test.pagerduty.com/incidents/P140S4Y) unacknowledged by [Test service](https://zulip-test.pagerduty.com/services/PIL5CUQ) and assigned to [armooo@](https://zulip-test.pagerduty.com/users/POBCFRJ)\n\n>foo'
)
def test_resolved(self):
data = ujson.loads(self.fixture_data('pagerduty', 'resolved'))
msg = self.send_webhook(data, 'pagerduty')
self.assertEqual(msg.subject, 'incident 1')
self.assertEqual(
msg.content,
':healthy_heart: Incident [1](https://zulip-test.pagerduty.com/incidents/PO1XIJ5) resolved by [armooo@](https://zulip-test.pagerduty.com/users/POBCFRJ)\n\n>It is on fire'
)
def test_auto_resolved(self):
data = ujson.loads(self.fixture_data('pagerduty', 'auto_resolved'))
msg = self.send_webhook(data, 'pagerduty')
self.assertEqual(msg.subject, 'incident 2')
self.assertEqual(
msg.content,
':healthy_heart: Incident [2](https://zulip-test.pagerduty.com/incidents/PX7K9J2) resolved\n\n>new'
)
def test_acknowledge(self):
data = ujson.loads(self.fixture_data('pagerduty', 'acknowledge'))
msg = self.send_webhook(data, 'pagerduty')
self.assertEqual(msg.subject, 'incident 1')
self.assertEqual(
msg.content,
':average_heart: Incident [1](https://zulip-test.pagerduty.com/incidents/PO1XIJ5) acknowledged by [armooo@](https://zulip-test.pagerduty.com/users/POBCFRJ)\n\n>It is on fire'
)
def test_bad_message(self):
data = {'messages': [{'type': 'incident.triggered'}]}
msg = self.send_webhook(data, 'pagerduty')
self.assertEqual(msg.subject, 'pagerduty')
self.assertEqual(
msg.content,
'Unknown pagerdudy message\n``` py\n{u\'type\': u\'incident.triggered\'}\n```'
)
def test_unknown_message_type(self):
data = {'messages': [{'type': 'foo'}]}
msg = self.send_webhook(data, 'pagerduty')
self.assertEqual(msg.subject, 'pagerduty')
self.assertEqual(
msg.content,
'Unknown pagerdudy message\n``` py\n{u\'type\': u\'foo\'}\n```'
)

View File

@@ -16,6 +16,7 @@ from django.db.models import Q
from defusedxml.ElementTree import fromstring as xml_fromstring from defusedxml.ElementTree import fromstring as xml_fromstring
import pprint
import base64 import base64
import logging import logging
import re import re
@@ -914,3 +915,108 @@ def api_zendesk_webhook(request, user_profile):
check_send_message(user_profile, get_client('ZulipZenDeskWebhook'), 'stream', check_send_message(user_profile, get_client('ZulipZenDeskWebhook'), 'stream',
[stream], subject, message) [stream], subject, message)
return json_success() return json_success()
PAGER_DUTY_EVENT_NAMES = {
'incident.trigger': 'triggered',
'incident.acknowledge': 'acknowledged',
'incident.unacknowledge': 'unacknowledged',
'incident.resolve': 'resolved',
'incident.assign': 'assigned',
'incident.escalate': 'escalated',
'incident.delegate': 'delineated',
}
def build_pagerdudy_formatdict(message):
# Normalize the message dict, after this all keys will exist. I would
# rather some strange looking messages than dropping pages.
format_dict = {}
format_dict['action'] = PAGER_DUTY_EVENT_NAMES[message['type']]
format_dict['incident_id'] = message['data']['incident']['id']
format_dict['incident_num'] = message['data']['incident']['incident_number']
format_dict['incident_url'] = message['data']['incident']['html_url']
format_dict['service_name'] = message['data']['incident']['service']['name']
format_dict['service_url'] = message['data']['incident']['service']['html_url']
# This key can be missing on null
if message['data']['incident'].get('assigned_to_user', None):
format_dict['assigned_to_email'] = message['data']['incident']['assigned_to_user']['email']
format_dict['assigned_to_username'] = message['data']['incident']['assigned_to_user']['email'].split('@')[0]
format_dict['assigned_to_url'] = message['data']['incident']['assigned_to_user']['html_url']
else:
format_dict['assigned_to_email'] = 'nobody'
format_dict['assigned_to_username'] = 'nobody'
format_dict['assigned_to_url'] = ''
# This key can be missing on null
if message['data']['incident'].get('resolved_by_user', None):
format_dict['resolved_by_email'] = message['data']['incident']['resolved_by_user']['email']
format_dict['resolved_by_username'] = message['data']['incident']['resolved_by_user']['email'].split('@')[0]
format_dict['resolved_by_url'] = message['data']['incident']['resolved_by_user']['html_url']
else:
format_dict['resolved_by_email'] = 'nobody'
format_dict['resolved_by_username'] = 'nobody'
format_dict['resolved_by_url'] = ''
format_dict['trigger_subject'] = message['data']['incident']['trigger_summary_data']['subject']
return format_dict
def send_raw_pagerduty_json(user_profile, stream, message):
subject = 'pagerduty'
body = (
'Unknown pagerdudy message\n'
'``` py\n'
'%s\n'
'```') % (pprint.pformat(message),)
check_send_message(user_profile, get_client('ZulipPagerDutyWebhook'), 'stream',
[stream], subject, body)
def send_formated_pagerduty(user_profile, stream, message_type, format_dict):
if message_type in ('incident.trigger', 'incident.unacknowledge'):
template = (':unhealthy_heart: Incident '
'[{incident_num}]({incident_url}) {action} by '
'[{service_name}]({service_url}) and assigned to '
'[{assigned_to_username}@]({assigned_to_url})\n\n>{trigger_subject}')
elif message_type == 'incident.resolve' and format_dict['resolved_by_url']:
template = (':healthy_heart: Incident '
'[{incident_num}]({incident_url}) resolved by '
'[{resolved_by_username}@]({resolved_by_url})\n\n>{trigger_subject}')
elif message_type == 'incident.resolve' and not format_dict['resolved_by_url']:
template = (':healthy_heart: Incident '
'[{incident_num}]({incident_url}) resolved\n\n>{trigger_subject}')
else:
template = (':average_heart: Incident [{incident_num}]({incident_url}) '
'{action} by [{assigned_to_username}@]({assigned_to_url})\n\n>{trigger_subject}')
subject = 'incident {incident_num}'.format(**format_dict)
body = template.format(**format_dict)
check_send_message(user_profile, get_client('ZulipPagerDutyWebhook'), 'stream',
[stream], subject, body)
@api_key_only_webhook_view
@has_request_variables
def api_pagerduty_webhook(request, user_profile, stream=REQ(default='pagerduty')):
payload = ujson.loads(request.body)
for message in payload['messages']:
message_type = message['type']
if message_type not in PAGER_DUTY_EVENT_NAMES:
send_raw_pagerduty_json(user_profile, stream, message)
try:
format_dict = build_pagerdudy_formatdict(message)
except:
send_raw_pagerduty_json(user_profile, stream, message)
else:
send_formated_pagerduty(user_profile, stream, message_type, format_dict)
return json_success()

View File

@@ -164,6 +164,7 @@ urlpatterns += patterns('zerver.views',
url(r'^api/v1/external/stash$', 'webhooks.api_stash_webhook'), url(r'^api/v1/external/stash$', 'webhooks.api_stash_webhook'),
url(r'^api/v1/external/freshdesk$', 'webhooks.api_freshdesk_webhook'), url(r'^api/v1/external/freshdesk$', 'webhooks.api_freshdesk_webhook'),
url(r'^api/v1/external/zendesk$', 'webhooks.api_zendesk_webhook'), url(r'^api/v1/external/zendesk$', 'webhooks.api_zendesk_webhook'),
url(r'^api/v1/external/pagerduty$', 'webhooks.api_pagerduty_webhook'),
url(r'^user_uploads/(?P<realm_id>(\d*|unk))/(?P<filename>.*)', 'get_uploaded_file'), url(r'^user_uploads/(?P<realm_id>(\d*|unk))/(?P<filename>.*)', 'get_uploaded_file'),
) )