def bugzilla(self): """ Return Bugzilla instance. TODO: check whether we should use cookies or not """ if self._bugzilla is not None: return self._bugzilla self._baseurl = self.options.get('baseurl') self._bugzilla = Bugz(self._baseurl) cj = CookiePot().make_lwp_cookiejar(self._bugzilla.cookiejar.filename, self._bugzilla.host) self._bugzilla.cookiejar = cj self._bugzilla.opener = build_opener(HTTPCookieProcessor(cj)) if self.username is None or self.password is None: if not self._bugzilla.try_auth(): self._bugzilla = None raise apport.crashdb.NeedsCredentials, self.distro else: self._bugzilla = Bugz(self._baseurl, self.username, self.password) try: self._bugzilla.auth() except RuntimeError: # Happens when the username/password pair is invalid. raise apport.crashdb.NeedsCredentials, self.distro return self._bugzilla
class Widget(QtGui.QWidget, ScreenWidget): title = ki18n("Bug Reporting Tool") desc = ki18n("Credentials Screen") def __init__(self, *args): QtGui.QWidget.__init__(self,None) self.ui = Ui_bugWidget() self.ui.setupUi(self) QObject.connect(self.ui.logoutButton, SIGNAL("clicked()"), self.logout) self.bugzilla = Bugz(BUGZILLA_URL) cj = CookiePot().make_lwp_cookiejar(self.bugzilla.cookiejar.filename, #'bugs.pardus.org.tr') 'landfill.bugzilla.org') self.bugzilla.cookiejar = cj self.bugzilla.opener = build_opener(HTTPCookieProcessor(cj)) self.is_logged_in = False def logout(self): self.bugzilla.authenticated = False self.bugzilla.opener.open(LOGOUT_URL).read() self.shown() def shown(self): if self.bugzilla.try_auth(): self.is_logged_in = True self.ui.logoutButton.setVisible(True) self.ui.logoutText.setVisible(True) self.ui.username.setEnabled(False) self.ui.password.setEnabled(False) else: self.is_logged_in = False self.ui.logoutButton.setVisible(False) self.ui.logoutText.setVisible(False) self.ui.username.setEnabled(True) self.ui.password.setEnabled(True) pass def execute(self): if not self.is_logged_in: if len(self.ui.password.text()) == 0 or\ len(self.ui.username.text()) == 0: return False else: self.bugzilla.user = self.ui.username.text() self.bugzilla.password = self.ui.password.text() try: self.bugzilla.auth() except RuntimeError: print 'Invalid user/pass pair!' return False self.shared['bugzilla'] = self.bugzilla return True @property def shared(self): return self.parent().parent().parent().shared_data
class Widget(QtGui.QWidget, ScreenWidget): title = ki18n("Bug Reporting Tool") desc = ki18n("Credentials Screen") def __init__(self, *args): QtGui.QWidget.__init__(self, None) self.ui = Ui_bugWidget() self.ui.setupUi(self) QObject.connect(self.ui.logoutButton, SIGNAL("clicked()"), self.logout) self.bugzilla = Bugz(BUGZILLA_URL) cj = CookiePot().make_lwp_cookiejar( self.bugzilla.cookiejar.filename, #'bugs.pardus.org.tr') 'landfill.bugzilla.org') self.bugzilla.cookiejar = cj self.bugzilla.opener = build_opener(HTTPCookieProcessor(cj)) self.is_logged_in = False def logout(self): self.bugzilla.authenticated = False self.bugzilla.opener.open(LOGOUT_URL).read() self.shown() def shown(self): if self.bugzilla.try_auth(): self.is_logged_in = True self.ui.logoutButton.setVisible(True) self.ui.logoutText.setVisible(True) self.ui.username.setEnabled(False) self.ui.password.setEnabled(False) else: self.is_logged_in = False self.ui.logoutButton.setVisible(False) self.ui.logoutText.setVisible(False) self.ui.username.setEnabled(True) self.ui.password.setEnabled(True) pass def execute(self): if not self.is_logged_in: if len(self.ui.password.text()) == 0 or\ len(self.ui.username.text()) == 0: return False else: self.bugzilla.user = self.ui.username.text() self.bugzilla.password = self.ui.password.text() try: self.bugzilla.auth() except RuntimeError: print 'Invalid user/pass pair!' return False self.shared['bugzilla'] = self.bugzilla return True @property def shared(self): return self.parent().parent().parent().shared_data
def __init__(self, *args): QtGui.QWidget.__init__(self, None) self.ui = Ui_bugWidget() self.ui.setupUi(self) QObject.connect(self.ui.logoutButton, SIGNAL("clicked()"), self.logout) self.bugzilla = Bugz(BUGZILLA_URL) cj = CookiePot().make_lwp_cookiejar( self.bugzilla.cookiejar.filename, #'bugs.pardus.org.tr') 'landfill.bugzilla.org') self.bugzilla.cookiejar = cj self.bugzilla.opener = build_opener(HTTPCookieProcessor(cj)) self.is_logged_in = False
def __init__(self, *args): QtGui.QWidget.__init__(self,None) self.ui = Ui_bugWidget() self.ui.setupUi(self) QObject.connect(self.ui.logoutButton, SIGNAL("clicked()"), self.logout) self.bugzilla = Bugz(BUGZILLA_URL) cj = CookiePot().make_lwp_cookiejar(self.bugzilla.cookiejar.filename, #'bugs.pardus.org.tr') 'landfill.bugzilla.org') self.bugzilla.cookiejar = cj self.bugzilla.opener = build_opener(HTTPCookieProcessor(cj)) self.is_logged_in = False
class CrashDatabase(apport.crashdb.CrashDatabase): """ Bugzilla implementation of crash database interface. """ def __init__(self, auth, bugpattern_baseurl, options): """ Constructor. auth_file -- authentication credentials bugpattern_baseurl -- base url for searching bug patterns options -- dictionary with settings from crashdb.conf """ apport.crashdb.CrashDatabase.__init__(self, auth, bugpattern_baseurl, options) self.distro = options.get('distro') self.options = options self.auth = auth self.arch_tag = 'need-%s-retrace' % apport.packaging.get_system_architecture() self._bugzilla = None self._baseurl = None self.username = None self.password = None @property def bugzilla(self): """ Return Bugzilla instance. TODO: check whether we should use cookies or not """ if self._bugzilla is not None: return self._bugzilla self._baseurl = self.options.get('baseurl') self._bugzilla = Bugz(self._baseurl) cj = CookiePot().make_lwp_cookiejar(self._bugzilla.cookiejar.filename, self._bugzilla.host) self._bugzilla.cookiejar = cj self._bugzilla.opener = build_opener(HTTPCookieProcessor(cj)) if self.username is None or self.password is None: if not self._bugzilla.try_auth(): self._bugzilla = None raise apport.crashdb.NeedsCredentials, self.distro else: self._bugzilla = Bugz(self._baseurl, self.username, self.password) try: self._bugzilla.auth() except RuntimeError: # Happens when the username/password pair is invalid. raise apport.crashdb.NeedsCredentials, self.distro return self._bugzilla def set_credentials(self, username, password): """Sets username and password to be used on log-in.""" self.username = username self.password = password def upload(self, report, progress_callback = None): '''Upload given problem report return a handle for it. This should happen noninteractively. If the implementation supports it, and a function progress_callback is passed, that is called repeatedly with two arguments: the number of bytes already sent, and the total number of bytes to send. This can be used to provide a proper upload progress indication on frontends.''' data = {} data.update(self.options['default_options']) # PyBugz mandatory args product = data.pop('product') component = data.pop('component') title = report.get('Title', report.standard_title()) description = '' # generating mime (from launchpad.py)#{{{ # set reprocessing tags hdr = {} hdr['Tags'] = 'apport-%s' % report['ProblemType'].lower() a = report.get('PackageArchitecture') if not a or a == 'all': a = report.get('Architecture') if a: hdr['Tags'] += ' ' + a if 'CoreDump' in report and a: hdr['Tags'] += ' need-%s-retrace' % a # set dup checking tag for Python crashes elif report.has_key('Traceback'): hdr['Tags'] += ' need-duplicate-check' # write MIME/Multipart version into temporary file mime = tempfile.NamedTemporaryFile() report.write_mime(mime, extra_headers=hdr) mime.flush() mime.seek(0) #}}} # Reding MIME data and uploading each file {{{ #FIXME: there should be a standard way of getting this message = Parser().parse(mime) attachables = [] for item in message.walk(): filename = item.get_filename() # If "Content-Disposition" is inline, filename will be None if filename is not None: bdata = b64decode(item.get_payload()) filetype = item.get_content_type() attachable = (filename, filetype, bdata) attachables.append(attachable) else: # Bug description is `inline` try: description += b64decode(item.get_payload()) except: pass mime.close() # }}} # optional args #if 'keywords' in data: # data['keywords'] += hdr['Tags'] #else: # data['keywords'] = hdr['Tags'] #data.pop('keywords') bug_id = self.bugzilla.post(product, component, title, description, **data) if bug_id == 0: raise RuntimeError, "Error uploading bug!" for filename, filetype, data in attachables: tmp = tempfile.NamedTemporaryFile() tmp.write(data) tmp.flush() tmp.seek(0) result = self.bugzilla.attach(bug_id, filename, '', tmp.name, filetype, filename) if not result: raise RuntimeError, "Error uploading attachment" tmp.close() return bug_id def get_comment_url(self, report, handle): '''Return an URL that should be opened after report has been uploaded and upload() returned handle. Should return None if no URL should be opened (anonymous filing without user comments); in that case this function should do whichever interactive steps it wants to perform.''' url = '%s?id=%s' % (urljoin(self._baseurl, config.urls['show']), handle) return url def download(self, bug_id): '''Download the problem report from given ID and return a Report.''' report = apport.Report() bug_etree = self.bugzilla.get(bug_id) bug = Bug(bug_id, bug_etree) # from launchpad crashdb # parse out fields from summary m = re.search(r'(ProblemType:.*)$', bug.description, re.S) if not m: m = re.search(r'^--- \r?$[\r\n]*(.*)', bug.description, re.M | re.S) assert m, 'bug description must contain standard apport format data' description = m.group(1).encode('UTF-8').replace('\xc2\xa0', ' ') if '\r\n\r\n' in description: # this often happens, remove all empty lines between top and # 'Uname' if 'Uname:' in description: # this will take care of bugs like LP #315728 where stuff # is added after the apport data (part1, part2) = description.split('Uname:', 1) description = part1.replace('\r\n\r\n', '\r\n') + 'Uname:' \ + part2.split('\r\n\r\n', 1)[0] else: description = description.replace('\r\n\r\n', '\r\n') report.load(StringIO(description)) when = bug.create_date if 'Date' not in report: report['Date'] = when if 'ProblemType' not in report: if 'apport-bug' in bug.keywords: report['ProblemType'] = 'Bug' elif 'apport-crash' in bug.keywords: report['ProblemType'] = 'Crash' elif 'apport-kernelcrash' in bug.keywords: report['ProblemType'] = 'KernelCrash' elif 'apport-package' in bug.keywords: report['ProblemType'] = 'Package' else: raise ValueError, 'cannot determine ProblemType from tags: '\ + str(bug.keywords) for attachment in bug.attachments: if attachment['filename'] in APPORT_FILES: key, ext = os.path.splitext(attachment['filename']) att = self.bugzilla.attachment(attachment['id']) if att is None: raise RuntimeError, 'Error downloading attachment' if ext == '.txt': report[key] = att['fd'].read() elif ext == '.gz': #report[key] = gzip.GzipFile(fileobj=att['fd']).read() report[key] = att['fd'].read() else: raise RuntimeError, 'Unable to read %s file' % ext return report def update(self, bug_id, report, comment): '''Update the given report ID with the retraced results from the report (Stacktrace, ThreadStacktrace, StacktraceTop; also Disassembly if desired) and an optional comment.''' bug_etree = self.bugzilla.get(bug_id) bug = Bug(bug_id, bug_etree) comment += '\n\nStacktraceTop:' + report['StacktraceTop'].decode('utf-8', 'replace').encode('utf-8') # FIXME: too much duplicated code. Itter over a list, perhaps? if report['Stacktrace']: # don't attach empty files tmp = tempfile.NamedTemporaryFile() s = report['Stacktrace'].decode('ascii', 'replace').encode('ascii', 'replace') tmp.write(s) tmp.flush() tmp.seek(0) self.bugzilla.attach(bug_id, 'Stacktrace.txt (retraced)', comment, tmp.name, filename_override='Stacktrace.txt') tmp.close() if report['ThreadStacktrace']: tmp = tempfile.NamedTemporaryFile() s = report['ThreadStacktrace'].decode('ascii', 'replace').encode('ascii', 'replace') tmp.write(s) tmp.flush() tmp.seek(0) self.bugzilla.attach(bug_id, 'ThreadStacktrace.txt (retraced)', '', tmp.name, filename_override='ThreadStacktrace.txt') tmp.close() if report.has_key('StacktraceSource') and report['StacktraceSource']: tmp = tempfile.NamedTemporaryFile() s = report['StacktraceSource'].decode('ascii', 'replace').encode('ascii', 'replace') tmp.write(s) tmp.flush() tmp.seek(0) self.bugzilla.attach(bug_id, 'StacktraceSource.txt (retraced)', '', tmp.name, filename_override='StacktraceSource.txt') tmp.close() # ensure it's assigned to the right package # TODO: implement-me #if report.has_key('SourcePackage') and \ # '+source' not in str(bug.bug_tasks[0].target): # try: # bug.bug_tasks[0].transitionToTarget(target= # self.lp_distro.getSourcePackage(name=report['SourcePackage'])) # except HTTPError: # pass # LP#342355 workaround # remove core dump if stack trace is usable if report.has_useful_stacktrace(): for a in bug.attachments: if a['filename'] == 'CoreDump.gz': # Setting the attachment as obsolete is the closest to # deleting it we can get with bugzilla params = { 'isobsolete': 1, } self.bugzilla.modify_attachment(a['id'], bug_id,**params) def get_distro_release(self, bug_id): '''Get 'DistroRelease: <release>' from the given report ID and return it.''' bug_etree = self.bugzilla.get(bug_id) bug = Bug(bug_id, bug_etree) m = re.search('DistroRelease: ([-a-zA-Z0-9.+/ ]+)', bug.description) if m: return m.group(1) raise ValueError, 'URL does not contain DistroRelease: field' def get_unretraced(self): '''Return an ID set of all crashes which have not been retraced yet and which happened on the current host architecture.''' bugs = self.bugzilla.search('', keywords=self.arch_tag) if bugs is None: return [] return set([int(bug['bugid']) for bug in bugs]) def get_dup_unchecked(self): '''Return an ID set of all crashes which have not been checked for being a duplicate. This is mainly useful for crashes of scripting languages such as Python, since they do not need to be retraced. It should not return bugs that are covered by get_unretraced().''' bugs = self.bugzilla.search('', keywords='need-duplicate-check') if bugs is None: return [] return set([int(bug['bugid']) for bug in bugs]) def get_unfixed(self): '''Return an ID set of all crashes which are not yet fixed. The list must not contain bugs which were rejected or duplicate. This function should make sure that the returned list is correct. If there are any errors with connecting to the crash database, it should raise an exception (preferably IOError).''' bugs = self.bugzilla.search('', keywords='apport-crash') if bugs is None: return [] return set([int(bug['bugid']) for bug in bugs]) def get_fixed_version(self, bug_id): '''Return the package version that fixes a given crash. Return None if the crash is not yet fixed, or an empty string if the crash is fixed, but it cannot be determined by which version. Return 'invalid' if the crash report got invalidated, such as closed a duplicate or rejected. This function should make sure that the returned result is correct. If there are any errors with connecting to the crash database, it should raise an exception (preferably IOError).''' bug_etree = self.bugzilla.get(bug_id) bug = Bug(bug_id, bug_etree) invalid_resolutions = ['DUPLICATE', 'WONTFIX', 'INVALID'] if bug.resolution == 'FIXED': #TODO: check if there's a way to know the solved version return '' elif bug.resolution in invalid_resolutions: return 'invalid' else: return None def duplicate_of(self, bug_id): '''Return master ID for a duplicate bug. If the bug is not a duplicate, return None. ''' bug_etree = self.bugzilla.get(bug_id) bug = Bug(bug_id, bug_etree) if bug.dup_id == 0: return None else: return bug.dup_id def close_duplicate(self, bug_id, master): '''Mark a crash id as duplicate of given master ID. If master is None, id gets un-duplicated. ''' if master is not None: self.bugzilla.modify(bug_id, duplicate=master, resolution='DUPLICATE') else: self.bugzilla.modify(bug_id, status='REOPENED') def mark_regression(self, bug_id, master): '''Mark a crash id as reintroducing an earlier crash which is already marked as fixed (having ID 'master').''' regression_keyword = 'regression-retracer' bug_etree = self.bugzilla.get(bug_id) bug = Bug(bug_id, bug_etree) kws = bug.keywords if regression_keyword not in kws: kws.append(regression_keyword) self.bugzilla.modify(bug_id, keywords=' '.join(kws)) def mark_retraced(self, bug_id): '''Mark crash id as retraced.''' bug_etree = self.bugzilla.get(bug_id) bug = Bug(bug_id, bug_etree) kws = bug.keywords if self.arch_tag in kws: kws.remove(self.arch_tag) args = { 'keywords': ' '.join(kws), } self.bugzilla.modify(bug_id, **args) def mark_retrace_failed(self, bug_id, invalid_msg=None): '''Mark crash id as 'failed to retrace'. If invalid_msg is given, the bug should be closed as invalid with given message, otherwise just marked as a failed retrace. This can be a no-op if you are not interested in this.''' if invalid_msg is not None: self.bugzilla.modify(bug_id, status='RESOLVED', resolution='INVALID', comment=invalid_msg) else: bug_etree = self.bugzilla.get(bug_id) bug = Bug(bug_id, bug_etree) kws = bug.keywords kws.append('apport-failed-retrace') new_kws = ' '.join(kws) self.bugzilla.modify(bug_id, keywords=new_kws) def _mark_dup_checked(self, bug_id, report): '''Mark crash id as checked for being a duplicate This is an internal method that should not be called from outside.''' bug_etree = self.bugzilla.get(bug_id) bug = Bug(bug_id, bug_etree) kws = bug.keywords try: kws.remove('need-duplicate-check') new_kws = ' '.join(kws) self.bugzilla.modify(bug_id, keywords=new_kws) except ValueError: # This happens when the bug doesn't have the # need-duplicate-check keyword pass