summaryrefslogtreecommitdiff
path: root/sitemodules/profiles/files/icinga2_master/icinga2-git-pull-hook
diff options
context:
space:
mode:
Diffstat (limited to 'sitemodules/profiles/files/icinga2_master/icinga2-git-pull-hook')
-rw-r--r--sitemodules/profiles/files/icinga2_master/icinga2-git-pull-hook214
1 files changed, 214 insertions, 0 deletions
diff --git a/sitemodules/profiles/files/icinga2_master/icinga2-git-pull-hook b/sitemodules/profiles/files/icinga2_master/icinga2-git-pull-hook
new file mode 100644
index 0000000..a0d3711
--- /dev/null
+++ b/sitemodules/profiles/files/icinga2_master/icinga2-git-pull-hook
@@ -0,0 +1,214 @@
+#!/usr/bin/env python3
+
+"""
+This script takes care of updating the configuration in a directory by
+performing git pull when triggered via an HTTP request to port 8000.
+
+The script needs the sshpass and git packages installed.
+
+Configuration is read from /etc/default/icinga2-git-pull-hook.ini,
+~/.icinga2-git-pull-hook.ini and a icinga2-git-pull-hook.ini in the working
+directory in that order.
+"""
+
+import logging
+import logging.config
+import os
+import subprocess
+from configparser import ConfigParser
+from http import HTTPStatus
+from http.server import HTTPServer, BaseHTTPRequestHandler
+
+ENV_FOR_GIT = {"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}
+
+TOKENS = []
+GIT_DIRECTORY = ""
+GIT_REPOSITORY = ""
+GIT_BRANCH = ""
+
+LOGGER = None
+
+
+def read_ini():
+ global ENV_FOR_GIT, TOKENS, GIT_DIRECTORY, GIT_REPOSITORY, GIT_BRANCH, LOGGER
+ config = ConfigParser()
+ config.read(
+ [
+ "/etc/default/icinga2-git-pull-hook.ini",
+ os.path.expanduser("~/.icinga2-git-pull-hook.ini"),
+ "icinga2-git-pull-hook.ini",
+ ]
+ )
+ ENV_FOR_GIT["SSHPASS"] = config["icinga2-git-pull-hook"]["ssh_passphrase"]
+ TOKENS = [
+ token.strip() for token in config["icinga2-git-pull-hook"]["tokens"].split(",")
+ ]
+ GIT_DIRECTORY = config["icinga2-git-pull-hook"]["git_directory"]
+ GIT_REPOSITORY = config["icinga2-git-pull-hook"]["git_repository"]
+ GIT_BRANCH = config["icinga2-git-pull-hook"]["git_branch"]
+
+ logging.config.dictConfig(
+ {
+ "version": 1,
+ "formatters": {
+ "full": {
+ "format": "%(asctime)s %(levelname)-8s %(message)s",
+ "datefmt": "%Y-%m-%d %H:%M:%S",
+ }
+ },
+ "handlers": {
+ "file": {
+ "class": "logging.FileHandler",
+ "filename": config.get("icinga2-git-pull-hook", "logfile"),
+ "formatter": "full",
+ }
+ },
+ "loggers": {
+ "icinga2-git-pull-hook": {"handlers": ["file"], "level": "INFO"}
+ },
+ }
+ )
+ LOGGER = logging.getLogger("icinga2-git-pull-hook")
+
+
+class GitHookRequestHandler(BaseHTTPRequestHandler):
+ """
+ Custom HTTP request handler for updating a git repository when called
+ with a known authentication token in an "Authentication" HTTP header.
+ """
+
+ def __init__(self, request, client_address, server):
+ global LOGGER
+ self.log = LOGGER
+ super().__init__(request, client_address, server)
+
+ def _send_data(self, message):
+ self.send_header("Content-Type", "text/plain; charset=utf8")
+ self.end_headers()
+ self.wfile.write(("%s\r\n" % message).encode("UTF-8"))
+
+ def _handle_pull(self):
+ try:
+ git_proc = subprocess.run(
+ [
+ "sshpass",
+ "-e",
+ "-P",
+ "passphrase",
+ "git",
+ "subtree",
+ "pull",
+ "--prefix",
+ "icinga2/conf.d",
+ GIT_REPOSITORY,
+ GIT_BRANCH,
+ ],
+ env=ENV_FOR_GIT,
+ cwd="/etc",
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ check=True,
+ universal_newlines=True,
+ )
+ for line in git_proc.stdout.splitlines():
+ self.log_message("git: %s", line)
+ except subprocess.CalledProcessError as e:
+ self.log_error(
+ "Could not pull changes for %s: %s", GIT_DIRECTORY, e.returncode
+ )
+ for line in e.stdout.splitlines():
+ self.log_message("git: %s", line)
+ self.send_response(HTTPStatus.INTERNAL_SERVER_ERROR)
+ self._send_data("Error updating the repository.")
+ return
+ self._send_data("updated %s" % GIT_DIRECTORY)
+ try:
+ icinga2_config_check_proc = subprocess.run(
+ ["/usr/sbin/icinga2", "daemon", "-C"],
+ cwd=GIT_DIRECTORY,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ check=True,
+ universal_newlines=True,
+ )
+ for line in icinga2_config_check_proc.stdout.splitlines():
+ self.log_message("icinga2: %s", line)
+ except subprocess.CalledProcessError as e:
+ self.log_error("configuration check failed: %d", e.returncode)
+ for line in e.stdout.splitlines():
+ self.log_message("icinga2: %s", line)
+ self.send_response(HTTPStatus.INTERNAL_SERVER_ERROR)
+ self._send_data("Error updating configuration.")
+ return
+ try:
+ icinga2_reload_check_proc = subprocess.run(
+ ["/bin/systemctl", "reload", "icinga2.service"],
+ cwd=GIT_DIRECTORY,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ check=True,
+ universal_newlines=True,
+ )
+ for line in icinga2_reload_check_proc.stdout.splitlines():
+ self.log_message("systemctl: %s", line)
+ except subprocess.CalledProcessError as e:
+ self.log_error("reload failed: %s", e.returncode)
+ for line in e.stdout.splitlines():
+ self.log_message("systemctl: %s", line)
+ self.send_response(HTTPStatus.INTERNAL_SERVER_ERROR)
+ self._send_data("Error reloading icinga2.")
+ return
+
+ self.send_response(HTTPStatus.OK)
+ self._send_data("Updated icinga2 configuration")
+ return
+
+ # noinspection PyPep8Naming
+ def do_GET(self):
+ """
+ Handle GET requests, requests to /health are allowed for every caller.
+ """
+ if self.path == "/health":
+ self.send_response(HTTPStatus.OK)
+ self._send_data("I'm healthy!")
+ else:
+ self.send_error(
+ HTTPStatus.NOT_FOUND, "You requested something I do not understand."
+ )
+
+ # noinspection PyPep8Naming
+ def do_POST(self):
+ """
+ Handle POST requests requests to / need a valid token in the
+ "Authentication" HTTP header and trigger a git pull in the configured
+ directory.
+ """
+ if self.path == "/":
+ if self.headers["Authentication"] in [token for token in TOKENS]:
+ self._handle_pull()
+ else:
+ self.send_error(
+ HTTPStatus.UNAUTHORIZED,
+ 'You have to send a valid token in the "Authentication" header.',
+ )
+ else:
+ self.send_error(
+ HTTPStatus.NOT_FOUND, "You requested something I do not understand."
+ )
+
+ def log_error(self, format, *args):
+ self.log.error("%s - %s" % (self.address_string(), format), *args)
+
+ def log_message(self, format, *args):
+ self.log.info("%s - %s" % (self.address_string(), format), *args)
+
+
+def run(server_class=HTTPServer, handler_class=GitHookRequestHandler):
+ server_address = ("", 8000)
+ httpd = server_class(server_address, handler_class)
+ httpd.serve_forever()
+
+
+if __name__ == "__main__":
+ read_ini()
+ run()