##// END OF EJS Templates
bugzilla: add a rest api backend (usable with bugzilla 5.0+)...
John Mulligan -
r30923:78de43ab default
parent child Browse files
Show More
@@ -15,14 +15,16 b' the Mercurial template mechanism.'
15 The bug references can optionally include an update for Bugzilla of the
15 The bug references can optionally include an update for Bugzilla of the
16 hours spent working on the bug. Bugs can also be marked fixed.
16 hours spent working on the bug. Bugs can also be marked fixed.
17
17
18 Three basic modes of access to Bugzilla are provided:
18 Four basic modes of access to Bugzilla are provided:
19
20 1. Access via the Bugzilla REST-API. Requires bugzilla 5.0 or later.
19
21
20 1. Access via the Bugzilla XMLRPC interface. Requires Bugzilla 3.4 or later.
22 2. Access via the Bugzilla XMLRPC interface. Requires Bugzilla 3.4 or later.
21
23
22 2. Check data via the Bugzilla XMLRPC interface and submit bug change
24 3. Check data via the Bugzilla XMLRPC interface and submit bug change
23 via email to Bugzilla email interface. Requires Bugzilla 3.4 or later.
25 via email to Bugzilla email interface. Requires Bugzilla 3.4 or later.
24
26
25 3. Writing directly to the Bugzilla database. Only Bugzilla installations
27 4. Writing directly to the Bugzilla database. Only Bugzilla installations
26 using MySQL are supported. Requires Python MySQLdb.
28 using MySQL are supported. Requires Python MySQLdb.
27
29
28 Writing directly to the database is susceptible to schema changes, and
30 Writing directly to the database is susceptible to schema changes, and
@@ -50,11 +52,16 b' user, the email associated with the Bugz'
50 Bugzilla is used instead as the source of the comment. Marking bugs fixed
52 Bugzilla is used instead as the source of the comment. Marking bugs fixed
51 works on all supported Bugzilla versions.
53 works on all supported Bugzilla versions.
52
54
55 Access via the REST-API needs either a Bugzilla username and password
56 or an apikey specified in the configuration. Comments are made under
57 the given username or the user assoicated with the apikey in Bugzilla.
58
53 Configuration items common to all access modes:
59 Configuration items common to all access modes:
54
60
55 bugzilla.version
61 bugzilla.version
56 The access type to use. Values recognized are:
62 The access type to use. Values recognized are:
57
63
64 :``restapi``: Bugzilla REST-API, Bugzilla 5.0 and later.
58 :``xmlrpc``: Bugzilla XMLRPC interface.
65 :``xmlrpc``: Bugzilla XMLRPC interface.
59 :``xmlrpc+email``: Bugzilla XMLRPC and email interfaces.
66 :``xmlrpc+email``: Bugzilla XMLRPC and email interfaces.
60 :``3.0``: MySQL access, Bugzilla 3.0 and later.
67 :``3.0``: MySQL access, Bugzilla 3.0 and later.
@@ -135,7 +142,7 b' The ``[usermap]`` section is used to spe'
135 committer email to Bugzilla user email. See also ``bugzilla.usermap``.
142 committer email to Bugzilla user email. See also ``bugzilla.usermap``.
136 Contains entries of the form ``committer = Bugzilla user``.
143 Contains entries of the form ``committer = Bugzilla user``.
137
144
138 XMLRPC access mode configuration:
145 XMLRPC and REST-API access mode configuration:
139
146
140 bugzilla.bzurl
147 bugzilla.bzurl
141 The base URL for the Bugzilla installation.
148 The base URL for the Bugzilla installation.
@@ -148,6 +155,13 b' bugzilla.user'
148 bugzilla.password
155 bugzilla.password
149 The password for Bugzilla login.
156 The password for Bugzilla login.
150
157
158 REST-API access mode uses the options listed above as well as:
159
160 bugzilla.apikey
161 An apikey generated on the Bugzilla instance for api access.
162 Using an apikey removes the need to store the user and password
163 options.
164
151 XMLRPC+email access mode uses the XMLRPC access mode configuration items,
165 XMLRPC+email access mode uses the XMLRPC access mode configuration items,
152 and also:
166 and also:
153
167
@@ -279,6 +293,7 b' All the above add a comment to the Bugzi'
279
293
280 from __future__ import absolute_import
294 from __future__ import absolute_import
281
295
296 import json
282 import re
297 import re
283 import time
298 import time
284
299
@@ -288,6 +303,7 b' from mercurial import ('
288 cmdutil,
303 cmdutil,
289 error,
304 error,
290 mail,
305 mail,
306 url,
291 util,
307 util,
292 )
308 )
293
309
@@ -773,6 +789,136 b' class bzxmlrpcemail(bzxmlrpc):'
773 cmds.append(self.makecommandline("resolution", self.fixresolution))
789 cmds.append(self.makecommandline("resolution", self.fixresolution))
774 self.send_bug_modify_email(bugid, cmds, text, committer)
790 self.send_bug_modify_email(bugid, cmds, text, committer)
775
791
792 class NotFound(LookupError):
793 pass
794
795 class bzrestapi(bzaccess):
796 """Read and write bugzilla data using the REST API available since
797 Bugzilla 5.0.
798 """
799 def __init__(self, ui):
800 bzaccess.__init__(self, ui)
801 bz = self.ui.config('bugzilla', 'bzurl',
802 'http://localhost/bugzilla/')
803 self.bzroot = '/'.join([bz, 'rest'])
804 self.apikey = self.ui.config('bugzilla', 'apikey', '')
805 self.user = self.ui.config('bugzilla', 'user', 'bugs')
806 self.passwd = self.ui.config('bugzilla', 'password')
807 self.fixstatus = self.ui.config('bugzilla', 'fixstatus', 'RESOLVED')
808 self.fixresolution = self.ui.config('bugzilla', 'fixresolution',
809 'FIXED')
810
811 def apiurl(self, targets, include_fields=None):
812 url = '/'.join([self.bzroot] + [str(t) for t in targets])
813 qv = {}
814 if self.apikey:
815 qv['api_key'] = self.apikey
816 elif self.user and self.passwd:
817 qv['login'] = self.user
818 qv['password'] = self.passwd
819 if include_fields:
820 qv['include_fields'] = include_fields
821 if qv:
822 url = '%s?%s' % (url, util.urlreq.urlencode(qv))
823 return url
824
825 def _fetch(self, burl):
826 try:
827 resp = url.open(self.ui, burl)
828 return json.loads(resp.read())
829 except util.urlerr.httperror as inst:
830 if inst.code == 401:
831 raise error.Abort(_('authorization failed'))
832 if inst.code == 404:
833 raise NotFound()
834 else:
835 raise
836
837 def _submit(self, burl, data, method='POST'):
838 data = json.dumps(data)
839 if method == 'PUT':
840 class putrequest(util.urlreq.request):
841 def get_method(self):
842 return 'PUT'
843 request_type = putrequest
844 else:
845 request_type = util.urlreq.request
846 req = request_type(burl, data,
847 {'Content-Type': 'application/json'})
848 try:
849 resp = url.opener(self.ui).open(req)
850 return json.loads(resp.read())
851 except util.urlerr.httperror as inst:
852 if inst.code == 401:
853 raise error.Abort(_('authorization failed'))
854 if inst.code == 404:
855 raise NotFound()
856 else:
857 raise
858
859 def filter_real_bug_ids(self, bugs):
860 '''remove bug IDs that do not exist in Bugzilla from bugs.'''
861 badbugs = set()
862 for bugid in bugs:
863 burl = self.apiurl(('bug', bugid), include_fields='status')
864 try:
865 self._fetch(burl)
866 except NotFound:
867 badbugs.add(bugid)
868 for bugid in badbugs:
869 del bugs[bugid]
870
871 def filter_cset_known_bug_ids(self, node, bugs):
872 '''remove bug IDs where node occurs in comment text from bugs.'''
873 sn = short(node)
874 for bugid in bugs.keys():
875 burl = self.apiurl(('bug', bugid, 'comment'), include_fields='text')
876 result = self._fetch(burl)
877 comments = result['bugs'][str(bugid)]['comments']
878 if any(sn in c['text'] for c in comments):
879 self.ui.status(_('bug %d already knows about changeset %s\n') %
880 (bugid, sn))
881 del bugs[bugid]
882
883 def updatebug(self, bugid, newstate, text, committer):
884 '''update the specified bug. Add comment text and set new states.
885
886 If possible add the comment as being from the committer of
887 the changeset. Otherwise use the default Bugzilla user.
888 '''
889 bugmod = {}
890 if 'hours' in newstate:
891 bugmod['work_time'] = newstate['hours']
892 if 'fix' in newstate:
893 bugmod['status'] = self.fixstatus
894 bugmod['resolution'] = self.fixresolution
895 if bugmod:
896 # if we have to change the bugs state do it here
897 bugmod['comment'] = {
898 'comment': text,
899 'is_private': False,
900 'is_markdown': False,
901 }
902 burl = self.apiurl(('bug', bugid))
903 self._submit(burl, bugmod, method='PUT')
904 self.ui.debug('updated bug %s\n' % bugid)
905 else:
906 burl = self.apiurl(('bug', bugid, 'comment'))
907 self._submit(burl, {
908 'comment': text,
909 'is_private': False,
910 'is_markdown': False,
911 })
912 self.ui.debug('added comment to bug %s\n' % bugid)
913
914 def notify(self, bugs, committer):
915 '''Force sending of Bugzilla notification emails.
916
917 Only required if the access method does not trigger notification
918 emails automatically.
919 '''
920 pass
921
776 class bugzilla(object):
922 class bugzilla(object):
777 # supported versions of bugzilla. different versions have
923 # supported versions of bugzilla. different versions have
778 # different schemas.
924 # different schemas.
@@ -781,7 +927,8 b' class bugzilla(object):'
781 '2.18': bzmysql_2_18,
927 '2.18': bzmysql_2_18,
782 '3.0': bzmysql_3_0,
928 '3.0': bzmysql_3_0,
783 'xmlrpc': bzxmlrpc,
929 'xmlrpc': bzxmlrpc,
784 'xmlrpc+email': bzxmlrpcemail
930 'xmlrpc+email': bzxmlrpcemail,
931 'restapi': bzrestapi,
785 }
932 }
786
933
787 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
934 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
General Comments 0
You need to be logged in to leave comments. Login now