6f9f87f396c21c4588a8cb3fb9ccd4a81855d100
2 from errbot
import BotPlugin
, botcmd
, webhook
3 from errbot
.backends
import xmpp
4 from errcron
.bot
import CrontabMixin
8 # TODO: Add certificate verification to the gitlab API calls.
11 class Marge(BotPlugin
, CrontabMixin
):
13 I remind you about merge requests
17 Add a merge request webook of the form
18 'https://webookserver/margeboot/<rooms>'
19 to the projects you want tracked. <rooms> should be a
20 comma-separated list of short room names (anything before the '@')
21 that you want notified.
23 Add <roomname>@domain to the CHATROOM_PRESENCE list in config.py for
24 rooms margebot should join
28 # Set in config now: '0 11,17 * * * .crontab_hook' # 7:00AM and 1:00PM EST warnings
31 def __init__(self
, *args
, **kwargs
):
33 self
.chatroom_host
= None
34 super().__init
__(*args
, **kwargs
)
36 def get_configuration_template(self
):
38 GITLAB_HOST: Host name of your gitlab server
39 GITLAB_ADMIN_TOKEN: PAT from an admin's https://${GIT_HOST}/profile/personal_access_tokens page.
40 CHATROOM_HOST: Chatroom host. Usually 'chatroom' + FQDN of Jabber server
41 CRONTAB: Schedule of automated merge request checks in '%M %H %d %m %w' format
42 VERIFY_SSL : True, False, or path to CA cert to verify cert
44 return {'GITLAB_HOST': 'gitlab.example.com',
45 'GITLAB_ADMIN_TOKEN' : 'gitlab-admin-user-private-token',
46 'CHATROOM_HOST': 'conference.jabber.example.com',
47 'CRONTAB' : '0 11,17 * * *',
50 def check_configuration(self
, configuration
):
51 super().check_configuration(configuration
)
55 self
.log
.info('Margebot is not configured. Forbid activation')
57 self
.git_host
= self
.config
['GITLAB_HOST']
58 self
.chatroom_host
= self
.config
['CHATROOM_HOST']
59 Marge
.CRONTAB
= ['{} .crontab_hook'.format(self
.config
['CRONTAB']) ]
60 self
.gitlab
= gitlab
.Gitlab(self
.git_host
, self
.config
['GITLAB_ADMIN_TOKEN'], verify_ssl
=self
.config
['VERIFY_SSL'])
61 self
.activate_crontab()
66 # TODO: Anything special for closing gitlab ?
69 @webhook('/margebot/<rooms>/')
70 def gitlab_hook(self
, request
, rooms
):
72 Webhook that listens on http://<server>:<port>/gitlab
75 self
.log
.info('margebot webhook request: {}'.format(request
))
76 self
.log
.info('margebot webhook rooms {}'.format(rooms
))
78 # TODO: Will errbot return a json struct or not ?
80 # verify it's a merge request
81 if request
['object_kind'] != 'merge_request':
82 self
.log
.error('expecting object_kind of merge_request but got {}'.format(request
['object_kind']))
83 self
.log
.error('request: {}'.format(request
))
84 elif request
['object_attributes']['state'] == 'opened':
87 # - check for reopened / request['object_attributes']['action'] == 'reopn'
88 # (there's no 'action': 'opened' for MRs are created...
89 # - pop open_mrs when MRs are closed (action == close / state == closed
91 if request
['object_attributes']['work_in_progress']:
95 url
= request
['project']['homepage']
96 state
= request
['object_attributes']['state']
97 title
= request
['object_attributes']['title']
99 author_id
= request
['object_attributes']['author_id'] # map this to user name ...
100 author
= self
.gitlab
.getuser(author_id
)
101 author_name
= author
['username']
103 target_project_id
= request
['object_attributes']['target_project_id']
104 iid
= request
['object_attributes']['iid']
106 user_name
= request
['user']['username'] # will this always be Administrator ?
108 msg_template
= "New Review: {} has opened a new {} MR: \"{}\"\n{}/merge_requests/{}"
109 msg
= msg_template
.format(author_name
, wip
, title
, url
, iid
)
111 for a_room
in rooms
.split(','):
113 self
.send(self
.build_identifier(a_room
+ '@' + self
.chatroom_host
), msg
)
115 if 'OPEN_MRS' not in self
.keys():
117 self
['OPEN_MRS'] = empty_dict
119 with self
.mutable('OPEN_MRS') as open_mrs
:
120 open_mrs
[(target_project_id
, iid
, rooms
)] = True
124 def crontab_hook(self
, polled_time
):
126 Send a scheduled message to the rooms margebot is watching
127 about open MRs the room cares about.
130 self
.log
.info("crontab_hook triggered at {}".format(polled_time
))
132 reminder_msg
= {} # Map of reminder_msg['roomname@domain'] = msg
134 # initialize the reminders
137 reminder_msg
[a_room
.node
] = ''
142 # Let's walk through the MRs we've seen already:
143 with self
.mutable('OPEN_MRS') as open_mrs
:
144 for (project
, iid
, notify_rooms
) in open_mrs
:
146 # Lookup the MR from the project/iid
147 a_mr
= self
.gitlab
.getmergerequest(project
, iid
)
149 self
.log
.info("a_mr: {} {} {} {}".format(project
, iid
, notify_rooms
, a_mr
['state']))
151 # If the MR is no longer open, skip to the next MR,
152 # and don't include this MR in the next check
153 if a_mr
['state'] != 'opened':
156 still_open_mrs
[(project
, iid
, notify_rooms
)] = True
158 # TODO: Warn if an open MR has has conflicts (merge_status == ??)
159 # TODO: Include the count of opened MR notes (does the API show resolved state ??)
161 # getapprovals is only available in GitLab 8.9 EE or greater (not the open source CE version)
162 # approvals = self.gitlab.getapprovals(a_mr['id'])
164 # for approved in approvals['approved_by']:
165 # also_approved += "," + approved['user']['name']
167 upvotes
= a_mr
['upvotes']
169 msg
= "\n{}: Has 2+ upvotes / Could be merged in now.".format(a_mr
['web_url'])
171 msg_template
= "\n{}: Waiting for another upvote."
172 msg
= msg_template
.format(a_mr
['web_url'])
174 msg
= '\n{}: Noo upvotes / Please Review.'.format(a_mr
['web_url'])
176 for a_room
in notify_rooms
.split(','):
177 reminder_msg
[a_room
] += msg
179 # Remind each of the rooms about open MRs
180 for a_room
, room_msg
in reminder_msg
.items():
183 msg_template
= "Heads up these MRs need some attention:{}\n"
184 msg_template
+= "You can get an updated list with the '/msg MargeB !reviews' command."
185 msg
= msg_template
.format(room_msg
)
186 self
.send(self
.build_identifier(a_room
+ '@' + self
.config
['CHATROOM_HOST']), msg
)
188 self
['OPEN_MRS'] = still_open_mrs
191 def reviews(self
, msg
, args
): # a command callable with !mrs
193 Returns a list of MRs that are waiting for some luv.
194 Also returns a list of MRs that have had enough luv but aren't merged in yet.
196 ## Sending directly to Margbot: sender in the form sender@....
197 ## Sending to a chatroom: snder in the form room@rooms/sender
199 if msg
.frm
.domain
== self
.config
['CHATROOM_HOST']:
200 sender
= msg
.frm
.resource
202 sender
= msg
.frm
.node
204 if 'OPEN_MRS' not in self
.keys():
205 return "No MRs to review"
207 sender_gitlab_id
= None
208 for user
in self
.gitlab
.getusers():
209 if user
['username'] == sender
:
210 sender_gitlab_id
= user
['id']
213 if not sender_gitlab_id
:
214 self
.log
.error('problem mapping {} to a gitlab user'.format(sender
))
215 return "Sorry, I couldn't find your gitlab account."
217 # Walk through the MRs we've seen already:
220 with self
.mutable('OPEN_MRS') as open_mrs
:
221 for (project
, iid
, notify_rooms
) in open_mrs
:
223 # Lookup the MR from the project/iid
224 a_mr
= self
.gitlab
.getmergerequest(project
, iid
)
226 # If the MR is no longer open, skip to the next MR,
227 # and don't include this MR in the next check
228 if a_mr
['state'] != 'opened':
231 still_open_mrs
[(project
, iid
, notify_rooms
)] = True
233 authored
= (a_mr
['author']['id'] == sender_gitlab_id
)
234 already_approved
= False
236 # getapprovals is currently only available in GitLab >= 8.9 EE (not available in the CE yet)
237 # approvals = self.gitlab.getapprovals(a_mr['id'])
239 # for approved in approvals['approved_by']:
240 # if approved['user']['id'] == sender_gitlab_id:
241 # already_approved = True
243 # also_approved += "," + approved['user']['name']
245 upvotes
= a_mr
['upvotes']
247 msg
+= "\n{}: has 2+ upvotes and could be merged in now.".format(a_mr
['web_url'])
250 msg
+= "\n{}: is waiting for another upvote.".format(a_mr
['web_url'])
252 msg
+= "\n{}: Your MR is waiting for another upvote.".format(a_mr
['web_url'])
256 msg
+= '\n{}: Has no upvotes and needs your attention.'.format(a_mr
['web_url'])
258 msg
+= '\n{}: Your MR has no upvotes.'.format(a_mr
['web_url'])
261 response
= 'Hi {}\n{}'.format(sender
, 'I found no open MRs for you.')
263 response
= 'Hi {}\n{}'.format(sender
,msg
)
265 # self.send(self.build_identifier(msg.frm), response)
267 with self
.mutable('OPEN_MRS') as open_mrs
:
268 open_mrs
= still_open_mrs
273 def hello(self
, msg
, args
):
277 def xyzzy(self
, msg
, args
):
278 yield "/me whispers \"All open MRs have ben merged into master.\""
280 yield "(just kidding)"