Blame bugzilla/rhbugzilla.py

Packit Service 4b33e2
# rhbugzilla.py - a Python interface to Red Hat Bugzilla using xmlrpclib.
Packit Service 4b33e2
#
Packit Service 4b33e2
# Copyright (C) 2008-2012 Red Hat Inc.
Packit Service 4b33e2
# Author: Will Woods <wwoods@redhat.com>
Packit Service 4b33e2
#
Packit Service 4b33e2
# This program is free software; you can redistribute it and/or modify it
Packit Service 4b33e2
# under the terms of the GNU General Public License as published by the
Packit Service 4b33e2
# Free Software Foundation; either version 2 of the License, or (at your
Packit Service 4b33e2
# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
Packit Service 4b33e2
# the full text of the license.
Packit Service 4b33e2
Packit Service 4b33e2
from logging import getLogger
Packit Service 4b33e2
Packit Service 4b33e2
from .base import Bugzilla
Packit Service 4b33e2
Packit Service 4b33e2
log = getLogger(__name__)
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
class RHBugzilla(Bugzilla):
Packit Service 4b33e2
    """
Packit Service 4b33e2
    Bugzilla class for connecting Red Hat's forked bugzilla instance,
Packit Service 4b33e2
    bugzilla.redhat.com
Packit Service 4b33e2
Packit Service 4b33e2
    Historically this class used many more non-upstream methods, but
Packit Service 4b33e2
    in 2012 RH started dropping most of its custom bits. By that time,
Packit Service 4b33e2
    upstream BZ had most of the important functionality.
Packit Service 4b33e2
Packit Service 4b33e2
    Much of the remaining code here is just trying to keep things operating
Packit Service 4b33e2
    in python-bugzilla back compatible manner.
Packit Service 4b33e2
Packit Service 4b33e2
    This class was written using bugzilla.redhat.com's API docs:
Packit Service 4b33e2
    https://bugzilla.redhat.com/docs/en/html/api/
Packit Service 4b33e2
    """
Packit Service 4b33e2
    def _init_class_state(self):
Packit Service 4b33e2
        def _add_both_alias(newname, origname):
Packit Service 4b33e2
            self._add_field_alias(newname, origname, is_api=False)
Packit Service 4b33e2
            self._add_field_alias(origname, newname, is_bug=False)
Packit Service 4b33e2
Packit Service 4b33e2
        _add_both_alias('fixed_in', 'cf_fixed_in')
Packit Service 4b33e2
        _add_both_alias('qa_whiteboard', 'cf_qa_whiteboard')
Packit Service 4b33e2
        _add_both_alias('devel_whiteboard', 'cf_devel_whiteboard')
Packit Service 4b33e2
        _add_both_alias('internal_whiteboard', 'cf_internal_whiteboard')
Packit Service 4b33e2
Packit Service 4b33e2
        self._add_field_alias('component', 'components', is_bug=False)
Packit Service 4b33e2
        self._add_field_alias('version', 'versions', is_bug=False)
Packit Service 4b33e2
        # Yes, sub_components is the field name the API expects
Packit Service 4b33e2
        self._add_field_alias('sub_components', 'sub_component', is_bug=False)
Packit Service 4b33e2
Packit Service 4b33e2
        # flags format isn't exactly the same but it's the closest approx
Packit Service 4b33e2
        self._add_field_alias('flags', 'flag_types')
Packit Service 4b33e2
Packit Service 4b33e2
        self._getbug_extra_fields = self._getbug_extra_fields + [
Packit Service 4b33e2
            "comments", "description",
Packit Service 4b33e2
            "external_bugs", "flags", "sub_components",
Packit Service 4b33e2
            "tags",
Packit Service 4b33e2
        ]
Packit Service 4b33e2
        self._supports_getbug_extra_fields = True
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    ######################
Packit Service 4b33e2
    # Bug update methods #
Packit Service 4b33e2
    ######################
Packit Service 4b33e2
Packit Service 4b33e2
    def build_update(self, **kwargs):
Packit Service 4b33e2
        # pylint: disable=arguments-differ
Packit Service 4b33e2
        adddict = {}
Packit Service 4b33e2
Packit Service 4b33e2
        def pop(key, destkey):
Packit Service 4b33e2
            val = kwargs.pop(key, None)
Packit Service 4b33e2
            if val is None:
Packit Service 4b33e2
                return
Packit Service 4b33e2
            adddict[destkey] = val
