#
# Small event handler to automatically open URLs and file
# bug reports at a bugzilla of your choiche
#
# This class requires python2.4 because of the urllib2 usage
#
def seppuku_spliturl(url):
"""
Split GET URL to return the host base and the query
as a param dictionary
"""
import urllib
(uri,query) = urllib.splitquery(url)
param = {}
for par in query.split("&"):
(key,value) = urllib.splitvalue(par)
if not key or len(key) == 0 or not value:
continue
key = urllib.unquote(key)
value = urllib.unquote(value)
param[key] = value
return (uri,param)
def seppuku_login(opener, login, user, password):
"""
We need to post to query.cgi with the parameters
Bugzilla_login and Bugzilla_password and will scan
the resulting page then
@param opened = cookie enabled urllib2 opener
@param login = http://bugs.openembedded.org/query.cgi?
@param user = Your username
@param password = Your password
"""
import urllib
param = urllib.urlencode( {"GoAheadAndLogIn" : 1, "Bugzilla_login" : user, "Bugzilla_password" : password } )
result = opener.open(login + param)
if result.code != 200:
return False
txt = result.read()
if not 'Log out' in txt:
return False
return True
def seppuku_find_bug_report_old():
from HTMLParser import HTMLParser
class BugQueryExtractor(HTMLParser):
STATE_NONE = 0
STATE_FOUND_TR = 1
STATE_FOUND_NUMBER = 2
STATE_FOUND_PRIO = 3
STATE_FOUND_PRIO2 = 4
STATE_FOUND_NAME = 5
STATE_FOUND_PLATFORM = 6
STATE_FOUND_STATUS = 7
STATE_FOUND_WHATEVER = 8 # I don't know this field
STATE_FOUND_DESCRIPTION =9
def __init__(self):
HTMLParser.__init__(self)
self.state = self.STATE_NONE
self.bugs = []
self.bug = None
def handle_starttag(self, tag, attr):
if self.state == self.STATE_NONE and tag.lower() == "tr":
if len(attr) == 1 and attr[0][0] == 'class' and \
('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]) \
and 'bz_P' in attr[0][1]:
self.state = self.STATE_FOUND_TR
elif self.state == self.STATE_FOUND_TR and tag.lower() == "td":
self.state += 1
def handle_endtag(self, tag):
if tag.lower() == "tr":
if self.state != self.STATE_NONE:
self.bugs.append( (self.bug,self.status) )
self.state = self.STATE_NONE
self.bug = None
if self.state > 1 and tag.lower() == "td":
self.state += 1
def handle_data(self,data):
data = data.strip()
# skip garbage
if len(data) == 0:
return
if self.state == self.STATE_FOUND_NUMBER:
"""
#1995 in bugs.oe.org has [SEC] additionally to the number and we want to ignore it
"""
if not self.bug:
self.bug = data
elif self.state == self.STATE_FOUND_STATUS:
self.status = data
def result(self):
return self.bugs
return BugQueryExtractor()
def seppuku_find_bug_report(debug_file, opener, query, product, component, bugname):
"""
Find a bug report with the sane name and return the bug id
and the status.
@param opener = urllib2 opener
@param query = e.g. http://bugs.openembedded.org/query.cgi?
@param product = search for this product
@param component = search for this component
@param bugname = the bug to search for
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
but it does not support ctype=csv...
"""
import urllib
product = urllib.quote(product)
component = urllib.quote(component)
bugname = urllib.quote(bugname)
result = opener.open("%(query)s?product=%(product)s&component=%(component)s&short_desc_type=substring&short_desc=%(bugname)s" % vars())
if result.code != 200:
raise "Can not query the bugzilla at all"
txt = result.read()
scanner = seppuku_find_bug_report_old()
scanner.feed(txt)
if len(scanner.result()) == 0:
print >> debug_file, "Scanner failed to scan the html site"
print >> debug_file, "%(query)s?product=%(product)s&component=%(component)s&short_desc_type=substring&short_desc=%(bugname)s" % vars()
print >> debug_file, txt
return (False,None)
else: # silently pick the first result
print >> debug_file, "Result of bug search is "
print >> debug_file, txt
(number,status) = scanner.result()[0]
return (not status in ["CLOS", "RESO", "VERI"],number)
def seppuku_reopen_bug(poster, file, product, component, bug_number, bugname, text):
"""
Reopen a bug report and append to the comment
Same as with opening a new report, some bits need to be inside the url
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
"""
import urllib2
(uri, param) = seppuku_spliturl( file )
# Prepare the post
param["product"] = product
param["component"] = component
param["longdesclength"] = 2
param["short_desc"] = bugname
param["knob"] = "reopen"
param["id"] = bug_number
param["comment"] = text
try:
result = poster.open( uri, param )
except urllib2.HTTPError, e:
print e.geturl()
print e.info()
return False
except Exception, e:
print e
return False
if result.code != 200:
return False
else:
return True
def seppuku_file_bug(poster, file, product, component, bugname, text):
"""
Create a completely new bug report
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
You are forced to add some default values to the bugzilla query and stop with '&'
@param opener urllib2 opener
@param file The url used to file a bug report
@param product Product
@param component Component
@param bugname Name of the to be created bug
@param text Text
"""
import urllib2
(uri, param) = seppuku_spliturl( file )
param["product"] = product
param["component"] = component
param["short_desc"] = bugname
param["comment"] = text
try:
result = poster.open( uri, param )
except urllib2.HTTPError, e:
print e.geturl()
print e.info()
return False
except Exception, e:
print e
return False
# scan the result for a bug number
# it will look like
# 'Back To BUG# 308'
import re
res = re.findall(("\>Back To BUG\# (?P\d+)\"), result.read() )
if result.code != 200 or len(res) != 1:
return None
else:
return res[0]
def seppuku_create_attachment(debug, poster, attach_query, product, component, bug_number, text, file):
"""
Create a new attachment for the failed report
"""
if not bug_number:
import bb
bb.note("Can't create an attachment, the bug is not present")
return False
import urllib2
param = { "bugid" : bug_number, "action" : "insert", "data" : file, "description" : "Build log", "ispatch" : "0", "contenttypemethod" : "list", "contenttypeselection" : "text/plain", "comment" : text }
try:
result = poster.open( attach_query, param )
except urllib2.HTTPError, e:
print e.geturl()
print e.info()
return False
except Exception, e:
print e
return False
print >> debug, result.read()
if result.code != 200:
return False
else:
return True
addhandler seppuku_eventhandler
python seppuku_eventhandler() {
"""
Report task failures to the bugzilla
and succeeded builds to the box
"""
from bb.event import NotHandled, getName
from bb import data, mkdirhier, build
import bb, os, glob
# Try to load our exotic libraries
try:
import MultipartPostHandler
except:
bb.note("You need to put the MultipartPostHandler into your PYTHONPATH. Download it from http://pipe.scs.fsu.edu/PostHandler/MultipartPostHandler.py")
return NotHandled
try:
import urllib2, cookielib
except:
bb.note("Failed to import the cookielib and urllib2, make sure to use python2.4")
return NotHandled
event = e
data = e.data
name = getName(event)
if name == "PkgFailed":
if not bb.data.getVar('SEPPUKU_AUTOBUILD', data, True) == "0":
build.exec_task('do_clean', data)
elif name == "TaskFailed" or name == "NoProvider":
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
poster = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj),MultipartPostHandler.MultipartPostHandler)
login = bb.data.getVar("SEPPUKU_LOGIN", data, True)
query = bb.data.getVar("SEPPUKU_QUERY", data, True)
newbug = bb.data.getVar("SEPPUKU_NEWREPORT", data, True)
reopen = bb.data.getVar("SEPPUKU_ADDCOMMENT", data, True)
attach = bb.data.getVar("SEPPUKU_ATTACHMENT", data, True)
user = bb.data.getVar("SEPPUKU_USER", data, True)
passw = bb.data.getVar("SEPPUKU_PASS", data, True)
product = bb.data.getVar("SEPPUKU_PRODUCT", data, True)
component = bb.data.getVar("SEPPUKU_COMPONENT", data, True)
# evil hack to figure out what is going on
debug_file = open(os.path.join(bb.data.getVar("TMPDIR", data, True),"..","seppuku-log"),"a")
if not seppuku_login(opener, login, user, passw):
bb.note("Login to bugzilla failed")
print >> debug_file, "Login to bugzilla failed"
return NotHandled
else:
print >> debug_file, "Logged into the box"
file = None
if name == "TaskFailed":
bugname = "%(package)s-%(pv)s-%(pr)s-%(task)s" % { "package" : bb.data.getVar("PN", data, True),
"pv" : bb.data.getVar("PV", data, True),
"pr" : bb.data.getVar("PR", data, True),
"task" : e.task }
log_file = glob.glob("%s/log.%s.*" % (bb.data.getVar('T', event.data, True), event.task))
text = "The package failed to build at %s" % bb.data.getVar('DATETIME', data, True)
if len(log_file) != 0:
print >> debug_file, "Adding log file %s" % log_file[0]
file = open(log_file[0], 'r')
else:
print >> debug_file, "No log file found for the glob"
elif name == "NoProvider":
bugname = "noprovider for %s runtime: %s" % (event.getItem, event.getisRuntime)
text = "Please fix it"
else:
print >> debug_file, "Unknown name '%s'" % name
assert False
(bug_open, bug_number) = seppuku_find_bug_report(debug_file, opener, query, product, component, bugname)
print >> debug_file, "Bug is open: %s and bug number: %s" % (bug_open, bug_number)
# The bug is present and still open, no need to attach an error log
if bug_number and bug_open:
print >> debug_file, "The bug is known as '%s'" % bug_number
return NotHandled
if bug_number and not bug_open:
if not seppuku_reopen_bug(poster, reopen, product, component, bug_number, bugname, text):
print >> debug_file, "Failed to reopen the bug report"
else:
print >> debug_file, "Reopened the bug report"
else:
bug_number = seppuku_file_bug(poster, newbug, product, component, bugname, text)
if not bug_number:
print >> debug_file, "Filing a bugreport failed"
else:
print >> debug_file, "The new bug_number: '%s'" % bug_number
if file:
if not seppuku_create_attachment(debug_file, poster, attach, product, component, bug_number, text, file):
print >> debug_file, "Failed to attach the build log"
else:
print >> debug_file, "Created an attachment for '%s' '%s' '%s'" % (product, component, bug_number)
else:
print >> debug_file, "Not trying to create an attachment"
return NotHandled
}