diff options
-rw-r--r-- | yagd.py | 271 |
1 files changed, 253 insertions, 18 deletions
@@ -1,46 +1,281 @@ #!/usr/bin/env python3 -from flask import Flask, request +from flask import Flask, request, abort from sh import cd, git import json, configparser +import traceback +import requests +import socket +import posixpath +from sys import stderr +from hashlib import sha1 +import hmac +import systemd.journal as journal +import logging app = Flask(__name__) -config = configparser.ConfigParser() -config.read('config.ini') +config = configparser.SafeConfigParser() +config.read('/etc/yagd.ini') +channels = config['yagd']['channels'] repos = config['yagd']['repos'] basedir = config['yagd']['repodir'] +log = logging.getLogger('yagd') +log.addHandler(journal.JournalHandler(SYSLOG_IDENTIFIER='yagd')) +log.setLevel(logging.INFO) + +COLORS = {'reset': '\x0f', 'yellow': '\x0307', 'green': '\x0303', 'bold': '\x02', 'red': '\x0305', 'cyan': '\x0310'} + def update_mirror(repo): cd(basedir + repo + '.git') git.remote.update() +def shorten(url): + res = requests.post('http://git.io', data={'url': url.encode('utf-8')}) + if res.status_code == 201: + url = res.headers.get('Location', url) + else: + log.info("git.io returned status: {}".format(res.reason)) + return url + +def verify_signature(client_secret, raw_response, signature): + digest = hmac.new(client_secret.encode('utf-8'), + msg = raw_response.encode('utf-8'), + digestmod = sha1, + ).hexdigest() + log.debug('Signature received: {:s}'.format(signature)) + log.debug('Digest calculated: {:s}'.format(digest)) + return digest == signature + +def format_commit(everything, commit): + short_url = shorten(commit["url"]) + message = commit["message"].split("\n")[0][:80] + return ("{green}{author}{reset} {repo}:{yellow}{branch}{reset} " + "{bold}[{sha}]{reset}: {msg} {red}<{url}>{reset}" + ).format( + project = everything["repository"]["owner"]["name"], + repo = everything["repository"]["name"], + branch = everything["ref"].split("/")[-1], + author = commit["author"]["username"], + sha = commit["id"][:7], + msg = message, + url = short_url, + **COLORS) + +def format_tag(everything): + return ("{green}{pusher}{reset} {repo}: " + "{bold}[{sha}]{reset} tagged as {yellow}{tag}{reset}" + ).format( + project = everything["repository"]["owner"]["name"], + repo = everything["repository"]["name"], + pusher = everything["pusher"]["username"], + tag = everything["ref"].split("/")[-1], + sha = everything["head_commit"]["id"][:7], + **COLORS) + +def format_issue(everything): + short_url = shorten(everything['issue']['html_url']) + variables = { + 'user': everything['issue']['user']['login'], + 'repo': everything['repository']['name'], + 'action': everything['action'], + 'number': everything['issue']['number'], + 'title': everything['issue']['title'], + 'url': short_url + } + variables.update(COLORS) + message = '{green}{user}{reset} {repo} {cyan}issue #{number}{reset}' + + if everything['action'] == 'labeled': + variables.update({'label': everything['label']['name']}) + message += ' {bold}label ‘{label}’ added{reset}: {title} {red}<{url}>{reset}' + message = message.format(**variables) + + elif everything['action'] == 'unlabeled': + variables.update({'label': everything['label']['name']}) + message += ' {bold}label ‘{label}’ removed{reset}: {title} {red}<{url}>{reset}' + message = message.format(**variables) + + elif everything['action'] == 'assigned': + variables.update({'assignee': everything['assignee']['login']}) + message += ' {bold}assigned to {assignee}{reset}: {title} {red}<{url}>{reset}' + message = message.format(**variables) + + elif everything['action'] == 'unassigned': + variables.update({'assignee': everything['assignee']['login']}) + message += ' {bold}unassigned from {assignee}{reset}: {title} {red}<{url}>{reset}' + message = message.format(**variables) + + else: + message += ' {bold}{action}{reset}: {title} {red}<{url}>{reset}' + message = message.format(**variables) + + return message + +def format_pull_request(everything): + short_url = shorten(everything['pull_request']['html_url']) + variables = { + 'user': everything['pull_request']['user']['login'], + 'repo': everything['repository']['name'], + 'action': everything['action'], + 'number': everything['number'], + 'title': everything['pull_request']['title'], + 'url': short_url + } + variables.update(COLORS) + message = '{green}{user}{reset} {repo} {cyan}PR #{number}{reset}' + + if everything['action'] == 'labeled': + variables.update({'label': everything['label']['name']}) + message += ' {bold}label ‘{label}’ added{reset}: {title} {red}<{url}>{reset}' + message = message.format(**variables) + + elif everything['action'] == 'unlabeled': + variables.update({'label': everything['label']['name']}) + message += ' {bold}label ‘{label}’ removed{reset}: {title} {red}<{url}>{reset}' + message = message.format(**variables) + + elif everything['action'] == 'assigned': + variables.update({'assignee': everything['assignee']['login']}) + message += ' {bold}assigned to {assignee}{reset}: {title} {red}<{url}>{reset}' + message = message.format(**variables) + + elif everything['action'] == 'unassigned': + variables.update({'assignee': everything['assignee']['login']}) + message += ' {bold}unassigned from {assignee}{reset}: {title} {red}<{url}>{reset}' + message = message.format(**variables) + + elif everything['action'] == 'closed': + if everything['pull_request']['merged'] == True: + message += ' {bold}merged{reset}: {title} {red}<{url}>{reset}' + else: + message += ' {bold}{action}{reset}: {title} {red}<{url}>{reset}' + message = message.format(**variables) + + elif everything['action'] == 'synchronize': + message += ' {bold}synchronized{reset}: {title} {red}<{url}>{reset}' + message = message.format(**variables) + + else: + message += ' {bold}{action}{reset}: {title} {red}<{url}>{reset}' + message = message.format(**variables) + + return message + + +def send_to_irker(message, channels): + log.info("{chans}: {msg}".format(chans=",".join(channels), msg=message)) + envelope = { "to": channels, "privmsg": message } + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.sendto(json.dumps(envelope).encode("utf-8"), ("localhost", 6659)) + +def process_blob(blob): + if (blob["created"] or blob["deleted"]) == True: + if blob["created"] == True: + action = 'created' + else: + action = 'deleted' + + message = ('{green}{pusher}{reset} {cyan}{action} branch{reset} ' + '{repo}:{yellow}{branch}{reset}' + ).format( + pusher = blob['pusher']['name'], + action = action, + repo = blob['repository']['name'], + branch = blob['ref'].split('/')[-1], + **COLORS) + send_to_irker(message, channels) + + elif blob["forced"] == True: + message = ('{green}{pusher}{reset} {red}force pushed{reset} ' + 'to {repo}:{yellow}{branch}{reset}' + ).format( + pusher = blob['pusher']['name'], + repo = blob['repository']['name'], + branch = blob['ref'].split('/')[-1], + **COLORS) + send_to_irker(message, channels) + + for commit in blob["commits"]: + try: + if blob["ref"].split("/")[-1] != 'coverity_scan': + message = format_commit(blob, commit) + send_to_irker(message, channels) + except: + traceback.print_exc() + if blob["ref"].startswith("refs/tags"): + try: + message = format_tag(blob) + fake_commit = {"author":blob["pusher"]} #TODO: factor this properly + send_to_irker(message, channels) + except: + traceback.print_exc() + +def handle_push(data): + user = data['pusher']['name'] + repo = data['repository']['name'] + repo_fn = data['repository']['full_name'] + ref = data['ref'] + before = data['before'] + after = data['after'] + compare = data['compare'] + forced = data['forced'] + process_blob(data) + + if repo in repos: + update_mirror(repo) + +def handle_issue(data): + repo = data['repository']['name'] + user = data['issue']['user']['login'] + action = data['action'] + number = data['issue']['number'] + title = data['issue']['title'] + url = data['issue']['html_url'] + + message = format_issue(data) + send_to_irker(message, channels) + +def handle_pull_request(data): + repo = data['repository']['name'] + user = data['pull_request']['user']['login'] + action = data['action'] + number = data['number'] + title = data['pull_request']['title'] + url = data['pull_request']['html_url'] + + message = format_pull_request(data) + send_to_irker(message, channels) @app.route('/',methods=['POST']) def index(): + secret = config['yagd']['github_secret'] data = json.loads(request.data.decode('utf-8')) + event = request.headers.get('X-GitHub-Event') + signature = request.headers.get('X-Hub-Signature').replace('sha1=', '') + + if not verify_signature(secret, request.data.decode('utf-8'), signature): + abort(401) - if request.headers.get('X-GitHub-Event') == 'ping': - print("Ping. Zen: {}".format(data['zen'])) + if event == 'ping': + log.info("Ping. Zen: {}".format(data['zen'])) - elif request.headers.get('X-GitHub-Event') == 'push': - user = data['pusher']['name'] - repo = data['repository']['name'] - repo_fn = data['repository']['full_name'] - ref = data['ref'] - before = data['before'] - after = data['after'] - compare = data['compare'] - forced = data['forced'] - print('Push to {}: user {} updated ref {} from {} to {}. {}'.format(repo_fn, user, ref, before, after, compare)) + elif event == 'push': + handle_push(data) - if repo in repos: - update_mirror(repo) + elif event == 'issues': + handle_issue(data) + + elif event == 'pull_request': + handle_pull_request(data) else: - print("New notification: {}".format(data)) + log.info("New notification: {}".format(data)) return "OK" if __name__ == '__main__': app.run(port=5002, debug=True) + app.logger.addHandler(journal.JournalHandler(SYSLOG_IDENTIFIER='yagd')) + |