mirror of
https://github.com/healthchecks/healthchecks.git
synced 2025-04-15 09:24:11 +00:00
Implement PagerDuty Simple Install Flow
This commit is contained in:
parent
2cd2bfed6f
commit
fd7ab5e767
16 changed files with 291 additions and 11 deletions
|
@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
|
|||
- Improve the handling of unknown email addresses in the Sign In form
|
||||
- Add support for "... is UP" SMS notifications
|
||||
- Add an option for weekly reports (in addition to monthly)
|
||||
- Implement PagerDuty Simple Install Flow
|
||||
|
||||
## v1.20.0 - 2020-04-22
|
||||
|
||||
|
|
10
README.md
10
README.md
|
@ -371,6 +371,16 @@ MATRIX_USER_ID=@mychecks:matrix.org
|
|||
MATRIX_ACCESS_TOKEN=[a long string of characters returned by the login call]
|
||||
```
|
||||
|
||||
### PagerDuty Simple Install Flow
|
||||
|
||||
To enable PagerDuty [Simple Install Flow](https://developer.pagerduty.com/docs/app-integration-development/events-integration/),
|
||||
|
||||
* Register a PagerDuty app at [PagerDuty](https://pagerduty.com/) › Developer Mode › My Apps
|
||||
* In the newly created app, add the "Events Integration" functionality
|
||||
* Specify a Redirect URL: `https://your-domain.com/integrations/add_pagerduty/`
|
||||
* Copy the displayed app_id value (PXXXXX) and put it in the `PD_APP_ID` environment
|
||||
variable
|
||||
|
||||
## Running in Production
|
||||
|
||||
Here is a non-exhaustive list of pointers and things to check before launching a Healthchecks instance
|
||||
|
|
|
@ -29,6 +29,7 @@ MATTERMOST_ENABLED=True
|
|||
MSTEAMS_ENABLED=True
|
||||
OPSGENIE_ENABLED=True
|
||||
PAGERTREE_ENABLED=True
|
||||
PD_APP_ID=
|
||||
PD_ENABLED=True
|
||||
PD_VENDOR_KEY=
|
||||
PING_BODY_LIMIT=10000
|
||||
|
|
|
@ -650,12 +650,19 @@ class Channel(models.Model):
|
|||
doc = json.loads(self.value)
|
||||
return doc["service_key"]
|
||||
|
||||
@property
|
||||
def pd_service_name(self):
|
||||
assert self.kind == "pd"
|
||||
if self.value.startswith("{"):
|
||||
doc = json.loads(self.value)
|
||||
return doc.get("name")
|
||||
|
||||
@property
|
||||
def pd_account(self):
|
||||
assert self.kind == "pd"
|
||||
if self.value.startswith("{"):
|
||||
doc = json.loads(self.value)
|
||||
return doc["account"]
|
||||
return doc.get("account")
|
||||
|
||||
def latest_notification(self):
|
||||
return Notification.objects.filter(channel=self).latest()
|
||||
|
|
63
hc/front/tests/test_add_pagerduty_complete.py
Normal file
63
hc/front/tests/test_add_pagerduty_complete.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
import json
|
||||
|
||||
from django.test.utils import override_settings
|
||||
from hc.api.models import Channel
|
||||
from hc.test import BaseTestCase
|
||||
from urllib.parse import urlencode
|
||||
|
||||
|
||||
@override_settings(PD_APP_ID="FOOBAR")
|
||||
class AddPagerDutyCompleteTestCase(BaseTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
session = self.client.session
|
||||
session["pagerduty"] = ("ABC", str(self.project.code))
|
||||
session.save()
|
||||
|
||||
def _url(self, state="ABC"):
|
||||
config = {
|
||||
"account": {"name": "Foo"},
|
||||
"integration_keys": [{"integration_key": "foo", "name": "bar"}],
|
||||
}
|
||||
|
||||
url = "/integrations/add_pagerduty/?"
|
||||
url += urlencode({"state": state, "config": json.dumps(config)})
|
||||
return url
|
||||
|
||||
def test_it_adds_channel(self):
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get(self._url())
|
||||
self.assertRedirects(r, self.channels_url)
|
||||
|
||||
channel = Channel.objects.get()
|
||||
self.assertEqual(channel.kind, "pd")
|
||||
self.assertEqual(channel.pd_service_key, "foo")
|
||||
self.assertEqual(channel.pd_account, "Foo")
|
||||
|
||||
def test_it_validates_state(self):
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get(self._url(state="XYZ"))
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
@override_settings(PD_APP_ID=None)
|
||||
def test_it_requires_app_id(self):
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
|
||||
r = self.client.get(self._url())
|
||||
self.assertEqual(r.status_code, 404)
|
||||
|
||||
@override_settings(PD_ENABLED=False)
|
||||
def test_it_requires_pd_enabled(self):
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
|
||||
r = self.client.get(self._url())
|
||||
self.assertEqual(r.status_code, 404)
|
||||
|
||||
def test_it_requires_rw_access(self):
|
||||
self.bobs_membership.rw = False
|
||||
self.bobs_membership.save()
|
||||
|
||||
self.client.login(username="bob@example.org", password="password")
|
||||
r = self.client.get(self._url())
|
||||
self.assertEqual(r.status_code, 403)
|
|
@ -3,6 +3,7 @@ from hc.api.models import Channel
|
|||
from hc.test import BaseTestCase
|
||||
|
||||
|
||||
@override_settings(PD_APP_ID=None)
|
||||
class AddPdTestCase(BaseTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
@ -47,3 +48,10 @@ class AddPdTestCase(BaseTestCase):
|
|||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get(self.url)
|
||||
self.assertEqual(r.status_code, 404)
|
||||
|
||||
@override_settings(PD_APP_ID="FOOBAR")
|
||||
def test_it_handles_pd_app_id(self):
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get(self.url)
|
||||
self.assertContains(r, "app_id=FOOBAR")
|
||||
self.assertIn("pagerduty", self.client.session)
|
||||
|
|
|
@ -27,6 +27,7 @@ channel_urls = [
|
|||
path("add_pushbullet/", views.add_pushbullet_complete),
|
||||
path("add_discord/", views.add_discord_complete),
|
||||
path("add_linenotify/", views.add_linenotify_complete),
|
||||
path("add_pagerduty/", views.add_pd_complete, name="hc-add-pd-complete"),
|
||||
path("add_pushover/", views.pushover_help, name="hc-pushover-help"),
|
||||
path("telegram/", views.telegram_help, name="hc-telegram-help"),
|
||||
path("telegram/bot/", views.telegram_bot, name="hc-telegram-webhook"),
|
||||
|
|
|
@ -1089,6 +1089,21 @@ def add_shell(request, code):
|
|||
def add_pd(request, code):
|
||||
project = _get_rw_project_for_user(request, code)
|
||||
|
||||
# Simple Install Flow
|
||||
if settings.PD_APP_ID:
|
||||
state = token_urlsafe()
|
||||
|
||||
redirect_url = settings.SITE_ROOT + reverse("hc-add-pd-complete")
|
||||
redirect_url += "?" + urlencode({"state": state})
|
||||
|
||||
install_url = "https://app.pagerduty.com/install/integration?" + urlencode(
|
||||
{"app_id": settings.PD_APP_ID, "redirect_url": redirect_url, "version": "2"}
|
||||
)
|
||||
|
||||
ctx = {"page": "channels", "project": project, "install_url": install_url}
|
||||
request.session["pagerduty"] = (state, str(project.code))
|
||||
return render(request, "integrations/add_pd_simple.html", ctx)
|
||||
|
||||
if request.method == "POST":
|
||||
form = forms.AddPdForm(request.POST)
|
||||
if form.is_valid():
|
||||
|
@ -1101,10 +1116,37 @@ def add_pd(request, code):
|
|||
else:
|
||||
form = forms.AddPdForm()
|
||||
|
||||
ctx = {"page": "channels", "form": form}
|
||||
ctx = {"page": "channels", "project": project, "form": form}
|
||||
return render(request, "integrations/add_pd.html", ctx)
|
||||
|
||||
|
||||
@require_setting("PD_ENABLED")
|
||||
@require_setting("PD_APP_ID")
|
||||
@login_required
|
||||
def add_pd_complete(request):
|
||||
if "pagerduty" not in request.session:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
state, code = request.session.pop("pagerduty")
|
||||
if request.GET.get("state") != state:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
project = _get_rw_project_for_user(request, code)
|
||||
|
||||
doc = json.loads(request.GET["config"])
|
||||
for item in doc["integration_keys"]:
|
||||
channel = Channel(kind="pd", project=project)
|
||||
channel.name = item["name"]
|
||||
channel.value = json.dumps(
|
||||
{"service_key": item["integration_key"], "account": doc["account"]["name"]}
|
||||
)
|
||||
channel.save()
|
||||
channel.assign_all_checks()
|
||||
|
||||
messages.success(request, "The PagerDuty integration has been added!")
|
||||
return redirect("hc-channels", project.code)
|
||||
|
||||
|
||||
@require_setting("PD_ENABLED")
|
||||
@require_setting("PD_VENDOR_KEY")
|
||||
def pdc_help(request):
|
||||
|
|
|
@ -215,6 +215,7 @@ PAGERTREE_ENABLED = envbool("PAGERTREE_ENABLED", "True")
|
|||
# PagerDuty
|
||||
PD_ENABLED = envbool("PD_ENABLED", "True")
|
||||
PD_VENDOR_KEY = os.getenv("PD_VENDOR_KEY")
|
||||
PD_APP_ID = os.getenv("PD_APP_ID")
|
||||
|
||||
# Prometheus
|
||||
PROMETHEUS_ENABLED = envbool("PROMETHEUS_ENABLED", "True")
|
||||
|
|
BIN
static/img/integrations/setup_pd_simple_0.png
Normal file
BIN
static/img/integrations/setup_pd_simple_0.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 92 KiB |
BIN
static/img/integrations/setup_pd_simple_1.png
Normal file
BIN
static/img/integrations/setup_pd_simple_1.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 44 KiB |
BIN
static/img/integrations/setup_pd_simple_2.png
Normal file
BIN
static/img/integrations/setup_pd_simple_2.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 49 KiB |
BIN
static/img/integrations/setup_pd_simple_3.png
Normal file
BIN
static/img/integrations/setup_pd_simple_3.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 46 KiB |
|
@ -152,12 +152,23 @@ integration.</p>
|
|||
<h2 id="PAGERTREE_ENABLED"><code>PAGERTREE_ENABLED</code></h2>
|
||||
<p>Default: <code>True</code></p>
|
||||
<p>A boolean that turns on/off the PagerTree integration. Enabled by default.</p>
|
||||
<h2 id="PD_APP_ID"><code>PD_APP_ID</code></h2>
|
||||
<p>Default: <code>None</code></p>
|
||||
<p>PagerDuty application ID. If set, enables the PagerDuty
|
||||
<a href="https://developer.pagerduty.com/docs/app-integration-development/events-integration/">Simple Install Flow</a>.
|
||||
If <code>None</code>, Healthchecks will fall back to the even simpler flow where users manually
|
||||
copy integration keys from PagerDuty and paste them in Healthchecks.</p>
|
||||
<p>To set up:</p>
|
||||
<ul>
|
||||
<li>Register a PagerDuty app at <a href="https://pagerduty.com/">PagerDuty</a> › Developer Mode › My Apps</li>
|
||||
<li>In the newly created app, add the "Events Integration" functionality</li>
|
||||
<li>Specify a Redirect URL: <code>https://your-domain.com/integrations/add_pagerduty/</code></li>
|
||||
<li>Copy the displayed app_id value (PXXXXX) and put it in the <code>PD_APP_ID</code> environment
|
||||
variable</li>
|
||||
</ul>
|
||||
<h2 id="PD_ENABLED"><code>PD_ENABLED</code></h2>
|
||||
<p>Default: <code>True</code></p>
|
||||
<p>A boolean that turns on/off the PagerDuty integration. Enabled by default.</p>
|
||||
<h2 id="PD_VENDOR_KEY"><code>PD_VENDOR_KEY</code></h2>
|
||||
<p>Default: <code>None</code></p>
|
||||
<p><a href="https://www.pagerduty.com/">PagerDuty</a> vendor key, used by the PagerDuty integration.</p>
|
||||
<h2 id="PING_BODY_LIMIT"><code>PING_BODY_LIMIT</code></h2>
|
||||
<p>Default: <code>10000</code></p>
|
||||
<p>The upper size limit in bytes for logged ping request bodies.
|
||||
|
|
|
@ -254,18 +254,29 @@ Default: `True`
|
|||
|
||||
A boolean that turns on/off the PagerTree integration. Enabled by default.
|
||||
|
||||
## `PD_APP_ID` {: #PD_APP_ID }
|
||||
|
||||
Default: `None`
|
||||
|
||||
PagerDuty application ID. If set, enables the PagerDuty
|
||||
[Simple Install Flow](https://developer.pagerduty.com/docs/app-integration-development/events-integration/).
|
||||
If `None`, Healthchecks will fall back to the even simpler flow where users manually
|
||||
copy integration keys from PagerDuty and paste them in Healthchecks.
|
||||
|
||||
To set up:
|
||||
|
||||
* Register a PagerDuty app at [PagerDuty](https://pagerduty.com/) › Developer Mode › My Apps
|
||||
* In the newly created app, add the "Events Integration" functionality
|
||||
* Specify a Redirect URL: `https://your-domain.com/integrations/add_pagerduty/`
|
||||
* Copy the displayed app_id value (PXXXXX) and put it in the `PD_APP_ID` environment
|
||||
variable
|
||||
|
||||
## `PD_ENABLED` {: #PD_ENABLED }
|
||||
|
||||
Default: `True`
|
||||
|
||||
A boolean that turns on/off the PagerDuty integration. Enabled by default.
|
||||
|
||||
## `PD_VENDOR_KEY` {: #PD_VENDOR_KEY }
|
||||
|
||||
Default: `None`
|
||||
|
||||
[PagerDuty](https://www.pagerduty.com/) vendor key, used by the PagerDuty integration.
|
||||
|
||||
## `PING_BODY_LIMIT` {: #PING_BODY_LIMIT }
|
||||
|
||||
Default: `10000`
|
||||
|
|
124
templates/integrations/add_pd_simple.html
Normal file
124
templates/integrations/add_pd_simple.html
Normal file
|
@ -0,0 +1,124 @@
|
|||
{% extends "base.html" %}
|
||||
{% load humanize static hc_extras %}
|
||||
|
||||
{% block title %}PagerDuty Integration for {{ site_name }}{% endblock %}
|
||||
|
||||
{% block description %}
|
||||
<meta name="description" content="Use {{ site_name }} with PagerDuty: configure {{ site_name }} to create a PagerDuty incident when a check goes down, and resolve it when a check goes back up.">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h1>PagerDuty</h1>
|
||||
|
||||
<div class="jumbotron">
|
||||
{% if request.user.is_authenticated %}
|
||||
<p>If your team uses <a href="https://www.pagerduty.com">PagerDuty</a>,
|
||||
you can set up {{ site_name }} to create a PagerDuty incident when
|
||||
a check goes down, and resolve it when a check goes back up.</p>
|
||||
|
||||
{% if install_url %}
|
||||
<div class="text-center">
|
||||
<div class="text-center">
|
||||
<a href="{{ install_url|safe }}" class="btn btn-lg btn-default">
|
||||
<img class="ai-icon" src="{% static 'img/integrations/pd.png' %}" alt="PagerDuty" />
|
||||
Connect PagerDuty
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
<p>
|
||||
{{ site_name }} is a <strong>free</strong> and
|
||||
<a href="https://github.com/healthchecks/healthchecks">open source</a>
|
||||
service for monitoring your cron jobs, background processes and
|
||||
scheduled tasks. Before adding PagerDuty integration, please log into
|
||||
{{ site_name }}:</p>
|
||||
|
||||
<div class="text-center">
|
||||
<a href="{% url 'hc-login' %}"
|
||||
class="btn btn-primary btn-lg">Sign In</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h2>Setup Guide</h2>
|
||||
|
||||
{% if not connect_url %}
|
||||
<div class="row ai-step">
|
||||
<div class="col-sm-6">
|
||||
<span class="step-no"></span>
|
||||
<p>
|
||||
{% if request.user.is_authenticated %}
|
||||
Go
|
||||
{% else %}
|
||||
After logging in, go
|
||||
{% endif %}
|
||||
|
||||
to the <strong>Integrations</strong> page,
|
||||
and click on <strong>Add Integration</strong> next to the
|
||||
PagerDuty integration.
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<img
|
||||
class="ai-guide-screenshot"
|
||||
alt="Screenshot"
|
||||
src="{% static 'img/integrations/setup_pd_simple_0.png' %}">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row ai-step">
|
||||
<div class="col-sm-6">
|
||||
<span class="step-no"></span>
|
||||
<p>
|
||||
Click on "Connect PagerDuty", and you will be
|
||||
asked to log into your PagerDuty account.
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<img
|
||||
class="ai-guide-screenshot"
|
||||
alt="Screenshot"
|
||||
src="{% static 'img/integrations/setup_pd_simple_1.png' %}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row ai-step">
|
||||
<div class="col-sm-6">
|
||||
<span class="step-no"></span>
|
||||
<p>
|
||||
Next, PagerDuty will let set the services
|
||||
for this integration.
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<img
|
||||
class="ai-guide-screenshot"
|
||||
alt="Screenshot"
|
||||
src="{% static 'img/integrations/setup_pd_simple_2.png' %}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row ai-step">
|
||||
<div class="col-sm-6">
|
||||
<span class="step-no"></span>
|
||||
<p>
|
||||
And that is all! You will then be redirected back to
|
||||
"Integrations" page on {{ site_name }} and see
|
||||
the new integration!
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<img
|
||||
class="ai-guide-screenshot"
|
||||
alt="Screenshot"
|
||||
src="{% static 'img/integrations/setup_pd_simple_3.png' %}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Add table
Reference in a new issue