Commit 030157b3 authored by Michal Sojka's avatar Michal Sojka Committed by ESW course

Add Gitlab-compatible webhook script

Also add systemd .service file to start the webhook script.
parent 419687a0
[Unit]
Description=Gitlab BRUTE AE sync
Wants=network.target
After=network.target
[Service]
Type=simple
WorkingDirectory=/home/%i/brute-ae-sync
ExecStart=/home/%i/brute-ae-sync/gitlab-webhook-receiver.py
Restart=always
User=%i
[Install]
WantedBy=multi-user.target
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2018 Michal Sojka <michal.sojka@cvut.cz>
# Copyright (c) 2017 Pascal Stauffer
#
# License: GPLv3
"""Gitlab Webhook Receiver
When gitlab is configured to "trigger" this script on Push events,
this scripts executes brutegit_ae_sync script to synchronize changes
in the triggering repository to the BRUTE repository.
You need to create a configuration file (config.yaml by default). A
simple config file is shown below:
https://gitlab.fel.cvut.cz/B161_B4M36ESW/materials:
gitlab_token: PTP2NSS2XMttNVpJfmk6
dir: /home/me/repo-dir
brutegit: git@brutegit.felk.cvut.cz:2018l_b4m36esw/ae.git
To use HTTPS, you can generate a self-signed certificate with this
command:
openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout cert.pem
"""
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer
import json
import logging
import os
import ssl
import subprocess
import sys
import yaml
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")
try:
# get payload
content_length = int(self.headers.get('content-length', "0"))
json_payload = self.rfile.read(content_length).decode()
json_params = {}
if len(json_payload) > 0:
json_params = json.loads(json_payload)
# get gitlab secret token
gitlab_token_header = self.headers.get('X-Gitlab-Token')
# get project homepage
project = json_params['project']['homepage']
ssh_url = json_params['project']['ssh_url']
before = json_params['before']
after = json_params['after']
ref = json_params['ref']
#print(json_params)
except Exception as err:
self.send_response(500, "ParamError")
logging.error("Exception during request parsing: %s(%s)", err.__class__.__name__, err)
self.end_headers()
raise
return
try:
# get directory and token from config file
gitlab_token = config[project]['gitlab_token']
gitdir = config[project]['dir']
brutegit = config[project]['brutegit']
except KeyError as err:
self.send_response(500, "KeyError")
logging.error("Project '%s' not found in %s (%s)", project, args.cfg, err)
self.end_headers()
return
# Check if the gitlab token is valid
if gitlab_token_header == gitlab_token:
try:
if not os.path.isdir(gitdir):
# Clone and setup repository for mirroring
logging.info("Clonning " + ssh_url + " to " + gitdir)
subprocess.run(["git", "clone", "--mirror", ssh_url, gitdir],
check=True, stdout=sys.stdout, stderr=sys.stderr)
os.chdir(gitdir)
subprocess.run(["git", "remote", "add", "brutegit", brutegit],
check=True, stdout=sys.stdout, stderr=sys.stderr)
subprocess.run(["git", "fetch", "brutegit", "master:ae"],
check=True, stdout=sys.stdout, stderr=sys.stderr)
else:
logging.info("Fetching updates from " + ssh_url)
os.chdir(gitdir)
subprocess.run(["git", "fetch", ssh_url],
check=True, stdout=sys.stdout, stderr=sys.stderr)
logging.info("Syncing to brutegit")
subprocess.run([brutegit_ae_sync],
input="{} {} {}\n".format(before, after, ref).encode(),
stdout=sys.stdout,
stderr=sys.stderr,
check=True)
self.send_response(200, "OK")
except OSError as err:
self.send_response(500, "OSError")
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=4553,
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.socket = ssl.wrap_socket (httpd.socket, certfile='./cert.pem', server_side=True)
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)
brutegit_ae_sync = os.getcwd() + "/brutegit-ae-sync"
main(args.addr, args.port)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment