Example #1
0
    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
Example #3
0
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
Example #4
0
 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
Example #6
0
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