|
Packit Service |
4b33e2 |
# base.py - the base classes etc. for a Python interface to bugzilla
|
|
Packit Service |
4b33e2 |
#
|
|
Packit Service |
4b33e2 |
# Copyright (C) 2007, 2008, 2009, 2010 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 |
import collections
|
|
Packit Service |
4b33e2 |
import getpass
|
|
Packit Service |
4b33e2 |
import locale
|
|
Packit Service |
4b33e2 |
from logging import getLogger
|
|
Packit Service |
4b33e2 |
import mimetypes
|
|
Packit Service |
4b33e2 |
import os
|
|
Packit Service |
4b33e2 |
import sys
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
from io import BytesIO
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# pylint: disable=import-error
|
|
Packit Service |
4b33e2 |
if sys.version_info[0] >= 3:
|
|
Packit Service |
4b33e2 |
# pylint: disable=no-name-in-module
|
|
Packit Service |
4b33e2 |
from configparser import ConfigParser
|
|
Packit Service |
4b33e2 |
from http.cookiejar import LoadError, MozillaCookieJar
|
|
Packit Service |
4b33e2 |
from urllib.parse import urlparse, parse_qsl
|
|
Packit Service |
4b33e2 |
from xmlrpc.client import Binary, Fault
|
|
Packit Service |
4b33e2 |
else:
|
|
Packit Service |
4b33e2 |
from ConfigParser import SafeConfigParser as ConfigParser
|
|
Packit Service |
4b33e2 |
from cookielib import LoadError, MozillaCookieJar
|
|
Packit Service |
4b33e2 |
from urlparse import urlparse, parse_qsl
|
|
Packit Service |
4b33e2 |
from xmlrpclib import Binary, Fault
|
|
Packit Service |
4b33e2 |
# pylint: enable=import-error
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
from .apiversion import __version__
|
|
Packit Service |
4b33e2 |
from .bug import Bug, User
|
|
Packit Service |
4b33e2 |
from .transport import BugzillaError, _BugzillaServerProxy, _RequestsTransport
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
log = getLogger(__name__)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _nested_update(d, u):
|
|
Packit Service |
4b33e2 |
# Helper for nested dict update()
|
|
Packit Service |
4b33e2 |
# https://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth
|
|
Packit Service |
4b33e2 |
for k, v in list(u.items()):
|
|
Packit Service |
4b33e2 |
if isinstance(v, collections.Mapping):
|
|
Packit Service |
4b33e2 |
d[k] = _nested_update(d.get(k, {}), v)
|
|
Packit Service |
4b33e2 |
else:
|
|
Packit Service |
4b33e2 |
d[k] = v
|
|
Packit Service |
4b33e2 |
return d
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _default_auth_location(filename):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Determine auth location for filename, like 'bugzillacookies'. If
|
|
Packit Service |
4b33e2 |
old style ~/.bugzillacookies exists, we use that, otherwise we
|
|
Packit Service |
4b33e2 |
use ~/.cache/python-bugzilla/bugzillacookies. Same for bugzillatoken
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
homepath = os.path.expanduser("~/.%s" % filename)
|
|
Packit Service |
4b33e2 |
xdgpath = os.path.expanduser("~/.cache/python-bugzilla/%s" % filename)
|
|
Packit Service |
4b33e2 |
if os.path.exists(xdgpath):
|
|
Packit Service |
4b33e2 |
return xdgpath
|
|
Packit Service |
4b33e2 |
if os.path.exists(homepath):
|
|
Packit Service |
4b33e2 |
return homepath
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if not os.path.exists(os.path.dirname(xdgpath)):
|
|
Packit Service |
4b33e2 |
os.makedirs(os.path.dirname(xdgpath), 0o700)
|
|
Packit Service |
4b33e2 |
return xdgpath
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _build_cookiejar(cookiefile):
|
|
Packit Service |
4b33e2 |
cj = MozillaCookieJar(cookiefile)
|
|
Packit Service |
4b33e2 |
if cookiefile is None:
|
|
Packit Service |
4b33e2 |
return cj
|
|
Packit Service |
4b33e2 |
if not os.path.exists(cookiefile):
|
|
Packit Service |
4b33e2 |
# Make sure a new file has correct permissions
|
|
Packit Service |
4b33e2 |
open(cookiefile, 'a').close()
|
|
Packit Service |
4b33e2 |
os.chmod(cookiefile, 0o600)
|
|
Packit Service |
4b33e2 |
cj.save()
|
|
Packit Service |
4b33e2 |
return cj
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
try:
|
|
Packit Service |
4b33e2 |
cj.load()
|
|
Packit Service |
4b33e2 |
return cj
|
|
Packit Service |
4b33e2 |
except LoadError:
|
|
Packit Service |
4b33e2 |
raise BugzillaError("cookiefile=%s not in Mozilla format" %
|
|
Packit Service |
4b33e2 |
cookiefile)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
_default_configpaths = [
|
|
Packit Service |
4b33e2 |
'/etc/bugzillarc',
|
|
Packit Service |
4b33e2 |
'~/.bugzillarc',
|
|
Packit Service |
4b33e2 |
'~/.config/python-bugzilla/bugzillarc',
|
|
Packit Service |
4b33e2 |
]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _open_bugzillarc(configpaths=-1):
|
|
Packit Service |
4b33e2 |
if configpaths == -1:
|
|
Packit Service |
4b33e2 |
configpaths = _default_configpaths[:]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# pylint: disable=protected-access
|
|
Packit Service |
4b33e2 |
configpaths = [os.path.expanduser(p) for p in
|
|
Packit Service |
4b33e2 |
Bugzilla._listify(configpaths)]
|
|
Packit Service |
4b33e2 |
# pylint: enable=protected-access
|
|
Packit Service |
4b33e2 |
cfg = ConfigParser()
|
|
Packit Service |
4b33e2 |
read_files = cfg.read(configpaths)
|
|
Packit Service |
4b33e2 |
if not read_files:
|
|
Packit Service |
4b33e2 |
return
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
log.info("Found bugzillarc files: %s", read_files)
|
|
Packit Service |
4b33e2 |
return cfg
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
class _FieldAlias(object):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Track API attribute names that differ from what we expose in users.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
For example, originally 'short_desc' was the name of the property that
|
|
Packit Service |
4b33e2 |
maps to 'summary' on modern bugzilla. We want pre-existing API users
|
|
Packit Service |
4b33e2 |
to be able to continue to use Bug.short_desc, and
|
|
Packit Service |
4b33e2 |
query({"short_desc": "foo"}). This class tracks that mapping.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
@oldname: The old attribute name
|
|
Packit Service |
4b33e2 |
@newname: The modern attribute name
|
|
Packit Service |
4b33e2 |
@is_api: If True, use this mapping for values sent to the xmlrpc API
|
|
Packit Service |
4b33e2 |
(like the query example)
|
|
Packit Service |
4b33e2 |
@is_bug: If True, use this mapping for Bug attribute names.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
def __init__(self, newname, oldname, is_api=True, is_bug=True):
|
|
Packit Service |
4b33e2 |
self.newname = newname
|
|
Packit Service |
4b33e2 |
self.oldname = oldname
|
|
Packit Service |
4b33e2 |
self.is_api = is_api
|
|
Packit Service |
4b33e2 |
self.is_bug = is_bug
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
class _BugzillaAPICache(object):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Helper class that holds cached API results for things like products,
|
|
Packit Service |
4b33e2 |
components, etc.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
def __init__(self):
|
|
Packit Service |
4b33e2 |
self.products = []
|
|
Packit Service |
4b33e2 |
self.component_names = {}
|
|
Packit Service |
4b33e2 |
self.bugfields = []
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
class Bugzilla(object):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
The main API object. Connects to a bugzilla instance over XMLRPC, and
|
|
Packit Service |
4b33e2 |
provides wrapper functions to simplify dealing with API calls.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
The most common invocation here will just be with just a URL:
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
bzapi = Bugzilla("http://bugzilla.example.com")
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
If you have previously logged into that URL, and have cached login
|
|
Packit Service |
4b33e2 |
cookies/tokens, you will automatically be logged in. Otherwise to
|
|
Packit Service |
4b33e2 |
log in, you can either pass auth options to __init__, or call a login
|
|
Packit Service |
4b33e2 |
helper like interactive_login().
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
If you are not logged in, you won be able to access restricted data like
|
|
Packit Service |
4b33e2 |
user email, or perform write actions like bug create/update. But simple
|
|
Packit Service |
4b33e2 |
querys will work correctly.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
If you are unsure if you are logged in, you can check the .logged_in
|
|
Packit Service |
4b33e2 |
property.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
Another way to specify auth credentials is via a 'bugzillarc' file.
|
|
Packit Service |
4b33e2 |
See readconfig() documentation for details.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# bugzilla version that the class is targeting. filled in by
|
|
Packit Service |
4b33e2 |
# subclasses
|
|
Packit Service |
4b33e2 |
bz_ver_major = 0
|
|
Packit Service |
4b33e2 |
bz_ver_minor = 0
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
@staticmethod
|
|
Packit Service |
4b33e2 |
def url_to_query(url):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Given a big huge bugzilla query URL, returns a query dict that can
|
|
Packit Service |
4b33e2 |
be passed along to the Bugzilla.query() method.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
q = {}
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# pylint: disable=unpacking-non-sequence
|
|
Packit Service |
4b33e2 |
(ignore, ignore, path,
|
|
Packit Service |
4b33e2 |
ignore, query, ignore) = urlparse(url)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
base = os.path.basename(path)
|
|
Packit Service |
4b33e2 |
if base not in ('buglist.cgi', 'query.cgi'):
|
|
Packit Service |
4b33e2 |
return {}
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
for (k, v) in parse_qsl(query):
|
|
Packit Service |
4b33e2 |
if k not in q:
|
|
Packit Service |
4b33e2 |
q[k] = v
|
|
Packit Service |
4b33e2 |
elif isinstance(q[k], list):
|
|
Packit Service |
4b33e2 |
q[k].append(v)
|
|
Packit Service |
4b33e2 |
else:
|
|
Packit Service |
4b33e2 |
oldv = q[k]
|
|
Packit Service |
4b33e2 |
q[k] = [oldv, v]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# Handle saved searches
|
|
Packit Service |
4b33e2 |
if base == "buglist.cgi" and "namedcmd" in q and "sharer_id" in q:
|
|
Packit Service |
4b33e2 |
q = {
|
|
Packit Service |
4b33e2 |
"sharer_id": q["sharer_id"],
|
|
Packit Service |
4b33e2 |
"savedsearch": q["namedcmd"],
|
|
Packit Service |
4b33e2 |
}
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
return q
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
@staticmethod
|
|
Packit Service |
4b33e2 |
def fix_url(url):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Turn passed url into a bugzilla XMLRPC web url
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
if '://' not in url:
|
|
Packit Service |
4b33e2 |
log.debug('No scheme given for url, assuming https')
|
|
Packit Service |
4b33e2 |
url = 'https://' + url
|
|
Packit Service |
4b33e2 |
if url.count('/') < 3:
|
|
Packit Service |
4b33e2 |
log.debug('No path given for url, assuming /xmlrpc.cgi')
|
|
Packit Service |
4b33e2 |
url = url + '/xmlrpc.cgi'
|
|
Packit Service |
4b33e2 |
return url
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
@staticmethod
|
|
Packit Service |
4b33e2 |
def _listify(val):
|
|
Packit Service |
4b33e2 |
if val is None:
|
|
Packit Service |
4b33e2 |
return val
|
|
Packit Service |
4b33e2 |
if isinstance(val, list):
|
|
Packit Service |
4b33e2 |
return val
|
|
Packit Service |
4b33e2 |
return [val]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def __init__(self, url=-1, user=None, password=None, cookiefile=-1,
|
|
Packit Service |
4b33e2 |
sslverify=True, tokenfile=-1, use_creds=True, api_key=None,
|
|
Packit Service |
4b33e2 |
cert=None):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
:param url: The bugzilla instance URL, which we will connect
|
|
Packit Service |
4b33e2 |
to immediately. Most users will want to specify this at
|
|
Packit Service |
4b33e2 |
__init__ time, but you can defer connecting by passing
|
|
Packit Service |
4b33e2 |
url=None and calling connect(URL) manually
|
|
Packit Service |
4b33e2 |
:param user: optional username to connect with
|
|
Packit Service |
4b33e2 |
:param password: optional password for the connecting user
|
|
Packit Service |
4b33e2 |
:param cert: optional certificate file for client side certificate
|
|
Packit Service |
4b33e2 |
authentication
|
|
Packit Service |
4b33e2 |
:param cookiefile: Location to cache the login session cookies so you
|
|
Packit Service |
4b33e2 |
don't have to keep specifying username/password. Bugzilla 5+ will
|
|
Packit Service |
4b33e2 |
use tokens instead of cookies.
|
|
Packit Service |
4b33e2 |
If -1, use the default path. If None, don't use or save
|
|
Packit Service |
4b33e2 |
any cookiefile.
|
|
Packit Service |
4b33e2 |
:param sslverify: Set this to False to skip SSL hostname and CA
|
|
Packit Service |
4b33e2 |
validation checks, like out of date certificate
|
|
Packit Service |
4b33e2 |
:param tokenfile: Location to cache the API login token so youi
|
|
Packit Service |
4b33e2 |
don't have to keep specifying username/password.
|
|
Packit Service |
4b33e2 |
If -1, use the default path. If None, don't use
|
|
Packit Service |
4b33e2 |
or save any tokenfile.
|
|
Packit Service |
4b33e2 |
:param use_creds: If False, this disables cookiefile, tokenfile,
|
|
Packit Service |
4b33e2 |
and any bugzillarc reading. This overwrites any tokenfile
|
|
Packit Service |
4b33e2 |
or cookiefile settings
|
|
Packit Service |
4b33e2 |
:param sslverify: Maps to 'requests' sslverify parameter. Set to
|
|
Packit Service |
4b33e2 |
False to disable SSL verification, but it can also be a path
|
|
Packit Service |
4b33e2 |
to file or directory for custom certs.
|
|
Packit Service |
4b33e2 |
:param api_key: A bugzilla
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
if url == -1:
|
|
Packit Service |
4b33e2 |
raise TypeError("Specify a valid bugzilla url, or pass url=None")
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# Settings the user might want to tweak
|
|
Packit Service |
4b33e2 |
self.user = user or ''
|
|
Packit Service |
4b33e2 |
self.password = password or ''
|
|
Packit Service |
4b33e2 |
self.api_key = api_key
|
|
Packit Service |
4b33e2 |
self.cert = cert or ''
|
|
Packit Service |
4b33e2 |
self.url = ''
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
self._proxy = None
|
|
Packit Service |
4b33e2 |
self._transport = None
|
|
Packit Service |
4b33e2 |
self._cookiejar = None
|
|
Packit Service |
4b33e2 |
self._sslverify = sslverify
|
|
Packit Service |
4b33e2 |
self._cache = _BugzillaAPICache()
|
|
Packit Service |
4b33e2 |
self._bug_autorefresh = False
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
self._field_aliases = []
|
|
Packit Service |
4b33e2 |
self._init_field_aliases()
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
self.configpath = _default_configpaths[:]
|
|
Packit Service |
4b33e2 |
if not use_creds:
|
|
Packit Service |
4b33e2 |
cookiefile = None
|
|
Packit Service |
4b33e2 |
tokenfile = None
|
|
Packit Service |
4b33e2 |
self.configpath = []
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if cookiefile == -1:
|
|
Packit Service |
4b33e2 |
cookiefile = _default_auth_location("bugzillacookies")
|
|
Packit Service |
4b33e2 |
if tokenfile == -1:
|
|
Packit Service |
4b33e2 |
tokenfile = _default_auth_location("bugzillatoken")
|
|
Packit Service |
4b33e2 |
log.debug("Using tokenfile=%s", tokenfile)
|
|
Packit Service |
4b33e2 |
self.cookiefile = cookiefile
|
|
Packit Service |
4b33e2 |
self.tokenfile = tokenfile
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if url:
|
|
Packit Service |
4b33e2 |
self.connect(url)
|
|
Packit Service |
4b33e2 |
self._init_class_from_url()
|
|
Packit Service |
4b33e2 |
self._init_class_state()
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _init_class_from_url(self):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Detect if we should use RHBugzilla class, and if so, set it
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
from bugzilla import RHBugzilla
|
|
Packit Service |
4b33e2 |
if isinstance(self, RHBugzilla):
|
|
Packit Service |
4b33e2 |
return
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
c = None
|
|
Packit Service |
4b33e2 |
if "bugzilla.redhat.com" in self.url:
|
|
Packit Service |
4b33e2 |
log.info("Using RHBugzilla for URL containing bugzilla.redhat.com")
|
|
Packit Service |
4b33e2 |
c = RHBugzilla
|
|
Packit Service |
4b33e2 |
else:
|
|
Packit Service |
4b33e2 |
try:
|
|
Packit Service |
4b33e2 |
extensions = self._proxy.Bugzilla.extensions()
|
|
Packit Service |
4b33e2 |
if "RedHat" in extensions.get('extensions', {}):
|
|
Packit Service |
4b33e2 |
log.info("Found RedHat bugzilla extension, "
|
|
Packit Service |
4b33e2 |
"using RHBugzilla")
|
|
Packit Service |
4b33e2 |
c = RHBugzilla
|
|
Packit Service |
4b33e2 |
except Fault:
|
|
Packit Service |
4b33e2 |
log.debug("Failed to fetch bugzilla extensions", exc_info=True)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if not c:
|
|
Packit Service |
4b33e2 |
return
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
self.__class__ = c
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _init_class_state(self):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Hook for subclasses to do any __init__ time setup
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
pass
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _init_field_aliases(self):
|
|
Packit Service |
4b33e2 |
# List of field aliases. Maps old style RHBZ parameter
|
|
Packit Service |
4b33e2 |
# names to actual upstream values. Used for createbug() and
|
|
Packit Service |
4b33e2 |
# query include_fields at least.
|
|
Packit Service |
4b33e2 |
self._add_field_alias('summary', 'short_desc')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('description', 'comment')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('platform', 'rep_platform')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('severity', 'bug_severity')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('status', 'bug_status')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('id', 'bug_id')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('blocks', 'blockedby')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('blocks', 'blocked')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('depends_on', 'dependson')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('creator', 'reporter')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('url', 'bug_file_loc')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('dupe_of', 'dupe_id')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('dupe_of', 'dup_id')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('comments', 'longdescs')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('creation_time', 'opendate')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('creation_time', 'creation_ts')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('whiteboard', 'status_whiteboard')
|
|
Packit Service |
4b33e2 |
self._add_field_alias('last_change_time', 'delta_ts')
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _get_user_agent(self):
|
|
Packit Service |
4b33e2 |
return 'python-bugzilla/%s' % __version__
|
|
Packit Service |
4b33e2 |
user_agent = property(_get_user_agent)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
###################
|
|
Packit Service |
4b33e2 |
# Private helpers #
|
|
Packit Service |
4b33e2 |
###################
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _check_version(self, major, minor):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Check if the detected bugzilla version is >= passed major/minor pair.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
if major < self.bz_ver_major:
|
|
Packit Service |
4b33e2 |
return True
|
|
Packit Service |
4b33e2 |
if (major == self.bz_ver_major and minor <= self.bz_ver_minor):
|
|
Packit Service |
4b33e2 |
return True
|
|
Packit Service |
4b33e2 |
return False
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _add_field_alias(self, *args, **kwargs):
|
|
Packit Service |
4b33e2 |
self._field_aliases.append(_FieldAlias(*args, **kwargs))
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _get_bug_aliases(self):
|
|
Packit Service |
4b33e2 |
return [(f.newname, f.oldname)
|
|
Packit Service |
4b33e2 |
for f in self._field_aliases if f.is_bug]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _get_api_aliases(self):
|
|
Packit Service |
4b33e2 |
return [(f.newname, f.oldname)
|
|
Packit Service |
4b33e2 |
for f in self._field_aliases if f.is_api]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
###################
|
|
Packit Service |
4b33e2 |
# Cookie handling #
|
|
Packit Service |
4b33e2 |
###################
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _getcookiefile(self):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
cookiefile is the file that bugzilla session cookies are loaded
|
|
Packit Service |
4b33e2 |
and saved from.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
return self._cookiejar.filename
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _delcookiefile(self):
|
|
Packit Service |
4b33e2 |
self._cookiejar = None
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _setcookiefile(self, cookiefile):
|
|
Packit Service |
4b33e2 |
if (self._cookiejar and cookiefile == self._cookiejar.filename):
|
|
Packit Service |
4b33e2 |
return
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if self._proxy is not None:
|
|
Packit Service |
4b33e2 |
raise RuntimeError("Can't set cookies with an open connection, "
|
|
Packit Service |
4b33e2 |
"disconnect() first.")
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
log.debug("Using cookiefile=%s", cookiefile)
|
|
Packit Service |
4b33e2 |
self._cookiejar = _build_cookiejar(cookiefile)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
cookiefile = property(_getcookiefile, _setcookiefile, _delcookiefile)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
#############################
|
|
Packit Service |
4b33e2 |
# Login/connection handling #
|
|
Packit Service |
4b33e2 |
#############################
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def readconfig(self, configpath=None):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
:param configpath: Optional bugzillarc path to read, instead of
|
|
Packit Service |
4b33e2 |
the default list.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
This function is called automatically from Bugzilla connect(), which
|
|
Packit Service |
4b33e2 |
is called at __init__ if a URL is passed. Calling it manually is
|
|
Packit Service |
4b33e2 |
just for passing in a non-standard configpath.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
The locations for the bugzillarc file are preferred in this order:
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
~/.config/python-bugzilla/bugzillarc
|
|
Packit Service |
4b33e2 |
~/.bugzillarc
|
|
Packit Service |
4b33e2 |
/etc/bugzillarc
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
It has content like:
|
|
Packit Service |
4b33e2 |
[bugzilla.yoursite.com]
|
|
Packit Service |
4b33e2 |
user = username
|
|
Packit Service |
4b33e2 |
password = password
|
|
Packit Service |
4b33e2 |
Or
|
|
Packit Service |
4b33e2 |
[bugzilla.yoursite.com]
|
|
Packit Service |
4b33e2 |
api_key = key
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
The file can have multiple sections for different bugzilla instances.
|
|
Packit Service |
4b33e2 |
A 'url' field in the [DEFAULT] section can be used to set a default
|
|
Packit Service |
4b33e2 |
URL for the bugzilla command line tool.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
Be sure to set appropriate permissions on bugzillarc if you choose to
|
|
Packit Service |
4b33e2 |
store your password in it!
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
cfg = _open_bugzillarc(configpath or self.configpath)
|
|
Packit Service |
4b33e2 |
if not cfg:
|
|
Packit Service |
4b33e2 |
return
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
section = ""
|
|
Packit Service |
4b33e2 |
log.debug("bugzillarc: Searching for config section matching %s",
|
|
Packit Service |
4b33e2 |
self.url)
|
|
Packit Service |
4b33e2 |
for s in sorted(cfg.sections()):
|
|
Packit Service |
4b33e2 |
# Substring match - prefer the longest match found
|
|
Packit Service |
4b33e2 |
if s in self.url:
|
|
Packit Service |
4b33e2 |
log.debug("bugzillarc: Found matching section: %s", s)
|
|
Packit Service |
4b33e2 |
section = s
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if not section:
|
|
Packit Service |
4b33e2 |
log.debug("bugzillarc: No section found")
|
|
Packit Service |
4b33e2 |
return
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
for key, val in cfg.items(section):
|
|
Packit Service |
4b33e2 |
if key == "api_key":
|
|
Packit Service |
4b33e2 |
log.debug("bugzillarc: setting api_key")
|
|
Packit Service |
4b33e2 |
self.api_key = val
|
|
Packit Service |
4b33e2 |
elif key == "user":
|
|
Packit Service |
4b33e2 |
log.debug("bugzillarc: setting user=%s", val)
|
|
Packit Service |
4b33e2 |
self.user = val
|
|
Packit Service |
4b33e2 |
elif key == "password":
|
|
Packit Service |
4b33e2 |
log.debug("bugzillarc: setting password")
|
|
Packit Service |
4b33e2 |
self.password = val
|
|
Packit Service |
4b33e2 |
elif key == "cert":
|
|
Packit Service |
4b33e2 |
log.debug("bugzillarc: setting cert")
|
|
Packit Service |
4b33e2 |
self.cert = val
|
|
Packit Service |
4b33e2 |
else:
|
|
Packit Service |
4b33e2 |
log.debug("bugzillarc: unknown key=%s", key)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _set_bz_version(self, version):
|
|
Packit Service |
4b33e2 |
try:
|
|
Packit Service |
4b33e2 |
self.bz_ver_major, self.bz_ver_minor = [
|
|
Packit Service |
4b33e2 |
int(i) for i in version.split(".")[0:2]]
|
|
Packit Service |
4b33e2 |
except Exception:
|
|
Packit Service |
4b33e2 |
log.debug("version doesn't match expected format X.Y.Z, "
|
|
Packit Service |
4b33e2 |
"assuming 5.0", exc_info=True)
|
|
Packit Service |
4b33e2 |
self.bz_ver_major = 5
|
|
Packit Service |
4b33e2 |
self.bz_ver_minor = 0
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def connect(self, url=None):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Connect to the bugzilla instance with the given url. This is
|
|
Packit Service |
4b33e2 |
called by __init__ if a URL is passed. Or it can be called manually
|
|
Packit Service |
4b33e2 |
at any time with a passed URL.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
This will also read any available config files (see readconfig()),
|
|
Packit Service |
4b33e2 |
which may set 'user' and 'password', and others.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
If 'user' and 'password' are both set, we'll run login(). Otherwise
|
|
Packit Service |
4b33e2 |
you'll have to login() yourself before some methods will work.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
if self._transport:
|
|
Packit Service |
4b33e2 |
self.disconnect()
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if url is None and self.url:
|
|
Packit Service |
4b33e2 |
url = self.url
|
|
Packit Service |
4b33e2 |
url = self.fix_url(url)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
self._transport = _RequestsTransport(
|
|
Packit Service |
4b33e2 |
url, self._cookiejar, sslverify=self._sslverify, cert=self.cert)
|
|
Packit Service |
4b33e2 |
self._transport.user_agent = self.user_agent
|
|
Packit Service |
4b33e2 |
self._proxy = _BugzillaServerProxy(url, self.tokenfile,
|
|
Packit Service |
4b33e2 |
self._transport)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
self.url = url
|
|
Packit Service |
4b33e2 |
# we've changed URLs - reload config
|
|
Packit Service |
4b33e2 |
self.readconfig()
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if (self.user and self.password):
|
|
Packit Service |
4b33e2 |
log.info("user and password present - doing login()")
|
|
Packit Service |
4b33e2 |
self.login()
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if self.api_key:
|
|
Packit Service |
4b33e2 |
log.debug("using API key")
|
|
Packit Service |
4b33e2 |
self._proxy.use_api_key(self.api_key)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
version = self._proxy.Bugzilla.version()["version"]
|
|
Packit Service |
4b33e2 |
log.debug("Bugzilla version string: %s", version)
|
|
Packit Service |
4b33e2 |
self._set_bz_version(version)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def disconnect(self):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Disconnect from the given bugzilla instance.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
self._proxy = None
|
|
Packit Service |
4b33e2 |
self._transport = None
|
|
Packit Service |
4b33e2 |
self._cache = _BugzillaAPICache()
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _login(self, user, password):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Backend login method for Bugzilla3
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
return self._proxy.User.login({'login': user, 'password': password})
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _logout(self):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Backend login method for Bugzilla3
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
return self._proxy.User.logout()
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def login(self, user=None, password=None):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Attempt to log in using the given username and password. Subsequent
|
|
Packit Service |
4b33e2 |
method calls will use this username and password. Returns False if
|
|
Packit Service |
4b33e2 |
login fails, otherwise returns some kind of login info - typically
|
|
Packit Service |
4b33e2 |
either a numeric userid, or a dict of user info.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
If user is not set, the value of Bugzilla.user will be used. If *that*
|
|
Packit Service |
4b33e2 |
is not set, ValueError will be raised. If login fails, BugzillaError
|
|
Packit Service |
4b33e2 |
will be raised.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
This method will be called implicitly at the end of connect() if user
|
|
Packit Service |
4b33e2 |
and password are both set. So under most circumstances you won't need
|
|
Packit Service |
4b33e2 |
to call this yourself.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
if self.api_key:
|
|
Packit Service |
4b33e2 |
raise ValueError("cannot login when using an API key")
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if user:
|
|
Packit Service |
4b33e2 |
self.user = user
|
|
Packit Service |
4b33e2 |
if password:
|
|
Packit Service |
4b33e2 |
self.password = password
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if not self.user:
|
|
Packit Service |
4b33e2 |
raise ValueError("missing username")
|
|
Packit Service |
4b33e2 |
if not self.password:
|
|
Packit Service |
4b33e2 |
raise ValueError("missing password")
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
try:
|
|
Packit Service |
4b33e2 |
ret = self._login(self.user, self.password)
|
|
Packit Service |
4b33e2 |
self.password = ''
|
|
Packit Service |
4b33e2 |
log.info("login successful for user=%s", self.user)
|
|
Packit Service |
4b33e2 |
return ret
|
|
Packit Service |
4b33e2 |
except Fault as e:
|
|
Packit Service |
4b33e2 |
raise BugzillaError("Login failed: %s" % str(e.faultString))
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def interactive_login(self, user=None, password=None, force=False):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Helper method to handle login for this bugzilla instance.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
:param user: bugzilla username. If not specified, prompt for it.
|
|
Packit Service |
4b33e2 |
:param password: bugzilla password. If not specified, prompt for it.
|
|
Packit Service |
4b33e2 |
:param force: Unused
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
ignore = force
|
|
Packit Service |
4b33e2 |
log.debug('Calling interactive_login')
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if not user:
|
|
Packit Service |
4b33e2 |
sys.stdout.write('Bugzilla Username: ')
|
|
Packit Service |
4b33e2 |
sys.stdout.flush()
|
|
Packit Service |
4b33e2 |
user = sys.stdin.readline().strip()
|
|
Packit Service |
4b33e2 |
if not password:
|
|
Packit Service |
4b33e2 |
password = getpass.getpass('Bugzilla Password: ')
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
log.info('Logging in... ')
|
|
Packit Service |
4b33e2 |
self.login(user, password)
|
|
Packit Service |
4b33e2 |
log.info('Authorization cookie received.')
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def logout(self):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Log out of bugzilla. Drops server connection and user info, and
|
|
Packit Service |
4b33e2 |
destroys authentication cookies.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
self._logout()
|
|
Packit Service |
4b33e2 |
self.disconnect()
|
|
Packit Service |
4b33e2 |
self.user = ''
|
|
Packit Service |
4b33e2 |
self.password = ''
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
@property
|
|
Packit Service |
4b33e2 |
def logged_in(self):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
This is True if this instance is logged in else False.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
We test if this session is authenticated by calling the User.get()
|
|
Packit Service |
4b33e2 |
XMLRPC method with ids set. Logged-out users cannot pass the 'ids'
|
|
Packit Service |
4b33e2 |
parameter and will result in a 505 error. If we tried to login with a
|
|
Packit Service |
4b33e2 |
token, but the token was incorrect or expired, the server returns a
|
|
Packit Service |
4b33e2 |
32000 error.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
For Bugzilla 5 and later, a new method, User.valid_login is available
|
|
Packit Service |
4b33e2 |
to test the validity of the token. However, this will require that the
|
|
Packit Service |
4b33e2 |
username be cached along with the token in order to work effectively in
|
|
Packit Service |
4b33e2 |
all scenarios and is not currently used. For more information, refer to
|
|
Packit Service |
4b33e2 |
the following url.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
http://bugzilla.readthedocs.org/en/latest/api/core/v1/user.html#valid-login
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
try:
|
|
Packit Service |
4b33e2 |
self._proxy.User.get({'ids': []})
|
|
Packit Service |
4b33e2 |
return True
|
|
Packit Service |
4b33e2 |
except Fault as e:
|
|
Packit Service |
4b33e2 |
if e.faultCode == 505 or e.faultCode == 32000:
|
|
Packit Service |
4b33e2 |
return False
|
|
Packit Service |
4b33e2 |
raise e
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
######################
|
|
Packit Service |
4b33e2 |
# Bugfields querying #
|
|
Packit Service |
4b33e2 |
######################
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _getbugfields(self):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Get the list of valid fields for Bug objects
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
r = self._proxy.Bug.fields({'include_fields': ['name']})
|
|
Packit Service |
4b33e2 |
return [f['name'] for f in r['fields']]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def getbugfields(self, force_refresh=False):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Calls getBugFields, which returns a list of fields in each bug
|
|
Packit Service |
4b33e2 |
for this bugzilla instance. This can be used to set the list of attrs
|
|
Packit Service |
4b33e2 |
on the Bug object.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
if force_refresh or not self._cache.bugfields:
|
|
Packit Service |
4b33e2 |
log.debug("Refreshing bugfields")
|
|
Packit Service |
4b33e2 |
self._cache.bugfields = self._getbugfields()
|
|
Packit Service |
4b33e2 |
self._cache.bugfields.sort()
|
|
Packit Service |
4b33e2 |
log.debug("bugfields = %s", self._cache.bugfields)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
return self._cache.bugfields
|
|
Packit Service |
4b33e2 |
bugfields = property(fget=lambda self: self.getbugfields(),
|
|
Packit Service |
4b33e2 |
fdel=lambda self: setattr(self, '_bugfields', None))
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
####################
|
|
Packit Service |
4b33e2 |
# Product querying #
|
|
Packit Service |
4b33e2 |
####################
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def product_get(self, ids=None, names=None,
|
|
Packit Service |
4b33e2 |
include_fields=None, exclude_fields=None,
|
|
Packit Service |
4b33e2 |
ptype=None):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Raw wrapper around Product.get
|
|
Packit Service |
4b33e2 |
https://bugzilla.readthedocs.io/en/latest/api/core/v1/product.html#get-product
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
This does not perform any caching like other product API calls.
|
|
Packit Service |
4b33e2 |
If ids, names, or ptype is not specified, we default to
|
|
Packit Service |
4b33e2 |
ptype=accessible for historical reasons
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
@ids: List of product IDs to lookup
|
|
Packit Service |
4b33e2 |
@names: List of product names to lookup
|
|
Packit Service |
4b33e2 |
@ptype: Either 'accessible', 'selectable', or 'enterable'. If
|
|
Packit Service |
4b33e2 |
specified, we return data for all those
|
|
Packit Service |
4b33e2 |
@include_fields: Only include these fields in the output
|
|
Packit Service |
4b33e2 |
@exclude_fields: Do not include these fields in the output
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
if ids is None and names is None and ptype is None:
|
|
Packit Service |
4b33e2 |
ptype = "accessible"
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if ptype:
|
|
Packit Service |
4b33e2 |
raw = None
|
|
Packit Service |
4b33e2 |
if ptype == "accessible":
|
|
Packit Service |
4b33e2 |
raw = self._proxy.Product.get_accessible_products()
|
|
Packit Service |
4b33e2 |
elif ptype == "selectable":
|
|
Packit Service |
4b33e2 |
raw = self._proxy.Product.get_selectable_products()
|
|
Packit Service |
4b33e2 |
elif ptype == "enterable":
|
|
Packit Service |
4b33e2 |
raw = self._proxy.Product.get_enterable_products()
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if raw is None:
|
|
Packit Service |
4b33e2 |
raise RuntimeError("Unknown ptype=%s" % ptype)
|
|
Packit Service |
4b33e2 |
ids = raw['ids']
|
|
Packit Service |
4b33e2 |
log.debug("For ptype=%s found ids=%s", ptype, ids)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
kwargs = {}
|
|
Packit Service |
4b33e2 |
if ids:
|
|
Packit Service |
4b33e2 |
kwargs["ids"] = self._listify(ids)
|
|
Packit Service |
4b33e2 |
if names:
|
|
Packit Service |
4b33e2 |
kwargs["names"] = self._listify(names)
|
|
Packit Service |
4b33e2 |
if include_fields:
|
|
Packit Service |
4b33e2 |
kwargs["include_fields"] = include_fields
|
|
Packit Service |
4b33e2 |
if exclude_fields:
|
|
Packit Service |
4b33e2 |
kwargs["exclude_fields"] = exclude_fields
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
ret = self._proxy.Product.get(kwargs)
|
|
Packit Service |
4b33e2 |
return ret['products']
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def refresh_products(self, **kwargs):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Refresh a product's cached info. Basically calls product_get
|
|
Packit Service |
4b33e2 |
with the passed arguments, and tries to intelligently update
|
|
Packit Service |
4b33e2 |
our product cache.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
For example, if we already have cached info for product=foo,
|
|
Packit Service |
4b33e2 |
and you pass in names=["bar", "baz"], the new cache will have
|
|
Packit Service |
4b33e2 |
info for products foo, bar, baz. Individual product fields are
|
|
Packit Service |
4b33e2 |
also updated.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
for product in self.product_get(**kwargs):
|
|
Packit Service |
4b33e2 |
updated = False
|
|
Packit Service |
4b33e2 |
for current in self._cache.products[:]:
|
|
Packit Service |
4b33e2 |
if (current.get("id", -1) != product.get("id", -2) and
|
|
Packit Service |
4b33e2 |
current.get("name", -1) != product.get("name", -2)):
|
|
Packit Service |
4b33e2 |
continue
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
_nested_update(current, product)
|
|
Packit Service |
4b33e2 |
updated = True
|
|
Packit Service |
4b33e2 |
break
|
|
Packit Service |
4b33e2 |
if not updated:
|
|
Packit Service |
4b33e2 |
self._cache.products.append(product)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def getproducts(self, force_refresh=False, **kwargs):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Query all products and return the raw dict info. Takes all the
|
|
Packit Service |
4b33e2 |
same arguments as product_get.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
On first invocation this will contact bugzilla and internally
|
|
Packit Service |
4b33e2 |
cache the results. Subsequent getproducts calls or accesses to
|
|
Packit Service |
4b33e2 |
self.products will return this cached data only.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
:param force_refresh: force refreshing via refresh_products()
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
if force_refresh or not self._cache.products:
|
|
Packit Service |
4b33e2 |
self.refresh_products(**kwargs)
|
|
Packit Service |
4b33e2 |
return self._cache.products
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
products = property(
|
|
Packit Service |
4b33e2 |
fget=lambda self: self.getproducts(),
|
|
Packit Service |
4b33e2 |
fdel=lambda self: setattr(self, '_products', None),
|
|
Packit Service |
4b33e2 |
doc="Helper for accessing the products cache. If nothing "
|
|
Packit Service |
4b33e2 |
"has been cached yet, this calls getproducts()")
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
#######################
|
|
Packit Service |
4b33e2 |
# components querying #
|
|
Packit Service |
4b33e2 |
#######################
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _lookup_product_in_cache(self, productname):
|
|
Packit Service |
4b33e2 |
prodstr = isinstance(productname, str) and productname or None
|
|
Packit Service |
4b33e2 |
prodint = isinstance(productname, int) and productname or None
|
|
Packit Service |
4b33e2 |
for proddict in self._cache.products:
|
|
Packit Service |
4b33e2 |
if prodstr == proddict.get("name", -1):
|
|
Packit Service |
4b33e2 |
return proddict
|
|
Packit Service |
4b33e2 |
if prodint == proddict.get("id", "nope"):
|
|
Packit Service |
4b33e2 |
return proddict
|
|
Packit Service |
4b33e2 |
return {}
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def getcomponentsdetails(self, product, force_refresh=False):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Wrapper around Product.get(include_fields=["components"]),
|
|
Packit Service |
4b33e2 |
returning only the "components" data for the requested product,
|
|
Packit Service |
4b33e2 |
slightly reworked to a dict mapping of components.name: components,
|
|
Packit Service |
4b33e2 |
for historical reasons.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
This uses the product cache, but will update it if the product
|
|
Packit Service |
4b33e2 |
isn't found or "components" isn't cached for the product.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
In cases like bugzilla.redhat.com where there are tons of
|
|
Packit Service |
4b33e2 |
components for some products, this API will time out. You
|
|
Packit Service |
4b33e2 |
should use product_get instead.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
proddict = self._lookup_product_in_cache(product)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if (force_refresh or not proddict or "components" not in proddict):
|
|
Packit Service |
4b33e2 |
self.refresh_products(names=[product],
|
|
Packit Service |
4b33e2 |
include_fields=["name", "id", "components"])
|
|
Packit Service |
4b33e2 |
proddict = self._lookup_product_in_cache(product)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
ret = {}
|
|
Packit Service |
4b33e2 |
for compdict in proddict["components"]:
|
|
Packit Service |
4b33e2 |
ret[compdict["name"]] = compdict
|
|
Packit Service |
4b33e2 |
return ret
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def getcomponentdetails(self, product, component, force_refresh=False):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Helper for accessing a single component's info. This is a wrapper
|
|
Packit Service |
4b33e2 |
around getcomponentsdetails, see that for explanation
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
d = self.getcomponentsdetails(product, force_refresh)
|
|
Packit Service |
4b33e2 |
return d[component]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def getcomponents(self, product, force_refresh=False):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Return a list of component names for the passed product.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
This can be implemented with Product.get, but behind the
|
|
Packit Service |
4b33e2 |
scenes it uses Bug.legal_values. Reason being that on bugzilla
|
|
Packit Service |
4b33e2 |
instances with tons of components, like bugzilla.redhat.com
|
|
Packit Service |
4b33e2 |
Product=Fedora for example, there's a 10x speed difference
|
|
Packit Service |
4b33e2 |
even with properly limited Product.get calls.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
On first invocation the value is cached, and subsequent calls
|
|
Packit Service |
4b33e2 |
will return the cached data.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
:param force_refresh: Force refreshing the cache, and return
|
|
Packit Service |
4b33e2 |
the new data
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
proddict = self._lookup_product_in_cache(product)
|
|
Packit Service |
4b33e2 |
product_id = proddict.get("id", None)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if (force_refresh or
|
|
Packit Service |
4b33e2 |
product_id is None or
|
|
Packit Service |
4b33e2 |
product_id not in self._cache.component_names):
|
|
Packit Service |
4b33e2 |
self.refresh_products(names=[product],
|
|
Packit Service |
4b33e2 |
include_fields=["names", "id"])
|
|
Packit Service |
4b33e2 |
proddict = self._lookup_product_in_cache(product)
|
|
Packit Service |
4b33e2 |
product_id = proddict["id"]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
opts = {'product_id': product_id, 'field': 'component'}
|
|
Packit Service |
4b33e2 |
names = self._proxy.Bug.legal_values(opts)["values"]
|
|
Packit Service |
4b33e2 |
self._cache.component_names[product_id] = names
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
return self._cache.component_names[product_id]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
############################
|
|
Packit Service |
4b33e2 |
# component adding/editing #
|
|
Packit Service |
4b33e2 |
############################
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _component_data_convert(self, data, update=False):
|
|
Packit Service |
4b33e2 |
# Back compat for the old RH interface
|
|
Packit Service |
4b33e2 |
convert_fields = [
|
|
Packit Service |
4b33e2 |
("initialowner", "default_assignee"),
|
|
Packit Service |
4b33e2 |
("initialqacontact", "default_qa_contact"),
|
|
Packit Service |
4b33e2 |
("initialcclist", "default_cc"),
|
|
Packit Service |
4b33e2 |
]
|
|
Packit Service |
4b33e2 |
for old, new in convert_fields:
|
|
Packit Service |
4b33e2 |
if old in data:
|
|
Packit Service |
4b33e2 |
data[new] = data.pop(old)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if update:
|
|
Packit Service |
4b33e2 |
names = {"product": data.pop("product"),
|
|
Packit Service |
4b33e2 |
"component": data.pop("component")}
|
|
Packit Service |
4b33e2 |
updates = {}
|
|
Packit Service |
4b33e2 |
for k in list(data.keys()):
|
|
Packit Service |
4b33e2 |
updates[k] = data.pop(k)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
data["names"] = [names]
|
|
Packit Service |
4b33e2 |
data["updates"] = updates
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def addcomponent(self, data):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
A method to create a component in Bugzilla. Takes a dict, with the
|
|
Packit Service |
4b33e2 |
following elements:
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
product: The product to create the component in
|
|
Packit Service |
4b33e2 |
component: The name of the component to create
|
|
Packit Service |
4b33e2 |
desription: A one sentence summary of the component
|
|
Packit Service |
4b33e2 |
default_assignee: The bugzilla login (email address) of the initial
|
|
Packit Service |
4b33e2 |
owner of the component
|
|
Packit Service |
4b33e2 |
default_qa_contact (optional): The bugzilla login of the
|
|
Packit Service |
4b33e2 |
initial QA contact
|
|
Packit Service |
4b33e2 |
default_cc: (optional) The initial list of users to be CC'ed on
|
|
Packit Service |
4b33e2 |
new bugs for the component.
|
|
Packit Service |
4b33e2 |
is_active: (optional) If False, the component is hidden from
|
|
Packit Service |
4b33e2 |
the component list when filing new bugs.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
data = data.copy()
|
|
Packit Service |
4b33e2 |
self._component_data_convert(data)
|
|
Packit Service |
4b33e2 |
return self._proxy.Component.create(data)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def editcomponent(self, data):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
A method to edit a component in Bugzilla. Takes a dict, with
|
|
Packit Service |
4b33e2 |
mandatory elements of product. component, and initialowner.
|
|
Packit Service |
4b33e2 |
All other elements are optional and use the same names as the
|
|
Packit Service |
4b33e2 |
addcomponent() method.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
data = data.copy()
|
|
Packit Service |
4b33e2 |
self._component_data_convert(data, update=True)
|
|
Packit Service |
4b33e2 |
return self._proxy.Component.update(data)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
###################
|
|
Packit Service |
4b33e2 |
# getbug* methods #
|
|
Packit Service |
4b33e2 |
###################
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _process_include_fields(self, include_fields, exclude_fields,
|
|
Packit Service |
4b33e2 |
extra_fields):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Internal helper to process include_fields lists
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
def _convert_fields(_in):
|
|
Packit Service |
4b33e2 |
if not _in:
|
|
Packit Service |
4b33e2 |
return _in
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
for newname, oldname in self._get_api_aliases():
|
|
Packit Service |
4b33e2 |
if oldname in _in:
|
|
Packit Service |
4b33e2 |
_in.remove(oldname)
|
|
Packit Service |
4b33e2 |
if newname not in _in:
|
|
Packit Service |
4b33e2 |
_in.append(newname)
|
|
Packit Service |
4b33e2 |
return _in
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
ret = {}
|
|
Packit Service |
4b33e2 |
if self._check_version(4, 0):
|
|
Packit Service |
4b33e2 |
if include_fields:
|
|
Packit Service |
4b33e2 |
include_fields = _convert_fields(include_fields)
|
|
Packit Service |
4b33e2 |
if "id" not in include_fields:
|
|
Packit Service |
4b33e2 |
include_fields.append("id")
|
|
Packit Service |
4b33e2 |
ret["include_fields"] = include_fields
|
|
Packit Service |
4b33e2 |
if exclude_fields:
|
|
Packit Service |
4b33e2 |
exclude_fields = _convert_fields(exclude_fields)
|
|
Packit Service |
4b33e2 |
ret["exclude_fields"] = exclude_fields
|
|
Packit Service |
4b33e2 |
if self._supports_getbug_extra_fields:
|
|
Packit Service |
4b33e2 |
if extra_fields:
|
|
Packit Service |
4b33e2 |
ret["extra_fields"] = _convert_fields(extra_fields)
|
|
Packit Service |
4b33e2 |
return ret
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _get_bug_autorefresh(self):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
This value is passed to Bug.autorefresh for all fetched bugs.
|
|
Packit Service |
4b33e2 |
If True, and an uncached attribute is requested from a Bug,
|
|
Packit Service |
4b33e2 |
the Bug will update its contents and try again.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
return self._bug_autorefresh
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _set_bug_autorefresh(self, val):
|
|
Packit Service |
4b33e2 |
self._bug_autorefresh = bool(val)
|
|
Packit Service |
4b33e2 |
bug_autorefresh = property(_get_bug_autorefresh, _set_bug_autorefresh)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# getbug_extra_fields: Extra fields that need to be explicitly
|
|
Packit Service |
4b33e2 |
# requested from Bug.get in order for the data to be returned.
|
|
Packit Service |
4b33e2 |
#
|
|
Packit Service |
4b33e2 |
# As of Dec 2012 it seems like only RH bugzilla actually has behavior
|
|
Packit Service |
4b33e2 |
# like this, for upstream bz it returns all info for every Bug.get()
|
|
Packit Service |
4b33e2 |
_getbug_extra_fields = []
|
|
Packit Service |
4b33e2 |
_supports_getbug_extra_fields = False
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _getbugs(self, idlist, permissive,
|
|
Packit Service |
4b33e2 |
include_fields=None, exclude_fields=None, extra_fields=None):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Return a list of dicts of full bug info for each given bug id.
|
|
Packit Service |
4b33e2 |
bug ids that couldn't be found will return None instead of a dict.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
oldidlist = idlist
|
|
Packit Service |
4b33e2 |
idlist = []
|
|
Packit Service |
4b33e2 |
for i in oldidlist:
|
|
Packit Service |
4b33e2 |
try:
|
|
Packit Service |
4b33e2 |
idlist.append(int(i))
|
|
Packit Service |
4b33e2 |
except ValueError:
|
|
Packit Service |
4b33e2 |
# String aliases can be passed as well
|
|
Packit Service |
4b33e2 |
idlist.append(i)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
extra_fields = self._listify(extra_fields or [])
|
|
Packit Service |
4b33e2 |
extra_fields += self._getbug_extra_fields
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
getbugdata = {"ids": idlist}
|
|
Packit Service |
4b33e2 |
if permissive:
|
|
Packit Service |
4b33e2 |
getbugdata["permissive"] = 1
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
getbugdata.update(self._process_include_fields(
|
|
Packit Service |
4b33e2 |
include_fields, exclude_fields, extra_fields))
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
r = self._proxy.Bug.get(getbugdata)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if self._check_version(4, 0):
|
|
Packit Service |
4b33e2 |
bugdict = dict([(b['id'], b) for b in r['bugs']])
|
|
Packit Service |
4b33e2 |
else:
|
|
Packit Service |
4b33e2 |
bugdict = dict([(b['id'], b['internals']) for b in r['bugs']])
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
ret = []
|
|
Packit Service |
4b33e2 |
for i in idlist:
|
|
Packit Service |
4b33e2 |
found = None
|
|
Packit Service |
4b33e2 |
if i in bugdict:
|
|
Packit Service |
4b33e2 |
found = bugdict[i]
|
|
Packit Service |
4b33e2 |
else:
|
|
Packit Service |
4b33e2 |
# Need to map an alias
|
|
Packit Service |
4b33e2 |
for valdict in bugdict.values():
|
|
Packit Service |
4b33e2 |
if i in self._listify(valdict.get("alias", None)):
|
|
Packit Service |
4b33e2 |
found = valdict
|
|
Packit Service |
4b33e2 |
break
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
ret.append(found)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
return ret
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _getbug(self, objid, **kwargs):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Thin wrapper around _getbugs to handle the slight argument tweaks
|
|
Packit Service |
4b33e2 |
for fetching a single bug. The main bit is permissive=False, which
|
|
Packit Service |
4b33e2 |
will tell bugzilla to raise an explicit error if we can't fetch
|
|
Packit Service |
4b33e2 |
that bug.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
This logic is called from Bug() too
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
return self._getbugs([objid], permissive=False, **kwargs)[0]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def getbug(self, objid,
|
|
Packit Service |
4b33e2 |
include_fields=None, exclude_fields=None, extra_fields=None):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Return a Bug object with the full complement of bug data
|
|
Packit Service |
4b33e2 |
already loaded.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
data = self._getbug(objid,
|
|
Packit Service |
4b33e2 |
include_fields=include_fields, exclude_fields=exclude_fields,
|
|
Packit Service |
4b33e2 |
extra_fields=extra_fields)
|
|
Packit Service |
4b33e2 |
return Bug(self, dict=data, autorefresh=self.bug_autorefresh)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def getbugs(self, idlist,
|
|
Packit Service |
4b33e2 |
include_fields=None, exclude_fields=None, extra_fields=None,
|
|
Packit Service |
4b33e2 |
permissive=True):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Return a list of Bug objects with the full complement of bug data
|
|
Packit Service |
4b33e2 |
already loaded. If there's a problem getting the data for a given id,
|
|
Packit Service |
4b33e2 |
the corresponding item in the returned list will be None.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
data = self._getbugs(idlist, include_fields=include_fields,
|
|
Packit Service |
4b33e2 |
exclude_fields=exclude_fields, extra_fields=extra_fields,
|
|
Packit Service |
4b33e2 |
permissive=permissive)
|
|
Packit Service |
4b33e2 |
return [(b and Bug(self, dict=b,
|
|
Packit Service |
4b33e2 |
autorefresh=self.bug_autorefresh)) or None
|
|
Packit Service |
4b33e2 |
for b in data]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def get_comments(self, idlist):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Returns a dictionary of bugs and comments. The comments key will
|
|
Packit Service |
4b33e2 |
be empty. See bugzilla docs for details
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
return self._proxy.Bug.comments({'ids': idlist})
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
#################
|
|
Packit Service |
4b33e2 |
# query methods #
|
|
Packit Service |
4b33e2 |
#################
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def build_query(self,
|
|
Packit Service |
4b33e2 |
product=None,
|
|
Packit Service |
4b33e2 |
component=None,
|
|
Packit Service |
4b33e2 |
version=None,
|
|
Packit Service |
4b33e2 |
long_desc=None,
|
|
Packit Service |
4b33e2 |
bug_id=None,
|
|
Packit Service |
4b33e2 |
short_desc=None,
|
|
Packit Service |
4b33e2 |
cc=None,
|
|
Packit Service |
4b33e2 |
assigned_to=None,
|
|
Packit Service |
4b33e2 |
reporter=None,
|
|
Packit Service |
4b33e2 |
qa_contact=None,
|
|
Packit Service |
4b33e2 |
status=None,
|
|
Packit Service |
4b33e2 |
blocked=None,
|
|
Packit Service |
4b33e2 |
dependson=None,
|
|
Packit Service |
4b33e2 |
keywords=None,
|
|
Packit Service |
4b33e2 |
keywords_type=None,
|
|
Packit Service |
4b33e2 |
url=None,
|
|
Packit Service |
4b33e2 |
url_type=None,
|
|
Packit Service |
4b33e2 |
status_whiteboard=None,
|
|
Packit Service |
4b33e2 |
status_whiteboard_type=None,
|
|
Packit Service |
4b33e2 |
fixed_in=None,
|
|
Packit Service |
4b33e2 |
fixed_in_type=None,
|
|
Packit Service |
4b33e2 |
flag=None,
|
|
Packit Service |
4b33e2 |
alias=None,
|
|
Packit Service |
4b33e2 |
qa_whiteboard=None,
|
|
Packit Service |
4b33e2 |
devel_whiteboard=None,
|
|
Packit Service |
4b33e2 |
boolean_query=None,
|
|
Packit Service |
4b33e2 |
bug_severity=None,
|
|
Packit Service |
4b33e2 |
priority=None,
|
|
Packit Service |
4b33e2 |
target_release=None,
|
|
Packit Service |
4b33e2 |
target_milestone=None,
|
|
Packit Service |
4b33e2 |
emailtype=None,
|
|
Packit Service |
4b33e2 |
booleantype=None,
|
|
Packit Service |
4b33e2 |
include_fields=None,
|
|
Packit Service |
4b33e2 |
quicksearch=None,
|
|
Packit Service |
4b33e2 |
savedsearch=None,
|
|
Packit Service |
4b33e2 |
savedsearch_sharer_id=None,
|
|
Packit Service |
4b33e2 |
sub_component=None,
|
|
Packit Service |
4b33e2 |
tags=None,
|
|
Packit Service |
4b33e2 |
exclude_fields=None,
|
|
Packit Service |
4b33e2 |
extra_fields=None):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Build a query string from passed arguments. Will handle
|
|
Packit Service |
4b33e2 |
query parameter differences between various bugzilla versions.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
Most of the parameters should be self explanatory. However
|
|
Packit Service |
4b33e2 |
if you want to perform a complex query, and easy way is to
|
|
Packit Service |
4b33e2 |
create it with the bugzilla web UI, copy the entire URL it
|
|
Packit Service |
4b33e2 |
generates, and pass it to the static method
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
Bugzilla.url_to_query
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
Then pass the output to Bugzilla.query()
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
For details about the specific argument formats, see the bugzilla docs:
|
|
Packit Service |
4b33e2 |
https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#search-bugs
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
if boolean_query or booleantype:
|
|
Packit Service |
4b33e2 |
raise RuntimeError("boolean_query format is no longer supported. "
|
|
Packit Service |
4b33e2 |
"If you need complicated URL queries, look into "
|
|
Packit Service |
4b33e2 |
"query --from-url/url_to_query().")
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
query = {
|
|
Packit Service |
4b33e2 |
"alias": alias,
|
|
Packit Service |
4b33e2 |
"product": self._listify(product),
|
|
Packit Service |
4b33e2 |
"component": self._listify(component),
|
|
Packit Service |
4b33e2 |
"version": version,
|
|
Packit Service |
4b33e2 |
"id": bug_id,
|
|
Packit Service |
4b33e2 |
"short_desc": short_desc,
|
|
Packit Service |
4b33e2 |
"bug_status": status,
|
|
Packit Service |
4b33e2 |
"bug_severity": bug_severity,
|
|
Packit Service |
4b33e2 |
"priority": priority,
|
|
Packit Service |
4b33e2 |
"target_release": target_release,
|
|
Packit Service |
4b33e2 |
"target_milestone": target_milestone,
|
|
Packit Service |
4b33e2 |
"tag": self._listify(tags),
|
|
Packit Service |
4b33e2 |
"quicksearch": quicksearch,
|
|
Packit Service |
4b33e2 |
"savedsearch": savedsearch,
|
|
Packit Service |
4b33e2 |
"sharer_id": savedsearch_sharer_id,
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# RH extensions... don't add any more. See comment below
|
|
Packit Service |
4b33e2 |
"sub_components": self._listify(sub_component),
|
|
Packit Service |
4b33e2 |
}
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def add_bool(bzkey, value, bool_id, booltype=None):
|
|
Packit Service |
4b33e2 |
value = self._listify(value)
|
|
Packit Service |
4b33e2 |
if value is None:
|
|
Packit Service |
4b33e2 |
return bool_id
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
query["query_format"] = "advanced"
|
|
Packit Service |
4b33e2 |
for boolval in value:
|
|
Packit Service |
4b33e2 |
def make_bool_str(prefix):
|
|
Packit Service |
4b33e2 |
# pylint: disable=cell-var-from-loop
|
|
Packit Service |
4b33e2 |
return "%s%i-0-0" % (prefix, bool_id)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
query[make_bool_str("field")] = bzkey
|
|
Packit Service |
4b33e2 |
query[make_bool_str("value")] = boolval
|
|
Packit Service |
4b33e2 |
query[make_bool_str("type")] = booltype or "substring"
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
bool_id += 1
|
|
Packit Service |
4b33e2 |
return bool_id
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# RH extensions that we have to maintain here for back compat,
|
|
Packit Service |
4b33e2 |
# but all future custom fields should be specified via
|
|
Packit Service |
4b33e2 |
# cli --field option, or via extending the query dict() manually.
|
|
Packit Service |
4b33e2 |
# No more supporting custom fields in this API
|
|
Packit Service |
4b33e2 |
bool_id = 0
|
|
Packit Service |
4b33e2 |
bool_id = add_bool("keywords", keywords, bool_id, keywords_type)
|
|
Packit Service |
4b33e2 |
bool_id = add_bool("blocked", blocked, bool_id)
|
|
Packit Service |
4b33e2 |
bool_id = add_bool("dependson", dependson, bool_id)
|
|
Packit Service |
4b33e2 |
bool_id = add_bool("bug_file_loc", url, bool_id, url_type)
|
|
Packit Service |
4b33e2 |
bool_id = add_bool("cf_fixed_in", fixed_in, bool_id, fixed_in_type)
|
|
Packit Service |
4b33e2 |
bool_id = add_bool("flagtypes.name", flag, bool_id)
|
|
Packit Service |
4b33e2 |
bool_id = add_bool("status_whiteboard",
|
|
Packit Service |
4b33e2 |
status_whiteboard, bool_id, status_whiteboard_type)
|
|
Packit Service |
4b33e2 |
bool_id = add_bool("cf_qa_whiteboard", qa_whiteboard, bool_id)
|
|
Packit Service |
4b33e2 |
bool_id = add_bool("cf_devel_whiteboard", devel_whiteboard, bool_id)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def add_email(key, value, count):
|
|
Packit Service |
4b33e2 |
if value is None:
|
|
Packit Service |
4b33e2 |
return count
|
|
Packit Service |
4b33e2 |
if not emailtype:
|
|
Packit Service |
4b33e2 |
query[key] = value
|
|
Packit Service |
4b33e2 |
return count
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
query["query_format"] = "advanced"
|
|
Packit Service |
4b33e2 |
query['email%i' % count] = value
|
|
Packit Service |
4b33e2 |
query['email%s%i' % (key, count)] = True
|
|
Packit Service |
4b33e2 |
query['emailtype%i' % count] = emailtype
|
|
Packit Service |
4b33e2 |
return count + 1
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
email_count = 1
|
|
Packit Service |
4b33e2 |
email_count = add_email("cc", cc, email_count)
|
|
Packit Service |
4b33e2 |
email_count = add_email("assigned_to", assigned_to, email_count)
|
|
Packit Service |
4b33e2 |
email_count = add_email("reporter", reporter, email_count)
|
|
Packit Service |
4b33e2 |
email_count = add_email("qa_contact", qa_contact, email_count)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if long_desc is not None:
|
|
Packit Service |
4b33e2 |
query["query_format"] = "advanced"
|
|
Packit Service |
4b33e2 |
query["longdesc"] = long_desc
|
|
Packit Service |
4b33e2 |
query["longdesc_type"] = "allwordssubstr"
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# 'include_fields' only available for Bugzilla4+
|
|
Packit Service |
4b33e2 |
# 'extra_fields' is an RHBZ extension
|
|
Packit Service |
4b33e2 |
query.update(self._process_include_fields(
|
|
Packit Service |
4b33e2 |
include_fields, exclude_fields, extra_fields))
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# Strip out None elements in the dict
|
|
Packit Service |
4b33e2 |
for k, v in query.copy().items():
|
|
Packit Service |
4b33e2 |
if v is None:
|
|
Packit Service |
4b33e2 |
del(query[k])
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
self.pre_translation(query)
|
|
Packit Service |
4b33e2 |
return query
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def query(self, query):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Query bugzilla and return a list of matching bugs.
|
|
Packit Service |
4b33e2 |
query must be a dict with fields like those in in querydata['fields'].
|
|
Packit Service |
4b33e2 |
Returns a list of Bug objects.
|
|
Packit Service |
4b33e2 |
Also see the _query() method for details about the underlying
|
|
Packit Service |
4b33e2 |
implementation.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
try:
|
|
Packit Service |
4b33e2 |
r = self._proxy.Bug.search(query)
|
|
Packit Service |
4b33e2 |
except Fault as e:
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# Try to give a hint in the error message if url_to_query
|
|
Packit Service |
4b33e2 |
# isn't supported by this bugzilla instance
|
|
Packit Service |
4b33e2 |
if ("query_format" not in str(e) or
|
|
Packit Service |
4b33e2 |
"RHBugzilla" in str(e.__class__) or
|
|
Packit Service |
4b33e2 |
self._check_version(5, 0)):
|
|
Packit Service |
4b33e2 |
raise
|
|
Packit Service |
4b33e2 |
raise BugzillaError("%s\nYour bugzilla instance does not "
|
|
Packit Service |
4b33e2 |
"appear to support API queries derived from bugzilla "
|
|
Packit Service |
4b33e2 |
"web URL queries." % e)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
log.debug("Query returned %s bugs", len(r['bugs']))
|
|
Packit Service |
4b33e2 |
return [Bug(self, dict=b,
|
|
Packit Service |
4b33e2 |
autorefresh=self.bug_autorefresh) for b in r['bugs']]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def pre_translation(self, query):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
In order to keep the API the same, Bugzilla4 needs to process the
|
|
Packit Service |
4b33e2 |
query and the result. This also applies to the refresh() function
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
pass
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def post_translation(self, query, bug):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
In order to keep the API the same, Bugzilla4 needs to process the
|
|
Packit Service |
4b33e2 |
query and the result. This also applies to the refresh() function
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
pass
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def bugs_history_raw(self, bug_ids):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Experimental. Gets the history of changes for
|
|
Packit Service |
4b33e2 |
particular bugs in the database.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
return self._proxy.Bug.history({'ids': bug_ids})
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
#######################################
|
|
Packit Service |
4b33e2 |
# Methods for modifying existing bugs #
|
|
Packit Service |
4b33e2 |
#######################################
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# Bug() also has individual methods for many ops, like setassignee()
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def update_bugs(self, ids, updates):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
A thin wrapper around bugzilla Bug.update(). Used to update all
|
|
Packit Service |
4b33e2 |
values of an existing bug report, as well as add comments.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
The dictionary passed to this function should be generated with
|
|
Packit Service |
4b33e2 |
build_update(), otherwise we cannot guarantee back compatibility.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
tmp = updates.copy()
|
|
Packit Service |
4b33e2 |
tmp["ids"] = self._listify(ids)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
return self._proxy.Bug.update(tmp)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def update_tags(self, idlist, tags_add=None, tags_remove=None):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Updates the 'tags' field for a bug.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
tags = {}
|
|
Packit Service |
4b33e2 |
if tags_add:
|
|
Packit Service |
4b33e2 |
tags["add"] = self._listify(tags_add)
|
|
Packit Service |
4b33e2 |
if tags_remove:
|
|
Packit Service |
4b33e2 |
tags["remove"] = self._listify(tags_remove)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
d = {
|
|
Packit Service |
4b33e2 |
"ids": self._listify(idlist),
|
|
Packit Service |
4b33e2 |
"tags": tags,
|
|
Packit Service |
4b33e2 |
}
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
return self._proxy.Bug.update_tags(d)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def update_flags(self, idlist, flags):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
A thin back compat wrapper around build_update(flags=X)
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
return self.update_bugs(idlist, self.build_update(flags=flags))
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def build_update(self,
|
|
Packit Service |
4b33e2 |
alias=None,
|
|
Packit Service |
4b33e2 |
assigned_to=None,
|
|
Packit Service |
4b33e2 |
blocks_add=None,
|
|
Packit Service |
4b33e2 |
blocks_remove=None,
|
|
Packit Service |
4b33e2 |
blocks_set=None,
|
|
Packit Service |
4b33e2 |
depends_on_add=None,
|
|
Packit Service |
4b33e2 |
depends_on_remove=None,
|
|
Packit Service |
4b33e2 |
depends_on_set=None,
|
|
Packit Service |
4b33e2 |
cc_add=None,
|
|
Packit Service |
4b33e2 |
cc_remove=None,
|
|
Packit Service |
4b33e2 |
is_cc_accessible=None,
|
|
Packit Service |
4b33e2 |
comment=None,
|
|
Packit Service |
4b33e2 |
comment_private=None,
|
|
Packit Service |
4b33e2 |
component=None,
|
|
Packit Service |
4b33e2 |
deadline=None,
|
|
Packit Service |
4b33e2 |
dupe_of=None,
|
|
Packit Service |
4b33e2 |
estimated_time=None,
|
|
Packit Service |
4b33e2 |
groups_add=None,
|
|
Packit Service |
4b33e2 |
groups_remove=None,
|
|
Packit Service |
4b33e2 |
keywords_add=None,
|
|
Packit Service |
4b33e2 |
keywords_remove=None,
|
|
Packit Service |
4b33e2 |
keywords_set=None,
|
|
Packit Service |
4b33e2 |
op_sys=None,
|
|
Packit Service |
4b33e2 |
platform=None,
|
|
Packit Service |
4b33e2 |
priority=None,
|
|
Packit Service |
4b33e2 |
product=None,
|
|
Packit Service |
4b33e2 |
qa_contact=None,
|
|
Packit Service |
4b33e2 |
is_creator_accessible=None,
|
|
Packit Service |
4b33e2 |
remaining_time=None,
|
|
Packit Service |
4b33e2 |
reset_assigned_to=None,
|
|
Packit Service |
4b33e2 |
reset_qa_contact=None,
|
|
Packit Service |
4b33e2 |
resolution=None,
|
|
Packit Service |
4b33e2 |
see_also_add=None,
|
|
Packit Service |
4b33e2 |
see_also_remove=None,
|
|
Packit Service |
4b33e2 |
severity=None,
|
|
Packit Service |
4b33e2 |
status=None,
|
|
Packit Service |
4b33e2 |
summary=None,
|
|
Packit Service |
4b33e2 |
target_milestone=None,
|
|
Packit Service |
4b33e2 |
target_release=None,
|
|
Packit Service |
4b33e2 |
url=None,
|
|
Packit Service |
4b33e2 |
version=None,
|
|
Packit Service |
4b33e2 |
whiteboard=None,
|
|
Packit Service |
4b33e2 |
work_time=None,
|
|
Packit Service |
4b33e2 |
fixed_in=None,
|
|
Packit Service |
4b33e2 |
qa_whiteboard=None,
|
|
Packit Service |
4b33e2 |
devel_whiteboard=None,
|
|
Packit Service |
4b33e2 |
internal_whiteboard=None,
|
|
Packit Service |
4b33e2 |
sub_component=None,
|
|
Packit Service |
4b33e2 |
flags=None,
|
|
Packit Service |
4b33e2 |
comment_tags=None):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Returns a python dict() with properly formatted parameters to
|
|
Packit Service |
4b33e2 |
pass to update_bugs(). See bugzilla documentation for the format
|
|
Packit Service |
4b33e2 |
of the individual fields:
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#create-bug
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
ret = {}
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# These are only supported for rhbugzilla
|
|
Packit Service |
4b33e2 |
for key, val in [
|
|
Packit Service |
4b33e2 |
("fixed_in", fixed_in),
|
|
Packit Service |
4b33e2 |
("devel_whiteboard", devel_whiteboard),
|
|
Packit Service |
4b33e2 |
("qa_whiteboard", qa_whiteboard),
|
|
Packit Service |
4b33e2 |
("internal_whiteboard", internal_whiteboard),
|
|
Packit Service |
4b33e2 |
("sub_component", sub_component),
|
|
Packit Service |
4b33e2 |
]:
|
|
Packit Service |
4b33e2 |
if val is not None:
|
|
Packit Service |
4b33e2 |
raise ValueError("bugzilla instance does not support "
|
|
Packit Service |
4b33e2 |
"updating '%s'" % key)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def s(key, val, convert=None):
|
|
Packit Service |
4b33e2 |
if val is None:
|
|
Packit Service |
4b33e2 |
return
|
|
Packit Service |
4b33e2 |
if convert:
|
|
Packit Service |
4b33e2 |
val = convert(val)
|
|
Packit Service |
4b33e2 |
ret[key] = val
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def add_dict(key, add, remove, _set=None, convert=None):
|
|
Packit Service |
4b33e2 |
if add is remove is _set is None:
|
|
Packit Service |
4b33e2 |
return
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def c(val):
|
|
Packit Service |
4b33e2 |
val = self._listify(val)
|
|
Packit Service |
4b33e2 |
if convert:
|
|
Packit Service |
4b33e2 |
val = [convert(v) for v in val]
|
|
Packit Service |
4b33e2 |
return val
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
newdict = {}
|
|
Packit Service |
4b33e2 |
if add is not None:
|
|
Packit Service |
4b33e2 |
newdict["add"] = c(add)
|
|
Packit Service |
4b33e2 |
if remove is not None:
|
|
Packit Service |
4b33e2 |
newdict["remove"] = c(remove)
|
|
Packit Service |
4b33e2 |
if _set is not None:
|
|
Packit Service |
4b33e2 |
newdict["set"] = c(_set)
|
|
Packit Service |
4b33e2 |
ret[key] = newdict
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
s("alias", alias)
|
|
Packit Service |
4b33e2 |
s("assigned_to", assigned_to)
|
|
Packit Service |
4b33e2 |
s("is_cc_accessible", is_cc_accessible, bool)
|
|
Packit Service |
4b33e2 |
s("component", component)
|
|
Packit Service |
4b33e2 |
s("deadline", deadline)
|
|
Packit Service |
4b33e2 |
s("dupe_of", dupe_of, int)
|
|
Packit Service |
4b33e2 |
s("estimated_time", estimated_time, int)
|
|
Packit Service |
4b33e2 |
s("op_sys", op_sys)
|
|
Packit Service |
4b33e2 |
s("platform", platform)
|
|
Packit Service |
4b33e2 |
s("priority", priority)
|
|
Packit Service |
4b33e2 |
s("product", product)
|
|
Packit Service |
4b33e2 |
s("qa_contact", qa_contact)
|
|
Packit Service |
4b33e2 |
s("is_creator_accessible", is_creator_accessible, bool)
|
|
Packit Service |
4b33e2 |
s("remaining_time", remaining_time, float)
|
|
Packit Service |
4b33e2 |
s("reset_assigned_to", reset_assigned_to, bool)
|
|
Packit Service |
4b33e2 |
s("reset_qa_contact", reset_qa_contact, bool)
|
|
Packit Service |
4b33e2 |
s("resolution", resolution)
|
|
Packit Service |
4b33e2 |
s("severity", severity)
|
|
Packit Service |
4b33e2 |
s("status", status)
|
|
Packit Service |
4b33e2 |
s("summary", summary)
|
|
Packit Service |
4b33e2 |
s("target_milestone", target_milestone)
|
|
Packit Service |
4b33e2 |
s("target_release", target_release)
|
|
Packit Service |
4b33e2 |
s("url", url)
|
|
Packit Service |
4b33e2 |
s("version", version)
|
|
Packit Service |
4b33e2 |
s("whiteboard", whiteboard)
|
|
Packit Service |
4b33e2 |
s("work_time", work_time, float)
|
|
Packit Service |
4b33e2 |
s("flags", flags)
|
|
Packit Service |
4b33e2 |
s("comment_tags", comment_tags, self._listify)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
add_dict("blocks", blocks_add, blocks_remove, blocks_set,
|
|
Packit Service |
4b33e2 |
convert=int)
|
|
Packit Service |
4b33e2 |
add_dict("depends_on", depends_on_add, depends_on_remove,
|
|
Packit Service |
4b33e2 |
depends_on_set, convert=int)
|
|
Packit Service |
4b33e2 |
add_dict("cc", cc_add, cc_remove)
|
|
Packit Service |
4b33e2 |
add_dict("groups", groups_add, groups_remove)
|
|
Packit Service |
4b33e2 |
add_dict("keywords", keywords_add, keywords_remove, keywords_set)
|
|
Packit Service |
4b33e2 |
add_dict("see_also", see_also_add, see_also_remove)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if comment is not None:
|
|
Packit Service |
4b33e2 |
ret["comment"] = {"comment": comment}
|
|
Packit Service |
4b33e2 |
if comment_private:
|
|
Packit Service |
4b33e2 |
ret["comment"]["is_private"] = comment_private
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
return ret
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
########################################
|
|
Packit Service |
4b33e2 |
# Methods for working with attachments #
|
|
Packit Service |
4b33e2 |
########################################
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _attachment_uri(self, attachid):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Returns the URI for the given attachment ID.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
att_uri = self.url.replace('xmlrpc.cgi', 'attachment.cgi')
|
|
Packit Service |
4b33e2 |
att_uri = att_uri + '?id=%s' % attachid
|
|
Packit Service |
4b33e2 |
return att_uri
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def attachfile(self, idlist, attachfile, description, **kwargs):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Attach a file to the given bug IDs. Returns the ID of the attachment
|
|
Packit Service |
4b33e2 |
or raises XMLRPC Fault if something goes wrong.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
attachfile may be a filename (which will be opened) or a file-like
|
|
Packit Service |
4b33e2 |
object, which must provide a 'read' method. If it's not one of these,
|
|
Packit Service |
4b33e2 |
this method will raise a TypeError.
|
|
Packit Service |
4b33e2 |
description is the short description of this attachment.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
Optional keyword args are as follows:
|
|
Packit Service |
4b33e2 |
file_name: this will be used as the filename for the attachment.
|
|
Packit Service |
4b33e2 |
REQUIRED if attachfile is a file-like object with no
|
|
Packit Service |
4b33e2 |
'name' attribute, otherwise the filename or .name
|
|
Packit Service |
4b33e2 |
attribute will be used.
|
|
Packit Service |
4b33e2 |
comment: An optional comment about this attachment.
|
|
Packit Service |
4b33e2 |
is_private: Set to True if the attachment should be marked private.
|
|
Packit Service |
4b33e2 |
is_patch: Set to True if the attachment is a patch.
|
|
Packit Service |
4b33e2 |
content_type: The mime-type of the attached file. Defaults to
|
|
Packit Service |
4b33e2 |
application/octet-stream if not set. NOTE that text
|
|
Packit Service |
4b33e2 |
files will *not* be viewable in bugzilla unless you
|
|
Packit Service |
4b33e2 |
remember to set this to text/plain. So remember that!
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
Returns the list of attachment ids that were added. If only one
|
|
Packit Service |
4b33e2 |
attachment was added, we return the single int ID for back compat
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
if isinstance(attachfile, str):
|
|
Packit Service |
4b33e2 |
f = open(attachfile, "rb")
|
|
Packit Service |
4b33e2 |
elif hasattr(attachfile, 'read'):
|
|
Packit Service |
4b33e2 |
f = attachfile
|
|
Packit Service |
4b33e2 |
else:
|
|
Packit Service |
4b33e2 |
raise TypeError("attachfile must be filename or file-like object")
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# Back compat
|
|
Packit Service |
4b33e2 |
if "contenttype" in kwargs:
|
|
Packit Service |
4b33e2 |
kwargs["content_type"] = kwargs.pop("contenttype")
|
|
Packit Service |
4b33e2 |
if "ispatch" in kwargs:
|
|
Packit Service |
4b33e2 |
kwargs["is_patch"] = kwargs.pop("ispatch")
|
|
Packit Service |
4b33e2 |
if "isprivate" in kwargs:
|
|
Packit Service |
4b33e2 |
kwargs["is_private"] = kwargs.pop("isprivate")
|
|
Packit Service |
4b33e2 |
if "filename" in kwargs:
|
|
Packit Service |
4b33e2 |
kwargs["file_name"] = kwargs.pop("filename")
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
kwargs['summary'] = description
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
data = f.read()
|
|
Packit Service |
4b33e2 |
if not isinstance(data, bytes):
|
|
Packit Service |
4b33e2 |
data = data.encode(locale.getpreferredencoding())
|
|
Packit Service |
4b33e2 |
kwargs['data'] = Binary(data)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
kwargs['ids'] = self._listify(idlist)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if 'file_name' not in kwargs and hasattr(f, "name"):
|
|
Packit Service |
4b33e2 |
kwargs['file_name'] = os.path.basename(f.name)
|
|
Packit Service |
4b33e2 |
if 'content_type' not in kwargs:
|
|
Packit Service |
4b33e2 |
ctype = None
|
|
Packit Service |
4b33e2 |
if kwargs['file_name']:
|
|
Packit Service |
4b33e2 |
ctype = mimetypes.guess_type(
|
|
Packit Service |
4b33e2 |
kwargs['file_name'], strict=False)[0]
|
|
Packit Service |
4b33e2 |
kwargs['content_type'] = ctype or 'application/octet-stream'
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
ret = self._proxy.Bug.add_attachment(kwargs)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if "attachments" in ret:
|
|
Packit Service |
4b33e2 |
# Up to BZ 4.2
|
|
Packit Service |
4b33e2 |
ret = [int(k) for k in ret["attachments"].keys()]
|
|
Packit Service |
4b33e2 |
elif "ids" in ret:
|
|
Packit Service |
4b33e2 |
# BZ 4.4+
|
|
Packit Service |
4b33e2 |
ret = ret["ids"]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
if isinstance(ret, list) and len(ret) == 1:
|
|
Packit Service |
4b33e2 |
ret = ret[0]
|
|
Packit Service |
4b33e2 |
return ret
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def openattachment(self, attachid):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Get the contents of the attachment with the given attachment ID.
|
|
Packit Service |
4b33e2 |
Returns a file-like object.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
attachments = self.get_attachments(None, attachid)
|
|
Packit Service |
4b33e2 |
data = attachments["attachments"][str(attachid)]
|
|
Packit Service |
4b33e2 |
xmlrpcbinary = data["data"]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
ret = BytesIO()
|
|
Packit Service |
4b33e2 |
ret.write(xmlrpcbinary.data)
|
|
Packit Service |
4b33e2 |
ret.name = data["file_name"]
|
|
Packit Service |
4b33e2 |
ret.seek(0)
|
|
Packit Service |
4b33e2 |
return ret
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def updateattachmentflags(self, bugid, attachid, flagname, **kwargs):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Updates a flag for the given attachment ID.
|
|
Packit Service |
4b33e2 |
Optional keyword args are:
|
|
Packit Service |
4b33e2 |
status: new status for the flag ('-', '+', '?', 'X')
|
|
Packit Service |
4b33e2 |
requestee: new requestee for the flag
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
# Bug ID was used for the original custom redhat API, no longer
|
|
Packit Service |
4b33e2 |
# needed though
|
|
Packit Service |
4b33e2 |
ignore = bugid
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
flags = {"name": flagname}
|
|
Packit Service |
4b33e2 |
flags.update(kwargs)
|
|
Packit Service |
4b33e2 |
update = {'ids': [int(attachid)], 'flags': [flags]}
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
return self._proxy.Bug.update_attachment(update)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def get_attachments(self, ids, attachment_ids,
|
|
Packit Service |
4b33e2 |
include_fields=None, exclude_fields=None):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Wrapper for Bug.attachments. One of ids or attachment_ids is required
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
:param ids: Get attachments for this bug ID
|
|
Packit Service |
4b33e2 |
:param attachment_ids: Specific attachment ID to get
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
https://bugzilla.readthedocs.io/en/latest/api/core/v1/attachment.html#get-attachment
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
params = {
|
|
Packit Service |
4b33e2 |
"ids": self._listify(ids) or [],
|
|
Packit Service |
4b33e2 |
"attachment_ids": self._listify(attachment_ids) or [],
|
|
Packit Service |
4b33e2 |
}
|
|
Packit Service |
4b33e2 |
if include_fields:
|
|
Packit Service |
4b33e2 |
params["include_fields"] = self._listify(include_fields)
|
|
Packit Service |
4b33e2 |
if exclude_fields:
|
|
Packit Service |
4b33e2 |
params["exclude_fields"] = self._listify(exclude_fields)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
return self._proxy.Bug.attachments(params)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
#####################
|
|
Packit Service |
4b33e2 |
# createbug methods #
|
|
Packit Service |
4b33e2 |
#####################
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
createbug_required = ('product', 'component', 'summary', 'version',
|
|
Packit Service |
4b33e2 |
'description')
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def build_createbug(self,
|
|
Packit Service |
4b33e2 |
product=None,
|
|
Packit Service |
4b33e2 |
component=None,
|
|
Packit Service |
4b33e2 |
version=None,
|
|
Packit Service |
4b33e2 |
summary=None,
|
|
Packit Service |
4b33e2 |
description=None,
|
|
Packit Service |
4b33e2 |
comment_private=None,
|
|
Packit Service |
4b33e2 |
blocks=None,
|
|
Packit Service |
4b33e2 |
cc=None,
|
|
Packit Service |
4b33e2 |
assigned_to=None,
|
|
Packit Service |
4b33e2 |
keywords=None,
|
|
Packit Service |
4b33e2 |
depends_on=None,
|
|
Packit Service |
4b33e2 |
groups=None,
|
|
Packit Service |
4b33e2 |
op_sys=None,
|
|
Packit Service |
4b33e2 |
platform=None,
|
|
Packit Service |
4b33e2 |
priority=None,
|
|
Packit Service |
4b33e2 |
qa_contact=None,
|
|
Packit Service |
4b33e2 |
resolution=None,
|
|
Packit Service |
4b33e2 |
severity=None,
|
|
Packit Service |
4b33e2 |
status=None,
|
|
Packit Service |
4b33e2 |
target_milestone=None,
|
|
Packit Service |
4b33e2 |
target_release=None,
|
|
Packit Service |
4b33e2 |
url=None,
|
|
Packit Service |
4b33e2 |
sub_component=None,
|
|
Packit Service |
4b33e2 |
alias=None,
|
|
Packit Service |
4b33e2 |
comment_tags=None):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Returns a python dict() with properly formatted parameters to
|
|
Packit Service |
4b33e2 |
pass to createbug(). See bugzilla documentation for the format
|
|
Packit Service |
4b33e2 |
of the individual fields:
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#update-bug
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
localdict = {}
|
|
Packit Service |
4b33e2 |
if blocks:
|
|
Packit Service |
4b33e2 |
localdict["blocks"] = self._listify(blocks)
|
|
Packit Service |
4b33e2 |
if cc:
|
|
Packit Service |
4b33e2 |
localdict["cc"] = self._listify(cc)
|
|
Packit Service |
4b33e2 |
if depends_on:
|
|
Packit Service |
4b33e2 |
localdict["depends_on"] = self._listify(depends_on)
|
|
Packit Service |
4b33e2 |
if groups:
|
|
Packit Service |
4b33e2 |
localdict["groups"] = self._listify(groups)
|
|
Packit Service |
4b33e2 |
if keywords:
|
|
Packit Service |
4b33e2 |
localdict["keywords"] = self._listify(keywords)
|
|
Packit Service |
4b33e2 |
if description:
|
|
Packit Service |
4b33e2 |
localdict["description"] = description
|
|
Packit Service |
4b33e2 |
if comment_private:
|
|
Packit Service |
4b33e2 |
localdict["comment_is_private"] = True
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# Most of the machinery and formatting here is the same as
|
|
Packit Service |
4b33e2 |
# build_update, so reuse that as much as possible
|
|
Packit Service |
4b33e2 |
ret = self.build_update(product=product, component=component,
|
|
Packit Service |
4b33e2 |
version=version, summary=summary, op_sys=op_sys,
|
|
Packit Service |
4b33e2 |
platform=platform, priority=priority, qa_contact=qa_contact,
|
|
Packit Service |
4b33e2 |
resolution=resolution, severity=severity, status=status,
|
|
Packit Service |
4b33e2 |
target_milestone=target_milestone,
|
|
Packit Service |
4b33e2 |
target_release=target_release, url=url,
|
|
Packit Service |
4b33e2 |
assigned_to=assigned_to, sub_component=sub_component,
|
|
Packit Service |
4b33e2 |
alias=alias, comment_tags=comment_tags)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
ret.update(localdict)
|
|
Packit Service |
4b33e2 |
return ret
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _validate_createbug(self, *args, **kwargs):
|
|
Packit Service |
4b33e2 |
# Previous API required users specifying keyword args that mapped
|
|
Packit Service |
4b33e2 |
# to the XMLRPC arg names. Maintain that bad compat, but also allow
|
|
Packit Service |
4b33e2 |
# receiving a single dictionary like query() does
|
|
Packit Service |
4b33e2 |
if kwargs and args:
|
|
Packit Service |
4b33e2 |
raise BugzillaError("createbug: cannot specify positional "
|
|
Packit Service |
4b33e2 |
"args=%s with kwargs=%s, must be one or the "
|
|
Packit Service |
4b33e2 |
"other." % (args, kwargs))
|
|
Packit Service |
4b33e2 |
if args:
|
|
Packit Service |
4b33e2 |
if len(args) > 1 or not isinstance(args[0], dict):
|
|
Packit Service |
4b33e2 |
raise BugzillaError("createbug: positional arguments only "
|
|
Packit Service |
4b33e2 |
"accept a single dictionary.")
|
|
Packit Service |
4b33e2 |
data = args[0]
|
|
Packit Service |
4b33e2 |
else:
|
|
Packit Service |
4b33e2 |
data = kwargs
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# If we're getting a call that uses an old fieldname, convert it to the
|
|
Packit Service |
4b33e2 |
# new fieldname instead.
|
|
Packit Service |
4b33e2 |
for newname, oldname in self._get_api_aliases():
|
|
Packit Service |
4b33e2 |
if (newname in self.createbug_required and
|
|
Packit Service |
4b33e2 |
newname not in data and
|
|
Packit Service |
4b33e2 |
oldname in data):
|
|
Packit Service |
4b33e2 |
data[newname] = data.pop(oldname)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# Back compat handling for check_args
|
|
Packit Service |
4b33e2 |
if "check_args" in data:
|
|
Packit Service |
4b33e2 |
del(data["check_args"])
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
return data
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def createbug(self, *args, **kwargs):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Create a bug with the given info. Returns a new Bug object.
|
|
Packit Service |
4b33e2 |
Check bugzilla API documentation for valid values, at least
|
|
Packit Service |
4b33e2 |
product, component, summary, version, and description need to
|
|
Packit Service |
4b33e2 |
be passed.
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
data = self._validate_createbug(*args, **kwargs)
|
|
Packit Service |
4b33e2 |
rawbug = self._proxy.Bug.create(data)
|
|
Packit Service |
4b33e2 |
return Bug(self, bug_id=rawbug["id"],
|
|
Packit Service |
4b33e2 |
autorefresh=self.bug_autorefresh)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
##############################
|
|
Packit Service |
4b33e2 |
# Methods for handling Users #
|
|
Packit Service |
4b33e2 |
##############################
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def _getusers(self, ids=None, names=None, match=None):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Return a list of users that match criteria.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
:kwarg ids: list of user ids to return data on
|
|
Packit Service |
4b33e2 |
:kwarg names: list of user names to return data on
|
|
Packit Service |
4b33e2 |
:kwarg match: list of patterns. Returns users whose real name or
|
|
Packit Service |
4b33e2 |
login name match the pattern.
|
|
Packit Service |
4b33e2 |
:raises XMLRPC Fault: Code 51: if a Bad Login Name was sent to the
|
|
Packit Service |
4b33e2 |
names array.
|
|
Packit Service |
4b33e2 |
Code 304: if the user was not authorized to see user they
|
|
Packit Service |
4b33e2 |
requested.
|
|
Packit Service |
4b33e2 |
Code 505: user is logged out and can't use the match or ids
|
|
Packit Service |
4b33e2 |
parameter.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
Available in Bugzilla-3.4+
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
params = {}
|
|
Packit Service |
4b33e2 |
if ids:
|
|
Packit Service |
4b33e2 |
params['ids'] = self._listify(ids)
|
|
Packit Service |
4b33e2 |
if names:
|
|
Packit Service |
4b33e2 |
params['names'] = self._listify(names)
|
|
Packit Service |
4b33e2 |
if match:
|
|
Packit Service |
4b33e2 |
params['match'] = self._listify(match)
|
|
Packit Service |
4b33e2 |
if not params:
|
|
Packit Service |
4b33e2 |
raise BugzillaError('_get() needs one of ids, '
|
|
Packit Service |
4b33e2 |
' names, or match kwarg.')
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
return self._proxy.User.get(params)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def getuser(self, username):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Return a bugzilla User for the given username
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
:arg username: The username used in bugzilla.
|
|
Packit Service |
4b33e2 |
:raises XMLRPC Fault: Code 51 if the username does not exist
|
|
Packit Service |
4b33e2 |
:returns: User record for the username
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
ret = self.getusers(username)
|
|
Packit Service |
4b33e2 |
return ret and ret[0]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def getusers(self, userlist):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Return a list of Users from .
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
:userlist: List of usernames to lookup
|
|
Packit Service |
4b33e2 |
:returns: List of User records
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
userobjs = [User(self, **rawuser) for rawuser in
|
|
Packit Service |
4b33e2 |
self._getusers(names=userlist).get('users', [])]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
# Return users in same order they were passed in
|
|
Packit Service |
4b33e2 |
ret = []
|
|
Packit Service |
4b33e2 |
for u in userlist:
|
|
Packit Service |
4b33e2 |
for uobj in userobjs[:]:
|
|
Packit Service |
4b33e2 |
if uobj.email == u:
|
|
Packit Service |
4b33e2 |
userobjs.remove(uobj)
|
|
Packit Service |
4b33e2 |
ret.append(uobj)
|
|
Packit Service |
4b33e2 |
break
|
|
Packit Service |
4b33e2 |
ret += userobjs
|
|
Packit Service |
4b33e2 |
return ret
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def searchusers(self, pattern):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Return a bugzilla User for the given list of patterns
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
:arg pattern: List of patterns to match against.
|
|
Packit Service |
4b33e2 |
:returns: List of User records
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
return [User(self, **rawuser) for rawuser in
|
|
Packit Service |
4b33e2 |
self._getusers(match=pattern).get('users', [])]
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def createuser(self, email, name='', password=''):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
Return a bugzilla User for the given username
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
:arg email: The email address to use in bugzilla
|
|
Packit Service |
4b33e2 |
:kwarg name: Real name to associate with the account
|
|
Packit Service |
4b33e2 |
:kwarg password: Password to set for the bugzilla account
|
|
Packit Service |
4b33e2 |
:raises XMLRPC Fault: Code 501 if the username already exists
|
|
Packit Service |
4b33e2 |
Code 500 if the email address isn't valid
|
|
Packit Service |
4b33e2 |
Code 502 if the password is too short
|
|
Packit Service |
4b33e2 |
Code 503 if the password is too long
|
|
Packit Service |
4b33e2 |
:return: User record for the username
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
self._proxy.User.create(email, name, password)
|
|
Packit Service |
4b33e2 |
return self.getuser(email)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
def updateperms(self, user, action, groups):
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
A method to update the permissions (group membership) of a bugzilla
|
|
Packit Service |
4b33e2 |
user.
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
:arg user: The e-mail address of the user to be acted upon. Can
|
|
Packit Service |
4b33e2 |
also be a list of emails.
|
|
Packit Service |
4b33e2 |
:arg action: add, remove, or set
|
|
Packit Service |
4b33e2 |
:arg groups: list of groups to be added to (i.e. ['fedora_contrib'])
|
|
Packit Service |
4b33e2 |
"""
|
|
Packit Service |
4b33e2 |
groups = self._listify(groups)
|
|
Packit Service |
4b33e2 |
if action == "rem":
|
|
Packit Service |
4b33e2 |
action = "remove"
|
|
Packit Service |
4b33e2 |
if action not in ["add", "remove", "set"]:
|
|
Packit Service |
4b33e2 |
raise BugzillaError("Unknown user permission action '%s'" % action)
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
update = {
|
|
Packit Service |
4b33e2 |
"names": self._listify(user),
|
|
Packit Service |
4b33e2 |
"groups": {
|
|
Packit Service |
4b33e2 |
action: groups,
|
|
Packit Service |
4b33e2 |
}
|
|
Packit Service |
4b33e2 |
}
|
|
Packit Service |
4b33e2 |
|
|
Packit Service |
4b33e2 |
return self._proxy.User.update(update)
|