Add an HTTP hook for updating code on the puppet server
[cacert-puppet.git] / sitemodules / profiles / files / puppet_server / git-pull-hook
1 #!/usr/bin/env python3
2
3 """
4 This script takes care of updating the code in a directory by performing
5 git pull when triggered via an HTTP request to port 8000.
6
7 The script needs the sshpass and git packages installed.
8
9 Configuration is read from /etc/git-pull-hook.ini, ~/.git-pull-hook.ini and
10 a git-pull-hook.ini in the working directory in that order.
11 """
12
13 from configparser import ConfigParser
14 from http import HTTPStatus
15 from http.server import HTTPServer, BaseHTTPRequestHandler
16 import os
17 from subprocess import Popen, PIPE
18
19 ENV_FOR_GIT = {
20 'PATH': '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
21 }
22
23 TOKENS = []
24 GIT_DIRECTORY = ""
25
26
27 def read_ini():
28 global ENV_FOR_GIT, TOKENS, GIT_DIRECTORY
29 config = ConfigParser()
30 config.read(['/etc/git-pull-hook.ini',
31 os.path.expanduser('~/.git-pull-hook.ini'),
32 'git-pull-hook.ini'])
33 ENV_FOR_GIT['SSHPASS'] = config['git-pull-hook']['ssh_passphrase']
34 TOKENS = [token.strip() for token in
35 config['git-pull-hook']['tokens'].split(',')]
36 GIT_DIRECTORY = config['git-pull-hook']['git_directory']
37
38
39 class GitHookRequestHandler(BaseHTTPRequestHandler):
40 """
41 Custom HTTP request handler for updating a git repository when called
42 with a known authentication token in an "Authentication" HTTP header.
43 """
44
45 def _handle_pull(self):
46 try:
47 git_proc = Popen(
48 ['sshpass', '-e', '-P', 'passphrase', 'git', 'pull'],
49 env=ENV_FOR_GIT, cwd=GIT_DIRECTORY, stdout=PIPE, stderr=PIPE)
50 stdout, stderr = git_proc.communicate()
51 for line in stderr.decode('UTF-8').splitlines():
52 self.log_error('git: %s', line)
53 for line in stdout.decode('UTF-8').splitlines():
54 self.log_message('git: %s', line)
55 except Exception as e:
56 self.log_error("Could not pull changes for %s: %s",
57 GIT_DIRECTORY, e)
58 self.send_response(HTTPStatus.OK)
59 self.send_header('Content-Type', 'text/plain; charset=utf8')
60 self.flush_headers()
61 self.wfile.write(("updated %s" % GIT_DIRECTORY).encode('UTF-8'))
62
63 # noinspection PyPep8Naming
64 def do_GET(self):
65 """
66 Handle GET requests, requests to /health are allowed for every caller,
67 requests to / need a valid token in the "Authentication" HTTP header
68 and trigger a git pull in the configured directory.
69 """
70 if self.path == '/':
71 if self.headers['Authentication'] in [token for token in TOKENS]:
72 self._handle_pull()
73 else:
74 self.send_response(HTTPStatus.UNAUTHORIZED)
75 self.flush_headers()
76 elif self.path == '/health':
77 self.send_response(HTTPStatus.OK)
78 self.flush_headers()
79 self.wfile.write(b"I'm healthy!")
80 else:
81 self.send_error(HTTPStatus.BAD_REQUEST)
82 self.flush_headers()
83 self.wfile.write(b"You requested something I do not understand")
84
85
86 def run(server_class=HTTPServer, handler_class=GitHookRequestHandler):
87 server_address = ('', 8000)
88 httpd = server_class(server_address, handler_class)
89 httpd.serve_forever()
90
91
92 if __name__ == '__main__':
93 read_ini()
94 run()