summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--yagd.py271
1 files changed, 253 insertions, 18 deletions
diff --git a/yagd.py b/yagd.py
index fe8442a..6db7651 100644
--- a/yagd.py
+++ b/yagd.py
@@ -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'))
+