Packit Service 4b33e2
Packit Service 4b33e2
        def get_sub_component():
Packit Service 4b33e2
            val = kwargs.pop("sub_component", None)
Packit Service 4b33e2
            if val is None:
Packit Service 4b33e2
                return
Packit Service 4b33e2
Packit Service 4b33e2
            if not isinstance(val, dict):
Packit Service 4b33e2
                component = self._listify(kwargs.get("component"))
Packit Service 4b33e2
                if not component:
Packit Service 4b33e2
                    raise ValueError("component must be specified if "
Packit Service 4b33e2
                        "specifying sub_component")
Packit Service 4b33e2
                val = {component[0]: val}
Packit Service 4b33e2
            adddict["sub_components"] = val
Packit Service 4b33e2
Packit Service 4b33e2
        def get_alias():
Packit Service 4b33e2
            # RHBZ has a custom extension to allow a bug to have multiple
Packit Service 4b33e2
            # aliases, so the format of aliases is
Packit Service 4b33e2
            #    {"add": [...], "remove": [...]}
Packit Service 4b33e2
            # But that means in order to approximate upstream, behavior
Packit Service 4b33e2
            # which just overwrites the existing alias, we need to read
Packit Service 4b33e2
            # the bug's state first to know what string to remove. Which
Packit Service 4b33e2
            # we can't do, since we don't know the bug numbers at this point.
Packit Service 4b33e2
            # So fail for now.
Packit Service 4b33e2
            #
Packit Service 4b33e2
            # The API should provide {"set": [...]}
Packit Service 4b33e2
            # https://bugzilla.redhat.com/show_bug.cgi?id=1173114
Packit Service 4b33e2
            #
Packit Service 4b33e2
            # Implementation will go here when it's available
Packit Service 4b33e2
            pass
Packit Service 4b33e2
Packit Service 4b33e2
        pop("fixed_in", "cf_fixed_in")
Packit Service 4b33e2
        pop("qa_whiteboard", "cf_qa_whiteboard")
Packit Service 4b33e2
        pop("devel_whiteboard", "cf_devel_whiteboard")
Packit Service 4b33e2
        pop("internal_whiteboard", "cf_internal_whiteboard")
Packit Service 4b33e2
Packit Service 4b33e2
        get_sub_component()
Packit Service 4b33e2
        get_alias()
Packit Service 4b33e2
Packit Service 4b33e2
        vals = Bugzilla.build_update(self, **kwargs)
Packit Service 4b33e2
        vals.update(adddict)
Packit Service 4b33e2
Packit Service 4b33e2
        return vals
