merge of '5b67d1cb3227253f3f4b525375b174d391b45a4a'
[openembedded.git] / classes / seppuku.bbclass
1 #
2 # Small event handler to automatically open URLs and file
3 # bug reports at a bugzilla of your choiche
4 #
5 # This class requires python2.4 because of the urllib2 usage
6 #
7
8 def seppuku_spliturl(url):
9     """
10     Split GET URL to return the host base and the query
11     as a param dictionary
12     """
13     import urllib
14     (uri,query)  = urllib.splitquery(url)
15     param = {}
16     for par in query.split("&"):
17         (key,value) = urllib.splitvalue(par)
18         if not key or len(key) == 0 or not value:
19             continue
20         key = urllib.unquote(key)
21         value = urllib.unquote(value)
22         param[key] = value
23
24     return (uri,param)
25
26
27
28 def seppuku_login(opener, login, user, password):
29     """
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
33
34     @param opened = cookie enabled urllib2 opener
35     @param login = http://bugzilla.openmoko.org/cgi-bin/bugzilla/query.cgi?
36     @param user  = Your username
37     @param password  = Your password
38     """
39     import urllib
40     param = urllib.urlencode( {"GoAheadAndLogIn" : 1, "Bugzilla_login" : user, "Bugzilla_password" : password } )
41     result = opener.open(login + param)
42
43     if result.code != 200:
44         return False
45     txt = result.read()
46     if not '<a href="relogin.cgi">Log&nbsp;out</a>' in txt:
47         return False
48
49     return True
50
51 def seppuku_find_bug_report_old():
52     from HTMLParser import HTMLParser
53
54     class BugQueryExtractor(HTMLParser):
55         STATE_NONE             = 0
56         STATE_FOUND_TR         = 1
57         STATE_FOUND_NUMBER     = 2
58         STATE_FOUND_PRIO       = 3
59         STATE_FOUND_PRIO2      = 4
60         STATE_FOUND_NAME       = 5
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
65
66         def __init__(self):
67             HTMLParser.__init__(self)
68             self.state = self.STATE_NONE
69             self.bugs = []
70             self.bug  = None
71
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":
79                 self.state += 1
80
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
86                 self.bug  = None
87             if self.state > 1 and tag.lower() == "td":
88                 self.state += 1
89
90         def handle_data(self,data):
91             data = data.strip()
92
93             # skip garbage
94             if len(data) == 0:
95                 return
96
97             if self.state == self.STATE_FOUND_NUMBER:
98                 """
99                 #1995 in bugs.oe.org has [SEC] additionally to the number and we want to ignore it
100                 """
101                 if not self.bug:
102                     self.bug = data
103             elif self.state == self.STATE_FOUND_STATUS:
104                 self.status = data
105
106         def result(self):
107             return self.bugs
108
109     return BugQueryExtractor()
110
111
112
113 def seppuku_find_bug_report(opener, query, product, component, bugname):
114     """
115     Find a bug report with the sane name and return the bug id
116     and the status.
117
118     @param opener = urllib2 opener
119     @param query  = e.g. https://bugzilla.openmoko.org/cgi-bin/bugzilla/query.cgi?
120     @param product = search for this product
121     @param component = search for this component
122     @param bugname = the bug to search for
123
124     https://bugzilla.openmoko.org/cgi-bin/bugzilla/buglist.cgi?short_desc_type=substring&short_desc=manual+test+bug&product=OpenMoko&emailreporter2=1&emailtype2=substring&email2=freyther%40yahoo.com
125     but it does not support ctype=csv...
126     """
127     result = opener.open("%(query)s?product=%(product)s&component=%(component)s&short_desc_type=substring&short_desc=%(bugname)s" % vars())
128     if result.code != 200:
129         raise "Can not query the bugzilla at all"
130     txt = result.read()
131     scanner = seppuku_find_bug_report_old()
132     scanner.feed(txt)
133     if len(scanner.result()) == 0:
134         return (False,None)
135     else: # silently pick the first result
136         (number,status) = scanner.result()[0]
137         return (not status in ["CLOS", "RESO", "VERI"],number)
138
139 def seppuku_reopen_bug(poster, file, product, component, bug_number, bugname, text):
140     """
141     Reopen a bug report and append to the comment
142
143     Same as with opening a new report, some bits need to be inside the url
144
145     http://bugzilla.openmoko.org/cgi-bin/bugzilla/process_bug.cgi?id=239&bug_file_loc=http%3A%2F%2F&version=2007&longdesclength=2&product=OpenMoko&component=autobuilds&comment=bla&priority=P2&bug_severity=normal&op_sys=Linux&rep_platform=Neo1973&knob=reopen&target_milestone=Phase+0&short_desc=foo
146     """
147
148     import urllib2
149     (uri, param) = seppuku_spliturl( file )
150
151     # Prepare the post
152     param["product"]        = product
153     param["component"]      = component
154     param["longdesclength"] = 2
155     param["short_desc"]     = bugname
156     param["knob"]           = "reopen"
157     param["id"]             = bug_number
158     param["comment"]        = text
159
160     try:
161         result = poster.open( uri, param )
162     except urllib2.HTTPError, e:
163         print e.geturl()
164         print e.info()
165         return False
166     except Exception, e:
167         print e
168         return False
169
170     if result.code != 200:
171         return False
172     else:
173         return True
174
175 def seppuku_file_bug(poster, file, product, component, bugname, text):
176     """
177     Create a completely new bug report
178
179
180     http://bugzilla.openmoko.org/cgi-bin/bugzilla/post_bug.cgi?bug_file_loc=http%3A%2F%2F&version=2007&product=OpenMoko&component=autobuilds&short_desc=foo&comment=bla&priority=P2&bug_severity=normal&op_sys=Linux&rep_platform=Neo1973
181
182     You are forced to add some default values to the bugzilla query and stop with '&'
183
184     @param opener  urllib2 opener
185     @param file    The url used to file a bug report
186     @param product Product
187     @param component Component
188     @param bugname  Name of the to be created bug
189     @param text Text
190     """
191
192     import urllib2
193     (uri, param) = seppuku_spliturl( file )
194     param["product"]    = product
195     param["component"]  = component
196     param["short_desc"] = bugname
197     param["comment"]    = text
198
199     try:
200         result = poster.open( uri, param )
201     except urllib2.HTTPError, e:
202         print e.geturl()
203         print e.info()
204         return False
205     except Exception, e:
206         print e
207         return False
208
209     if result.code != 200:
210         return False
211     else:
212         return True
213
214 def seppuku_create_attachment(poster, attach_query, product, component, bug_number, text, file):
215     """
216
217     Create a new attachment for the failed report
218     """
219
220     if not bug_number:
221         import bb
222         bb.note("Can't create an attachment, the bug is not present")
223         return False
224
225     import urllib2
226     param = { "bugid" : bug_number, "action" : "insert", "data" : file, "description" : "Build log", "ispatch" : "0", "contenttypemethod" : "list", "contenttypeselection" : "text/plain", "comment" : text }
227
228     try:
229         result = poster.open( attach_query, param )
230     except urllib2.HTTPError, e:
231         print e.geturl()
232         print e.info()
233         return False
234     except Exception, e:
235         print e
236         return False
237
238     print result.read()
239     if result.code != 200:
240         return False
241     else:
242         return True
243
244
245 addhandler seppuku_eventhandler
246 python seppuku_eventhandler() {
247     """
248     Report task failures to the bugzilla
249     and succeeded builds to the box
250     """
251     from bb.event import NotHandled, getName
252     from bb import data, mkdirhier, build
253     import bb, os, glob
254
255     # Try to load our exotic libraries
256     try:
257         import MultipartPostHandler
258     except:
259         bb.note("You need to put the MultipartPostHandler into your PYTHONPATH. Download it from http://pipe.scs.fsu.edu/PostHandler/MultipartPostHandler.py")
260         return NotHandled
261
262     try:
263         import urllib2, cookielib
264     except:
265         bb.note("Failed to import the cookielib and urllib2, make sure to use python2.4")
266         return NotHandled
267
268     event = e
269     data = e.data
270     name = getName(event)
271     if name == "PkgFailed":
272         if not bb.data.getVar('SEPPUKU_AUTOBUILD', data, True) == "0":
273             build.exec_task('do_clean', data)
274     elif name == "TaskFailed" or name == "NoProvider":
275         cj = cookielib.CookieJar()
276         opener  = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
277         poster  = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj),MultipartPostHandler.MultipartPostHandler)
278         login   = bb.data.getVar("SEPPUKU_LOGIN", data, True)
279         query   = bb.data.getVar("SEPPUKU_QUERY", data, True)
280         newbug  = bb.data.getVar("SEPPUKU_NEWREPORT",  data, True)
281         reopen  = bb.data.getVar("SEPPUKU_ADDCOMMENT",  data, True)
282         attach  = bb.data.getVar("SEPPUKU_ATTACHMENT", data, True)
283         user    = bb.data.getVar("SEPPUKU_USER",  data, True)
284         passw   = bb.data.getVar("SEPPUKU_PASS",  data, True)
285         product = bb.data.getVar("SEPPUKU_PRODUCT", data, True)
286         component = bb.data.getVar("SEPPUKU_COMPONENT", data, True)
287
288         if not seppuku_login(opener, login, user, passw):
289             bb.note("Login to bugzilla failed")
290             return NotHandled
291         else:
292             print "Logged into the box"
293
294         file = None
295         if name == "TaskFailed":
296             bugname = "%(package)s-%(pv)s-%(pr)s-%(task)s" % { "package" : bb.data.getVar("PN", data, True),
297                                                                "pv"      : bb.data.getVar("PV", data, True),
298                                                                "pr"      : bb.data.getVar("PR", data, True),
299                                                                "task"    : e.task }
300             log_file = glob.glob("%s/log.%s.*" % (bb.data.getVar('T', event.data, True), event.task))
301             text     = "The package failed to build at %s" % bb.data.getVar('DATETIME', data, True) 
302             if len(log_file) != 0:
303                 file     = open(log_file[0], 'r')
304         elif name == "NoProvider":
305             bugname = "noprovider for %s runtime: %s" % (event.getItem, event.getisRuntime)
306             text    = "Please fix it"
307         else:
308             assert False
309
310         (bug_open, bug_number) = seppuku_find_bug_report(opener, query, product, component, bugname)
311
312         bb.note("Bug is open: %s and bug number: %s" % (bug_open, bug_number))
313
314         # The bug is present and still open, no need to attach an error log
315         if bug_number and bug_open:
316             bb.note("The bug is known as '%s'" % bug_number)
317             return NotHandled
318
319         if bug_number and not bug_open:
320             if not seppuku_reopen_bug(poster, reopen, product, component, bug_number, bugname, text):
321                 bb.note("Failed to reopen the bug report")
322         elif not seppuku_file_bug(poster, newbug, product, component, bugname, text):
323             bb.note("Filing a bugreport failed")
324         else:
325             # get the new bug number and create an attachment
326             (bug_open, bug_number) = seppuku_find_bug_report(opener, query, product, component, bugname)
327
328         if file:
329             if not seppuku_create_attachment(poster, attach, product, component, bug_number, text, file):
330                 bb.note("Failed to attach the build log")
331
332     return NotHandled
333 }