from django.conf import settings
|
|
from django.template.loader import render_to_string
|
|
from django.utils import timezone
|
|
import json
|
|
import requests
|
|
from six.moves.urllib.parse import quote
|
|
|
|
from hc.lib import emails
|
|
|
|
|
|
def tmpl(template_name, **ctx):
|
|
template_path = "integrations/%s" % template_name
|
|
return render_to_string(template_path, ctx).strip()
|
|
|
|
|
|
class Transport(object):
|
|
def __init__(self, channel):
|
|
self.channel = channel
|
|
|
|
def notify(self, check):
|
|
""" Send notification about current status of the check.
|
|
|
|
This method returns None on success, and error message
|
|
on error.
|
|
|
|
"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
def test(self):
|
|
""" Send test message.
|
|
|
|
This method returns None on success, and error message
|
|
on error.
|
|
|
|
"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
def checks(self):
|
|
return self.channel.user.check_set.order_by("created")
|
|
|
|
|
|
class Email(Transport):
|
|
def notify(self, check):
|
|
if not self.channel.email_verified:
|
|
return "Email not verified"
|
|
|
|
show_upgrade_note = False
|
|
if settings.USE_PAYMENTS and check.status == "up":
|
|
if not check.user.profile.team_access_allowed:
|
|
show_upgrade_note = True
|
|
|
|
ctx = {
|
|
"check": check,
|
|
"checks": self.checks(),
|
|
"now": timezone.now(),
|
|
"show_upgrade_note": show_upgrade_note
|
|
}
|
|
emails.alert(self.channel.value, ctx)
|
|
|
|
|
|
class HttpTransport(Transport):
|
|
|
|
def request(self, method, url, **kwargs):
|
|
try:
|
|
options = dict(kwargs)
|
|
if "headers" not in options:
|
|
options["headers"] = {}
|
|
|
|
options["timeout"] = 5
|
|
options["headers"]["User-Agent"] = "healthchecks.io"
|
|
|
|
r = requests.request(method, url, **options)
|
|
if r.status_code not in (200, 201, 204):
|
|
return "Received status code %d" % r.status_code
|
|
except requests.exceptions.Timeout:
|
|
# Well, we tried
|
|
return "Connection timed out"
|
|
except requests.exceptions.ConnectionError:
|
|
return "Connection failed"
|
|
|
|
def get(self, url):
|
|
return self.request("get", url)
|
|
|
|
def post(self, url, json, **kwargs):
|
|
return self.request("post", url, json=json, **kwargs)
|
|
|
|
def post_form(self, url, data):
|
|
return self.request("post", url, data=data)
|
|
|
|
|
|
class Webhook(HttpTransport):
|
|
def notify(self, check):
|
|
url = self.channel.value_down
|
|
if check.status == "up":
|
|
url = self.channel.value_up
|
|
|
|
if not url:
|
|
# If the URL is empty then we do nothing
|
|
return "no-op"
|
|
|
|
# Replace variables with actual values.
|
|
# There should be no bad translations if users use $ symbol in
|
|
# check's name or tags, because $ gets urlencoded to %24
|
|
|
|
if "$CODE" in url:
|
|
url = url.replace("$CODE", str(check.code))
|
|
|
|
if "$STATUS" in url:
|
|
url = url.replace("$STATUS", check.status)
|
|
|
|
if "$NAME" in url:
|
|
url = url.replace("$NAME", quote(check.name))
|
|
|
|
if "$TAG" in url:
|
|
for i, tag in enumerate(check.tags_list()):
|
|
placeholder = "$TAG%d" % (i + 1)
|
|
url = url.replace(placeholder, quote(tag))
|
|
|
|
return self.get(url)
|
|
|
|
def test(self):
|
|
return self.get(self.channel.value)
|
|
|
|
|
|
class Slack(HttpTransport):
|
|
def notify(self, check):
|
|
text = tmpl("slack_message.json", check=check)
|
|
payload = json.loads(text)
|
|
return self.post(self.channel.slack_webhook_url, payload)
|
|
|
|
|
|
class HipChat(HttpTransport):
|
|
def notify(self, check):
|
|
text = tmpl("hipchat_message.html", check=check)
|
|
payload = {
|
|
"message": text,
|
|
"color": "green" if check.status == "up" else "red",
|
|
}
|
|
return self.post(self.channel.value, payload)
|
|
|
|
|
|
class OpsGenie(HttpTransport):
|
|
|
|
def notify(self, check):
|
|
payload = {
|
|
"apiKey": self.channel.value,
|
|
"alias": str(check.code),
|
|
"source": "healthchecks.io"
|
|
}
|
|
|
|
if check.status == "down":
|
|
payload["tags"] = ",".join(check.tags_list())
|
|
payload["message"] = tmpl("opsgenie_message.html", check=check)
|
|
payload["note"] = tmpl("opsgenie_note.html", check=check)
|
|
|
|
url = "https://api.opsgenie.com/v1/json/alert"
|
|
if check.status == "up":
|
|
url += "/close"
|
|
|
|
return self.post(url, payload)
|
|
|
|
|
|
class PagerDuty(HttpTransport):
|
|
URL = "https://events.pagerduty.com/generic/2010-04-15/create_event.json"
|
|
|
|
def notify(self, check):
|
|
description = tmpl("pd_description.html", check=check)
|
|
payload = {
|
|
"service_key": self.channel.value,
|
|
"incident_key": str(check.code),
|
|
"event_type": "trigger" if check.status == "down" else "resolve",
|
|
"description": description,
|
|
"client": "healthchecks.io",
|
|
"client_url": settings.SITE_ROOT
|
|
}
|
|
|
|
return self.post(self.URL, payload)
|
|
|
|
|
|
class Pushbullet(HttpTransport):
|
|
def notify(self, check):
|
|
text = tmpl("pushbullet_message.html", check=check)
|
|
url = "https://api.pushbullet.com/v2/pushes"
|
|
headers = {
|
|
"Access-Token": self.channel.value,
|
|
"Conent-Type": "application/json"
|
|
}
|
|
payload = {
|
|
"type": "note",
|
|
"title": "healthchecks.io",
|
|
"body": text
|
|
}
|
|
|
|
return self.post(url, payload, headers=headers)
|
|
|
|
|
|
class Pushover(HttpTransport):
|
|
URL = "https://api.pushover.net/1/messages.json"
|
|
|
|
def notify(self, check):
|
|
others = self.checks().filter(status="down").exclude(code=check.code)
|
|
ctx = {
|
|
"check": check,
|
|
"down_checks": others,
|
|
}
|
|
text = tmpl("pushover_message.html", **ctx)
|
|
title = tmpl("pushover_title.html", **ctx)
|
|
user_key, prio = self.channel.value.split("|")
|
|
payload = {
|
|
"token": settings.PUSHOVER_API_TOKEN,
|
|
"user": user_key,
|
|
"message": text,
|
|
"title": title,
|
|
"html": 1,
|
|
"priority": int(prio),
|
|
}
|
|
|
|
# Emergency notification
|
|
if prio == "2":
|
|
payload["retry"] = settings.PUSHOVER_EMERGENCY_RETRY_DELAY
|
|
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": check.name_then_code(),
|
|
"state_message": description,
|
|
"monitoring_tool": "healthchecks.io",
|
|
}
|
|
|
|
return self.post(self.channel.value, payload)
|
|
|
|
|
|
class Discord(HttpTransport):
|
|
def notify(self, check):
|
|
text = tmpl("slack_message.json", check=check)
|
|
payload = json.loads(text)
|
|
return self.post(self.channel.discord_webhook_url + "/slack", payload)
|