diff --git a/hc/api/admin.py b/hc/api/admin.py index 6538d083..b2642d30 100644 --- a/hc/api/admin.py +++ b/hc/api/admin.py @@ -153,6 +153,8 @@ class ChannelsAdmin(admin.ModelAdmin): def formatted_kind(self, obj): if obj.kind == "pd": return "PagerDuty" + elif obj.kind == "victorops": + return "VictorOps" elif obj.kind == "po": return "Pushover" elif obj.kind == "webhook": diff --git a/hc/api/migrations/0024_auto_20160203_2227.py b/hc/api/migrations/0024_auto_20160203_2227.py new file mode 100644 index 00000000..236a11dd --- /dev/null +++ b/hc/api/migrations/0024_auto_20160203_2227.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2016-02-03 22:27 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0023_auto_20160131_1919'), + ] + + operations = [ + migrations.AlterField( + model_name='channel', + name='kind', + field=models.CharField(choices=[(b'email', b'Email'), (b'webhook', b'Webhook'), (b'hipchat', b'HipChat'), (b'slack', b'Slack'), (b'pd', b'PagerDuty'), (b'po', b'Pushover'), (b'victorops', b'VictorOps')], max_length=20), + ), + ] diff --git a/hc/api/models.py b/hc/api/models.py index e35058d3..6edad11c 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -22,7 +22,8 @@ DEFAULT_TIMEOUT = td(days=1) DEFAULT_GRACE = td(hours=1) CHANNEL_KINDS = (("email", "Email"), ("webhook", "Webhook"), ("hipchat", "HipChat"), - ("slack", "Slack"), ("pd", "PagerDuty"), ("po", "Pushover")) + ("slack", "Slack"), ("pd", "PagerDuty"), ("po", "Pushover"), + ("victorops", "VictorOps")) PO_PRIORITIES = { -2: "lowest", @@ -140,6 +141,8 @@ class Channel(models.Model): return transports.HipChat(self) elif self.kind == "pd": return transports.PagerDuty(self) + elif self.kind == "victorops": + return transports.VictorOps(self) elif self.kind == "po": return transports.Pushover(self) else: diff --git a/hc/api/tests/test_notify.py b/hc/api/tests/test_notify.py index 3f63382c..29dbb478 100644 --- a/hc/api/tests/test_notify.py +++ b/hc/api/tests/test_notify.py @@ -152,3 +152,15 @@ class NotifyTestCase(BaseTestCase): args, kwargs = mock_post.call_args json = kwargs["data"] self.assertIn("DOWN", json["title"]) + + @patch("hc.api.transports.requests.request") + def test_victorops(self, mock_post): + self._setup_data("victorops", "123") + mock_post.return_value.status_code = 200 + + self.channel.notify(self.check) + assert Notification.objects.count() == 1 + + args, kwargs = mock_post.call_args + json = kwargs["json"] + self.assertEqual(json["message_type"], "CRITICAL") diff --git a/hc/api/transports.py b/hc/api/transports.py index 68cd4d50..8d05c5dd 100644 --- a/hc/api/transports.py +++ b/hc/api/transports.py @@ -152,3 +152,16 @@ class Pushover(HttpTransport): payload["expire"] = settings.PUSHOVER_EMERGENCY_EXPIRATION return self.post_form(self.URL, payload) + + +class VictorOps(HttpTransport): + def notify(self, check): + description = tmpl("victorops_description.html", check=check) + payload = { + "entity_id": str(check.code), + "message_type": "CRITICAL" if check.status == "down" else "RECOVERY", + "entity_display_name": description, + "monitoring_tool": "healthchecks.io", + } + + return self.post(self.channel.value, payload) diff --git a/hc/front/tests/test_add_channel.py b/hc/front/tests/test_add_channel.py index 106a2290..7b0807d6 100644 --- a/hc/front/tests/test_add_channel.py +++ b/hc/front/tests/test_add_channel.py @@ -40,7 +40,7 @@ class AddChannelTestCase(BaseTestCase): def test_instructions_work(self): self.client.login(username="alice@example.org", password="password") - for frag in ("email", "webhook", "pd", "pushover", "slack", "hipchat"): + for frag in ("email", "webhook", "pd", "pushover", "slack", "hipchat", "victorops"): url = "/integrations/add_%s/" % frag r = self.client.get(url) self.assertContains(r, "Integration Settings", status_code=200) diff --git a/hc/front/urls.py b/hc/front/urls.py index 235ce58f..155c8ee1 100644 --- a/hc/front/urls.py +++ b/hc/front/urls.py @@ -22,6 +22,7 @@ urlpatterns = [ url(r'^integrations/add_slack/$', views.add_slack, name="hc-add-slack"), url(r'^integrations/add_hipchat/$', views.add_hipchat, name="hc-add-hipchat"), url(r'^integrations/add_pushover/$', views.add_pushover, name="hc-add-pushover"), + url(r'^integrations/add_victorops/$', views.add_victorops, name="hc-add-victorops"), url(r'^integrations/([\w-]+)/checks/$', views.channel_checks, name="hc-channel-checks"), url(r'^integrations/([\w-]+)/remove/$', views.remove_channel, name="hc-remove-channel"), url(r'^integrations/([\w-]+)/verify/([\w-]+)/$', diff --git a/hc/front/views.py b/hc/front/views.py index 1936e54f..edf92b39 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -439,6 +439,11 @@ def add_pushover(request): } return render(request, "integrations/add_pushover.html", ctx) +@login_required +def add_victorops(request): + ctx = {"page": "channels"} + return render(request, "integrations/add_victorops.html", ctx) + def privacy(request): return render(request, "front/privacy.html", {}) diff --git a/static/img/integrations/setup_victorops_1.png b/static/img/integrations/setup_victorops_1.png new file mode 100644 index 00000000..14d79aa3 Binary files /dev/null and b/static/img/integrations/setup_victorops_1.png differ diff --git a/static/img/integrations/setup_victorops_2.png b/static/img/integrations/setup_victorops_2.png new file mode 100644 index 00000000..d1bd8c52 Binary files /dev/null and b/static/img/integrations/setup_victorops_2.png differ diff --git a/static/img/integrations/setup_victorops_3.png b/static/img/integrations/setup_victorops_3.png new file mode 100644 index 00000000..3eeb66d7 Binary files /dev/null and b/static/img/integrations/setup_victorops_3.png differ diff --git a/static/img/integrations/victorops.png b/static/img/integrations/victorops.png new file mode 100644 index 00000000..a219c15e Binary files /dev/null and b/static/img/integrations/victorops.png differ diff --git a/templates/front/channels.html b/templates/front/channels.html index 6035e01a..db5ce617 100644 --- a/templates/front/channels.html +++ b/templates/front/channels.html @@ -25,12 +25,14 @@ {% if ch.kind == "hipchat" %} HipChat {% endif %} {% if ch.kind == "pd" %} PagerDuty {% endif %} {% if ch.kind == "po" %} Pushover {% endif %} + {% if ch.kind == "victorops" %} VictorOps {% endif %} {% if ch.kind == "email" %} to {% endif %} {% if ch.kind == "pd" %} API key {% endif %} {% if ch.kind == "po" %} user key {% endif %} + {% if ch.kind == "victorops" %} Post URL {% endif %} {% if ch.kind == "po" %} @@ -131,6 +133,15 @@ Add Integration +
  • + VictorOp icon + +

    VictorOps

    +

    On-call scheduling, alerting, and incident tracking.

    + + Add Integration +
  • {% if enable_pushover %}
  • +
    +

    VictorOps

    + +

    VictorOps is + another incident management system similar to PagerDuty. + If you use or plan on using VitorOps, you can can integrate it + with your healthchecks.io account in few simple steps.

    + +

    Setup Guide

    +
    +
    + 1 +

    + Log into your VictorOps account, + go to Settings > Schedules, + and find or create the Team Schedule you + would like to use for healthchecks.io alerts. +

    +
    +
    + +
    +
    +
    +
    + 2 +

    + Make note of the routing key. If this team schedule + does not already have a routing key, + click Setup Routing + or go to Integrations + and scroll to the bottom to create a routing rule and routing key + for this team. +

    +
    +
    + +
    +
    +
    +
    + 3 + Go to Settings > Integrations + and click on REST Endpoint. + Make note of the Post URL. +
    +
    + +
    +
    + +
    +
    + 4 +

    Paste the Post URL from step 3 in the field below, being careful to replace $routing_key with your actual routing key from step 2. Save the integration, and it's done!

    +
    +
    + +

    Integration Settings

    + +
    + {% csrf_token %} + +
    + +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + + + + + + + +{% endblock %} + +{% block scripts %} +{% compress js %} + + +{% endcompress %} +{% endblock %} diff --git a/templates/integrations/victorops_description.html b/templates/integrations/victorops_description.html new file mode 100644 index 00000000..7326efa7 --- /dev/null +++ b/templates/integrations/victorops_description.html @@ -0,0 +1,5 @@ +{% if check.status == "down" %} + {{ check.name_then_code }} is DOWN +{% else %} + {{ check.name_then_code }} received a ping and is now UP +{% endif %} \ No newline at end of file