Browse Source

Implement PagerDuty Simple Install Flow

pull/537/head
Pēteris Caune 3 years ago
parent
commit
fd7ab5e767
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
  1. 1
      CHANGELOG.md
  2. 10
      README.md
  3. 1
      docker/.env
  4. 9
      hc/api/models.py
  5. 63
      hc/front/tests/test_add_pagerduty_complete.py
  6. 8
      hc/front/tests/test_add_pd.py
  7. 1
      hc/front/urls.py
  8. 44
      hc/front/views.py
  9. 1
      hc/settings.py
  10. BIN
      static/img/integrations/setup_pd_simple_0.png
  11. BIN
      static/img/integrations/setup_pd_simple_1.png
  12. BIN
      static/img/integrations/setup_pd_simple_2.png
  13. BIN
      static/img/integrations/setup_pd_simple_3.png
  14. 17
      templates/docs/self_hosted_configuration.html
  15. 23
      templates/docs/self_hosted_configuration.md
  16. 124
      templates/integrations/add_pd_simple.html

1
CHANGELOG.md

@ -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

@ -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

1
docker/.env

@ -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

9
hc/api/models.py

@ -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

@ -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)

8
hc/front/tests/test_add_pd.py

@ -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)

1
hc/front/urls.py

@ -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"),

44
hc/front/views.py

@ -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):

1
hc/settings.py

@ -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

After

Width: 1054  |  Height: 776  |  Size: 92 KiB

BIN
static/img/integrations/setup_pd_simple_1.png

After

Width: 420  |  Height: 690  |  Size: 44 KiB

BIN
static/img/integrations/setup_pd_simple_2.png

After

Width: 720  |  Height: 559  |  Size: 49 KiB

BIN
static/img/integrations/setup_pd_simple_3.png

After

Width: 1007  |  Height: 435  |  Size: 46 KiB

17
templates/docs/self_hosted_configuration.html

@ -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.

23
templates/docs/self_hosted_configuration.md

@ -254,17 +254,28 @@ Default: `True`
A boolean that turns on/off the PagerTree integration. Enabled by default.
## `PD_ENABLED` {: #PD_ENABLED }
## `PD_APP_ID` {: #PD_APP_ID }
Default: `True`
Default: `None`
A boolean that turns on/off the PagerDuty integration. Enabled by default.
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.
## `PD_VENDOR_KEY` {: #PD_VENDOR_KEY }
To set up:
Default: `None`
* 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
[PagerDuty](https://www.pagerduty.com/) vendor key, used by the PagerDuty integration.
## `PD_ENABLED` {: #PD_ENABLED }
Default: `True`
A boolean that turns on/off the PagerDuty integration. Enabled by default.
## `PING_BODY_LIMIT` {: #PING_BODY_LIMIT }

124
templates/integrations/add_pd_simple.html

@ -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…
Cancel
Save