From 4788f36e55911e3c733ef6f55959ba721ec06175 Mon Sep 17 00:00:00 2001 From: Scott Wallace Date: Tue, 13 Oct 2020 09:19:35 +0100 Subject: [PATCH] Initial commit --- .dockerignore | 25 ++++++++ .gitignore | 1 + Dockerfile | 27 +++++++++ alertify.py | 149 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 3 + 5 files changed, 205 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 alertify.py create mode 100644 requirements.txt diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5be8957 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +**/__pycache__ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..27573b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.pyenv/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4298d88 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# For more information, please refer to https://aka.ms/vscode-docker-python +FROM python:3.8-slim-buster + +# Keeps Python from generating .pyc files in the container +ENV PYTHONDONTWRITEBYTECODE 1 + +# Turns off buffering for easier container logging +ENV PYTHONUNBUFFERED 1 + +# Install pip requirements +ADD requirements.txt . +RUN python -m pip install -r requirements.txt + +WORKDIR /app +ADD alertify.py /app + +# Switching to a non-root user, please refer to https://aka.ms/vscode-docker-python-user-rights +# RUN useradd appuser && chown -R appuser /app +# USER appuser + +EXPOSE 8080 + +# During debugging, this entry point will be overridden. For more information, please refer to https://aka.ms/vscode-docker-python-debug +CMD ["python", "alertify.py"] + +HEALTHCHECK --interval=30s --timeout=3s --retries=1 \ + CMD python3 /app/alertify.py --healthcheck diff --git a/alertify.py b/alertify.py new file mode 100644 index 0000000..708eb91 --- /dev/null +++ b/alertify.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +""" +Module to act as a Prometheus Exporter for Docker containers with a + healthcheck configured +""" + +import argparse +import http.client +import json +import logging +import os +import sys +from http.server import HTTPServer, SimpleHTTPRequestHandler + +LISTEN_PORT = 8080 + +logger = logging.getLogger() +logger.setLevel(logging.DEBUG) + + +class HTTPHandler(SimpleHTTPRequestHandler): + """ + Class to encompass the requirements of a Prometheus Exporter + for Docker containers with a healthcheck configured + """ + + # Override built-in method + # pylint: disable=invalid-name + def do_GET(self): + """ + Method to handle GET requests + """ + if self.path == '/healthcheck': + self._healthcheck() + + # Override built-in method + # pylint: disable=invalid-name + def do_POST(self): + """ + Method to handle POST requests from AlertManager + """ + if self.path == '/alert': + self._alerts() + + def _healthcheck(self, message=True): + """ + Method to return 200 or 500 response and an optional message + """ + if not healthy(): + self.send_response(500) + self.end_headers() + if message: + 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): + """ + Method to handle the request for alerts + """ + if not self._healthcheck(message=False): + return + + content_length = int(self.headers['Content-Length']) + rawdata = self.rfile.read(content_length) + + alert = json.loads(rawdata.decode()) + + gotify_msg = { + 'message': '{}: {}'.format( + alert['commonLabels']['severity'].capitalize(), + alert['commonAnnotations']['description'] + ), + 'priority': int(alert['commonLabels']['priority']) or 5 + } + + (status, reason) = gotify_send( + os.environ['GOTIFY_SERVER'], + os.environ['GOTIFY_PORT'], + os.environ['GOTIFY_KEY'], + gotify_msg + ) + + self.wfile.write(f'Status: {status}, Reason: {reason}'.encode()) + + +def gotify_send(server, port, authkey, payload): + """ + Function to POST data to a Gotify server + """ + + gotify = http.client.HTTPConnection(server, port) + headers = { + 'X-Gotify-Key': authkey, + 'Content-type': 'application/json', + } + + gotify.request('POST', '/message', json.dumps(payload), headers) + response = gotify.getresponse() + + return (response.status, response.reason) + + +def healthy(): + """ + Simple funtion to return if all the requirements are met + """ + return all([ + 'GOTIFY_SERVER' in os.environ, + 'GOTIFY_PORT' in os.environ, + 'GOTIFY_KEY' in os.environ, + ]) + + +if __name__ == '__main__': + def cli_parse(): + """ + Function to parse the CLI + """ + parser = argparse.ArgumentParser() + + parser.add_argument( + '-H', '--healthcheck', + action='store_true', + help='Simply exit with 0 for healthy or 1 when unhealthy', + ) + + return parser.parse_args() + + def main(): + """ + main() + """ + args = cli_parse() + + if args.healthcheck: + # Invert the sense of 'healthy' for Unix CLI usage + return not healthy() + + HTTPServer(('', LISTEN_PORT), HTTPHandler).serve_forever() + + return 0 + + sys.exit(main()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..90b4360 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +# To ensure app dependencies are ported from your virtual environment/host machine into your container, run 'pip freeze > requirements.txt' in the terminal to overwrite this file +docker +prometheus_client