+
+
Zulip supports integration with Pingdom and can notify you of
+ uptime status changes from your Pingdom dashboard.
+
+
First, create the stream you'd like to use for Pingdom notifications,
+ and subscribe all interested parties to this stream. We
+ recommend the name pingdom.
+
+
{{ external_api_uri }}/v1/external/pingdom?api_key=abcdefgh&stream=pingdom
+
+
where api_key is the API key of your Zulip bot,
+ and stream is the stream name you want the
+ notifications sent to.
+
+
Next, under following url:
+
https://my.pingdom.com/reports/integration/settings
+
create your integration by clicking on Add Integration button and filling form as following:
+

+
+
Last, during creating or editing your check, scroll down to Connect Integrations
+ section and ensure your integration is checked
+
+
Congratulations! You're done!
Example Zulip notification
+ looks like this:
+
+

+
+
+
-
Zulip supports Pivotal Tracker integration and can notify you of changes
+
Zulip supports integration with Pivotal Tracker and can notify you of changes
to the stories in your Pivotal Tracker project.
First, create the stream you'd like to use for Pivotal Tracker
diff --git a/zerver/fixtures/pingdom/pingdom_http_up_to_down.json b/zerver/fixtures/pingdom/pingdom_http_up_to_down.json
new file mode 100644
index 0000000000..871171e574
--- /dev/null
+++ b/zerver/fixtures/pingdom/pingdom_http_up_to_down.json
@@ -0,0 +1,34 @@
+{
+ "check_id": 2048467,
+ "check_name": "Test check",
+ "check_type": "HTTP",
+ "check_params": {
+ "basic_auth": false,
+ "encryption": false,
+ "full_url": "http:\/\/someurl.com\/",
+ "header": "User-Agent:Pingdom.com_bot_version_1.4_(http:\/\/www.pingdom.com\/)",
+ "hostname": "someurl.com",
+ "ipv6": false,
+ "port": 80,
+ "url": "\/"
+ },
+ "tags": [
+
+ ],
+ "previous_state": "UP",
+ "current_state": "DOWN",
+ "state_changed_timestamp": 1457939434,
+ "state_changed_utc_time": "2016-03-14T07:10:34",
+ "long_description": "Non-recoverable failure in name resolution",
+ "description": "DNS error",
+ "first_probe": {
+ "ip": "85.17.156.99",
+ "ipv6": "2001:1af8:4100:a09b::454",
+ "location": "Amsterdam 5, Netherlands"
+ },
+ "second_probe": {
+ "ip": "64.141.100.136",
+ "ipv6": "",
+ "location": "Calgary, Canada"
+ }
+}
diff --git a/zerver/fixtures/pingdom/pingdom_imap_down_to_up.json b/zerver/fixtures/pingdom/pingdom_imap_down_to_up.json
new file mode 100644
index 0000000000..bf7c4e6350
--- /dev/null
+++ b/zerver/fixtures/pingdom/pingdom_imap_down_to_up.json
@@ -0,0 +1,25 @@
+{
+ "check_id": 2051859,
+ "check_name": "IMAP check",
+ "check_type": "IMAP",
+ "check_params": {
+ "basic_auth": false,
+ "encryption": true,
+ "hostname": "imap.someurl.com",
+ "ipv6": false,
+ "port": 993
+ },
+ "tags": [],
+ "previous_state": "DOWN",
+ "current_state": "UP",
+ "state_changed_timestamp": 1458070380,
+ "state_changed_utc_time": "2016-03-15T19:33:00",
+ "long_description": "OK",
+ "description": "OK",
+ "first_probe": {
+ "ip": "69.64.56.47",
+ "ipv6": "",
+ "location": "St. Louis, MO"
+ },
+ "second_probe": {}
+}
diff --git a/zerver/fixtures/pingdom/pingdom_imap_up_to_down.json b/zerver/fixtures/pingdom/pingdom_imap_up_to_down.json
new file mode 100644
index 0000000000..a7be53f9e7
--- /dev/null
+++ b/zerver/fixtures/pingdom/pingdom_imap_up_to_down.json
@@ -0,0 +1,29 @@
+{
+ "check_id": 2051859,
+ "check_name": "IMAP check",
+ "check_type": "IMAP",
+ "check_params": {
+ "basic_auth": false,
+ "encryption": true,
+ "hostname": "imap.someurl.com",
+ "ipv6": false,
+ "port": 993
+ },
+ "tags": [],
+ "previous_state": "UP",
+ "current_state": "DOWN",
+ "state_changed_timestamp": 1458069480,
+ "state_changed_utc_time": "2016-03-15T19:18:00",
+ "long_description": "Invalid hostname, address or socket",
+ "description": "Unknown target",
+ "first_probe": {
+ "ip": "188.138.118.184",
+ "ipv6": "",
+ "location": "Strasbourg 2, France"
+ },
+ "second_probe": {
+ "ip": "76.164.194.74",
+ "ipv6": "2605:6f80:0:c::449",
+ "location": "Las Vegas 2, NV"
+ }
+}
diff --git a/zerver/fixtures/pingdom/pingdom_smtp_up_to_down.json b/zerver/fixtures/pingdom/pingdom_smtp_up_to_down.json
new file mode 100644
index 0000000000..e15ad02811
--- /dev/null
+++ b/zerver/fixtures/pingdom/pingdom_smtp_up_to_down.json
@@ -0,0 +1,29 @@
+{
+ "check_id": 2051844,
+ "check_name": "SMTP check",
+ "check_type": "SMTP",
+ "check_params": {
+ "basic_auth": false,
+ "encryption": false,
+ "hostname": "smtp.someurl.com",
+ "ipv6": false,
+ "port": 25
+ },
+ "tags": [],
+ "previous_state": "UP",
+ "current_state": "DOWN",
+ "state_changed_timestamp": 1458068544,
+ "state_changed_utc_time": "2016-03-15T19:02:24",
+ "long_description": "Connection refused",
+ "description": "Connection refused",
+ "first_probe": {
+ "ip": "174.34.162.242",
+ "ipv6": "",
+ "location": "Atlanta, GA"
+ },
+ "second_probe": {
+ "ip": "64.141.100.136",
+ "ipv6": "",
+ "location": "Calgary, Canada"
+ }
+}
diff --git a/zerver/test_hooks.py b/zerver/test_hooks.py
index a5ef0ef3d2..f19e4de157 100644
--- a/zerver/test_hooks.py
+++ b/zerver/test_hooks.py
@@ -864,3 +864,71 @@ class TravisHookTests(AuthedTestCase):
u"Details: [changes](https://github.com/hl7-fhir/fhir-sv"
u"n/compare/6dccb98bcfd9...6c457d366a31), [build log](ht"
u"tps://travis-ci.org/hl7-fhir/fhir-svn/builds/92495257)"))
+
+
+class PingdomHookTests(AuthedTestCase):
+ STREAM_NAME = 'pingdom'
+ TEST_USER_EMAIL = 'hamlet@zulip.com'
+ URL_TEMPLATE = "/api/v1/external/pingdom?stream={stream}&api_key={api_key}"
+
+ def setUp(self):
+ api_key = self.get_api_key(self.TEST_USER_EMAIL)
+ self._url = self.URL_TEMPLATE.format(stream=self.STREAM_NAME, api_key=api_key)
+ self.subscribe_to_stream(self.TEST_USER_EMAIL, self.STREAM_NAME)
+
+ def test_pingdom_from_up_to_down_http_check_message(self):
+ """
+ Tests if pingdom http check from up to down is handled correctly
+ """
+ body = self._get_fixture_data('http_up_to_down')
+ self._send_post_request_with_params(body)
+
+ expected_message = u"Service someurl.com changed its HTTP status from UP to DOWN.\nDescription: Non-recoverable failure in name resolution."
+ msg = self._get_recently_added_message()
+ self.assertEqual(msg.subject, u"Test check status.")
+ self.assertEqual(msg.content, expected_message)
+
+ def test_pingdom_from_up_to_down_smtp_check_message(self):
+ """
+ Tests if pingdom smtp check from up to down is handled correctly
+ """
+ body = self._get_fixture_data('smtp_up_to_down')
+ self._send_post_request_with_params(body)
+
+ expected_message = u"Service smtp.someurl.com changed its SMTP status from UP to DOWN.\nDescription: Connection refused."
+ msg = self._get_recently_added_message()
+ self.assertEqual(msg.subject, u"SMTP check status.")
+ self.assertEqual(msg.content, expected_message)
+
+ def test_pingdom_from_up_to_down_imap_check_message(self):
+ """
+ Tests if pingdom imap check from up to down is handled correctly
+ """
+ body = self._get_fixture_data('imap_up_to_down')
+ self._send_post_request_with_params(body)
+
+ expected_message = u"Service imap.someurl.com changed its IMAP status from UP to DOWN.\nDescription: Invalid hostname, address or socket."
+ msg = self._get_recently_added_message()
+ self.assertEqual(msg.subject, u"IMAP check status.")
+ self.assertEqual(msg.content, expected_message)
+
+ def test_pingdom_from_down_to_up_imap_check_message(self):
+ """
+ Tests if pingdom imap check from down to up is handled correctly
+ """
+ body = self._get_fixture_data('imap_down_to_up')
+ self._send_post_request_with_params(body)
+
+ expected_message = u"Service imap.someurl.com changed its IMAP status from DOWN to UP."
+ msg = self._get_recently_added_message()
+ self.assertEqual(msg.subject, u"IMAP check status.")
+ self.assertEqual(msg.content, expected_message)
+
+ def _get_recently_added_message(self):
+ return Message.objects.filter().order_by('-id')[0]
+
+ def _get_fixture_data(self, name):
+ return ujson.dumps(ujson.loads(self.fixture_data('pingdom', name)))
+
+ def _send_post_request_with_params(self, json):
+ return self.client.post(self._url, json, stream_name=self.STREAM_NAME, content_type="application/json")
diff --git a/zerver/views/webhooks/pingdom.py b/zerver/views/webhooks/pingdom.py
new file mode 100644
index 0000000000..a4d1e850de
--- /dev/null
+++ b/zerver/views/webhooks/pingdom.py
@@ -0,0 +1,68 @@
+# Webhooks for external integrations.
+from __future__ import absolute_import
+from zerver.models import get_client
+from zerver.lib.actions import check_send_message
+from zerver.lib.response import json_success, json_error
+from zerver.decorator import REQ, has_request_variables, api_key_only_webhook_view
+
+import ujson
+
+
+PINGDOM_SUBJECT_TEMPLATE = '{name} status.'
+PINGDOM_MESSAGE_TEMPLATE = 'Service {service_url} changed its {type} status from {previous_state} to {current_state}.'
+PINGDOM_MESSAGE_DESCRIPTION_TEMPLATE = 'Description: {description}.'
+
+
+SUPPORTED_CHECK_TYPES = (
+ 'HTTP',
+ 'HTTP_CUSTOM'
+ 'HTTPS',
+ 'SMTP',
+ 'POP3',
+ 'IMAP',
+ 'PING',
+ 'DNS',
+ 'UDP',
+ 'PORT_TCP',
+)
+
+
+@api_key_only_webhook_view
+@has_request_variables
+def api_pingdom_webhook(request, user_profile, stream=REQ(default='pingdom')):
+ payload = ujson.loads(request.body)
+ check_type = get_check_type(payload)
+
+ if check_type in SUPPORTED_CHECK_TYPES:
+ subject = get_subject_for_http_request(payload)
+ body = get_body_for_http_request(payload)
+ else:
+ return json_error('Unsupported check_type: {check_type}'.format(check_type=check_type))
+
+ check_send_message(user_profile, get_client('ZulipPingdomWebhook'), 'stream', [stream], subject, body)
+ return json_success()
+
+
+def get_subject_for_http_request(payload):
+ return PINGDOM_SUBJECT_TEMPLATE.format(name=payload['check_name'])
+
+
+def get_body_for_http_request(payload):
+ current_state = payload['current_state']
+ previous_state = payload['previous_state']
+
+ data = {
+ 'service_url': payload['check_params']['hostname'],
+ 'previous_state': previous_state,
+ 'current_state': current_state,
+ 'type': get_check_type(payload)
+ }
+ body = PINGDOM_MESSAGE_TEMPLATE.format(**data)
+ if current_state == 'DOWN' and previous_state == 'UP':
+ description = PINGDOM_MESSAGE_DESCRIPTION_TEMPLATE.format(description=payload['long_description'])
+ body += '\n{description}'.format(description=description)
+ return body
+
+
+def get_check_type(payload):
+ return payload['check_type']
diff --git a/zproject/urls.py b/zproject/urls.py
index ec32d448c3..f6131ab096 100644
--- a/zproject/urls.py
+++ b/zproject/urls.py
@@ -161,6 +161,7 @@ urlpatterns += patterns('zerver.views',
url(r'^api/v1/external/zendesk$', 'webhooks.zendesk.api_zendesk_webhook'),
url(r'^api/v1/external/pagerduty$', 'webhooks.pagerduty.api_pagerduty_webhook'),
url(r'^api/v1/external/travis$', 'webhooks.travis.api_travis_webhook'),
+ url(r'^api/v1/external/pingdom$', 'webhooks.pingdom.api_pingdom_webhook'),
url(r'^user_uploads/(?P(\d*|unk))/(?P.*)', 'get_uploaded_file'),
)