Packit Service 4b33e2
Packit Service 4b33e2
    def add_external_tracker(self, bug_ids, ext_bz_bug_id, ext_type_id=None,
Packit Service 4b33e2
                             ext_type_description=None, ext_type_url=None,
Packit Service 4b33e2
                             ext_status=None, ext_description=None,
Packit Service 4b33e2
                             ext_priority=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Wrapper method to allow adding of external tracking bugs using the
Packit Service 4b33e2
        ExternalBugs::WebService::add_external_bug method.
Packit Service 4b33e2
Packit Service 4b33e2
        This is documented at
Packit Service 4b33e2
        https://bugzilla.redhat.com/docs/en/html/api/extensions/ExternalBugs/lib/WebService.html#add_external_bug
Packit Service 4b33e2
Packit Service 4b33e2
        bug_ids: A single bug id or list of bug ids to have external trackers
Packit Service 4b33e2
            added.
Packit Service 4b33e2
        ext_bz_bug_id: The external bug id (ie: the bug number in the
Packit Service 4b33e2
            external tracker).
Packit Service 4b33e2
        ext_type_id: The external tracker id as used by Bugzilla.
Packit Service 4b33e2
        ext_type_description: The external tracker description as used by
Packit Service 4b33e2
            Bugzilla.
Packit Service 4b33e2
        ext_type_url: The external tracker url as used by Bugzilla.
Packit Service 4b33e2
        ext_status: The status of the external bug.
Packit Service 4b33e2
        ext_description: The description of the external bug.
Packit Service 4b33e2
        ext_priority: The priority of the external bug.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        param_dict = {'ext_bz_bug_id': ext_bz_bug_id}
Packit Service 4b33e2
        if ext_type_id is not None:
Packit Service 4b33e2
            param_dict['ext_type_id'] = ext_type_id
Packit Service 4b33e2
        if ext_type_description is not None:
Packit Service 4b33e2
            param_dict['ext_type_description'] = ext_type_description
Packit Service 4b33e2
        if ext_type_url is not None:
Packit Service 4b33e2
            param_dict['ext_type_url'] = ext_type_url
Packit Service 4b33e2
        if ext_status is not None:
Packit Service 4b33e2
            param_dict['ext_status'] = ext_status
Packit Service 4b33e2
        if ext_description is not None:
Packit Service 4b33e2
            param_dict['ext_description'] = ext_description
Packit Service 4b33e2
        if ext_priority is not None:
Packit Service 4b33e2
            param_dict['ext_priority'] = ext_priority
Packit Service 4b33e2
        params = {
Packit Service 4b33e2
            'bug_ids': self._listify(bug_ids),
Packit Service 4b33e2
            'external_bugs': [param_dict],
Packit Service 4b33e2
        }
Packit Service 4b33e2
Packit Service 4b33e2
        log.debug("Calling ExternalBugs.add_external_bug(%s)", params)
Packit Service 4b33e2
        return self._proxy.ExternalBugs.add_external_bug(params)
Packit Service 4b33e2
Packit Service 4b33e2
    def update_external_tracker(self, ids=None, ext_type_id=None,
Packit Service 4b33e2
                                ext_type_description=None, ext_type_url=None,
Packit Service 4b33e2
                                ext_bz_bug_id=None, bug_ids=None,
Packit Service 4b33e2
                                ext_status=None, ext_description=None,
Packit Service 4b33e2
                                ext_priority=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Wrapper method to allow adding of external tracking bugs using the
Packit Service 4b33e2
        ExternalBugs::WebService::update_external_bug method.
Packit Service 4b33e2
Packit Service 4b33e2
        This is documented at
Packit Service 4b33e2
        https://bugzilla.redhat.com/docs/en/html/api/extensions/ExternalBugs/lib/WebService.html#update_external_bug
Packit Service 4b33e2
Packit Service 4b33e2
        ids: A single external tracker bug id or list of external tracker bug
Packit Service 4b33e2
            ids.
Packit Service 4b33e2
        ext_type_id: The external tracker id as used by Bugzilla.
Packit Service 4b33e2
        ext_type_description: The external tracker description as used by
Packit Service 4b33e2
            Bugzilla.
Packit Service 4b33e2
        ext_type_url: The external tracker url as used by Bugzilla.
Packit Service 4b33e2
        ext_bz_bug_id: A single external bug id or list of external bug ids
Packit Service 4b33e2
            (ie: the bug number in the external tracker).
Packit Service 4b33e2
        bug_ids: A single bug id or list of bug ids to have external tracker
Packit Service 4b33e2
            info updated.
Packit Service 4b33e2
        ext_status: The status of the external bug.
Packit Service 4b33e2
        ext_description: The description of the external bug.
Packit Service 4b33e2
        ext_priority: The priority of the external bug.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        params = {}
Packit Service 4b33e2
        if ids is not None:
Packit Service 4b33e2
            params['ids'] = self._listify(ids)
Packit Service 4b33e2
        if ext_type_id is not None:
Packit Service 4b33e2
            params['ext_type_id'] = ext_type_id
Packit Service 4b33e2
        if ext_type_description is not None:
Packit Service 4b33e2
            params['ext_type_description'] = ext_type_description
Packit Service 4b33e2
        if ext_type_url is not None:
Packit Service 4b33e2
            params['ext_type_url'] = ext_type_url
Packit Service 4b33e2
        if ext_bz_bug_id is not None:
Packit Service 4b33e2
            params['ext_bz_bug_id'] = self._listify(ext_bz_bug_id)
Packit Service 4b33e2
        if bug_ids is not None:
Packit Service 4b33e2
            params['bug_ids'] = self._listify(bug_ids)
Packit Service 4b33e2
        if ext_status is not None:
Packit Service 4b33e2
            params['ext_status'] = ext_status
Packit Service 4b33e2
        if ext_description is not None:
Packit Service 4b33e2
            params['ext_description'] = ext_description
Packit Service 4b33e2
        if ext_priority is not None:
Packit Service 4b33e2
            params['ext_priority'] = ext_priority
Packit Service 4b33e2
Packit Service 4b33e2
        log.debug("Calling ExternalBugs.update_external_bug(%s)", params)
Packit Service 4b33e2
        return self._proxy.ExternalBugs.update_external_bug(params)
Packit Service 4b33e2
Packit Service 4b33e2
    def remove_external_tracker(self, ids=None, ext_type_id=None,
Packit Service 4b33e2
                                ext_type_description=None, ext_type_url=None,
Packit Service 4b33e2
                                ext_bz_bug_id=None, bug_ids=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Wrapper method to allow removal of external tracking bugs using the
Packit Service 4b33e2
        ExternalBugs::WebService::remove_external_bug method.
Packit Service 4b33e2
Packit Service 4b33e2
        This is documented at
Packit Service 4b33e2
        https://bugzilla.redhat.com/docs/en/html/api/extensions/ExternalBugs/lib/WebService.html#remove_external_bug
Packit Service 4b33e2
Packit Service 4b33e2
        ids: A single external tracker bug id or list of external tracker bug
Packit Service 4b33e2
            ids.
Packit Service 4b33e2
        ext_type_id: The external tracker id as used by Bugzilla.
Packit Service 4b33e2
        ext_type_description: The external tracker description as used by
Packit Service 4b33e2
            Bugzilla.
Packit Service 4b33e2
        ext_type_url: The external tracker url as used by Bugzilla.
Packit Service 4b33e2
        ext_bz_bug_id: A single external bug id or list of external bug ids
Packit Service 4b33e2
            (ie: the bug number in the external tracker).
Packit Service 4b33e2
        bug_ids: A single bug id or list of bug ids to have external tracker
Packit Service 4b33e2
            info updated.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        params = {}
Packit Service 4b33e2
        if ids is not None:
Packit Service 4b33e2
            params['ids'] = self._listify(ids)
Packit Service 4b33e2
        if ext_type_id is not None:
Packit Service 4b33e2
            params['ext_type_id'] = ext_type_id
Packit Service 4b33e2
        if ext_type_description is not None:
Packit Service 4b33e2
            params['ext_type_description'] = ext_type_description
Packit Service 4b33e2
        if ext_type_url is not None:
Packit Service 4b33e2
            params['ext_type_url'] = ext_type_url
Packit Service 4b33e2
        if ext_bz_bug_id is not None:
Packit Service 4b33e2
            params['ext_bz_bug_id'] = self._listify(ext_bz_bug_id)
Packit Service 4b33e2
        if bug_ids is not None:
Packit Service 4b33e2
            params['bug_ids'] = self._listify(bug_ids)
Packit Service 4b33e2
Packit Service 4b33e2
        log.debug("Calling ExternalBugs.remove_external_bug(%s)", params)
Packit Service 4b33e2
        return self._proxy.ExternalBugs.remove_external_bug(params)
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    #################
Packit Service 4b33e2
    # Query methods #
Packit Service 4b33e2
    #################
Packit Service 4b33e2
Packit Service 4b33e2
    def pre_translation(self, query):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Translates the query for possible aliases
Packit Service 4b33e2
        """
Packit Service 4b33e2
        old = query.copy()
Packit Service 4b33e2
Packit Service 4b33e2
        if 'bug_id' in query:
Packit Service 4b33e2
            if not isinstance(query['bug_id'], list):
Packit Service 4b33e2
                query['id'] = query['bug_id'].split(',')
Packit Service 4b33e2
            else:
Packit Service 4b33e2
                query['id'] = query['bug_id']
Packit Service 4b33e2
            del query['bug_id']
Packit Service 4b33e2
Packit Service 4b33e2
        if 'component' in query:
Packit Service 4b33e2
            if not isinstance(query['component'], list):
Packit Service 4b33e2
                query['component'] = query['component'].split(',')
Packit Service 4b33e2
Packit Service 4b33e2
        if 'include_fields' not in query and 'column_list' not in query:
Packit Service 4b33e2
            return
Packit Service 4b33e2
Packit Service 4b33e2
        if 'include_fields' not in query:
Packit Service 4b33e2
            query['include_fields'] = []
Packit Service 4b33e2
            if 'column_list' in query:
Packit Service 4b33e2
                query['include_fields'] = query['column_list']
Packit Service 4b33e2
                del query['column_list']
Packit Service 4b33e2
Packit Service 4b33e2
        # We need to do this for users here for users that
Packit Service 4b33e2
        # don't call build_query
Packit Service 4b33e2
        query.update(self._process_include_fields(query["include_fields"],
Packit Service 4b33e2
            None, None))
Packit Service 4b33e2
Packit Service 4b33e2
        if old != query:
Packit Service 4b33e2
            log.debug("RHBugzilla pretranslated query to: %s", query)
Packit Service 4b33e2
Packit Service 4b33e2
    def post_translation(self, query, bug):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Convert the results of getbug back to the ancient RHBZ value
Packit Service 4b33e2
        formats
Packit Service 4b33e2
        """
Packit Service 4b33e2
        ignore = query
Packit Service 4b33e2
Packit Service 4b33e2
        # RHBZ _still_ returns component and version as lists, which
Packit Service 4b33e2
        # deviates from upstream. Copy the list values to components
Packit Service 4b33e2
        # and versions respectively.
Packit Service 4b33e2
        if 'component' in bug and "components" not in bug:
Packit Service 4b33e2
            val = bug['component']
Packit Service 4b33e2
            bug['components'] = isinstance(val, list) and val or [val]
Packit Service 4b33e2
            bug['component'] = bug['components'][0]
Packit Service 4b33e2
Packit Service 4b33e2
        if 'version' in bug and "versions" not in bug:
Packit Service 4b33e2
            val = bug['version']
Packit Service 4b33e2
            bug['versions'] = isinstance(val, list) and val or [val]
Packit Service 4b33e2
            bug['version'] = bug['versions'][0]
Packit Service 4b33e2
Packit Service 4b33e2
        # sub_components isn't too friendly of a format, add a simpler
Packit Service 4b33e2
        # sub_component value
Packit Service 4b33e2
        if 'sub_components' in bug and 'sub_component' not in bug:
Packit Service 4b33e2
            val = bug['sub_components']
Packit Service 4b33e2
            bug['sub_component'] = ""
Packit Service 4b33e2
            if isinstance(val, dict):
Packit Service 4b33e2
                values = []
Packit Service 4b33e2
                for vallist in val.values():
Packit Service 4b33e2
                    values += vallist
Packit Service 4b33e2
                bug['sub_component'] = " ".join(values)
Packit Service 4b33e2
Packit Service 4b33e2
    def build_external_tracker_boolean_query(self, *args, **kwargs):
Packit Service 4b33e2
        ignore1 = args
Packit Service 4b33e2
        ignore2 = kwargs
Packit Service 4b33e2
        raise RuntimeError("Building external boolean queries is "
Packit Service 4b33e2
            "no longer supported. Please build a URL query "
Packit Service 4b33e2
            "via the bugzilla web UI and pass it to 'query --from-url' "
Packit Service 4b33e2
            "or url_to_query()")
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    def build_query(self, **kwargs):
Packit Service 4b33e2
        # pylint: disable=arguments-differ
Packit Service 4b33e2
Packit Service 4b33e2
        # We previously accepted a text format to approximate boolean
Packit Service 4b33e2
        # queries, and only for RHBugzilla. Upstream bz has --from-url
Packit Service 4b33e2
        # support now, so point people to that instead so we don't have
Packit Service 4b33e2
        # to document and maintain this logic anymore
Packit Service 4b33e2
        def _warn_bool(kwkey):
Packit Service 4b33e2
            vallist = self._listify(kwargs.get(kwkey, None))
Packit Service 4b33e2
            for value in vallist or []:
Packit Service 4b33e2
                for s in value.split(" "):
Packit Service 4b33e2
                    if s not in ["|", "&", "!"]:
Packit Service 4b33e2
                        continue
Packit Service 4b33e2
                    log.warning("%s value '%s' appears to use the now "
Packit Service 4b33e2
                        "unsupported boolean formatting, your query may "
Packit Service 4b33e2
                        "be incorrect. If you need complicated URL queries, "
Packit Service 4b33e2
                        "look into bugzilla --from-url/url_to_query().",
Packit Service 4b33e2
                        kwkey, value)
Packit Service 4b33e2
                    return
Packit Service 4b33e2
Packit Service 4b33e2
        _warn_bool("fixed_in")
Packit Service 4b33e2
        _warn_bool("blocked")
Packit Service 4b33e2
        _warn_bool("dependson")
Packit Service 4b33e2
        _warn_bool("flag")
Packit Service 4b33e2
        _warn_bool("qa_whiteboard")
Packit Service 4b33e2
        _warn_bool("devel_whiteboard")
Packit Service 4b33e2
        _warn_bool("alias")
Packit Service 4b33e2
Packit Service 4b33e2
        return Bugzilla.build_query(self, **kwargs)