From 5663516d57a2d5c1329643f27833929117eea76d Mon Sep 17 00:00:00 2001 From: pstauffer Date: Tue, 7 Feb 2017 14:01:25 +0100 Subject: [PATCH] initial commit --- .gitignore | 2 + README.md | 63 +++++++++++- examples/config.yaml | 9 ++ examples/gitlab-webhook-receiver.service | 9 ++ gitlab-webhook-receiver.py | 118 +++++++++++++++++++++++ 5 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 examples/config.yaml create mode 100644 examples/gitlab-webhook-receiver.service create mode 100755 gitlab-webhook-receiver.py diff --git a/.gitignore b/.gitignore index 72364f9..12de8e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +./config.yaml + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/README.md b/README.md index 6af7936..fdf35c2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,63 @@ # gitlab-webhook-receiver -Simple gitlab webhook receiver +Simple gitlab webhook receiver. + +## Source +The idea and base of the script is from this [github repo](https://github.com/schickling/docker-hook). + +## Configuration + +### Gitlab Secret Token +The script requires, that the gitlab secret token is set! You can define the value in the [configuration file](#example-config). + +### Gitlab Project Homepage +The structure of the [configuration file](#example-config) requires the homepage of the gitlab project as key. + +### Command +Define, which command should be run after the hook was received. + +### Example config +``` +# file: config.yaml +--- +# myrepo +https://git.example.ch/exmaple/myrepo: + command: uname + gitlab_token: mysecret-myrepo +# test-repo +https://git.example.ch/exmaple/test-repo: + command: uname + gitlab_token: mysecret-test-repo +``` + +## Script Arguments + +### Port +Define the listen port for the webserver. Default: **8666** + +### Addr +Define the listen address for the webserver. Default: **0.0.0.0** + +### Cfg +Define the path to your configuration file. Default: **config.yaml** + + + +## Run Script + +``` +python gitlab-webhook-receiver.py --port 8080 --cfg /etc/hook.yaml +``` + + +### Help +``` +usage: gitlab-webhook-receiver.py [-h] [--addr ADDR] [--port PORT] [--cfg CFG] + +Gitlab Webhook Receiver + +optional arguments: + -h, --help show this help message and exit + --addr ADDR address where it listens (default: 0.0.0.0) + --port PORT port where it listens (default: 8666) + --cfg CFG path to the config file (default: config.yaml) +``` \ No newline at end of file diff --git a/examples/config.yaml b/examples/config.yaml new file mode 100644 index 0000000..91b98d9 --- /dev/null +++ b/examples/config.yaml @@ -0,0 +1,9 @@ +--- +# myrepo +https://git.example.ch/exmaple/myrepo: + command: uname + gitlab_token: mysecret-myrepo +# test-repo +https://git.example.ch/exmaple/test-repo: + command: uname + gitlab_token: mysecret-test-repo diff --git a/examples/gitlab-webhook-receiver.service b/examples/gitlab-webhook-receiver.service new file mode 100644 index 0000000..7135fba --- /dev/null +++ b/examples/gitlab-webhook-receiver.service @@ -0,0 +1,9 @@ +[Unit] +Description=Gitlab Webhook Receiver +After=network.target + +[Service] +ExecStart=gitlab-webhook-receiver.py + +[Install] +WantedBy=multi-user.target diff --git a/gitlab-webhook-receiver.py b/gitlab-webhook-receiver.py new file mode 100755 index 0000000..d7dde94 --- /dev/null +++ b/gitlab-webhook-receiver.py @@ -0,0 +1,118 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- + +""" Gitlab Webhook Receiver """ +# Based on: https://github.com/schickling/docker-hook + +import json +import yaml +import subprocess +from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter +try: + # For Python 3.0 and later + from http.server import HTTPServer + from http.server import BaseHTTPRequestHandler +except ImportError: + # Fall back to Python 2 + from BaseHTTPServer import BaseHTTPRequestHandler + from BaseHTTPServer import HTTPServer as HTTPServer +import sys +import logging + +logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', + level=logging.DEBUG, + stream=sys.stdout) + + +class RequestHandler(BaseHTTPRequestHandler): + """A POST request handler.""" + + def do_POST(self): + logging.info("Hook received") + + # get payload + header_length = int(self.headers.getheader('content-length', "0")) + json_payload = self.rfile.read(header_length) + json_params = {} + if len(json_payload) > 0: + json_params = json.loads(json_payload) + + # get gitlab secret token + gitlab_token_header = self.headers.getheader('X-Gitlab-Token') + + # get project homepage + project = json_params['project']['homepage'] + + try: + # get command and token from config file + command = config[project]['command'] + gitlab_token = config[project]['gitlab_token'] + + logging.info("Load project '%s' and run command '%s'", project, command) + except KeyError as err: + self.send_response(500, "KeyError") + logging.error("Project '%s' not found in %s", project, args.cfg) + self.end_headers() + return + + # Check if the gitlab token is valid + if gitlab_token_header == gitlab_token: + logging.info("Start executing '%s'" % command) + try: + subprocess.call(command) + self.send_response(200, "OK") + except OSError as err: + self.send_response(500, "OSError") + logging.error("Command could not run successfully.") + logging.error(err) + else: + logging.error("Not authorized, Gitlab_Token not authorized") + self.send_response(401, "Gitlab Token not authorized") + self.end_headers() + + +def get_parser(): + """Get a command line parser.""" + parser = ArgumentParser(description=__doc__, + formatter_class=ArgumentDefaultsHelpFormatter) + + parser.add_argument("--addr", + dest="addr", + default="0.0.0.0", + help="address where it listens") + parser.add_argument("--port", + dest="port", + type=int, + default=8666, + metavar="PORT", + help="port where it listens") + parser.add_argument("--cfg", + dest="cfg", + default="config.yaml", + help="path to the config file") + return parser + + +def main(addr, port): + """Start a HTTPServer which waits for requests.""" + httpd = HTTPServer((addr, port), RequestHandler) + httpd.serve_forever() + + +if __name__ == '__main__': + parser = get_parser() + + if len(sys.argv) == 0: + parser.print_help() + sys.exit(1) + args = parser.parse_args() + + # load config file + try: + with open(args.cfg, 'r') as stream: + config = yaml.load(stream) + except IOError as err: + logging.error("Config file %s could not be loaded", args.cfg) + sys.exit(1) + + main(args.addr, args.port)