From 1b4b578d0b72f9e9dc8dec57e36dfe2051feab88 Mon Sep 17 00:00:00 2001 From: Jude N Date: Wed, 28 Jun 2017 07:41:49 -0400 Subject: [PATCH] Initial commit --- README.md | 5 + Secret.py | 9 ++ marge.plug | 11 +++ marge.py | 266 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 291 insertions(+) create mode 100644 README.md create mode 100755 Secret.py create mode 100644 marge.plug create mode 100755 marge.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..4acd309 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +_margebot_ -- I remind you about outstanding Gitlab merge requests + + + + diff --git a/Secret.py b/Secret.py new file mode 100755 index 0000000..18d15e5 --- /dev/null +++ b/Secret.py @@ -0,0 +1,9 @@ +# go away + +# https://docs.gitlab.com/ee/api/README.html#personal-access-tokens + +# token for a normal, non-admin user +my_token="get-this-from-gitlab" + +# token for actions that require admin access +admin_token="get-this-from-gitlab" diff --git a/marge.plug b/marge.plug new file mode 100644 index 0000000..6138f43 --- /dev/null +++ b/marge.plug @@ -0,0 +1,11 @@ +[Core] +Name = Marge +Module = marge + +[Documentation] +Description = Marge helps with Gitlab merge requests + +[Python] +Version = 3 + + diff --git a/marge.py b/marge.py new file mode 100755 index 0000000..f00b3fc --- /dev/null +++ b/marge.py @@ -0,0 +1,266 @@ +import re +from errbot import BotPlugin, botcmd, webhook +from errbot.backends import xmpp +from errcron.bot import CrontabMixin +import gitlab + +from Secret import admin_token +git_host = "gitlab.services.zz" #TODO: move this to some sort of plugin config + + +class Marge(BotPlugin, CrontabMixin): + """ + I remind you about merge requests + + Use: + In gitlab: + Add a merge request webook of the form 'https://webookserver/margeboot/' to the projects you want tracked. + should be a comma-separated list of rooms you want notified. + In errbot: + Add @domain to the CHATROOM_PRESENCE list in config.py for rooms margebot should join + """ + + CRONTAB = [ + '0 11,17 * * * .crontab_hook' # 7:00AM and 1:00PM EST warnings + ] + +# def __init__(self, bot): +# self.gitlab = None + + def configure(self, configuration): + ## TODO: set up a connection to the gitlab API + + self.gitlab = gitlab.Gitlab(git_host,admin_token,verify_ssl=False) + +# notify_re = re.compile('notify_(.*)') + +# def get_mr_rooms(self, project_id): +# """ +# Return a list of errbot room '@domain' names which have a 'notify_' label on the project +# Log an error if you found a 'notify_' but margebot isn't in a room... +# """ +# retval = [] +# labels = this.gitlab.getlabels(project_id) +# for a_label in labels: +# notify_match = self.notify_re.search(a_label['name']) +# if notify_match: +# roomname = notify_match.group(1) +# +# b_room_found = False +# marge_rooms = xmpp.rooms() +# for a_room in marge_rooms: +# if a_room.node() == roomname: +# retval.append(a_room.person()) # yeah rooms are people: person = node@domain +# b_room_found = True +# if not b_room_found: +# self.log.error("Label of {} found, but margebot isn't tracking that room".format(roomname)) +# else: +# retval.append(roomname) +# return retval + + @webhook('/margebot//' + def gitlab_hook(self, request): + """ + Webhook that listens on http://:/gitlab + """ + + # TODO: Will errbot return a json struct or not ? + + # verify it's a merge request + if requests['object_kind'] != 'merge_request': + self.log.error('expecting object_kind of merge_request but got {}'.format(requests['object_kind'])) + elif request['object_attributes']['state'] == 'opened': + if request['object_attributes']['work_in_progress']: + wip = "WIP" + else: + wip = "" + url = request['object_attributes']['url'] + state = request['object_attributes']['state'] + title = request['object_attributes']['title'] + + author_id = request['object_attributes']['author_id'] # map this to user name ... + author = self.gitlab.getuser(author_id) + author_name = author['username'] + + target_project_id = request['object_attributes']['target_project_id'] + iid = request['object_attributes']['iid'] + + user_name = request['user']['username'] # will this always be Administrator ? + + message = "Reviews: {} has opened a new {} MR: {}\n{}".format(author_name, wip, title, url) + + # TODO: Maybe also check the notify_ labels assigned to the MR as well ? + #mr_rooms = self.get_mr_rooms(target_project_id) + for a_room in rooms.split(','): + self.send( self.build_identifier(a_room), message) + + with self.mutable('OPEN_MRS') as open_mrs: + open_mrs[(target_project_id,iid,rooms)] = True + + return "OK" + +# def get_open_mrs(self, roomname=None, log_warnings=False): +# mrs = [] +# for a_project in self.gitlab.getprojects(): +# for a_mr in self.gitlab.getmergerequests(a_project['id'], state='opened'): +# rooms = self.get_mr_rooms(a_mr['target_project_id']) +# if len(rooms) == 0 and log_warnings: +# self.log.warning('No notify room with MRs in project: {}'.format(a_mr['target_project_id'])) +# elif not roomname: +# mrs.append(a_mr) +# else: +# for a_room in rooms: +# if a_room.startswith(roomname): +# mrs.append(a_mr) +# return mrs + + def crontab_hook(self): + """ + Send a scheduled message to the rooms margebot is watching about open MRs the room cares about. + """ + + reminder_msg = {} # Map of reminder_msg['roomname@domain'] = msg + + # initialize the reminders + rooms = xmpp.rooms() + for a_room in rooms: + reminder_msg[a_room.node()] = '' + + still_open_mrs = {} + + # Let's walk through the MRs we've seen already: + for (project,iid,notify_rooms) in self['OPEM_MRS']: + + # Lookup the MR from the project/iid + a_mr = self.gitlab.getmergerequest(project, iid) + + # If the MR is no longer open, skip to the next MR, + # and don't include this MR in the next check + if a_mr['state'] != 'open': + continue + else: + still_open_mrs[(project, iid, notify_rooms) = True + + # TODO: Warn if an open MR has has conflicts (merge_status == ??) + # TODO: Include the count of opened MR notes (does the API show resolved state ??) + + approvals = self.gitlab.getapprovals(a_mr['id']) + also_approved = "" + for approved in approvals['approved_by']: + also_approved += "," + approved['user']['name'] + + upvotes = a_mr['upvotes'] + if upvotes >= 2: + msg = "\n{}: Has 2+ upvotes and could be merged in now.".format(a_mr['web_url']) + elif upvotes == 1: + msg = "\n{}: {} already approved and is waiting for another upvote.".format(a_mr['web_url'], also_approved[1:]) + else: + msg = '\n{}: Has no upvotes.'.format(a_mr['web_url']) + + for a_room in notify_rooms.split(','): + reminder_msg[a_room] += msg + + # Remind each of the rooms about open MRs + for a_room, room_msg in reminder_msg.iteritems(): + if room_msg != "": + self.send(self.build_identifier(a_room), "Heads up these MRs need some luv:{}\n You can get a list of open reviews I know about by sending me a 'Marge, reviews' command.".format(room_msg)) + + self['OPEN_MRS'] = still_open_mrs + +# def callback_message(self, mess): +# """ +# Look for messages that include an URL that looks like 'https://///merge_request/' +# Check if gitlab mergerequest webhook already exists for this group/project, and this it reports to this room ? +# Add the project to the OPEN_MRS list +# """ +# TODO: compiled re check against mess.body searching for +# https://= 2: + msg = "\n{}: has 2+ upvotes from {} and could be merged in now.".format(a_mr['web_url'], also_approved[1:]) + elif upvotes == 1: + if not authored: + if already_approved: + msg = "\n{}: has already been approved by you and is waiting for another upvote.".format(a_mr['web_url']) + else: + msg = "\n{}: has been approved by {} and is waiting for your upvote.".format(a_mr['web_url'], also_approved[1:]) + else: + msg = "\n{}: Your MR has approved by {} and is waiting for another upvote.".format(a_mr['web_url'], also_approved[1:]) + + else: + if not authored: + msg = '\n{}: Has no upvotes and needs your attention.'.format(a_mr['web_url']) + else: + msg = '\n{}: Your MR has no upvotes.'.format(a_mr['web_url']) + + + if msg == "": + response = 'I found no open merge requests for you.' + else: + response = msg + + self.send(self.build_identifier(msg.frm), response) + + self['OPEN_MRS'] = still_open_mrs + + return + + @botcmd() + def hello(self,msg, args): + return "Hi there" -- 2.39.2