parent
0bdfa3502c
commit
2d5f17bacb
74
alertify.py
74
alertify.py
|
@ -7,15 +7,12 @@ Module to act as a Prometheus Exporter for Docker containers with a
|
||||||
import argparse
|
import argparse
|
||||||
import http.client
|
import http.client
|
||||||
import json
|
import json
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||||
|
|
||||||
LISTEN_PORT = 8080
|
LISTEN_PORT = 8080
|
||||||
|
VERBOSE = int(os.environ.get('ALERTIFY_VERBOSE', 0))
|
||||||
logger = logging.getLogger()
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPHandler(SimpleHTTPRequestHandler):
|
class HTTPHandler(SimpleHTTPRequestHandler):
|
||||||
|
@ -31,7 +28,11 @@ class HTTPHandler(SimpleHTTPRequestHandler):
|
||||||
Method to handle GET requests
|
Method to handle GET requests
|
||||||
"""
|
"""
|
||||||
if self.path == '/healthcheck':
|
if self.path == '/healthcheck':
|
||||||
self._healthcheck()
|
if not healthy():
|
||||||
|
print('ERROR: Check requirements')
|
||||||
|
self._respond(500, 'ERR')
|
||||||
|
|
||||||
|
self._respond(200, 'OK')
|
||||||
|
|
||||||
# Override built-in method
|
# Override built-in method
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
|
@ -42,56 +43,72 @@ class HTTPHandler(SimpleHTTPRequestHandler):
|
||||||
if self.path == '/alert':
|
if self.path == '/alert':
|
||||||
self._alerts()
|
self._alerts()
|
||||||
|
|
||||||
def _healthcheck(self, message=True):
|
def _respond(self, status, message):
|
||||||
"""
|
self.send_response(int(status) or 500)
|
||||||
Method to return 200 or 500 response and an optional message
|
|
||||||
"""
|
|
||||||
if not healthy():
|
|
||||||
self.send_response(500)
|
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
if message:
|
self.wfile.write(bytes(str(message).encode()))
|
||||||
self.wfile.write(b'ERR')
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.send_response(200)
|
|
||||||
self.end_headers()
|
|
||||||
if message:
|
|
||||||
self.wfile.write(b'OK')
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _alerts(self):
|
def _alerts(self):
|
||||||
"""
|
"""
|
||||||
Method to handle the request for alerts
|
Method to handle the request for alerts
|
||||||
"""
|
"""
|
||||||
if not self._healthcheck(message=False):
|
if not healthy():
|
||||||
|
print('ERROR: Check requirements')
|
||||||
|
self._respond(500, 'Server not configured correctly')
|
||||||
return
|
return
|
||||||
|
|
||||||
content_length = int(self.headers['Content-Length'])
|
content_length = int(self.headers['Content-Length'])
|
||||||
rawdata = self.rfile.read(content_length)
|
rawdata = self.rfile.read(content_length)
|
||||||
|
|
||||||
|
try:
|
||||||
alert = json.loads(rawdata.decode())
|
alert = json.loads(rawdata.decode())
|
||||||
|
except json.decoder.JSONDecodeError as error:
|
||||||
|
print(f'ERROR: Bad JSON: {error}')
|
||||||
|
self._respond(400, f'Bad JSON: {error}')
|
||||||
|
return
|
||||||
|
|
||||||
|
if VERBOSE:
|
||||||
|
print('Received from Alertmanager:')
|
||||||
|
print(json.dumps(alert, indent=2))
|
||||||
|
|
||||||
|
try:
|
||||||
if alert['status'] == 'resolved':
|
if alert['status'] == 'resolved':
|
||||||
prefix = 'Resolved'
|
prefix = 'Resolved'
|
||||||
else:
|
else:
|
||||||
prefix = alert['commonLabels'].get('severity', 'default').capitalize()
|
prefix = alert['commonLabels'].get(
|
||||||
|
'severity', 'default').capitalize()
|
||||||
|
|
||||||
gotify_msg = {
|
gotify_msg = {
|
||||||
|
'title': '{}: {}'.format(
|
||||||
|
alert['receiver'],
|
||||||
|
alert['commonLabels'].get('instance', 'Unknown')
|
||||||
|
),
|
||||||
'message': '{}: {}'.format(
|
'message': '{}: {}'.format(
|
||||||
prefix,
|
prefix,
|
||||||
alert['commonAnnotations'].get('description', '...')
|
alert['commonAnnotations'].get('description', '...')
|
||||||
),
|
),
|
||||||
'priority': int(alert['commonLabels'].get('priority', 5))
|
'priority': int(alert['commonLabels'].get('priority', 5))
|
||||||
}
|
}
|
||||||
|
except KeyError as error:
|
||||||
|
print(f'ERROR: KeyError: {error}')
|
||||||
|
self._respond(400, f'Missing field: {error}')
|
||||||
|
return
|
||||||
|
|
||||||
(status, reason) = gotify_send(
|
if VERBOSE:
|
||||||
|
print('Sending to Gotify:')
|
||||||
|
print(json.dumps(gotify_msg, indent=2))
|
||||||
|
response = 'Status: {status}, Reason: {reason}'.format(
|
||||||
|
**gotify_send(
|
||||||
os.environ['GOTIFY_SERVER'],
|
os.environ['GOTIFY_SERVER'],
|
||||||
os.environ['GOTIFY_PORT'],
|
os.environ['GOTIFY_PORT'],
|
||||||
os.environ['GOTIFY_KEY'],
|
os.environ['GOTIFY_KEY'],
|
||||||
gotify_msg
|
gotify_msg
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.wfile.write(f'Status: {status}, Reason: {reason}'.encode())
|
if VERBOSE:
|
||||||
|
print(response)
|
||||||
|
self._respond(200, response)
|
||||||
|
|
||||||
|
|
||||||
def gotify_send(server, port, authkey, payload):
|
def gotify_send(server, port, authkey, payload):
|
||||||
|
@ -108,7 +125,10 @@ def gotify_send(server, port, authkey, payload):
|
||||||
gotify.request('POST', '/message', json.dumps(payload), headers)
|
gotify.request('POST', '/message', json.dumps(payload), headers)
|
||||||
response = gotify.getresponse()
|
response = gotify.getresponse()
|
||||||
|
|
||||||
return (response.status, response.reason)
|
return {
|
||||||
|
'status': response.status,
|
||||||
|
'reason': response.reason
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def healthy():
|
def healthy():
|
||||||
|
@ -154,7 +174,11 @@ if __name__ == '__main__':
|
||||||
# Invert the sense of 'healthy' for Unix CLI usage
|
# Invert the sense of 'healthy' for Unix CLI usage
|
||||||
return not healthy()
|
return not healthy()
|
||||||
|
|
||||||
|
print(f'Starting web server on port {LISTEN_PORT}')
|
||||||
|
try:
|
||||||
HTTPServer(('', LISTEN_PORT), HTTPHandler).serve_forever()
|
HTTPServer(('', LISTEN_PORT), HTTPHandler).serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('Exiting')
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
58
test.sh
Executable file
58
test.sh
Executable file
|
@ -0,0 +1,58 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
SERVER=${1:-'localhost:8080'}
|
||||||
|
|
||||||
|
name=testAlert-$RANDOM
|
||||||
|
URL="http://${SERVER}/alert"
|
||||||
|
bold=$(tput bold)
|
||||||
|
normal=$(tput sgr0)
|
||||||
|
|
||||||
|
call_alertmanager() {
|
||||||
|
curl -v "${URL}" --header 'Content-type: application/json' --data @<(cat <<EOF
|
||||||
|
{
|
||||||
|
"version": "4",
|
||||||
|
"groupKey": "testGroup",
|
||||||
|
"truncatedAlerts": 0,
|
||||||
|
"status": "${STATUS}",
|
||||||
|
"receiver": "alertify",
|
||||||
|
"commonLabels": {
|
||||||
|
"alertname": "${name}",
|
||||||
|
"service": "testService",
|
||||||
|
"severity":"warning",
|
||||||
|
"instance": "server.example.net",
|
||||||
|
"namespace": "testNamespace",
|
||||||
|
"label_costcentre": "testCostCentre"
|
||||||
|
},
|
||||||
|
"commonAnnotations": {
|
||||||
|
"summary": "Testing latency is high!",
|
||||||
|
"description": "Testing latency is at ${1}"
|
||||||
|
},
|
||||||
|
"alerts": [
|
||||||
|
{
|
||||||
|
"status": "${STATUS}",
|
||||||
|
"generatorURL": "http://alertmanager.example.net/$name",
|
||||||
|
"startsAt": "${START}",
|
||||||
|
"endsAt": "${END}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "${bold}Firing alert ${name} ${normal}"
|
||||||
|
STATUS='firing'
|
||||||
|
START=$(date --rfc-3339=seconds | sed 's/ /T/')
|
||||||
|
END="0001-01-01T00:00:00Z"
|
||||||
|
call_alertmanager 42
|
||||||
|
echo -e "\n"
|
||||||
|
|
||||||
|
echo "${bold}Press enter to resolve alert ${name} ${normal}"
|
||||||
|
read -r
|
||||||
|
|
||||||
|
|
||||||
|
echo "${bold}Sending resolved ${normal}"
|
||||||
|
STATUS='resolved'
|
||||||
|
END=$(date --rfc-3339=seconds | sed 's/ /T/')
|
||||||
|
call_alertmanager 0
|
||||||
|
echo -e "\n"
|
Loading…
Reference in a new issue