12221.1.7
by Jeroen Vermeulen
s/Unparseable/Unparsable/g, plus lint. |
1 |
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
|
8687.15.15
by Karl Fogel
Add the copyright header block to files under lib/lp/bugs/. |
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
3 |
|
4 |
"""Bugzilla ExternalBugTracker utility."""
|
|
5 |
||
6 |
__metaclass__ = type |
|
6290.4.8
by Graham Binns
Review changes for barry. |
7 |
__all__ = [ |
8 |
'Bugzilla', |
|
9150.2.2
by Graham Binns
Add a BugzillaAPI ExternalBugTracker. |
9 |
'BugzillaAPI', |
6290.4.8
by Graham Binns
Review changes for barry. |
10 |
'BugzillaLPPlugin', |
6532.1.10
by Graham Binns
Added tests for @needs_authentication. |
11 |
'needs_authentication', |
6290.4.8
by Graham Binns
Review changes for barry. |
12 |
]
|
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
13 |
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
14 |
from email.Utils import parseaddr |
10136.3.2
by Gavin Panella
Make _parseVersion() just pick out numbers from the version string. |
15 |
import re |
12599.4.2
by Leonard Richardson
Merge from trunk. |
16 |
from httplib import BadStatusLine |
17 |
from urllib2 import URLError |
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
18 |
from xml.dom import minidom |
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
19 |
import xml.parsers.expat |
6290.4.4
by Graham Binns
Added tests and implementation for getCurrentDBTime(). |
20 |
import xmlrpclib |
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
21 |
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
22 |
import pytz |
6506.5.8
by Graham Binns
Added tests and implementation for getMessageForComment(). |
23 |
from zope.component import getUtility |
6506.5.5
by Graham Binns
Added tests and implementation for getCommentIds(). |
24 |
from zope.interface import implements |
25 |
||
6532.1.3
by Graham Binns
Added tests for _authenticate. |
26 |
from canonical.config import config |
12929.9.2
by j.c.sackett
Moved messages from canonical to lp.services |
27 |
from lp.services.messages.interfaces.message import IMessageSet |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
28 |
from canonical.launchpad.webapp.url import ( |
29 |
urlappend, |
|
30 |
urlparse, |
|
31 |
)
|
|
8523.3.1
by Gavin Panella
Bugs tree reorg after automated migration. |
32 |
from lp.bugs.externalbugtracker.base import ( |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
33 |
BugNotFound, |
34 |
BugTrackerAuthenticationError, |
|
35 |
BugTrackerConnectError, |
|
36 |
ExternalBugTracker, |
|
37 |
InvalidBugId, |
|
38 |
LookupTree, |
|
11403.1.6
by Henning Eggers
Merged devel r11406, resolved conflict. |
39 |
UnknownRemoteImportanceError, |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
40 |
UnknownRemoteStatusError, |
12221.1.7
by Jeroen Vermeulen
s/Unparseable/Unparsable/g, plus lint. |
41 |
UnparsableBugData, |
42 |
UnparsableBugTrackerVersion, |
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
43 |
)
|
44 |
from lp.bugs.externalbugtracker.xmlrpc import UrlLib2Transport |
|
45 |
from lp.bugs.interfaces.bugtask import ( |
|
46 |
BugTaskImportance, |
|
47 |
BugTaskStatus, |
|
48 |
)
|
|
8523.3.1
by Gavin Panella
Bugs tree reorg after automated migration. |
49 |
from lp.bugs.interfaces.externalbugtracker import ( |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
50 |
ISupportsBackLinking, |
51 |
ISupportsCommentImport, |
|
52 |
ISupportsCommentPushing, |
|
53 |
UNKNOWN_REMOTE_IMPORTANCE, |
|
54 |
)
|
|
12398.2.15
by Jonathan Lange
Fix some more imports. |
55 |
from lp.services import encoding |
12579.1.1
by William Grant
Move lp.bugs.externalbugtracker.isolation to lp.services.database. It's not Bugs-specific. |
56 |
from lp.services.database.isolation import ensure_no_transaction |
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
57 |
|
5863.5.5
by Graham Binns
Minor tweak. |
58 |
|
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
59 |
class Bugzilla(ExternalBugTracker): |
60 |
"""An ExternalBugTrack for dealing with remote Bugzilla systems."""
|
|
61 |
||
62 |
batch_query_threshold = 0 # Always use the batch method. |
|
6604.1.18
by Tom Berger
if a test proxy member is present use it (only used in tests) |
63 |
_test_xmlrpc_proxy = None |
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
64 |
|
65 |
def __init__(self, baseurl, version=None): |
|
66 |
super(Bugzilla, self).__init__(baseurl) |
|
67 |
self.version = self._parseVersion(version) |
|
68 |
self.is_issuezilla = False |
|
69 |
self.remote_bug_status = {} |
|
11304.1.4
by Bryce Harrington
Somewhat cleaner stubbing out of remote bug importance initting |
70 |
self.remote_bug_importance = {} |
7781.1.3
by Bjorn Tillenius
Implement Bugzilla.getRemoteProduct(). |
71 |
self.remote_bug_product = {} |
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
72 |
|
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
73 |
@ensure_no_transaction
|
9570.2.3
by Graham Binns
Bugzilla.getExternalBugTrackerToUse() now knows how to recognise a Bugzilla with an API. |
74 |
def _remoteSystemHasBugzillaAPI(self): |
75 |
"""Return True if the remote host offers the Bugzilla API.
|
|
6570.1.1
by Graham Binns
Added tests and implementation for Bugzilla.getExternalBugTrackerToUse(). |
76 |
|
9570.2.3
by Graham Binns
Bugzilla.getExternalBugTrackerToUse() now knows how to recognise a Bugzilla with an API. |
77 |
:return: True if the remote host offers an XML-RPC API and its
|
78 |
version is > 3.4. Return False otherwise.
|
|
6570.1.1
by Graham Binns
Added tests and implementation for Bugzilla.getExternalBugTrackerToUse(). |
79 |
"""
|
9570.2.2
by Graham Binns
BugzillaAPI instances are now detected properly. Of course, everything else is broken. |
80 |
api = BugzillaAPI(self.baseurl) |
9570.2.3
by Graham Binns
Bugzilla.getExternalBugTrackerToUse() now knows how to recognise a Bugzilla with an API. |
81 |
if self._test_xmlrpc_proxy is not None: |
82 |
proxy = self._test_xmlrpc_proxy |
|
83 |
else: |
|
84 |
proxy = api.xmlrpc_proxy |
|
85 |
||
9570.2.2
by Graham Binns
BugzillaAPI instances are now detected properly. Of course, everything else is broken. |
86 |
try: |
87 |
# We try calling Bugzilla.version() on the remote
|
|
88 |
# server because it's the most lightweight method there is.
|
|
10136.3.3
by Gavin Panella
Rename remote_version_dict to remote_version, because it might be a tuple. Suggested by bac in review. |
89 |
remote_version = proxy.Bugzilla.version() |
9570.2.2
by Graham Binns
BugzillaAPI instances are now detected properly. Of course, everything else is broken. |
90 |
except xmlrpclib.Fault, fault: |
10553.2.1
by Gavin Panella
Treat the fault code 'Client' the same as METHOD_NOT_FOUND when talking to Bugzilla over XML-RPC. |
91 |
# 'Client' is a hangover. Either Bugzilla or the Perl
|
92 |
# XML-RPC lib in use returned it as faultCode. It's wrong,
|
|
93 |
# but it's known wrongness, so we recognize it here.
|
|
94 |
if fault.faultCode in (xmlrpclib.METHOD_NOT_FOUND, 'Client'): |
|
9570.2.3
by Graham Binns
Bugzilla.getExternalBugTrackerToUse() now knows how to recognise a Bugzilla with an API. |
95 |
return False |
9570.2.2
by Graham Binns
BugzillaAPI instances are now detected properly. Of course, everything else is broken. |
96 |
else: |
97 |
raise
|
|
98 |
except xmlrpclib.ProtocolError, error: |
|
99 |
# We catch 404s, which occur when xmlrpc.cgi doesn't exist
|
|
100 |
# on the remote server, and 500s, which sometimes occur when
|
|
9570.2.3
by Graham Binns
Bugzilla.getExternalBugTrackerToUse() now knows how to recognise a Bugzilla with an API. |
101 |
# an invalid request is made to the remote server. We allow
|
102 |
# any other error types to propagate upward.
|
|
9570.2.2
by Graham Binns
BugzillaAPI instances are now detected properly. Of course, everything else is broken. |
103 |
if error.errcode in (404, 500): |
9570.2.3
by Graham Binns
Bugzilla.getExternalBugTrackerToUse() now knows how to recognise a Bugzilla with an API. |
104 |
return False |
9570.2.2
by Graham Binns
BugzillaAPI instances are now detected properly. Of course, everything else is broken. |
105 |
else: |
106 |
raise
|
|
12336.3.1
by Gavin Panella
Resurrect all the externalbugtracker stuff. |
107 |
except (xmlrpclib.ResponseError, xml.parsers.expat.ExpatError): |
9570.2.2
by Graham Binns
BugzillaAPI instances are now detected properly. Of course, everything else is broken. |
108 |
# The server returned an unparsable response.
|
9570.2.3
by Graham Binns
Bugzilla.getExternalBugTrackerToUse() now knows how to recognise a Bugzilla with an API. |
109 |
return False |
9570.2.2
by Graham Binns
BugzillaAPI instances are now detected properly. Of course, everything else is broken. |
110 |
else: |
10136.3.1
by Gavin Panella
Check that the version information returned by the Bugzilla API is a mapping. |
111 |
# Older versions of the Bugzilla API return tuples. We
|
112 |
# consider anything other than a mapping to be unsupported.
|
|
10136.3.3
by Gavin Panella
Rename remote_version_dict to remote_version, because it might be a tuple. Suggested by bac in review. |
113 |
if isinstance(remote_version, dict): |
114 |
if remote_version['version'] >= '3.4': |
|
10136.3.1
by Gavin Panella
Check that the version information returned by the Bugzilla API is a mapping. |
115 |
return True |
116 |
return False |
|
9570.2.2
by Graham Binns
BugzillaAPI instances are now detected properly. Of course, everything else is broken. |
117 |
|
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
118 |
@ensure_no_transaction
|
9570.2.3
by Graham Binns
Bugzilla.getExternalBugTrackerToUse() now knows how to recognise a Bugzilla with an API. |
119 |
def _remoteSystemHasPluginAPI(self): |
120 |
"""Return True if the remote host has the Launchpad plugin installed.
|
|
121 |
"""
|
|
6604.1.15
by Tom Berger
use the same plugin instance for probing and for communicating later ; also, only establish the xmlrpc endpoint in the class that actually uses it |
122 |
plugin = BugzillaLPPlugin(self.baseurl) |
9570.2.3
by Graham Binns
Bugzilla.getExternalBugTrackerToUse() now knows how to recognise a Bugzilla with an API. |
123 |
if self._test_xmlrpc_proxy is not None: |
124 |
proxy = self._test_xmlrpc_proxy |
|
125 |
else: |
|
126 |
proxy = plugin.xmlrpc_proxy |
|
127 |
||
6570.1.1
by Graham Binns
Added tests and implementation for Bugzilla.getExternalBugTrackerToUse(). |
128 |
try: |
129 |
# We try calling Launchpad.plugin_version() on the remote
|
|
130 |
# server because it's the most lightweight method there is.
|
|
6604.1.18
by Tom Berger
if a test proxy member is present use it (only used in tests) |
131 |
proxy.Launchpad.plugin_version() |
6570.1.1
by Graham Binns
Added tests and implementation for Bugzilla.getExternalBugTrackerToUse(). |
132 |
except xmlrpclib.Fault, fault: |
10553.2.1
by Gavin Panella
Treat the fault code 'Client' the same as METHOD_NOT_FOUND when talking to Bugzilla over XML-RPC. |
133 |
# 'Client' is a hangover. Either Bugzilla or the Perl
|
134 |
# XML-RPC lib in use returned it as faultCode. It's wrong,
|
|
135 |
# but it's known wrongness, so we recognize it here.
|
|
136 |
if fault.faultCode in (xmlrpclib.METHOD_NOT_FOUND, 'Client'): |
|
9570.2.3
by Graham Binns
Bugzilla.getExternalBugTrackerToUse() now knows how to recognise a Bugzilla with an API. |
137 |
return False |
6570.1.1
by Graham Binns
Added tests and implementation for Bugzilla.getExternalBugTrackerToUse(). |
138 |
else: |
139 |
raise
|
|
140 |
except xmlrpclib.ProtocolError, error: |
|
6645.1.1
by Graham Binns
Added tests and a fix for bug 246285. |
141 |
# We catch 404s, which occur when xmlrpc.cgi doesn't exist
|
142 |
# on the remote server, and 500s, which sometimes occur when
|
|
143 |
# the Launchpad Plugin isn't installed. Everything else we
|
|
144 |
# can consider to be a problem, so we let it travel up the
|
|
145 |
# stack for the error log.
|
|
146 |
if error.errcode in (404, 500): |
|
9570.2.3
by Graham Binns
Bugzilla.getExternalBugTrackerToUse() now knows how to recognise a Bugzilla with an API. |
147 |
return False |
6570.1.1
by Graham Binns
Added tests and implementation for Bugzilla.getExternalBugTrackerToUse(). |
148 |
else: |
149 |
raise
|
|
12336.3.1
by Gavin Panella
Resurrect all the externalbugtracker stuff. |
150 |
except (xmlrpclib.ResponseError, xml.parsers.expat.ExpatError): |
6715.1.1
by Bjorn Tillenius
catch ResponseError when checking if the LP plugin is installed on bugzilla. |
151 |
# The server returned an unparsable response.
|
9570.2.3
by Graham Binns
Bugzilla.getExternalBugTrackerToUse() now knows how to recognise a Bugzilla with an API. |
152 |
return False |
153 |
else: |
|
154 |
return True |
|
155 |
||
156 |
def getExternalBugTrackerToUse(self): |
|
157 |
"""Return the correct `Bugzilla` subclass for the current bugtracker.
|
|
158 |
||
159 |
See `IExternalBugTracker`.
|
|
160 |
"""
|
|
12599.4.2
by Leonard Richardson
Merge from trunk. |
161 |
# checkwatches isn't set up to handle errors here, so we supress
|
162 |
# known connection issues. They'll be handled and logged later on when
|
|
163 |
# further requests are attempted.
|
|
164 |
try: |
|
165 |
if self._remoteSystemHasPluginAPI(): |
|
166 |
return BugzillaLPPlugin(self.baseurl) |
|
167 |
elif self._remoteSystemHasBugzillaAPI(): |
|
168 |
return BugzillaAPI(self.baseurl) |
|
169 |
except (xmlrpclib.ProtocolError, URLError, BadStatusLine): |
|
170 |
pass
|
|
171 |
return self |
|
6570.1.1
by Graham Binns
Added tests and implementation for Bugzilla.getExternalBugTrackerToUse(). |
172 |
|
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
173 |
def _parseDOMString(self, contents): |
174 |
"""Return a minidom instance representing the XML contents supplied"""
|
|
175 |
# Some Bugzilla sites will return pages with content that has
|
|
176 |
# broken encoding. It's unfortunate but we need to guess the
|
|
177 |
# encoding that page is in, and then encode() it into the utf-8
|
|
178 |
# that minidom requires.
|
|
179 |
contents = encoding.guess(contents).encode("utf-8") |
|
180 |
return minidom.parseString(contents) |
|
181 |
||
182 |
def _probe_version(self): |
|
183 |
"""Retrieve and return a remote bugzilla version.
|
|
184 |
||
185 |
If the version cannot be parsed from the remote server
|
|
12221.1.7
by Jeroen Vermeulen
s/Unparseable/Unparsable/g, plus lint. |
186 |
`UnparsableBugTrackerVersion` will be raised. If the remote
|
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
187 |
server cannot be reached `BugTrackerConnectError` will be
|
188 |
raised.
|
|
189 |
"""
|
|
190 |
version_xml = self._getPage('xml.cgi?id=1') |
|
191 |
try: |
|
192 |
document = self._parseDOMString(version_xml) |
|
193 |
except xml.parsers.expat.ExpatError, e: |
|
194 |
raise BugTrackerConnectError(self.baseurl, |
|
195 |
"Failed to parse output when probing for version: %s" % e) |
|
196 |
bugzilla = document.getElementsByTagName("bugzilla") |
|
197 |
if not bugzilla: |
|
198 |
# Welcome to Disneyland. The Issuezilla tracker replaces
|
|
199 |
# "bugzilla" with "issuezilla".
|
|
200 |
bugzilla = document.getElementsByTagName("issuezilla") |
|
201 |
if bugzilla: |
|
202 |
self.is_issuezilla = True |
|
203 |
else: |
|
12221.1.7
by Jeroen Vermeulen
s/Unparseable/Unparsable/g, plus lint. |
204 |
raise UnparsableBugTrackerVersion( |
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
205 |
'Failed to parse version from xml.cgi for %s: could ' |
206 |
'not find top-level bugzilla element'
|
|
207 |
% self.baseurl) |
|
208 |
version = bugzilla[0].getAttribute("version") |
|
209 |
return self._parseVersion(version) |
|
210 |
||
211 |
def _parseVersion(self, version): |
|
212 |
"""Return a Bugzilla version parsed into a tuple.
|
|
213 |
||
214 |
A typical tuple will be in the form (major_version,
|
|
215 |
minor_version), so the version string '2.15' would be returned
|
|
216 |
as (2, 15).
|
|
217 |
||
218 |
If the passed version is None, None will be returned.
|
|
12221.1.7
by Jeroen Vermeulen
s/Unparseable/Unparsable/g, plus lint. |
219 |
If the version cannot be parsed `UnparsableBugTrackerVersion`
|
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
220 |
will be raised.
|
221 |
"""
|
|
222 |
if version is None: |
|
223 |
return None |
|
224 |
||
10136.3.2
by Gavin Panella
Make _parseVersion() just pick out numbers from the version string. |
225 |
version_numbers = re.findall('[0-9]+', version) |
226 |
if len(version_numbers) == 0: |
|
12221.1.7
by Jeroen Vermeulen
s/Unparseable/Unparsable/g, plus lint. |
227 |
raise UnparsableBugTrackerVersion( |
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
228 |
'Failed to parse version %r for %s' % |
229 |
(version, self.baseurl)) |
|
230 |
||
10136.3.2
by Gavin Panella
Make _parseVersion() just pick out numbers from the version string. |
231 |
return tuple(int(number) for number in version_numbers) |
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
232 |
|
11304.1.3
by Bryce Harrington
Stub in convertRemoteImportance() |
233 |
_importance_lookup = { |
12221.1.2
by Jeroen Vermeulen
Repost on redirect to Bugzilla's search page. |
234 |
'blocker': BugTaskImportance.CRITICAL, |
235 |
'critical': BugTaskImportance.CRITICAL, |
|
236 |
'immediate': BugTaskImportance.CRITICAL, |
|
237 |
'urgent': BugTaskImportance.CRITICAL, |
|
12435.1.2
by William Grant
Add OOo priorities, since they have no separate severity field. |
238 |
'p5': BugTaskImportance.CRITICAL, |
12435.1.1
by William Grant
Add additional KDE severities. |
239 |
'crash': BugTaskImportance.HIGH, |
240 |
'grave': BugTaskImportance.HIGH, |
|
12221.1.2
by Jeroen Vermeulen
Repost on redirect to Bugzilla's search page. |
241 |
'major': BugTaskImportance.HIGH, |
242 |
'high': BugTaskImportance.HIGH, |
|
12435.1.2
by William Grant
Add OOo priorities, since they have no separate severity field. |
243 |
'p4': BugTaskImportance.HIGH, |
12221.1.2
by Jeroen Vermeulen
Repost on redirect to Bugzilla's search page. |
244 |
'normal': BugTaskImportance.MEDIUM, |
245 |
'medium': BugTaskImportance.MEDIUM, |
|
12435.1.2
by William Grant
Add OOo priorities, since they have no separate severity field. |
246 |
'p3': BugTaskImportance.MEDIUM, |
12221.1.2
by Jeroen Vermeulen
Repost on redirect to Bugzilla's search page. |
247 |
'minor': BugTaskImportance.LOW, |
248 |
'low': BugTaskImportance.LOW, |
|
249 |
'trivial': BugTaskImportance.LOW, |
|
12435.1.2
by William Grant
Add OOo priorities, since they have no separate severity field. |
250 |
'p2': BugTaskImportance.LOW, |
251 |
'p1': BugTaskImportance.LOW, |
|
11304.1.22
by Bryce Harrington
Cleanup a heapload of lint errors |
252 |
'enhancement': BugTaskImportance.WISHLIST, |
12435.1.1
by William Grant
Add additional KDE severities. |
253 |
'wishlist': BugTaskImportance.WISHLIST, |
11304.1.3
by Bryce Harrington
Stub in convertRemoteImportance() |
254 |
}
|
255 |
||
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
256 |
def convertRemoteImportance(self, remote_importance): |
11304.1.21
by Bryce Harrington
Define UnknownRemoteImportanceError class |
257 |
"""See `ExternalBugTracker`."""
|
11629.1.4
by Bryce Harrington
Handle case of bugzillas which can return '' for priority and severity. |
258 |
words = remote_importance.lower().split() |
11304.1.7
by Bryce Harrington
Implement convertRemoteImportance() |
259 |
try: |
11629.1.4
by Bryce Harrington
Handle case of bugzillas which can return '' for priority and severity. |
260 |
return self._importance_lookup[words.pop()] |
11304.1.7
by Bryce Harrington
Implement convertRemoteImportance() |
261 |
except KeyError: |
262 |
raise UnknownRemoteImportanceError(remote_importance) |
|
11629.1.4
by Bryce Harrington
Handle case of bugzillas which can return '' for priority and severity. |
263 |
except IndexError: |
264 |
return BugTaskImportance.UNKNOWN |
|
11304.1.7
by Bryce Harrington
Implement convertRemoteImportance() |
265 |
|
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
266 |
return BugTaskImportance.UNKNOWN |
267 |
||
6326.8.20
by Gavin Panella
Add titles. |
268 |
_status_lookup_titles = 'Bugzilla status', 'Bugzilla resolution' |
6326.8.34
by Gavin Panella
Rename Lookup to LookupTree in the externalbugtracker modules. |
269 |
_status_lookup = LookupTree( |
6326.8.11
by Gavin Panella
More cleanups. |
270 |
('ASSIGNED', 'ON_DEV', 'FAILS_QA', 'STARTED', |
271 |
BugTaskStatus.INPROGRESS), |
|
12435.1.4
by William Grant
KDE uses NEEDSINFO instead of NEEDINFO. |
272 |
('NEEDINFO', 'NEEDINFO_REPORTER', 'NEEDSINFO', 'WAITING', 'SUSPENDED', |
11411.7.24
by j.c.sackett
Merged from devel. |
273 |
'PLEASETEST', |
6326.8.11
by Gavin Panella
More cleanups. |
274 |
BugTaskStatus.INCOMPLETE), |
275 |
('PENDINGUPLOAD', 'MODIFIED', 'RELEASE_PENDING', 'ON_QA', |
|
276 |
BugTaskStatus.FIXCOMMITTED), |
|
277 |
('REJECTED', BugTaskStatus.INVALID), |
|
6326.8.59
by Gavin Panella
A few small post-review changes. |
278 |
('RESOLVED', 'VERIFIED', 'CLOSED', |
279 |
LookupTree( |
|
6326.8.11
by Gavin Panella
More cleanups. |
280 |
('CODE_FIX', 'CURRENTRELEASE', 'ERRATA', 'NEXTRELEASE', |
11411.7.24
by j.c.sackett
Merged from devel. |
281 |
'PATCH_ALREADY_AVAILABLE', 'FIXED', 'RAWHIDE', |
282 |
'DOCUMENTED', |
|
6326.8.11
by Gavin Panella
More cleanups. |
283 |
BugTaskStatus.FIXRELEASED), |
11411.7.24
by j.c.sackett
Merged from devel. |
284 |
('WONTFIX', 'WILL_NOT_FIX', 'NOTOURBUG', 'UPSTREAM', |
285 |
BugTaskStatus.WONTFIX), |
|
286 |
('OBSOLETE', 'INSUFFICIENT_DATA', 'INCOMPLETE', 'EXPIRED', |
|
287 |
BugTaskStatus.EXPIRED), |
|
288 |
('INVALID', 'WORKSFORME', 'NOTABUG', 'CANTFIX', |
|
12435.1.3
by William Grant
A Bugzilla resolution of DUPLICATE is Invalid until we can handle duplicates directly. |
289 |
'UNREPRODUCIBLE', 'DUPLICATE', |
12435.1.5
by William Grant
Don't explicitly map unknown RESOLVED/CLOSED/VERIFIED statuses to Unknown. Let the exception bubble up so it gets logged. |
290 |
BugTaskStatus.INVALID))), |
11411.7.24
by j.c.sackett
Merged from devel. |
291 |
('REOPENED', 'NEW', 'UPSTREAM', 'DEFERRED', |
292 |
BugTaskStatus.CONFIRMED), |
|
6326.8.11
by Gavin Panella
More cleanups. |
293 |
('UNCONFIRMED', BugTaskStatus.NEW), |
294 |
)
|
|
295 |
||
296 |
def convertRemoteStatus(self, remote_status): |
|
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
297 |
"""See `IExternalBugTracker`.
|
298 |
||
299 |
Bugzilla status consist of two parts separated by space, where
|
|
300 |
the last part is the resolution. The resolution is optional.
|
|
301 |
"""
|
|
6326.8.6
by Gavin Panella
Convert bugzilla. |
302 |
try: |
6326.8.41
by Gavin Panella
Change __call__ to find in externalbugtracker too. |
303 |
return self._status_lookup.find(*remote_status.split()) |
6326.8.6
by Gavin Panella
Convert bugzilla. |
304 |
except KeyError: |
6326.8.11
by Gavin Panella
More cleanups. |
305 |
raise UnknownRemoteStatusError(remote_status) |
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
306 |
|
307 |
def initializeRemoteBugDB(self, bug_ids): |
|
308 |
"""See `ExternalBugTracker`.
|
|
309 |
||
310 |
This method is overriden so that Bugzilla version issues can be
|
|
311 |
accounted for.
|
|
312 |
"""
|
|
313 |
if self.version is None: |
|
314 |
self.version = self._probe_version() |
|
315 |
||
316 |
super(Bugzilla, self).initializeRemoteBugDB(bug_ids) |
|
317 |
||
318 |
def getRemoteBug(self, bug_id): |
|
319 |
"""See `ExternalBugTracker`."""
|
|
320 |
return (bug_id, self.getRemoteBugBatch([bug_id])) |
|
321 |
||
12221.1.4
by Jeroen Vermeulen
Check that bugzilla search result really looks like a search result page. |
322 |
def _checkBugSearchResult(self, document): |
323 |
"""Does `document` appear to be a bug search result page?
|
|
324 |
||
325 |
:param document: An `xml.dom.Document` built from a bug search result
|
|
326 |
on the bugzilla instance.
|
|
12221.1.7
by Jeroen Vermeulen
s/Unparseable/Unparsable/g, plus lint. |
327 |
:raise UnparsableBugData: If `document` does not appear to be a bug
|
12221.1.4
by Jeroen Vermeulen
Check that bugzilla search result really looks like a search result page. |
328 |
search result.
|
329 |
"""
|
|
330 |
root = document.documentElement |
|
12221.1.5
by Jeroen Vermeulen
Be a bit more liberal about bugzilla search result root tags: forbid only HTML. |
331 |
if root.tagName == 'html': |
12221.1.7
by Jeroen Vermeulen
s/Unparseable/Unparsable/g, plus lint. |
332 |
raise UnparsableBugData( |
12221.1.4
by Jeroen Vermeulen
Check that bugzilla search result really looks like a search result page. |
333 |
"Bug search on %s returned a <%s> instead of an RDF page." % ( |
334 |
self.baseurl, root.tagName)) |
|
335 |
||
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
336 |
def getRemoteBugBatch(self, bug_ids): |
337 |
"""See `ExternalBugTracker`."""
|
|
338 |
# XXX: GavinPanella 2007-10-25 bug=153532: The modification of
|
|
339 |
# self.remote_bug_status later on is a side-effect that should
|
|
340 |
# really not be in this method, but for the fact that
|
|
341 |
# getRemoteStatus needs it at other times. Perhaps
|
|
342 |
# getRemoteBug and getRemoteBugBatch could return RemoteBug
|
|
343 |
# objects which have status properties that would replace
|
|
344 |
# getRemoteStatus.
|
|
345 |
if self.is_issuezilla: |
|
346 |
buglist_page = 'xml.cgi' |
|
12221.1.2
by Jeroen Vermeulen
Repost on redirect to Bugzilla's search page. |
347 |
data = { |
348 |
'download_type': 'browser', |
|
349 |
'output_configured': 'true', |
|
350 |
'include_attachments': 'false', |
|
351 |
'include_dtd': 'true', |
|
352 |
'id': ','.join(bug_ids), |
|
353 |
}
|
|
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
354 |
bug_tag = 'issue' |
355 |
id_tag = 'issue_id' |
|
356 |
status_tag = 'issue_status' |
|
357 |
resolution_tag = 'resolution' |
|
11304.1.12
by Bryce Harrington
Implement retrieving priority+severity from bugzilla into importance |
358 |
priority_tag = 'priority' |
11304.1.18
by Bryce Harrington
Further handling of IssueZilla's lack of severity field |
359 |
severity_tag = None |
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
360 |
elif self.version < (2, 16): |
361 |
buglist_page = 'xml.cgi' |
|
362 |
data = {'id': ','.join(bug_ids)} |
|
363 |
bug_tag = 'bug' |
|
364 |
id_tag = 'bug_id' |
|
365 |
status_tag = 'bug_status' |
|
366 |
resolution_tag = 'resolution' |
|
11304.1.12
by Bryce Harrington
Implement retrieving priority+severity from bugzilla into importance |
367 |
priority_tag = 'priority' |
11304.1.13
by Bryce Harrington
Looks like the bugzilla term is 'bug_severity'. Go consistency. |
368 |
severity_tag = 'bug_severity' |
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
369 |
else: |
370 |
buglist_page = 'buglist.cgi' |
|
12221.1.2
by Jeroen Vermeulen
Repost on redirect to Bugzilla's search page. |
371 |
data = { |
372 |
'form_name': 'buglist.cgi', |
|
373 |
'bug_id_type': 'include', |
|
12266.2.1
by Gavin Panella
For some reason bugs.freedesktop.org (Bugzilla 3.4.6) has decided to take notice of the columnlist parameter we pass over. |
374 |
'columnlist': |
375 |
('id,product,bug_status,resolution,' |
|
376 |
'priority,bug_severity'), |
|
12221.1.2
by Jeroen Vermeulen
Repost on redirect to Bugzilla's search page. |
377 |
'bug_id': ','.join(bug_ids), |
378 |
}
|
|
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
379 |
if self.version < (2, 17, 1): |
12221.1.2
by Jeroen Vermeulen
Repost on redirect to Bugzilla's search page. |
380 |
data.update({'format': 'rdf'}) |
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
381 |
else: |
12221.1.2
by Jeroen Vermeulen
Repost on redirect to Bugzilla's search page. |
382 |
data.update({'ctype': 'rdf'}) |
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
383 |
bug_tag = 'bz:bug' |
384 |
id_tag = 'bz:id' |
|
385 |
status_tag = 'bz:bug_status' |
|
386 |
resolution_tag = 'bz:resolution' |
|
11304.1.12
by Bryce Harrington
Implement retrieving priority+severity from bugzilla into importance |
387 |
priority_tag = 'bz:priority' |
11304.1.13
by Bryce Harrington
Looks like the bugzilla term is 'bug_severity'. Go consistency. |
388 |
severity_tag = 'bz:bug_severity' |
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
389 |
|
12221.1.2
by Jeroen Vermeulen
Repost on redirect to Bugzilla's search page. |
390 |
buglist_xml = self._postPage( |
391 |
buglist_page, data, repost_on_redirect=True) |
|
392 |
||
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
393 |
try: |
394 |
document = self._parseDOMString(buglist_xml) |
|
395 |
except xml.parsers.expat.ExpatError, e: |
|
12221.1.7
by Jeroen Vermeulen
s/Unparseable/Unparsable/g, plus lint. |
396 |
raise UnparsableBugData( |
397 |
"Failed to parse XML description for %s bugs %s: %s" |
|
398 |
% (self.baseurl, bug_ids, e)) |
|
12221.1.4
by Jeroen Vermeulen
Check that bugzilla search result really looks like a search result page. |
399 |
self._checkBugSearchResult(document) |
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
400 |
|
401 |
bug_nodes = document.getElementsByTagName(bug_tag) |
|
402 |
for bug_node in bug_nodes: |
|
403 |
# We use manual iteration to pick up id_tags instead of
|
|
404 |
# getElementsByTagName because the latter does a recursive
|
|
405 |
# search, and in some documents we've found the id_tag to
|
|
406 |
# appear under other elements (such as "has_duplicates") in
|
|
407 |
# the document hierarchy.
|
|
408 |
bug_id_nodes = [node for node in bug_node.childNodes if |
|
409 |
node.nodeName == id_tag] |
|
410 |
if not bug_id_nodes: |
|
411 |
# Something in the output is really weird; this will
|
|
412 |
# show up as a bug not found, but we can catch that
|
|
413 |
# later in the error logs.
|
|
414 |
continue
|
|
415 |
bug_id_node = bug_id_nodes[0] |
|
416 |
assert len(bug_id_node.childNodes) == 1, ( |
|
417 |
"id node should contain a non-empty text string.") |
|
418 |
bug_id = str(bug_id_node.childNodes[0].data) |
|
419 |
# This assertion comes in late so we can at least tell what
|
|
420 |
# bug caused this crash.
|
|
421 |
assert len(bug_id_nodes) == 1, ("Should be only one id node, " |
|
422 |
"but %s had %s." % (bug_id, len(bug_id_nodes))) |
|
423 |
||
424 |
status_nodes = bug_node.getElementsByTagName(status_tag) |
|
425 |
if not status_nodes: |
|
426 |
# Older versions of bugzilla used bz:status; this was
|
|
427 |
# later changed to bz:bug_status. For robustness, and
|
|
428 |
# because there is practically no risk of reading wrong
|
|
429 |
# data here, just try the older format as well.
|
|
430 |
status_nodes = bug_node.getElementsByTagName("bz:status") |
|
431 |
assert len(status_nodes) == 1, ("Couldn't find a status " |
|
432 |
"node for bug %s." % bug_id) |
|
433 |
bug_status_node = status_nodes[0] |
|
434 |
assert len(bug_status_node.childNodes) == 1, ( |
|
435 |
"status node for bug %s should contain a non-empty " |
|
436 |
"text string." % bug_id) |
|
437 |
status = bug_status_node.childNodes[0].data |
|
438 |
||
439 |
resolution_nodes = bug_node.getElementsByTagName(resolution_tag) |
|
440 |
assert len(resolution_nodes) <= 1, ( |
|
441 |
"Should be only one resolution node for bug %s." % bug_id) |
|
442 |
if resolution_nodes: |
|
443 |
assert len(resolution_nodes[0].childNodes) <= 1, ( |
|
444 |
"Resolution for bug %s should just contain " |
|
445 |
"a string." % bug_id) |
|
446 |
if resolution_nodes[0].childNodes: |
|
447 |
resolution = resolution_nodes[0].childNodes[0].data |
|
448 |
status += ' %s' % resolution |
|
449 |
self.remote_bug_status[bug_id] = status |
|
450 |
||
11304.1.12
by Bryce Harrington
Implement retrieving priority+severity from bugzilla into importance |
451 |
# Priority (for Importance)
|
452 |
priority = '' |
|
453 |
priority_nodes = bug_node.getElementsByTagName(priority_tag) |
|
454 |
assert len(priority_nodes) <= 1, ( |
|
455 |
"Should only be one priority node for bug %s" % bug_id) |
|
456 |
if priority_nodes: |
|
457 |
bug_priority_node = priority_nodes[0] |
|
458 |
assert len(bug_priority_node.childNodes) == 1, ( |
|
459 |
"priority node for bug %s should contain a non-empty " |
|
460 |
"text string." % bug_id) |
|
461 |
priority = bug_priority_node.childNodes[0].data |
|
462 |
||
463 |
# Severity (for Importance)
|
|
11304.1.18
by Bryce Harrington
Further handling of IssueZilla's lack of severity field |
464 |
if severity_tag: |
465 |
severity_nodes = bug_node.getElementsByTagName(severity_tag) |
|
466 |
assert len(severity_nodes) <= 1, ( |
|
467 |
"Should only be one severity node for bug %s." % bug_id) |
|
468 |
if severity_nodes: |
|
469 |
assert len(severity_nodes[0].childNodes) <= 1, ( |
|
470 |
"Severity for bug %s should just contain " |
|
471 |
"a string." % bug_id) |
|
472 |
if severity_nodes[0].childNodes: |
|
473 |
severity = severity_nodes[0].childNodes[0].data |
|
474 |
priority += ' %s' % severity |
|
11304.1.12
by Bryce Harrington
Implement retrieving priority+severity from bugzilla into importance |
475 |
self.remote_bug_importance[bug_id] = priority |
476 |
||
477 |
# Product
|
|
7781.1.3
by Bjorn Tillenius
Implement Bugzilla.getRemoteProduct(). |
478 |
product_nodes = bug_node.getElementsByTagName('bz:product') |
7781.1.4
by Bjorn Tillenius
Handle the case where we don't get the product in the XML listing. |
479 |
assert len(product_nodes) <= 1, ( |
480 |
"Should be at most one product node for bug %s." % bug_id) |
|
481 |
if len(product_nodes) == 0: |
|
482 |
self.remote_bug_product[bug_id] = None |
|
483 |
else: |
|
484 |
product_node = product_nodes[0] |
|
485 |
self.remote_bug_product[bug_id] = ( |
|
486 |
product_node.childNodes[0].data) |
|
7781.1.3
by Bjorn Tillenius
Implement Bugzilla.getRemoteProduct(). |
487 |
|
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
488 |
def getRemoteImportance(self, bug_id): |
11304.1.1
by Bryce Harrington
Stub in a getRemoteImportance() call. Doesn't do much but tests pass. |
489 |
"""See `ExternalBugTracker`."""
|
490 |
try: |
|
11304.1.2
by Bryce Harrington
Lookup importance locally |
491 |
if bug_id not in self.remote_bug_importance: |
11304.1.1
by Bryce Harrington
Stub in a getRemoteImportance() call. Doesn't do much but tests pass. |
492 |
return "Bug %s is not in remote_bug_importance" %(bug_id) |
493 |
return self.remote_bug_importance[bug_id] |
|
494 |
except: |
|
495 |
return UNKNOWN_REMOTE_IMPORTANCE |
|
5863.5.3
by Graham Binns
Moved bugzilla into its own module. |
496 |
|
497 |
def getRemoteStatus(self, bug_id): |
|
498 |
"""See ExternalBugTracker."""
|
|
499 |
if not bug_id.isdigit(): |
|
500 |
raise InvalidBugId( |
|
501 |
"Bugzilla (%s) bug number not an integer: %s" % ( |
|
502 |
self.baseurl, bug_id)) |
|
503 |
try: |
|
504 |
return self.remote_bug_status[bug_id] |
|
505 |
except KeyError: |
|
506 |
raise BugNotFound(bug_id) |
|
6290.4.4
by Graham Binns
Added tests and implementation for getCurrentDBTime(). |
507 |
|
7781.1.3
by Bjorn Tillenius
Implement Bugzilla.getRemoteProduct(). |
508 |
def getRemoteProduct(self, remote_bug): |
509 |
"""See `IExternalBugTracker`."""
|
|
7781.1.6
by Bjorn Tillenius
Make sure getRemoteProduct() raises BugNotFound. |
510 |
if remote_bug not in self.remote_bug_product: |
511 |
raise BugNotFound(remote_bug) |
|
7781.1.3
by Bjorn Tillenius
Implement Bugzilla.getRemoteProduct(). |
512 |
return self.remote_bug_product[remote_bug] |
513 |
||
6290.4.4
by Graham Binns
Added tests and implementation for getCurrentDBTime(). |
514 |
|
6532.1.15
by Graham Binns
Moved the decorator above the class it decorates. |
515 |
def needs_authentication(func): |
516 |
"""Decorator for automatically authenticating if needed.
|
|
517 |
||
518 |
If an `xmlrpclib.Fault` with error code 410 is raised by the
|
|
519 |
function, we'll try to authenticate and call the function again.
|
|
520 |
"""
|
|
11304.1.22
by Bryce Harrington
Cleanup a heapload of lint errors |
521 |
|
6532.1.15
by Graham Binns
Moved the decorator above the class it decorates. |
522 |
def decorator(self, *args, **kwargs): |
523 |
try: |
|
524 |
return func(self, *args, **kwargs) |
|
525 |
except xmlrpclib.Fault, fault: |
|
526 |
# Catch authentication errors only.
|
|
527 |
if fault.faultCode != 410: |
|
528 |
raise
|
|
11304.1.22
by Bryce Harrington
Cleanup a heapload of lint errors |
529 |
|
6532.1.15
by Graham Binns
Moved the decorator above the class it decorates. |
530 |
self._authenticate() |
531 |
return func(self, *args, **kwargs) |
|
532 |
return decorator |
|
533 |
||
534 |
||
9150.2.2
by Graham Binns
Add a BugzillaAPI ExternalBugTracker. |
535 |
class BugzillaAPI(Bugzilla): |
536 |
"""An `ExternalBugTracker` to handle Bugzillas that offer an API."""
|
|
6506.5.5
by Graham Binns
Added tests and implementation for getCommentIds(). |
537 |
|
9947.4.2
by Graham Binns
Added tests and implementation for BugzillaAPI's implementation of ISupportsBackLinking. |
538 |
implements( |
539 |
ISupportsBackLinking, ISupportsCommentImport, ISupportsCommentPushing) |
|
9150.5.6
by Graham Binns
BugzillaAPI now implements ISupportsCommentImport. |
540 |
|
6532.1.3
by Graham Binns
Added tests for _authenticate. |
541 |
def __init__(self, baseurl, xmlrpc_transport=None, |
542 |
internal_xmlrpc_transport=None): |
|
9150.2.2
by Graham Binns
Add a BugzillaAPI ExternalBugTracker. |
543 |
super(BugzillaAPI, self).__init__(baseurl) |
6797.2.5
by Graham Binns
Made properties of BugzillaLPPlugin non-public. |
544 |
self._bugs = {} |
545 |
self._bug_aliases = {} |
|
6290.4.4
by Graham Binns
Added tests and implementation for getCurrentDBTime(). |
546 |
|
6604.1.15
by Tom Berger
use the same plugin instance for probing and for communicating later ; also, only establish the xmlrpc endpoint in the class that actually uses it |
547 |
self.xmlrpc_endpoint = urlappend(self.baseurl, 'xmlrpc.cgi') |
548 |
||
6532.1.3
by Graham Binns
Added tests for _authenticate. |
549 |
self.internal_xmlrpc_transport = internal_xmlrpc_transport |
6290.4.4
by Graham Binns
Added tests and implementation for getCurrentDBTime(). |
550 |
if xmlrpc_transport is None: |
6604.1.2
by Tom Berger
use a new XMLRPC transport which uses urllib2, handles cookies and proxies |
551 |
self.xmlrpc_transport = UrlLib2Transport(self.xmlrpc_endpoint) |
6290.4.8
by Graham Binns
Review changes for barry. |
552 |
else: |
553 |
self.xmlrpc_transport = xmlrpc_transport |
|
6290.4.4
by Graham Binns
Added tests and implementation for getCurrentDBTime(). |
554 |
|
10065.1.1
by Gavin Panella
BugzillaAPI and BugzillaLPPlugin.getExternalBugTrackerToUse() needs to just return self, rather than use the inherited version. |
555 |
def getExternalBugTrackerToUse(self): |
556 |
"""The Bugzilla API has been chosen, so return self."""
|
|
557 |
return self |
|
558 |
||
6604.1.15
by Tom Berger
use the same plugin instance for probing and for communicating later ; also, only establish the xmlrpc endpoint in the class that actually uses it |
559 |
@property
|
560 |
def xmlrpc_proxy(self): |
|
561 |
"""Return an `xmlrpclib.ServerProxy` to self.xmlrpc_endpoint."""
|
|
562 |
return xmlrpclib.ServerProxy( |
|
563 |
self.xmlrpc_endpoint, transport=self.xmlrpc_transport) |
|
564 |
||
9150.2.3
by Graham Binns
Added authentication methods. |
565 |
@property
|
566 |
def credentials(self): |
|
567 |
credentials_config = config['checkwatches.credentials'] |
|
9150.2.4
by Graham Binns
Review changes for jtv. |
568 |
|
569 |
# Extract the hostname from the current base url using urlparse.
|
|
9150.2.3
by Graham Binns
Added authentication methods. |
570 |
hostname = urlparse(self.baseurl)[1] |
571 |
try: |
|
9150.2.5
by Graham Binns
Review changes for jtv. |
572 |
# XXX gmb 2009-08-19 bug=391131
|
573 |
# We shouldn't be using this here. Ideally we'd be able
|
|
574 |
# to get the credentials from the BugTracker object.
|
|
575 |
# If you find yourself adding credentials for, for
|
|
576 |
# example, www.password.username.pirateninjah4x0rz.org,
|
|
577 |
# think about fixing the above bug instead.
|
|
9150.2.3
by Graham Binns
Added authentication methods. |
578 |
username = credentials_config['%s.username' % hostname] |
579 |
password = credentials_config['%s.password' % hostname] |
|
580 |
return {'login': username, 'password': password} |
|
581 |
except KeyError: |
|
582 |
raise BugTrackerAuthenticationError( |
|
583 |
self.baseurl, "No credentials found.") |
|
584 |
||
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
585 |
@ensure_no_transaction
|
9150.2.3
by Graham Binns
Added authentication methods. |
586 |
def _authenticate(self): |
587 |
"""Authenticate with the remote Bugzilla instance.
|
|
588 |
||
589 |
The native Bugzilla API uses a standard (username, password)
|
|
590 |
paradigm for authentication. If the username and password are
|
|
591 |
correct, Bugzilla will send back a login cookie which we can use
|
|
592 |
to re-authenticate with each subsequent method call.
|
|
593 |
"""
|
|
594 |
try: |
|
595 |
self.xmlrpc_proxy.User.login(self.credentials) |
|
596 |
except xmlrpclib.Fault, fault: |
|
597 |
raise BugTrackerAuthenticationError( |
|
598 |
self.baseurl, |
|
599 |
"Fault %s: %s" % (fault.faultCode, fault.faultString)) |
|
9150.2.2
by Graham Binns
Add a BugzillaAPI ExternalBugTracker. |
600 |
|
9150.2.9
by Graham Binns
Added initializeRemoteBugDB() to BugzillaAPI and refactored some utility functions out of BugzillaLPPlugin. |
601 |
def _storeBugs(self, remote_bugs): |
602 |
"""Store remote bugs in the local `bugs` dict."""
|
|
603 |
for remote_bug in remote_bugs: |
|
604 |
self._bugs[remote_bug['id']] = remote_bug |
|
605 |
||
606 |
# The bug_aliases dict is a mapping between aliases and bug
|
|
607 |
# IDs. We use the aliases dict to look up the correct ID for
|
|
608 |
# a bug. This allows us to reference a bug by either ID or
|
|
609 |
# alias.
|
|
11707.1.1
by Graham Binns
Fixed bug 660873. |
610 |
if remote_bug.get('alias', '') != '': |
9150.2.9
by Graham Binns
Added initializeRemoteBugDB() to BugzillaAPI and refactored some utility functions out of BugzillaLPPlugin. |
611 |
self._bug_aliases[remote_bug['alias']] = remote_bug['id'] |
612 |
||
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
613 |
@ensure_no_transaction
|
9150.2.7
by Graham Binns
Implemented getting the current DB time from the remote server and converting it to UTC. |
614 |
def getCurrentDBTime(self): |
615 |
"""See `IExternalBugTracker`."""
|
|
616 |
time_dict = self.xmlrpc_proxy.Bugzilla.time() |
|
617 |
||
618 |
# The server's DB time is the one that we want to use. However,
|
|
9150.2.8
by Graham Binns
Huzzah. Timezones work and I don't have to kill myself. |
619 |
# this may not be in UTC, so we need to convert it. Since we
|
620 |
# can't guarantee that the timezone data returned by the server
|
|
621 |
# is sane, we work out the server's offset from UTC by looking
|
|
622 |
# at the difference between the web_time and the web_time_utc
|
|
623 |
# values.
|
|
10060.1.4
by Gavin Panella
Remove more xmlrpclib.DateTime hackery. |
624 |
server_web_datetime = time_dict['web_time'] |
625 |
server_web_datetime_utc = time_dict['web_time_utc'] |
|
9150.2.8
by Graham Binns
Huzzah. Timezones work and I don't have to kill myself. |
626 |
server_utc_offset = server_web_datetime - server_web_datetime_utc |
10060.1.4
by Gavin Panella
Remove more xmlrpclib.DateTime hackery. |
627 |
server_db_datetime = time_dict['db_time'] |
9150.2.8
by Graham Binns
Huzzah. Timezones work and I don't have to kill myself. |
628 |
server_utc_datetime = server_db_datetime - server_utc_offset |
629 |
return server_utc_datetime.replace(tzinfo=pytz.timezone('UTC')) |
|
9150.2.7
by Graham Binns
Implemented getting the current DB time from the remote server and converting it to UTC. |
630 |
|
9150.2.9
by Graham Binns
Added initializeRemoteBugDB() to BugzillaAPI and refactored some utility functions out of BugzillaLPPlugin. |
631 |
def _getActualBugId(self, bug_id): |
632 |
"""Return the actual bug id for an alias or id."""
|
|
633 |
# See if bug_id is actually an alias.
|
|
634 |
actual_bug_id = self._bug_aliases.get(bug_id) |
|
635 |
||
636 |
# bug_id isn't an alias, so try turning it into an int and
|
|
637 |
# looking the bug up by ID.
|
|
638 |
if actual_bug_id is not None: |
|
639 |
return actual_bug_id |
|
640 |
else: |
|
641 |
try: |
|
642 |
actual_bug_id = int(bug_id) |
|
643 |
except ValueError: |
|
644 |
# If bug_id can't be int()'d then it's likely an alias
|
|
645 |
# that doesn't exist, so raise BugNotFound.
|
|
646 |
raise BugNotFound(bug_id) |
|
647 |
||
648 |
# Check that the bug does actually exist. That way we're
|
|
649 |
# treating integer bug IDs and aliases in the same way.
|
|
650 |
if actual_bug_id not in self._bugs: |
|
651 |
raise BugNotFound(bug_id) |
|
652 |
||
653 |
return actual_bug_id |
|
654 |
||
655 |
def _getBugIdsToRetrieve(self, bug_ids): |
|
656 |
"""For a set of bug IDs, return those for which we have no data."""
|
|
657 |
bug_ids_to_retrieve = [] |
|
658 |
for bug_id in bug_ids: |
|
659 |
try: |
|
10060.1.2
by Gavin Panella
Fix lint. |
660 |
self._getActualBugId(bug_id) |
9150.2.9
by Graham Binns
Added initializeRemoteBugDB() to BugzillaAPI and refactored some utility functions out of BugzillaLPPlugin. |
661 |
except BugNotFound: |
662 |
bug_ids_to_retrieve.append(bug_id) |
|
663 |
||
664 |
return bug_ids_to_retrieve |
|
665 |
||
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
666 |
@ensure_no_transaction
|
9150.2.9
by Graham Binns
Added initializeRemoteBugDB() to BugzillaAPI and refactored some utility functions out of BugzillaLPPlugin. |
667 |
def initializeRemoteBugDB(self, bug_ids): |
668 |
"""See `IExternalBugTracker`."""
|
|
669 |
# First, discard all those bug IDs about which we already have
|
|
670 |
# data.
|
|
671 |
bug_ids_to_retrieve = self._getBugIdsToRetrieve(bug_ids) |
|
672 |
||
673 |
# Pull the bug data from the remote server. permissive=True here
|
|
674 |
# prevents Bugzilla from erroring if we ask for a bug it doesn't
|
|
675 |
# have.
|
|
676 |
response_dict = self.xmlrpc_proxy.Bug.get({ |
|
677 |
'ids': bug_ids_to_retrieve, |
|
678 |
'permissive': True, |
|
679 |
})
|
|
680 |
remote_bugs = response_dict['bugs'] |
|
681 |
||
682 |
self._storeBugs(remote_bugs) |
|
683 |
||
9150.3.1
by Graham Binns
Refactored getRemoteStatuses() into BugzillaAPI. |
684 |
def getRemoteStatus(self, bug_id): |
685 |
"""See `IExternalBugTracker`."""
|
|
686 |
actual_bug_id = self._getActualBugId(bug_id) |
|
687 |
||
688 |
# Attempt to get the status and resolution from the bug. If
|
|
689 |
# we don't have the data for either of them, raise an error.
|
|
690 |
try: |
|
691 |
status = self._bugs[actual_bug_id]['status'] |
|
692 |
resolution = self._bugs[actual_bug_id]['resolution'] |
|
10060.1.2
by Gavin Panella
Fix lint. |
693 |
except KeyError: |
12221.1.7
by Jeroen Vermeulen
s/Unparseable/Unparsable/g, plus lint. |
694 |
raise UnparsableBugData( |
695 |
"No status or resolution defined for bug %i" % (bug_id)) |
|
9150.3.1
by Graham Binns
Refactored getRemoteStatuses() into BugzillaAPI. |
696 |
|
697 |
if resolution != '': |
|
698 |
return "%s %s" % (status, resolution) |
|
699 |
else: |
|
700 |
return status |
|
701 |
||
11304.1.5
by Bryce Harrington
Implement getRemoteImportance() for BugzillaAPI sub-class |
702 |
def getRemoteImportance(self, bug_id): |
703 |
"""See `IExternalBugTracker`."""
|
|
704 |
actual_bug_id = self._getActualBugId(bug_id) |
|
705 |
||
706 |
# Attempt to get the priority and severity from the bug.
|
|
707 |
# If we don't have the data for either, raise an error.
|
|
708 |
try: |
|
709 |
priority = self._bugs[actual_bug_id]['priority'] |
|
710 |
severity = self._bugs[actual_bug_id]['severity'] |
|
711 |
except KeyError: |
|
12221.1.7
by Jeroen Vermeulen
s/Unparseable/Unparsable/g, plus lint. |
712 |
raise UnparsableBugData( |
713 |
"No priority or severity defined for bug %i" % bug_id) |
|
11304.1.5
by Bryce Harrington
Implement getRemoteImportance() for BugzillaAPI sub-class |
714 |
|
715 |
if severity != '': |
|
716 |
return "%s %s" % (priority, severity) |
|
717 |
else: |
|
718 |
return priority |
|
719 |
||
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
720 |
@ensure_no_transaction
|
9150.3.4
by Graham Binns
Added implementation of getModifiedRemoteBugs(). |
721 |
def getModifiedRemoteBugs(self, bug_ids, last_checked): |
722 |
"""See `IExternalBugTracker`."""
|
|
10060.1.1
by Gavin Panella
Force the use of datetime in the XML-RPC transport, and remove the workarounds in BugzillaAPI and BugzillaLPPlugin. |
723 |
response_dict = self.xmlrpc_proxy.Bug.search( |
724 |
{'id': bug_ids, 'last_change_time': last_checked}) |
|
9150.3.4
by Graham Binns
Added implementation of getModifiedRemoteBugs(). |
725 |
remote_bugs = response_dict['bugs'] |
726 |
# Store the bugs we've imported and return only their IDs.
|
|
727 |
self._storeBugs(remote_bugs) |
|
9245.2.1
by Graham Binns
BugzillaAPI.getModifiedRemoteBugs() now returns a list of strings, not ints. |
728 |
# Marshal the bug IDs into strings before returning them since
|
729 |
# the remote Bugzilla may return ints rather than strings.
|
|
10060.1.1
by Gavin Panella
Force the use of datetime in the XML-RPC transport, and remove the workarounds in BugzillaAPI and BugzillaLPPlugin. |
730 |
return [str(remote_bug['id']) for remote_bug in remote_bugs] |
9150.3.4
by Graham Binns
Added implementation of getModifiedRemoteBugs(). |
731 |
|
9150.3.6
by Graham Binns
Moved getRemoteProduct() to BugzillaAPI. |
732 |
def getRemoteProduct(self, remote_bug): |
733 |
"""See `IExternalBugTracker`."""
|
|
734 |
actual_bug_id = self._getActualBugId(remote_bug) |
|
735 |
return self._bugs[actual_bug_id]['product'] |
|
736 |
||
9150.3.7
by Graham Binns
Moved getProductsForRemoteBugs() into BugzillaAPI. |
737 |
def getProductsForRemoteBugs(self, bug_ids): |
738 |
"""Return the products to which a set of remote bugs belong.
|
|
739 |
||
740 |
:param bug_ids: A list of bug IDs or aliases.
|
|
741 |
:returns: A dict of (bug_id_or_alias, product) mappings. If a
|
|
742 |
bug ID specified in `bug_ids` is invalid, it will be ignored.
|
|
743 |
"""
|
|
744 |
# Fetch from the server those bugs that we haven't already
|
|
745 |
# fetched.
|
|
746 |
self.initializeRemoteBugDB(bug_ids) |
|
747 |
||
748 |
bug_products = {} |
|
749 |
for bug_id in bug_ids: |
|
750 |
# If one of the bugs we're trying to get the product for
|
|
751 |
# doesn't exist, just skip it.
|
|
752 |
try: |
|
753 |
actual_bug_id = self._getActualBugId(bug_id) |
|
754 |
except BugNotFound: |
|
755 |
continue
|
|
756 |
||
757 |
bug_dict = self._bugs[actual_bug_id] |
|
758 |
bug_products[bug_id] = bug_dict['product'] |
|
759 |
||
760 |
return bug_products |
|
761 |
||
10122.2.1
by Gavin Panella
Don't pass BugWatch objects (i.e. a db/model object) into code in the externalbugtracker module. |
762 |
def getCommentIds(self, remote_bug_id): |
9150.5.3
by Graham Binns
Added tests and implementation for getCommentIds(). |
763 |
"""See `ISupportsCommentImport`."""
|
10122.2.1
by Gavin Panella
Don't pass BugWatch objects (i.e. a db/model object) into code in the externalbugtracker module. |
764 |
actual_bug_id = self._getActualBugId(remote_bug_id) |
9150.5.3
by Graham Binns
Added tests and implementation for getCommentIds(). |
765 |
|
766 |
# Check that the bug exists, first.
|
|
767 |
if actual_bug_id not in self._bugs: |
|
10122.2.1
by Gavin Panella
Don't pass BugWatch objects (i.e. a db/model object) into code in the externalbugtracker module. |
768 |
raise BugNotFound(remote_bug_id) |
9150.5.3
by Graham Binns
Added tests and implementation for getCommentIds(). |
769 |
|
770 |
# Get only the remote comment IDs and store them in the
|
|
771 |
# 'comments' field of the bug.
|
|
9302.1.1
by Graham Binns
Fixed bugs 422848 and 423046. |
772 |
return_dict = self.xmlrpc_proxy.Bug.comments({ |
9150.5.3
by Graham Binns
Added tests and implementation for getCommentIds(). |
773 |
'ids': [actual_bug_id], |
774 |
'include_fields': ['id'], |
|
775 |
})
|
|
776 |
||
777 |
# We need to convert bug and comment ids to strings (see bugs
|
|
778 |
# 248662 amd 248938).
|
|
9302.1.1
by Graham Binns
Fixed bugs 422848 and 423046. |
779 |
bug_comments_dict = return_dict['bugs'] |
780 |
bug_comments = bug_comments_dict[str(actual_bug_id)]['comments'] |
|
781 |
||
9150.5.3
by Graham Binns
Added tests and implementation for getCommentIds(). |
782 |
return [str(comment['id']) for comment in bug_comments] |
783 |
||
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
784 |
@ensure_no_transaction
|
10122.2.1
by Gavin Panella
Don't pass BugWatch objects (i.e. a db/model object) into code in the externalbugtracker module. |
785 |
def fetchComments(self, remote_bug_id, comment_ids): |
9150.5.4
by Graham Binns
Added tests and implementation for fetchComments(). |
786 |
"""See `ISupportsCommentImport`."""
|
10122.2.1
by Gavin Panella
Don't pass BugWatch objects (i.e. a db/model object) into code in the externalbugtracker module. |
787 |
actual_bug_id = self._getActualBugId(remote_bug_id) |
9150.5.4
by Graham Binns
Added tests and implementation for fetchComments(). |
788 |
|
789 |
# We need to cast comment_ids to integers, since
|
|
10694.2.25
by Graham Binns
Renamed BugWatchUpdater -> CheckwatchesMaster. This is the wrong name for it, but I did it to avoid bikeshedding. |
790 |
# CheckwatchesMaster.importBugComments() will pass us a list of
|
9150.5.4
by Graham Binns
Added tests and implementation for fetchComments(). |
791 |
# strings (see bug 248938).
|
792 |
comment_ids = [int(comment_id) for comment_id in comment_ids] |
|
793 |
||
794 |
# Fetch the comments we want.
|
|
795 |
return_dict = self.xmlrpc_proxy.Bug.comments({ |
|
796 |
'comment_ids': comment_ids, |
|
797 |
})
|
|
798 |
comments = return_dict['comments'] |
|
799 |
||
800 |
# As a sanity check, drop any comments that don't belong to the
|
|
10122.2.1
by Gavin Panella
Don't pass BugWatch objects (i.e. a db/model object) into code in the externalbugtracker module. |
801 |
# bug in remote_bug_id.
|
9150.5.4
by Graham Binns
Added tests and implementation for fetchComments(). |
802 |
for comment_id, comment in comments.items(): |
803 |
if int(comment['bug_id']) != actual_bug_id: |
|
804 |
del comments[comment_id] |
|
805 |
||
9302.1.1
by Graham Binns
Fixed bugs 422848 and 423046. |
806 |
# Ensure that comment IDs are converted to ints.
|
807 |
comments_with_int_ids = dict( |
|
808 |
(int(id), comments[id]) for id in comments) |
|
809 |
self._bugs[actual_bug_id]['comments'] = comments_with_int_ids |
|
9150.5.4
by Graham Binns
Added tests and implementation for fetchComments(). |
810 |
|
10122.2.1
by Gavin Panella
Don't pass BugWatch objects (i.e. a db/model object) into code in the externalbugtracker module. |
811 |
def getPosterForComment(self, remote_bug_id, comment_id): |
9150.5.5
by Graham Binns
Refactored getPoster* and getMessageForComment() into BugzillaAPI. |
812 |
"""See `ISupportsCommentImport`."""
|
10122.2.1
by Gavin Panella
Don't pass BugWatch objects (i.e. a db/model object) into code in the externalbugtracker module. |
813 |
actual_bug_id = self._getActualBugId(remote_bug_id) |
9150.5.5
by Graham Binns
Refactored getPoster* and getMessageForComment() into BugzillaAPI. |
814 |
|
815 |
# We need to cast comment_id to integers, since
|
|
10694.2.25
by Graham Binns
Renamed BugWatchUpdater -> CheckwatchesMaster. This is the wrong name for it, but I did it to avoid bikeshedding. |
816 |
# CheckwatchesMaster.importBugComments() will pass us a string (see
|
9150.5.5
by Graham Binns
Refactored getPoster* and getMessageForComment() into BugzillaAPI. |
817 |
# bug 248938).
|
818 |
comment_id = int(comment_id) |
|
819 |
||
820 |
comment = self._bugs[actual_bug_id]['comments'][comment_id] |
|
821 |
display_name, email = parseaddr(comment['author']) |
|
822 |
||
823 |
# If the name is empty then we return None so that
|
|
824 |
# IPersonSet.ensurePerson() can actually do something with it.
|
|
825 |
if not display_name: |
|
826 |
display_name = None |
|
827 |
||
828 |
return (display_name, email) |
|
829 |
||
10122.2.1
by Gavin Panella
Don't pass BugWatch objects (i.e. a db/model object) into code in the externalbugtracker module. |
830 |
def getMessageForComment(self, remote_bug_id, comment_id, poster): |
9150.5.5
by Graham Binns
Refactored getPoster* and getMessageForComment() into BugzillaAPI. |
831 |
"""See `ISupportsCommentImport`."""
|
10122.2.1
by Gavin Panella
Don't pass BugWatch objects (i.e. a db/model object) into code in the externalbugtracker module. |
832 |
actual_bug_id = self._getActualBugId(remote_bug_id) |
9150.5.5
by Graham Binns
Refactored getPoster* and getMessageForComment() into BugzillaAPI. |
833 |
|
834 |
# We need to cast comment_id to integers, since
|
|
10694.2.25
by Graham Binns
Renamed BugWatchUpdater -> CheckwatchesMaster. This is the wrong name for it, but I did it to avoid bikeshedding. |
835 |
# CheckwatchesMaster.importBugComments() will pass us a string (see
|
9150.5.5
by Graham Binns
Refactored getPoster* and getMessageForComment() into BugzillaAPI. |
836 |
# bug 248938).
|
837 |
comment_id = int(comment_id) |
|
838 |
comment = self._bugs[actual_bug_id]['comments'][comment_id] |
|
10060.1.4
by Gavin Panella
Remove more xmlrpclib.DateTime hackery. |
839 |
return getUtility(IMessageSet).fromText( |
9150.5.5
by Graham Binns
Refactored getPoster* and getMessageForComment() into BugzillaAPI. |
840 |
owner=poster, subject='', content=comment['text'], |
10060.1.4
by Gavin Panella
Remove more xmlrpclib.DateTime hackery. |
841 |
datecreated=comment['time'].replace(tzinfo=pytz.timezone('UTC'))) |
9150.5.5
by Graham Binns
Refactored getPoster* and getMessageForComment() into BugzillaAPI. |
842 |
|
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
843 |
@ensure_no_transaction
|
9234.2.2
by Graham Binns
BugzillaAPI now implements ISupportsCommentPushing. |
844 |
@needs_authentication
|
845 |
def addRemoteComment(self, remote_bug, comment_body, rfc822msgid): |
|
846 |
"""Add a comment to the remote bugtracker.
|
|
847 |
||
848 |
See `ISupportsCommentPushing`.
|
|
849 |
"""
|
|
850 |
actual_bug_id = self._getActualBugId(remote_bug) |
|
851 |
||
852 |
request_params = { |
|
853 |
'id': actual_bug_id, |
|
854 |
'comment': comment_body, |
|
855 |
}
|
|
856 |
return_dict = self.xmlrpc_proxy.Bug.add_comment(request_params) |
|
857 |
||
858 |
# We cast the return value to string, since that's what
|
|
10694.2.25
by Graham Binns
Renamed BugWatchUpdater -> CheckwatchesMaster. This is the wrong name for it, but I did it to avoid bikeshedding. |
859 |
# CheckwatchesMaster will expect (see bug 248938).
|
9234.2.2
by Graham Binns
BugzillaAPI now implements ISupportsCommentPushing. |
860 |
return str(return_dict['id']) |
861 |
||
9947.4.2
by Graham Binns
Added tests and implementation for BugzillaAPI's implementation of ISupportsBackLinking. |
862 |
def getLaunchpadBugId(self, remote_bug): |
863 |
"""Return the Launchpad bug ID for the remote bug.
|
|
864 |
||
865 |
See `ISupportsBackLinking`.
|
|
866 |
"""
|
|
9947.4.3
by Graham Binns
Added XXX for bug 490267 at Abel's request. |
867 |
# XXX gmb 2009-11-30 bug=490267
|
868 |
# In fact, this method always returns None due to bug
|
|
869 |
# 490267. Once the bug is fixed in Bugzilla we should update
|
|
870 |
# this method.
|
|
9947.4.2
by Graham Binns
Added tests and implementation for BugzillaAPI's implementation of ISupportsBackLinking. |
871 |
return None |
872 |
||
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
873 |
@ensure_no_transaction
|
9947.4.2
by Graham Binns
Added tests and implementation for BugzillaAPI's implementation of ISupportsBackLinking. |
874 |
@needs_authentication
|
10694.2.24
by Gavin Panella
Change the function signature style as suggested in review. |
875 |
def setLaunchpadBugId(self, remote_bug, launchpad_bug_id, |
876 |
launchpad_bug_url): |
|
9947.4.2
by Graham Binns
Added tests and implementation for BugzillaAPI's implementation of ISupportsBackLinking. |
877 |
"""Set the Launchpad bug for a given remote bug.
|
878 |
||
879 |
See `ISupportsBackLinking`.
|
|
880 |
"""
|
|
881 |
actual_bug_id = self._getActualBugId(remote_bug) |
|
882 |
||
883 |
request_params = { |
|
884 |
'ids': [actual_bug_id], |
|
885 |
'add': [launchpad_bug_url], |
|
886 |
}
|
|
887 |
||
888 |
self.xmlrpc_proxy.Bug.update_see_also(request_params) |
|
889 |
||
9150.2.2
by Graham Binns
Add a BugzillaAPI ExternalBugTracker. |
890 |
|
891 |
class BugzillaLPPlugin(BugzillaAPI): |
|
892 |
"""An `ExternalBugTracker` to handle Bugzillas using the LP Plugin."""
|
|
893 |
||
894 |
implements( |
|
895 |
ISupportsBackLinking, ISupportsCommentImport, |
|
896 |
ISupportsCommentPushing) |
|
897 |
||
10065.1.1
by Gavin Panella
BugzillaAPI and BugzillaLPPlugin.getExternalBugTrackerToUse() needs to just return self, rather than use the inherited version. |
898 |
def getExternalBugTrackerToUse(self): |
899 |
"""The Bugzilla LP Plugin has been chosen, so return self."""
|
|
900 |
return self |
|
901 |
||
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
902 |
@ensure_no_transaction
|
6532.1.3
by Graham Binns
Added tests for _authenticate. |
903 |
def _authenticate(self): |
6532.1.18
by Graham Binns
Review changes for bac. |
904 |
"""Authenticate with the remote Bugzilla instance.
|
905 |
||
906 |
Authentication works by means of using a LoginToken of type
|
|
907 |
BUGTRACKER. We send the token text to the remote server as a
|
|
908 |
parameter to Launchpad.login(), which verifies it using the
|
|
909 |
standard launchpad.net/token/$token/+bugtracker-handshake URL.
|
|
910 |
||
911 |
If the token is valid, Bugzilla will send us a user ID as a
|
|
912 |
return value for the call to Launchpad.login() and will set two
|
|
913 |
cookies in the response header, Bugzilla_login and
|
|
914 |
Bugzilla_logincookie, which we can then use to re-authenticate
|
|
915 |
ourselves for each subsequent method call.
|
|
916 |
"""
|
|
6532.1.3
by Graham Binns
Added tests for _authenticate. |
917 |
internal_xmlrpc_server = xmlrpclib.ServerProxy( |
918 |
config.checkwatches.xmlrpc_url, |
|
919 |
transport=self.internal_xmlrpc_transport) |
|
920 |
||
921 |
token_text = internal_xmlrpc_server.newBugTrackerToken() |
|
922 |
||
7130.2.8
by Graham Binns
BugzillaLPPlugin._authenticate() now raises a BugTrackerAuthenticationError when an error occurs during authentication. |
923 |
try: |
10060.1.2
by Gavin Panella
Fix lint. |
924 |
self.xmlrpc_proxy.Launchpad.login( |
7130.2.8
by Graham Binns
BugzillaLPPlugin._authenticate() now raises a BugTrackerAuthenticationError when an error occurs during authentication. |
925 |
{'token': token_text}) |
926 |
except xmlrpclib.Fault, fault: |
|
927 |
message = 'XML-RPC Fault: %s "%s"' % ( |
|
928 |
fault.faultCode, fault.faultString) |
|
929 |
raise BugTrackerAuthenticationError( |
|
930 |
self.baseurl, message) |
|
931 |
except xmlrpclib.ProtocolError, error: |
|
932 |
message = 'Protocol error: %s "%s"' % ( |
|
933 |
error.errcode, error.errmsg) |
|
934 |
raise BugTrackerAuthenticationError( |
|
935 |
self.baseurl, message) |
|
6532.1.9
by Graham Binns
Tests seem to work now. |
936 |
|
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
937 |
@ensure_no_transaction
|
6759.2.3
by Graham Binns
Added implementation to covert bug 203559. |
938 |
def getModifiedRemoteBugs(self, bug_ids, last_checked): |
6797.2.2
by Graham Binns
Review changes for Barry. |
939 |
"""See `IExternalBugTracker`."""
|
10060.1.1
by Gavin Panella
Force the use of datetime in the XML-RPC transport, and remove the workarounds in BugzillaAPI and BugzillaLPPlugin. |
940 |
# We pass permissive=True to ensure that Bugzilla won't error
|
941 |
# if we ask for a bug that doesn't exist.
|
|
942 |
response_dict = self.xmlrpc_proxy.Launchpad.get_bugs({ |
|
6759.2.3
by Graham Binns
Added implementation to covert bug 203559. |
943 |
'ids': bug_ids, |
10060.1.1
by Gavin Panella
Force the use of datetime in the XML-RPC transport, and remove the workarounds in BugzillaAPI and BugzillaLPPlugin. |
944 |
'changed_since': last_checked, |
6759.2.3
by Graham Binns
Added implementation to covert bug 203559. |
945 |
'permissive': True, |
10060.1.1
by Gavin Panella
Force the use of datetime in the XML-RPC transport, and remove the workarounds in BugzillaAPI and BugzillaLPPlugin. |
946 |
})
|
6759.2.3
by Graham Binns
Added implementation to covert bug 203559. |
947 |
remote_bugs = response_dict['bugs'] |
948 |
# Store the bugs we've imported and return only their IDs.
|
|
949 |
self._storeBugs(remote_bugs) |
|
10060.1.1
by Gavin Panella
Force the use of datetime in the XML-RPC transport, and remove the workarounds in BugzillaAPI and BugzillaLPPlugin. |
950 |
return [remote_bug['id'] for remote_bug in remote_bugs] |
6759.2.3
by Graham Binns
Added implementation to covert bug 203559. |
951 |
|
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
952 |
@ensure_no_transaction
|
7147.1.3
by Graham Binns
You can now pass a products list to BugzillaLPPlugin.initializeRemoteBugDB(). |
953 |
def initializeRemoteBugDB(self, bug_ids, products=None): |
6759.2.3
by Graham Binns
Added implementation to covert bug 203559. |
954 |
"""See `IExternalBugTracker`."""
|
955 |
# First, discard all those bug IDs about which we already have
|
|
956 |
# data.
|
|
9150.2.9
by Graham Binns
Added initializeRemoteBugDB() to BugzillaAPI and refactored some utility functions out of BugzillaLPPlugin. |
957 |
bug_ids_to_retrieve = self._getBugIdsToRetrieve(bug_ids) |
6759.2.3
by Graham Binns
Added implementation to covert bug 203559. |
958 |
|
959 |
# Next, grab the bugs we still need from the remote server.
|
|
6797.2.2
by Graham Binns
Review changes for Barry. |
960 |
# We pass permissive=True to ensure that Bugzilla won't error if
|
961 |
# we ask for a bug that doesn't exist.
|
|
6759.2.3
by Graham Binns
Added implementation to covert bug 203559. |
962 |
request_args = { |
963 |
'ids': bug_ids_to_retrieve, |
|
964 |
'permissive': True, |
|
965 |
}
|
|
7147.1.3
by Graham Binns
You can now pass a products list to BugzillaLPPlugin.initializeRemoteBugDB(). |
966 |
|
967 |
if products is not None: |
|
968 |
request_args['products'] = products |
|
969 |
||
6759.2.3
by Graham Binns
Added implementation to covert bug 203559. |
970 |
response_dict = self.xmlrpc_proxy.Launchpad.get_bugs(request_args) |
971 |
remote_bugs = response_dict['bugs'] |
|
972 |
||
973 |
self._storeBugs(remote_bugs) |
|
974 |
||
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
975 |
@ensure_no_transaction
|
6290.4.4
by Graham Binns
Added tests and implementation for getCurrentDBTime(). |
976 |
def getCurrentDBTime(self): |
977 |
"""See `IExternalBugTracker`."""
|
|
6570.1.2
by Graham Binns
Altered naming in BugzillaLPPlugin and merged parent branch. |
978 |
time_dict = self.xmlrpc_proxy.Launchpad.time() |
6290.4.4
by Graham Binns
Added tests and implementation for getCurrentDBTime(). |
979 |
|
980 |
# Return the UTC time sent by the server so that we don't have
|
|
981 |
# to care about timezones.
|
|
10060.1.4
by Gavin Panella
Remove more xmlrpclib.DateTime hackery. |
982 |
server_utc_time = time_dict['utc_time'] |
6290.4.4
by Graham Binns
Added tests and implementation for getCurrentDBTime(). |
983 |
return server_utc_time.replace(tzinfo=pytz.timezone('UTC')) |
984 |
||
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
985 |
@ensure_no_transaction
|
10122.2.1
by Gavin Panella
Don't pass BugWatch objects (i.e. a db/model object) into code in the externalbugtracker module. |
986 |
def getCommentIds(self, remote_bug_id): |
6506.5.5
by Graham Binns
Added tests and implementation for getCommentIds(). |
987 |
"""See `ISupportsCommentImport`."""
|
10122.2.1
by Gavin Panella
Don't pass BugWatch objects (i.e. a db/model object) into code in the externalbugtracker module. |
988 |
actual_bug_id = self._getActualBugId(remote_bug_id) |
6506.5.5
by Graham Binns
Added tests and implementation for getCommentIds(). |
989 |
|
990 |
# Check that the bug exists, first.
|
|
6797.2.5
by Graham Binns
Made properties of BugzillaLPPlugin non-public. |
991 |
if actual_bug_id not in self._bugs: |
10122.2.1
by Gavin Panella
Don't pass BugWatch objects (i.e. a db/model object) into code in the externalbugtracker module. |
992 |
raise BugNotFound(remote_bug_id) |
6506.5.5
by Graham Binns
Added tests and implementation for getCommentIds(). |
993 |
|
994 |
# Get only the remote comment IDs and store them in the
|
|
995 |
# 'comments' field of the bug.
|
|
996 |
request_params = { |
|
997 |
'bug_ids': [actual_bug_id], |
|
6671.1.5
by Graham Binns
Altered the method signature of Launchpad.comment(). |
998 |
'include_fields': ['id'], |
6506.5.5
by Graham Binns
Added tests and implementation for getCommentIds(). |
999 |
}
|
6671.1.6
by Graham Binns
Removed lint. |
1000 |
bug_comments_dict = self.xmlrpc_proxy.Launchpad.comments( |
1001 |
request_params) |
|
6506.5.5
by Graham Binns
Added tests and implementation for getCommentIds(). |
1002 |
|
6706.1.1
by Graham Binns
Added tests and fix for bug 248662. |
1003 |
# We need to convert actual_bug_id to a string due to a quirk
|
1004 |
# with XML-RPC (see bug 248662).
|
|
6706.1.2
by Graham Binns
Change suggested by bac. |
1005 |
bug_comments = bug_comments_dict['bugs'][str(actual_bug_id)] |
6712.1.1
by Graham Binns
Fixed bug 248938. |
1006 |
|
1007 |
# We also need to convert each comment ID to a string, since
|
|
10694.2.25
by Graham Binns
Renamed BugWatchUpdater -> CheckwatchesMaster. This is the wrong name for it, but I did it to avoid bikeshedding. |
1008 |
# that's what CheckwatchesMaster.importBugComments() expects (see
|
6712.1.1
by Graham Binns
Fixed bug 248938. |
1009 |
# bug 248938).
|
1010 |
return [str(comment['id']) for comment in bug_comments] |
|
6506.5.6
by Graham Binns
Added tests and implementation for fetchComments(). Made getCommentIds() work correctly. |
1011 |
|
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
1012 |
@ensure_no_transaction
|
10122.2.1
by Gavin Panella
Don't pass BugWatch objects (i.e. a db/model object) into code in the externalbugtracker module. |
1013 |
def fetchComments(self, remote_bug_id, comment_ids): |
6506.5.6
by Graham Binns
Added tests and implementation for fetchComments(). Made getCommentIds() work correctly. |
1014 |
"""See `ISupportsCommentImport`."""
|
10122.2.1
by Gavin Panella
Don't pass BugWatch objects (i.e. a db/model object) into code in the externalbugtracker module. |
1015 |
actual_bug_id = self._getActualBugId(remote_bug_id) |
6506.5.6
by Graham Binns
Added tests and implementation for fetchComments(). Made getCommentIds() work correctly. |
1016 |
|
6712.1.1
by Graham Binns
Fixed bug 248938. |
1017 |
# We need to cast comment_ids to integers, since
|
10694.2.25
by Graham Binns
Renamed BugWatchUpdater -> CheckwatchesMaster. This is the wrong name for it, but I did it to avoid bikeshedding. |
1018 |
# CheckwatchesMaster.importBugComments() will pass us a list of
|
6712.1.1
by Graham Binns
Fixed bug 248938. |
1019 |
# strings (see bug 248938).
|
1020 |
comment_ids = [int(comment_id) for comment_id in comment_ids] |
|
1021 |
||
6506.5.6
by Graham Binns
Added tests and implementation for fetchComments(). Made getCommentIds() work correctly. |
1022 |
# Fetch the comments we want.
|
1023 |
request_params = { |
|
1024 |
'bug_ids': [actual_bug_id], |
|
1025 |
'ids': comment_ids, |
|
1026 |
}
|
|
6671.1.6
by Graham Binns
Removed lint. |
1027 |
bug_comments_dict = self.xmlrpc_proxy.Launchpad.comments( |
1028 |
request_params) |
|
6706.1.1
by Graham Binns
Added tests and fix for bug 248662. |
1029 |
|
1030 |
# We need to convert actual_bug_id to a string here due to a
|
|
1031 |
# quirk with XML-RPC (see bug 248662).
|
|
1032 |
comment_list = bug_comments_dict['bugs'][str(actual_bug_id)] |
|
6506.5.8
by Graham Binns
Added tests and implementation for getMessageForComment(). |
1033 |
|
1034 |
# Transfer the comment list into a dict.
|
|
6506.5.10
by Graham Binns
Review changes for Gavin. |
1035 |
bug_comments = dict( |
1036 |
(comment['id'], comment) for comment in comment_list) |
|
6506.5.6
by Graham Binns
Added tests and implementation for fetchComments(). Made getCommentIds() work correctly. |
1037 |
|
6797.2.5
by Graham Binns
Made properties of BugzillaLPPlugin non-public. |
1038 |
self._bugs[actual_bug_id]['comments'] = bug_comments |
6506.5.5
by Graham Binns
Added tests and implementation for getCommentIds(). |
1039 |
|
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
1040 |
@ensure_no_transaction
|
6527.10.8
by Graham Binns
Added an ordered_dict_by_string() method. |
1041 |
@needs_authentication
|
1042 |
def addRemoteComment(self, remote_bug, comment_body, rfc822msgid): |
|
1043 |
"""Add a comment to the remote bugtracker.
|
|
1044 |
||
1045 |
See `ISupportsCommentPushing`.
|
|
1046 |
"""
|
|
6527.10.9
by Graham Binns
Added a test to ensure that the comment is pushed properly to the remote bugtracker. |
1047 |
actual_bug_id = self._getActualBugId(remote_bug) |
1048 |
||
6527.10.8
by Graham Binns
Added an ordered_dict_by_string() method. |
1049 |
request_params = { |
6527.10.9
by Graham Binns
Added a test to ensure that the comment is pushed properly to the remote bugtracker. |
1050 |
'id': actual_bug_id, |
6527.10.8
by Graham Binns
Added an ordered_dict_by_string() method. |
1051 |
'comment': comment_body, |
1052 |
}
|
|
6671.1.3
by Graham Binns
Updated Bugzilla[LPPlugin] to point to the right namespace. |
1053 |
return_dict = self.xmlrpc_proxy.Launchpad.add_comment(request_params) |
6527.10.8
by Graham Binns
Added an ordered_dict_by_string() method. |
1054 |
|
6712.1.1
by Graham Binns
Fixed bug 248938. |
1055 |
# We cast the return value to string, since that's what
|
10694.2.25
by Graham Binns
Renamed BugWatchUpdater -> CheckwatchesMaster. This is the wrong name for it, but I did it to avoid bikeshedding. |
1056 |
# CheckwatchesMaster will expect (see bug 248938).
|
6712.1.1
by Graham Binns
Fixed bug 248938. |
1057 |
return str(return_dict['comment_id']) |
7026.2.2
by Graham Binns
Added implementation for getLaunchpadBugId() and setLaunchpadBugId(). |
1058 |
|
1059 |
def getLaunchpadBugId(self, remote_bug): |
|
1060 |
"""Return the current Launchpad bug ID for a given remote bug.
|
|
1061 |
||
1062 |
See `ISupportsBackLinking`.
|
|
1063 |
"""
|
|
1064 |
actual_bug_id = self._getActualBugId(remote_bug) |
|
1065 |
||
1066 |
# Grab the internals dict from the bug, if there is one. If
|
|
1067 |
# there isn't, return None, since there's no Launchpad bug ID to
|
|
1068 |
# be had.
|
|
1069 |
internals = self._bugs[actual_bug_id].get('internals', None) |
|
1070 |
if internals is None: |
|
1071 |
return None |
|
1072 |
||
1073 |
# Extract the Launchpad bug ID and return it. Return None if
|
|
1074 |
# there isn't one or it's set to an empty string.
|
|
1075 |
launchpad_bug_id = internals.get('launchpad_id', None) |
|
7026.2.4
by Graham Binns
Review changes for Celso. |
1076 |
if launchpad_bug_id == '': |
1077 |
launchpad_bug_id = None |
|
1078 |
||
1079 |
return launchpad_bug_id |
|
7026.2.2
by Graham Binns
Added implementation for getLaunchpadBugId() and setLaunchpadBugId(). |
1080 |
|
10512.4.3
by Gavin Panella
Sprinkle ensure_no_transaction() in good places. |
1081 |
@ensure_no_transaction
|
7026.2.2
by Graham Binns
Added implementation for getLaunchpadBugId() and setLaunchpadBugId(). |
1082 |
@needs_authentication
|
10694.2.24
by Gavin Panella
Change the function signature style as suggested in review. |
1083 |
def setLaunchpadBugId(self, remote_bug, launchpad_bug_id, |
1084 |
launchpad_bug_url): |
|
7026.2.2
by Graham Binns
Added implementation for getLaunchpadBugId() and setLaunchpadBugId(). |
1085 |
"""Set the Launchpad bug for a given remote bug.
|
1086 |
||
1087 |
See `ISupportsBackLinking`.
|
|
1088 |
"""
|
|
1089 |
actual_bug_id = self._getActualBugId(remote_bug) |
|
1090 |
||
1091 |
request_params = { |
|
1092 |
'id': actual_bug_id, |
|
1093 |
'launchpad_id': launchpad_bug_id, |
|
1094 |
}
|
|
1095 |
||
1096 |
self.xmlrpc_proxy.Launchpad.set_link(request_params) |