2 # Small event handler to automatically open URLs and file
3 # bug reports at a bugzilla of your choiche
5 # This class requires python2.4 because of the urllib2 usage
8 def seppuku_spliturl(url):
10 Split GET URL to return the host base and the query
14 (uri,query) = urllib.splitquery(url)
16 for par in query.split("&"):
17 (key,value) = urllib.splitvalue(par)
18 if not key or len(key) == 0 or not value:
20 key = urllib.unquote(key)
21 value = urllib.unquote(value)
28 def seppuku_login(opener, login, user, password):
30 We need to post to query.cgi with the parameters
31 Bugzilla_login and Bugzilla_password and will scan
32 the resulting page then
34 @param opened = cookie enabled urllib2 opener
35 @param login = http://bugs.openembedded.org/query.cgi?
36 @param user = Your username
37 @param password = Your password
40 param = urllib.urlencode( {"GoAheadAndLogIn" : 1, "Bugzilla_login" : user, "Bugzilla_password" : password } )
41 result = opener.open(login + param)
43 if result.code != 200:
46 if not '<a href="relogin.cgi">Log out</a>' in txt:
51 def seppuku_find_bug_report_old():
52 from HTMLParser import HTMLParser
54 class BugQueryExtractor(HTMLParser):
57 STATE_FOUND_NUMBER = 2
61 STATE_FOUND_PLATFORM = 6
62 STATE_FOUND_STATUS = 7
63 STATE_FOUND_WHATEVER = 8 # I don't know this field
64 STATE_FOUND_DESCRIPTION =9
67 HTMLParser.__init__(self)
68 self.state = self.STATE_NONE
72 def handle_starttag(self, tag, attr):
73 if self.state == self.STATE_NONE and tag.lower() == "tr":
74 if len(attr) == 1 and attr[0][0] == 'class' and \
75 ('bz_normal' in attr[0][1] or 'bz_blocker' in attr[0][1] or 'bz_enhancement' in attr[0][1] or 'bz_major' in attr[0][1] or 'bz_minor' in attr[0][1] or 'bz_trivial' in attr[0][1] or 'bz_critical' in attr[0][1] or 'bz_wishlist' in attr[0][1]) \
76 and 'bz_P' in attr[0][1]:
77 self.state = self.STATE_FOUND_TR
78 elif self.state == self.STATE_FOUND_TR and tag.lower() == "td":
81 def handle_endtag(self, tag):
82 if tag.lower() == "tr":
83 if self.state != self.STATE_NONE:
84 self.bugs.append( (self.bug,self.status) )
85 self.state = self.STATE_NONE
87 if self.state > 1 and tag.lower() == "td":
90 def handle_data(self,data):
97 if self.state == self.STATE_FOUND_NUMBER:
99 #1995 in bugs.oe.org has [SEC] additionally to the number and we want to ignore it
103 elif self.state == self.STATE_FOUND_STATUS:
109 return BugQueryExtractor()
113 def seppuku_find_bug_report(debug_file, opener, query, product, component, bugname):
115 Find a bug report with the sane name and return the bug id
118 @param opener = urllib2 opener
119 @param query = e.g. http://bugs.openembedded.org/query.cgi?
120 @param product = search for this product
121 @param component = search for this component
122 @param bugname = the bug to search for
124 http://bugs.openembedded.org/buglist.cgi?short_desc_type=substring&short_desc=manual+test+bug&product=Openembedded&emailreporter2=1&emailtype2=substring&email2=freyther%40yahoo.com
125 but it does not support ctype=csv...
128 product = urllib.quote(product)
129 component = urllib.quote(component)
130 bugname = urllib.quote(bugname)
132 result = opener.open("%(query)s?product=%(product)s&component=%(component)s&short_desc_type=substring&short_desc=%(bugname)s" % vars())
133 if result.code != 200:
134 raise "Can not query the bugzilla at all"
136 scanner = seppuku_find_bug_report_old()
138 if len(scanner.result()) == 0:
139 print >> debug_file, "Scanner failed to scan the html site"
140 print >> debug_file, "%(query)s?product=%(product)s&component=%(component)s&short_desc_type=substring&short_desc=%(bugname)s" % vars()
141 print >> debug_file, txt
143 else: # silently pick the first result
144 print >> debug_file, "Result of bug search is "
145 print >> debug_file, txt
146 (number,status) = scanner.result()[0]
147 return (not status in ["CLOS", "RESO", "VERI"],number)
149 def seppuku_reopen_bug(poster, file, product, component, bug_number, bugname, text):
151 Reopen a bug report and append to the comment
153 Same as with opening a new report, some bits need to be inside the url
155 http://bugs.openembedded.org/process_bug.cgi?id=239&bug_file_loc=http%3A%2F%2F&version=Angstrom&longdesclength=2&product=Openembedded&component=Build&comment=bla&priority=P2&bug_severity=normal&op_sys=Linux&rep_platform=Other&knob=reopen&short_desc=foo
159 (uri, param) = seppuku_spliturl( file )
162 param["product"] = product
163 param["component"] = component
164 param["longdesclength"] = 2
165 param["short_desc"] = bugname
166 param["knob"] = "reopen"
167 param["id"] = bug_number
168 param["comment"] = text
171 result = poster.open( uri, param )
172 except urllib2.HTTPError, e:
180 if result.code != 200:
185 def seppuku_file_bug(poster, file, product, component, bugname, text):
187 Create a completely new bug report
190 http://bugs.openembedded.org/post_bug.cgi?bug_file_loc=http%3A%2F%2F&version=Angstrom&product=Openembedded&component=Build&short_desc=foo&comment=bla&priority=P2&bug_severity=normal&op_sys=Linux&rep_platform=Other
192 You are forced to add some default values to the bugzilla query and stop with '&'
194 @param opener urllib2 opener
195 @param file The url used to file a bug report
196 @param product Product
197 @param component Component
198 @param bugname Name of the to be created bug
203 (uri, param) = seppuku_spliturl( file )
204 param["product"] = product
205 param["component"] = component
206 param["short_desc"] = bugname
207 param["comment"] = text
210 result = poster.open( uri, param )
211 except urllib2.HTTPError, e:
219 # scan the result for a bug number
221 # '<title>Bug 2742 Submitted</title>'
223 res = re.findall(("\>Bug (?P<int>\d+) Submitted"), result.read() )
224 if result.code != 200 or len(res) != 1:
229 def seppuku_create_attachment(debug, poster, attach_query, product, component, bug_number, text, file):
232 Create a new attachment for the failed report
237 bb.note("Can't create an attachment, no bugnumber passed to method")
241 param = { "bugid" : bug_number, "action" : "insert", "data" : file, "description" : "Build log", "ispatch" : "0", "contenttypemethod" : "list", "contenttypeselection" : "text/plain", "comment" : text }
244 result = poster.open( attach_query, param )
245 except urllib2.HTTPError, e:
253 print >> debug, result.read()
254 if result.code != 200:
260 addhandler seppuku_eventhandler
261 python seppuku_eventhandler() {
263 Report task failures to the bugzilla
264 and succeeded builds to the box
266 from bb.event import NotHandled, getName
267 from bb import data, mkdirhier, build
272 name = getName(event)
273 if name == "MsgNote":
277 # Try to load our exotic libraries
279 import MultipartPostHandler
281 bb.note("You need to put the MultipartPostHandler into your PYTHONPATH. Download it from http://pipe.scs.fsu.edu/PostHandler/MultipartPostHandler.py")
285 import urllib2, cookielib
287 bb.note("Failed to import the cookielib and urllib2, make sure to use python2.4")
290 if name == "PkgFailed":
291 if not bb.data.getVar('SEPPUKU_AUTOBUILD', data, True) == "0":
292 build.exec_task('do_clean', data)
293 elif name == "TaskFailed" or name == "NoProvider":
294 cj = cookielib.CookieJar()
295 opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
296 poster = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj),MultipartPostHandler.MultipartPostHandler)
297 login = bb.data.getVar("SEPPUKU_LOGIN", data, True)
298 query = bb.data.getVar("SEPPUKU_QUERY", data, True)
299 newbug = bb.data.getVar("SEPPUKU_NEWREPORT", data, True)
300 reopen = bb.data.getVar("SEPPUKU_ADDCOMMENT", data, True)
301 attach = bb.data.getVar("SEPPUKU_ATTACHMENT", data, True)
302 user = bb.data.getVar("SEPPUKU_USER", data, True)
303 passw = bb.data.getVar("SEPPUKU_PASS", data, True)
304 product = bb.data.getVar("SEPPUKU_PRODUCT", data, True)
305 component = bb.data.getVar("SEPPUKU_COMPONENT", data, True)
306 # evil hack to figure out what is going on
307 debug_file = open(os.path.join(bb.data.getVar("TMPDIR", data, True),"..","seppuku-log"),"a")
309 if not seppuku_login(opener, login, user, passw):
310 bb.note("Login to bugzilla failed")
311 print >> debug_file, "Login to bugzilla failed"
314 print >> debug_file, "Logged into the box"
317 if name == "TaskFailed":
318 bugname = "%(package)s-%(pv)s-%(pr)s-%(task)s" % { "package" : bb.data.getVar("PN", data, True),
319 "pv" : bb.data.getVar("PV", data, True),
320 "pr" : bb.data.getVar("PR", data, True),
322 log_file = glob.glob("%s/log.%s.*" % (bb.data.getVar('T', event.data, True), event.task))
323 text = "The package failed to build at %s for machine %s" % (bb.data.getVar('DATETIME', data, True), bb.data.getVar( 'MACHINE', data, True ) )
324 if len(log_file) != 0:
325 print >> debug_file, "Adding log file %s" % log_file[0]
326 file = open(log_file[0], 'r')
328 print >> debug_file, "No log file found for the glob"
329 #elif name == "NoProvider":
330 # bugname = "noprovider for %s " % (event.getItem)
331 # text = "Please fix it"
333 print >> debug_file, "Unknown name '%s'" % name
336 (bug_open, bug_number) = seppuku_find_bug_report(debug_file, opener, query, product, component, bugname)
337 print >> debug_file, "Bug is open: %s and bug number: %s" % (bug_open, bug_number)
339 # The bug is present and still open, no need to attach an error log
340 if bug_number and bug_open:
341 print >> debug_file, "The bug is known as '%s'" % bug_number
344 if bug_number and not bug_open:
345 if not seppuku_reopen_bug(poster, reopen, product, component, bug_number, bugname, text):
346 print >> debug_file, "Failed to reopen the bug report"
348 print >> debug_file, "Reopened the bug report"
350 bug_number = seppuku_file_bug(poster, newbug, product, component, bugname, text)
352 print >> debug_file, "Couldn't acquire a new bug_numer, filing a bugreport failed"
354 print >> debug_file, "The new bug_number: '%s'" % bug_number
356 if bug_number and file:
357 if not seppuku_create_attachment(debug_file, poster, attach, product, component, bug_number, text, file):
358 print >> debug_file, "Failed to attach the build log"
360 print >> debug_file, "Created an attachment for '%s' '%s' '%s'" % (product, component, bug_number)
362 print >> debug_file, "Not trying to create an attachment"