You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

255 lines
7.4 KiB

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"
ctx = {
"check": check,
"checks": self.checks(),
"now": timezone.now(),
"unsub_link": self.channel.get_unsub_link()
}
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, **kwargs):
return self.request("post", url, **kwargs)
class Webhook(HttpTransport):
def prepare(self, template, check, urlencode=False):
""" 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
"""
def safe(s):
return quote(s) if urlencode else s
result = template
if "$CODE" in result:
result = result.replace("$CODE", str(check.code))
if "$STATUS" in result:
result = result.replace("$STATUS", check.status)
if "$NOW" in result:
s = timezone.now().replace(microsecond=0).isoformat()
result = result.replace("$NOW", safe(s))
if "$NAME" in result:
result = result.replace("$NAME", safe(check.name))
if "$TAG" in result:
for i, tag in enumerate(check.tags_list()):
placeholder = "$TAG%d" % (i + 1)
result = result.replace(placeholder, safe(tag))
return result
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"
url = self.prepare(url, check, urlencode=True)
if self.channel.post_data:
payload = self.prepare(self.channel.post_data, check)
return self.post(url, data=payload)
else:
return self.get(url)
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, json=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, json=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, json=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, json=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, json=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(self.URL, data=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, json=payload)
class Discord(HttpTransport):
def notify(self, check):
text = tmpl("slack_message.json", check=check)
payload = json.loads(text)
url = self.channel.discord_webhook_url + "/slack"
return self.post(url, json=payload)