[Libreoffice-commits] dev-tools.git: 2 commits - ciabot/bugzilla ciabot/INSTALL ciabot/README lionss/tpl
Guilhem Moulin (via logerrit)
logerrit at kemper.freedesktop.org
Sat Feb 22 04:15:58 UTC 2020
ciabot/INSTALL | 4
ciabot/README | 4
ciabot/bugzilla/__init__.py | 133 ---
ciabot/bugzilla/base.py | 1758 ------------------------------------------
ciabot/bugzilla/bug.py | 517 ------------
ciabot/bugzilla/bugzilla3.py | 34
ciabot/bugzilla/bugzilla4.py | 47 -
ciabot/bugzilla/rhbugzilla.py | 368 --------
lionss/tpl/footer.html | 2
9 files changed, 5 insertions(+), 2862 deletions(-)
New commits:
commit 1b8d02e74b2b391c7a6adea7150134f815f1b147
Author: Guilhem Moulin <guilhem at libreoffice.org>
AuthorDate: Sat Feb 22 05:14:28 2020 +0100
Commit: Guilhem Moulin <guilhem at libreoffice.org>
CommitDate: Sat Feb 22 05:14:58 2020 +0100
Upgrade irc:// URIs to ircs://
diff --git a/ciabot/README b/ciabot/README
index 6124f53..4ea4e1b 100644
--- a/ciabot/README
+++ b/ciabot/README
@@ -38,8 +38,8 @@ How to test the Bugzilla integration:
- Edit file projmap.json to change the IRC channel
-e.g. "to": "irc://irc.freenode.net/libreoffice-dev" ->
- "to": "irc://irc.freenode.net/libreoffice-dev-test"
+e.g. "to": "ircs://irc.freenode.net/libreoffice-dev" ->
+ "to": "ircs://irc.freenode.net/libreoffice-dev-test"
- Edit config.cfg to change the url for the Bugzilla install
diff --git a/lionss/tpl/footer.html b/lionss/tpl/footer.html
index 134ff27..4dbb275 100644
--- a/lionss/tpl/footer.html
+++ b/lionss/tpl/footer.html
@@ -1,6 +1,6 @@
<div id="footer">
If you need help or want to discuss, do not hesitate to join
- <a href="irc://chat.freenode.net/libreoffice-dev">#libreoffice-dev on freenode IRC</a>
+ <a href="ircs://chat.freenode.net/libreoffice-dev">#libreoffice-dev on freenode IRC</a>
</div>
</body>
commit 8f5898d563bf6f638f6ad27f0951d1d598e10bec
Author: Guilhem Moulin <guilhem at libreoffice.org>
AuthorDate: Sat Feb 22 03:28:40 2020 +0100
Commit: Guilhem Moulin <guilhem at libreoffice.org>
CommitDate: Sat Feb 22 05:14:50 2020 +0100
ciabot: Remove python-bugzilla.
Let's just use the version from the distro instead. Debian Stretch has
1.2.2, Buster 2.2.0-1.
diff --git a/ciabot/INSTALL b/ciabot/INSTALL
index ddc2da3..3f36105 100644
--- a/ciabot/INSTALL
+++ b/ciabot/INSTALL
@@ -2,8 +2,8 @@ The following steps are needed to use this code:
Install some packages:
- Python
- * git module (ubuntu: python-git)
- * bugzilla module (?) (ubuntu: not in standard repos)
+ * git module (Debian: python-git)
+ * bugzilla module (Debian: python-bugzilla)
Configure the software:
- Copy config-example.cfg -> config.cfg and change the defaults
diff --git a/ciabot/bugzilla/__init__.py b/ciabot/bugzilla/__init__.py
deleted file mode 100644
index 382db52..0000000
--- a/ciabot/bugzilla/__init__.py
+++ /dev/null
@@ -1,133 +0,0 @@
-# python-bugzilla - a Python interface to bugzilla using xmlrpclib.
-#
-# Copyright (C) 2007, 2008 Red Hat Inc.
-# Author: Will Woods <wwoods at redhat.com>
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the
-# Free Software Foundation; either version 2 of the License, or (at your
-# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
-# the full text of the license.
-
-__version__ = "1.1.0"
-version = __version__
-
-import sys
-from logging import getLogger
-
-if hasattr(sys.version_info, "major") and sys.version_info.major >= 3:
- # pylint: disable=F0401
- from xmlrpc.client import Fault, ServerProxy
-else:
- from xmlrpclib import Fault, ServerProxy
-
-log = getLogger("bugzilla")
-
-
-from bugzilla.base import BugzillaBase as _BugzillaBase
-from bugzilla.base import BugzillaError
-from bugzilla.base import RequestsTransport as _RequestsTransport
-from bugzilla.bugzilla3 import Bugzilla3, Bugzilla32, Bugzilla34, Bugzilla36
-from bugzilla.bugzilla4 import Bugzilla4, Bugzilla42, Bugzilla44
-from bugzilla.rhbugzilla import RHBugzilla, RHBugzilla3, RHBugzilla4
-
-
-# Back compat for deleted NovellBugzilla
-class NovellBugzilla(Bugzilla34):
- pass
-
-
-def _getBugzillaClassForURL(url, sslverify):
- url = Bugzilla3.fix_url(url)
- log.debug("Detecting subclass for %s", url)
- s = ServerProxy(url, _RequestsTransport(url, sslverify=sslverify))
- rhbz = False
- bzversion = ''
- c = None
-
- if "bugzilla.redhat.com" in url:
- log.info("Using RHBugzilla for URL containing bugzilla.redhat.com")
- return RHBugzilla
- if "bugzilla.novell.com" in url:
- log.info("Using NovellBugzilla for URL containing novell.com")
- return NovellBugzilla
-
- # Check for a Red Hat extension
- try:
- log.debug("Checking for Red Hat Bugzilla extension")
- extensions = s.Bugzilla.extensions()
- if extensions.get('extensions', {}).get('RedHat', False):
- rhbz = True
- except Fault:
- pass
- log.debug("rhbz=%s", str(rhbz))
-
- # Try to get the bugzilla version string
- try:
- log.debug("Checking return value of Bugzilla.version()")
- r = s.Bugzilla.version()
- bzversion = r['version']
- except Fault:
- pass
- log.debug("bzversion='%s'", str(bzversion))
-
- # note preference order: RHBugzilla* wins if available
- if rhbz:
- c = RHBugzilla
- elif bzversion.startswith("4."):
- if bzversion.startswith("4.0"):
- c = Bugzilla4
- elif bzversion.startswith("4.2"):
- c = Bugzilla42
- else:
- log.debug("No explicit match for %s, using latest bz4", bzversion)
- c = Bugzilla44
- else:
- if bzversion.startswith('3.6'):
- c = Bugzilla36
- elif bzversion.startswith('3.4'):
- c = Bugzilla34
- elif bzversion.startswith('3.2'):
- c = Bugzilla32
- else:
- log.debug("No explicit match for %s, fall through", bzversion)
- c = Bugzilla3
-
- return c
-
-
-class Bugzilla(_BugzillaBase):
- '''
- Magical Bugzilla class that figures out which Bugzilla implementation
- to use and uses that.
- '''
- def _init_class_from_url(self, url, sslverify):
- if url is None:
- raise TypeError("You must pass a valid bugzilla URL")
-
- c = _getBugzillaClassForURL(url, sslverify)
- if not c:
- raise ValueError("Couldn't determine Bugzilla version for %s" %
- url)
-
- self.__class__ = c
- log.info("Chose subclass %s v%s", c.__name__, c.version)
- return True
-
-
-# This is the list of possible Bugzilla instances an app can use,
-# bin/bugzilla uses it for the --bztype field
-classlist = [
- "Bugzilla3", "Bugzilla32", "Bugzilla34", "Bugzilla36",
- "Bugzilla4", "Bugzilla42", "Bugzilla44",
- "RHBugzilla3", "RHBugzilla4", "RHBugzilla",
- "NovellBugzilla",
-]
-
-# This is the public API. If you are explicitly instantiating any other
-# class, using some function, or poking into internal files, don't complain
-# if things break on you.
-__all__ = classlist + [
- 'BugzillaError',
- 'Bugzilla',
-]
diff --git a/ciabot/bugzilla/base.py b/ciabot/bugzilla/base.py
deleted file mode 100644
index 81dffb4..0000000
--- a/ciabot/bugzilla/base.py
+++ /dev/null
@@ -1,1758 +0,0 @@
-# base.py - the base classes etc. for a Python interface to bugzilla
-#
-# Copyright (C) 2007, 2008, 2009, 2010 Red Hat Inc.
-# Author: Will Woods <wwoods at redhat.com>
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the
-# Free Software Foundation; either version 2 of the License, or (at your
-# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
-# the full text of the license.
-
-import locale
-import os
-import sys
-
-from io import BytesIO
-
-if hasattr(sys.version_info, "major") and sys.version_info.major >= 3:
- # pylint: disable=F0401,E0611
- from configparser import SafeConfigParser
- from http.cookiejar import LoadError, LWPCookieJar, MozillaCookieJar
- from urllib.parse import urlparse, parse_qsl
- from xmlrpc.client import (
- Binary, Fault, ProtocolError, ServerProxy, Transport)
-else:
- from ConfigParser import SafeConfigParser
- from cookielib import LoadError, LWPCookieJar, MozillaCookieJar
- from urlparse import urlparse, parse_qsl
- from xmlrpclib import (
- Binary, Fault, ProtocolError, ServerProxy, Transport)
-
-import requests
-
-from bugzilla import __version__, log
-from bugzilla.bug import _Bug, _User
-
-
-# Backwards compatibility
-Bug = _Bug
-
-mimemagic = None
-
-
-def _detect_filetype(fname):
- # pylint: disable=E1103
- # E1103: Instance of 'bool' has no '%s' member
- # pylint confuses mimemagic to be of type 'bool'
- global mimemagic
-
- if mimemagic is None:
- try:
- # pylint: disable=F0401
- # F0401: Unable to import 'magic' (import-error)
- import magic
- mimemagic = magic.open(getattr(magic, "MAGIC_MIME_TYPE", 16))
- mimemagic.load()
- except ImportError:
- e = sys.exc_info()[1]
- log.debug("Could not load python-magic: %s", e)
- mimemagic = False
- if mimemagic is False:
- return None
-
- if not os.path.isabs(fname):
- return None
-
- try:
- return mimemagic.file(fname)
- except Exception:
- e = sys.exc_info()[1]
- log.debug("Could not detect content_type: %s", e)
- return None
-
-
-def _build_cookiejar(cookiefile):
- cj = MozillaCookieJar(cookiefile)
- if cookiefile is None:
- return cj
- if not os.path.exists(cookiefile):
- # Make sure a new file has correct permissions
- open(cookiefile, 'a').close()
- os.chmod(cookiefile, 0o600)
- cj.save()
- return cj
-
- # We always want to use Mozilla cookies, but we previously accepted
- # LWP cookies. If we see the latter, convert it to former
- try:
- cj.load()
- return cj
- except LoadError:
- pass
-
- try:
- cj = LWPCookieJar(cookiefile)
- cj.load()
- except LoadError:
- raise BugzillaError("cookiefile=%s not in LWP or Mozilla format" %
- cookiefile)
-
- retcj = MozillaCookieJar(cookiefile)
- for cookie in cj:
- retcj.set_cookie(cookie)
- retcj.save()
- return retcj
-
-
-class _BugzillaToken(object):
- def __init__(self, uri, tokenfilename):
- self.tokenfilename = tokenfilename
- self.tokenfile = SafeConfigParser()
- self.domain = urlparse(uri)[1]
-
- if self.tokenfilename:
- self.tokenfile.read(self.tokenfilename)
-
- if self.domain not in self.tokenfile.sections():
- self.tokenfile.add_section(self.domain)
-
- @property
- def value(self):
- if self.tokenfile.has_option(self.domain, 'token'):
- return self.tokenfile.get(self.domain, 'token')
- else:
- return None
-
- @value.setter
- def value(self, value):
- if self.value == value:
- return
-
- if value is None:
- self.tokenfile.remove_option(self.domain, 'token')
- else:
- self.tokenfile.set(self.domain, 'token', value)
-
- if self.tokenfilename:
- with open(self.tokenfilename, 'w') as tokenfile:
- log.debug("Saving to tokenfile")
- self.tokenfile.write(tokenfile)
-
- def __repr__(self):
- return '<Bugzilla Token :: %s>' % (self.value)
-
-
-class _BugzillaServerProxy(ServerProxy):
- def __init__(self, uri, tokenfile, *args, **kwargs):
- # pylint: disable=super-init-not-called
- # No idea why pylint complains here, must be a bug
- ServerProxy.__init__(self, uri, *args, **kwargs)
- self.token = _BugzillaToken(uri, tokenfile)
-
- def clear_token(self):
- self.token.value = None
-
- def _ServerProxy__request(self, methodname, params):
- if self.token.value is not None:
- if len(params) == 0:
- params = ({}, )
-
- if 'Bugzilla_token' not in params[0]:
- params[0]['Bugzilla_token'] = self.token.value
-
- # pylint: disable=maybe-no-member
- ret = ServerProxy._ServerProxy__request(self, methodname, params)
- # pylint: enable=maybe-no-member
-
- if isinstance(ret, dict) and 'token' in ret.keys():
- self.token.value = ret.get('token')
- return ret
-
-
-class RequestsTransport(Transport):
- user_agent = 'Python/Bugzilla'
-
- def __init__(self, url, cookiejar=None,
- sslverify=True, sslcafile=None, debug=0):
- # pylint: disable=W0231
- # pylint does not handle multiple import of Transport well
- if hasattr(Transport, "__init__"):
- Transport.__init__(self, use_datetime=False)
-
- self.verbose = debug
- self._cookiejar = cookiejar
-
- # transport constructor needs full url too, as xmlrpc does not pass
- # scheme to request
- self.scheme = urlparse(url)[0]
- if self.scheme not in ["http", "https"]:
- raise Exception("Invalid URL scheme: %s (%s)" % (self.scheme, url))
-
- self.use_https = self.scheme == 'https'
-
- self.request_defaults = {
- 'cert': sslcafile if self.use_https else None,
- 'cookies': cookiejar,
- 'verify': sslverify,
- 'headers': {
- 'Content-Type': 'text/xml',
- 'User-Agent': self.user_agent,
- }
- }
-
- def parse_response(self, response):
- """ Parse XMLRPC response """
- parser, unmarshaller = self.getparser()
- parser.feed(response.text.encode('utf-8'))
- parser.close()
- return unmarshaller.close()
-
- def _request_helper(self, url, request_body):
- """
- A helper method to assist in making a request and provide a parsed
- response.
- """
- response = None
- try:
- response = requests.post(
- url, data=request_body, **self.request_defaults)
-
- # We expect utf-8 from the server
- response.encoding = 'UTF-8'
-
- # update/set any cookies
- if self._cookiejar is not None:
- for cookie in response.cookies:
- self._cookiejar.set_cookie(cookie)
-
- if self._cookiejar.filename is not None:
- # Save is required only if we have a filename
- self._cookiejar.save()
-
- response.raise_for_status()
- return self.parse_response(response)
- except requests.RequestException:
- e = sys.exc_info()[1]
- if not response:
- raise
- raise ProtocolError(
- url, response.status_code, str(e), response.headers)
- except Fault:
- raise sys.exc_info()[1]
- except Exception:
- # pylint: disable=W0201
- e = BugzillaError(str(sys.exc_info()[1]))
- e.__traceback__ = sys.exc_info()[2]
- raise e
-
- def request(self, host, handler, request_body, verbose=0):
- self.verbose = verbose
- url = "%s://%s%s" % (self.scheme, host, handler)
-
- # xmlrpclib fails to escape \r
- request_body = request_body.replace(b'\r', b'
')
-
- return self._request_helper(url, request_body)
-
-
-class BugzillaError(Exception):
- '''Error raised in the Bugzilla client code.'''
- pass
-
-
-class _FieldAlias(object):
- """
- Track API attribute names that differ from what we expose in users.
-
- For example, originally 'short_desc' was the name of the property that
- maps to 'summary' on modern bugzilla. We want pre-existing API users
- to be able to continue to use Bug.short_desc, and
- query({"short_desc": "foo"}). This class tracks that mapping.
-
- @oldname: The old attribute name
- @newname: The modern attribute name
- @is_api: If True, use this mapping for values sent to the xmlrpc API
- (like the query example)
- @is_bug: If True, use this mapping for Bug attribute names.
- """
- def __init__(self, newname, oldname, is_api=True, is_bug=True):
- self.newname = newname
- self.oldname = oldname
- self.is_api = is_api
- self.is_bug = is_bug
-
-
-class BugzillaBase(object):
- '''An object which represents the data and methods exported by a Bugzilla
- instance. Uses xmlrpclib to do its thing. You'll want to create one thusly:
- bz=Bugzilla(url='https://bugzilla.redhat.com/xmlrpc.cgi',
- user=u, password=p)
-
- You can get authentication cookies by calling the login() method. These
- cookies will be stored in a MozillaCookieJar-style file specified by the
- 'cookiefile' attribute (which defaults to ~/.bugzillacookies). Once you
- get cookies this way, you will be considered logged in until the cookie
- expires.
-
- You may also specify 'user' and 'password' in a bugzillarc file, either
- /etc/bugzillarc or ~/.bugzillarc. The latter will override the former.
- The format works like this:
- [bugzilla.yoursite.com]
- user = username
- password = password
- You can also use the [DEFAULT] section to set defaults that apply to
- any site without a specific section of its own.
- Be sure to set appropriate permissions on bugzillarc if you choose to
- store your password in it!
-
- This is an abstract class; it must be implemented by a concrete subclass
- which actually connects the methods provided here to the appropriate
- methods on the bugzilla instance.
-
- :kwarg url: base url for the bugzilla instance
- :kwarg user: usename to connect with
- :kwarg password: password for the connecting user
- :kwarg cookiefile: Location to save the session cookies so you don't have
- to keep giving the library your username and password. This defaults
- to ~/.bugzillacookies. If set to None, the library won't save the
- cookies persistently.
- '''
-
- # bugzilla version that the class is targetting. filled in by
- # subclasses
- bz_ver_major = 0
- bz_ver_minor = 0
-
- # Intended to be the API version of the class, but historically is
- # unused and basically worthless since we don't plan on breaking API.
- version = "0.1"
-
- @staticmethod
- def url_to_query(url):
- '''
- Given a big huge bugzilla query URL, returns a query dict that can
- be passed along to the Bugzilla.query() method.
- '''
- q = {}
-
- # pylint: disable=unpacking-non-sequence
- (ignore, ignore, path,
- ignore, query, ignore) = urlparse(url)
-
- base = os.path.basename(path)
- if base not in ('buglist.cgi', 'query.cgi'):
- return {}
-
- for (k, v) in parse_qsl(query):
- if k not in q:
- q[k] = v
- elif isinstance(q[k], list):
- q[k].append(v)
- else:
- oldv = q[k]
- q[k] = [oldv, v]
-
- # Handle saved searches
- if base == "buglist.cgi" and "namedcmd" in q and "sharer_id" in q:
- q = {
- "sharer_id": q["sharer_id"],
- "savedsearch": q["namedcmd"],
- }
-
- return q
-
- @staticmethod
- def fix_url(url):
- """
- Turn passed url into a bugzilla XMLRPC web url
- """
- if '://' not in url:
- log.debug('No scheme given for url, assuming https')
- url = 'https://' + url
- if url.count('/') < 3:
- log.debug('No path given for url, assuming /xmlrpc.cgi')
- url = url + '/xmlrpc.cgi'
- return url
-
- def __init__(self, url=None, user=None, password=None, cookiefile=-1,
- sslverify=True, tokenfile=-1):
- # Hook to allow Bugzilla autodetection without weirdly overriding
- # __init__
- if self._init_class_from_url(url, sslverify):
- kwargs = locals().copy()
- del(kwargs["self"])
-
- # pylint: disable=non-parent-init-called
- self.__class__.__init__(self, **kwargs)
- return
-
- # Settings the user might want to tweak
- self.user = user or ''
- self.password = password or ''
- self.url = ''
-
- self._transport = None
- self._cookiejar = None
- self._sslverify = bool(sslverify)
-
- self.logged_in = False
- self.bug_autorefresh = True
-
- # Bugzilla object state info that users shouldn't mess with
- self._proxy = None
- self._products = None
- self._bugfields = None
- self._components = {}
- self._components_details = {}
- self._init_private_data()
-
- if cookiefile == -1:
- cookiefile = os.path.expanduser('~/.bugzillacookies')
- if tokenfile == -1:
- tokenfile = os.path.expanduser("~/.bugzillatoken")
- log.debug("Using tokenfile=%s", tokenfile)
- self.cookiefile = cookiefile
- self.tokenfile = tokenfile
-
- # List of field aliases. Maps old style RHBZ parameter
- # names to actual upstream values. Used for createbug() and
- # query include_fields at least.
- self._field_aliases = []
- self._add_field_alias('summary', 'short_desc')
- self._add_field_alias('description', 'comment')
- self._add_field_alias('platform', 'rep_platform')
- self._add_field_alias('severity', 'bug_severity')
- self._add_field_alias('status', 'bug_status')
- self._add_field_alias('id', 'bug_id')
- self._add_field_alias('blocks', 'blockedby')
- self._add_field_alias('blocks', 'blocked')
- self._add_field_alias('depends_on', 'dependson')
- self._add_field_alias('creator', 'reporter')
- self._add_field_alias('url', 'bug_file_loc')
- self._add_field_alias('dupe_of', 'dupe_id')
- self._add_field_alias('dupe_of', 'dup_id')
- self._add_field_alias('comments', 'longdescs')
- self._add_field_alias('creation_time', 'opendate')
- self._add_field_alias('creation_time', 'creation_ts')
- self._add_field_alias('whiteboard', 'status_whiteboard')
- self._add_field_alias('last_change_time', 'delta_ts')
-
- if url:
- self.connect(url)
-
- def _init_class_from_url(self, url, sslverify):
- ignore = url
- ignore = sslverify
-
- def _init_private_data(self):
- '''initialize private variables used by this bugzilla instance.'''
- self._proxy = None
- self._products = None
- self._bugfields = None
- self._components = {}
- self._components_details = {}
-
- def _get_user_agent(self):
- ret = ('Python-urllib bugzilla.py/%s %s' %
- (__version__, str(self.__class__.__name__)))
- return ret
- user_agent = property(_get_user_agent)
-
-
- ###################
- # Private helpers #
- ###################
-
- def _check_version(self, major, minor):
- """
- Check if the detected bugzilla version is >= passed major/minor pair.
- """
- if major < self.bz_ver_major:
- return True
- if (major == self.bz_ver_major and minor <= self.bz_ver_minor):
- return True
- return False
-
- def _listify(self, val):
- if val is None:
- return val
- if type(val) is list:
- return val
- return [val]
-
- def _product_id_to_name(self, productid):
- '''Convert a product ID (int) to a product name (str).'''
- for p in self.products:
- if p['id'] == productid:
- return p['name']
- raise ValueError('No product with id #%i' % productid)
-
- def _product_name_to_id(self, product):
- '''Convert a product name (str) to a product ID (int).'''
- for p in self.products:
- if p['name'] == product:
- return p['id']
- raise ValueError('No product named "%s"' % product)
-
- def _add_field_alias(self, *args, **kwargs):
- self._field_aliases.append(_FieldAlias(*args, **kwargs))
-
- def _get_bug_aliases(self):
- return [(f.newname, f.oldname)
- for f in self._field_aliases if f.is_bug]
-
- def _get_api_aliases(self):
- return [(f.newname, f.oldname)
- for f in self._field_aliases if f.is_api]
-
-
- ###################
- # Cookie handling #
- ###################
-
- def _getcookiefile(self):
- '''cookiefile is the file that bugzilla session cookies are loaded
- and saved from.
- '''
- return self._cookiejar.filename
-
- def _delcookiefile(self):
- self._cookiejar = None
-
- def _setcookiefile(self, cookiefile):
- if (self._cookiejar and cookiefile == self._cookiejar.filename):
- return
-
- if self._proxy is not None:
- raise RuntimeError("Can't set cookies with an open connection, "
- "disconnect() first.")
-
- log.debug("Using cookiefile=%s", cookiefile)
- self._cookiejar = _build_cookiejar(cookiefile)
-
- cookiefile = property(_getcookiefile, _setcookiefile, _delcookiefile)
-
-
- #############################
- # Login/connection handling #
- #############################
-
- configpath = ['/etc/bugzillarc', '~/.bugzillarc']
-
- def readconfig(self, configpath=None):
- '''
- Read bugzillarc file(s) into memory.
- '''
- if not configpath:
- configpath = self.configpath
- configpath = [os.path.expanduser(p) for p in configpath]
- c = SafeConfigParser()
- r = c.read(configpath)
- if not r:
- return
- # See if we have a config section that matches this url.
- section = ""
- # Substring match - prefer the longest match found
- log.debug("Searching for config section matching %s", self.url)
- for s in sorted(c.sections()):
- if s in self.url:
- log.debug("Found matching section: %s", s)
- section = s
- if not section:
- return
- for k, v in c.items(section):
- if k in ('user', 'password'):
- log.debug("Setting '%s' from configfile", k)
- setattr(self, k, v)
-
- def connect(self, url=None):
- '''
- Connect to the bugzilla instance with the given url.
-
- This will also read any available config files (see readconfig()),
- which may set 'user' and 'password'.
-
- If 'user' and 'password' are both set, we'll run login(). Otherwise
- you'll have to login() yourself before some methods will work.
- '''
- if url is None and self.url:
- url = self.url
- url = self.fix_url(url)
-
- self._transport = RequestsTransport(
- url, self._cookiejar, sslverify=self._sslverify)
- self._transport.user_agent = self.user_agent
- self._proxy = _BugzillaServerProxy(url, self.tokenfile,
- self._transport)
-
- self.url = url
- # we've changed URLs - reload config
- self.readconfig()
-
- if (self.user and self.password):
- log.info("user and password present - doing login()")
- self.login()
-
- def disconnect(self):
- '''
- Disconnect from the given bugzilla instance.
- '''
- # clears all the connection state
- self._init_private_data()
-
-
- def _login(self, user, password):
- '''Backend login method for Bugzilla3'''
- return self._proxy.User.login({'login': user, 'password': password})
-
- def _logout(self):
- '''Backend login method for Bugzilla3'''
- return self._proxy.User.logout()
-
- def login(self, user=None, password=None):
- '''Attempt to log in using the given username and password. Subsequent
- method calls will use this username and password. Returns False if
- login fails, otherwise returns some kind of login info - typically
- either a numeric userid, or a dict of user info. It also sets the
- logged_in attribute to True, if successful.
-
- If user is not set, the value of Bugzilla.user will be used. If *that*
- is not set, ValueError will be raised. If login fails, BugzillaError
- will be raised.
-
- This method will be called implicitly at the end of connect() if user
- and password are both set. So under most circumstances you won't need
- to call this yourself.
- '''
- if user:
- self.user = user
- if password:
- self.password = password
-
- if not self.user:
- raise ValueError("missing username")
- if not self.password:
- raise ValueError("missing password")
-
- try:
- ret = self._login(self.user, self.password)
- self.logged_in = True
- self.password = ''
- log.info("login successful for user=%s", self.user)
- return ret
- except Fault:
- e = sys.exc_info()[1]
- raise BugzillaError("Login failed: %s" % str(e.faultString))
-
- def logout(self):
- '''Log out of bugzilla. Drops server connection and user info, and
- destroys authentication cookies.'''
- self._logout()
- self.disconnect()
- self.user = ''
- self.password = ''
- self.logged_in = False
-
-
- #############################################
- # Fetching info about the bugzilla instance #
- #############################################
-
- def _getbugfields(self):
- raise RuntimeError("This bugzilla version does not support listing "
- "bug fields.")
-
- def getbugfields(self, force_refresh=False):
- '''
- Calls getBugFields, which returns a list of fields in each bug
- for this bugzilla instance. This can be used to set the list of attrs
- on the Bug object.
- '''
- if force_refresh or self._bugfields is None:
- log.debug("Refreshing bugfields")
- self._bugfields = self._getbugfields()
- self._bugfields.sort()
- log.debug("bugfields = %s", self._bugfields)
-
- return self._bugfields
- bugfields = property(fget=lambda self: self.getbugfields(),
- fdel=lambda self: setattr(self, '_bugfields', None))
-
-
- def refresh_products(self, **kwargs):
- """
- Refresh a product's cached info
- Takes same arguments as _getproductinfo
- """
- if self._products is None:
- self._products = []
-
- for product in self._getproductinfo(**kwargs):
- added = False
- for current in self._products[:]:
- if (current.get("id", -1) != product.get("id", -2) and
- current.get("name", -1) != product.get("name", -2)):
- continue
-
- self._products.remove(current)
- self._products.append(product)
- added = True
- break
- if not added:
- self._products.append(product)
-
- def getproducts(self, force_refresh=False, **kwargs):
- '''Get product data: names, descriptions, etc.
- The data varies between Bugzilla versions but the basic format is a
- list of dicts, where the dicts will have at least the following keys:
- {'id':1, 'name':"Some Product", 'description':"This is a product"}
-
- Any method that requires a 'product' can be given either the
- id or the name.'''
- if force_refresh or not self._products:
- self._products = self._getproducts(**kwargs)
- return self._products
-
- products = property(fget=lambda self: self.getproducts(),
- fdel=lambda self: setattr(self, '_products', None))
-
-
- def getcomponentsdetails(self, product, force_refresh=False):
- '''Returns a dict of dicts, containing detailed component information
- for the given product. The keys of the dict are component names. For
- each component, the value is a dict with the following keys:
- description, initialowner, initialqacontact'''
- if force_refresh or product not in self._components_details:
- clist = self._getcomponentsdetails(product)
- cdict = {}
- for item in clist:
- name = item['component']
- del item['component']
- cdict[name] = item
- self._components_details[product] = cdict
-
- return self._components_details[product]
-
- def getcomponentdetails(self, product, component, force_refresh=False):
- '''Get details for a single component. Returns a dict with the
- following keys:
- description, initialowner, initialqacontact, initialcclist'''
- d = self.getcomponentsdetails(product, force_refresh)
- return d[component]
-
- def getcomponents(self, product, force_refresh=False):
- '''Return a dict of components:descriptions for the given product.'''
- if force_refresh or product not in self._components:
- self._components[product] = self._getcomponents(product)
- return self._components[product]
-
- def _component_data_convert(self, data, update=False):
- if type(data['product']) is int:
- data['product'] = self._product_id_to_name(data['product'])
-
-
- # Back compat for the old RH interface
- convert_fields = [
- ("initialowner", "default_assignee"),
- ("initialqacontact", "default_qa_contact"),
- ("initialcclist", "default_cc"),
- ]
- for old, new in convert_fields:
- if old in data:
- data[new] = data.pop(old)
-
- if update:
- names = {"product": data.pop("product"),
- "component": data.pop("component")}
- updates = {}
- for k in data.keys():
- updates[k] = data.pop(k)
-
- data["names"] = [names]
- data["updates"] = updates
-
-
- def addcomponent(self, data):
- '''
- A method to create a component in Bugzilla. Takes a dict, with the
- following elements:
-
- product: The product to create the component in
- component: The name of the component to create
- desription: A one sentence summary of the component
- default_assignee: The bugzilla login (email address) of the initial
- owner of the component
- default_qa_contact (optional): The bugzilla login of the
- initial QA contact
- default_cc: (optional) The initial list of users to be CC'ed on
- new bugs for the component.
- '''
- data = data.copy()
- self._component_data_convert(data)
- log.debug("Calling Component.create with: %s", data)
- return self._proxy.Component.create(data)
-
- def editcomponent(self, data):
- '''
- A method to edit a component in Bugzilla. Takes a dict, with
- mandatory elements of product. component, and initialowner.
- All other elements are optional and use the same names as the
- addcomponent() method.
- '''
- data = data.copy()
- self._component_data_convert(data, update=True)
- log.debug("Calling Component.update with: %s", data)
- return self._proxy.Component.update(data)
-
-
- def _getproductinfo(self, ids=None, names=None,
- include_fields=None, exclude_fields=None):
- '''
- Get all info for the requested products.
-
- @ids: List of product IDs to lookup
- @names: List of product names to lookup (since bz 4.2,
- though we emulate it for older versions)
- @include_fields: Only include these fields in the output (since bz 4.2)
- @exclude_fields: Do not include these fields in the output (since
- bz 4.2)
- '''
- if ids is None and names is None:
- raise RuntimeError("Products must be specified")
-
- kwargs = {}
- if not self._check_version(4, 2):
- if names:
- ids = [self._product_name_to_id(name) for name in names]
- names = None
- include_fields = None
- exclude_fields = None
-
- if ids:
- kwargs["ids"] = self._listify(ids)
- if names:
- kwargs["names"] = self._listify(names)
- if include_fields:
- kwargs["include_fields"] = include_fields
- if exclude_fields:
- kwargs["exclude_fields"] = exclude_fields
-
- # The bugzilla4 name is Product.get(), but Bugzilla3 only had
- # Product.get_product, and bz4 kept an alias.
- log.debug("Calling Product.get_products with: %s", kwargs)
- ret = self._proxy.Product.get_products(kwargs)
- return ret['products']
-
- def _getproducts(self, **kwargs):
- product_ids = self._proxy.Product.get_accessible_products()
- r = self._getproductinfo(product_ids['ids'], **kwargs)
- return r
-
- def _getcomponents(self, product):
- if type(product) == str:
- product = self._product_name_to_id(product)
- r = self._proxy.Bug.legal_values({'product_id': product,
- 'field': 'component'})
- return r['values']
-
- def _getcomponentsdetails(self, product):
- # Originally this was a RH extension getProdCompDetails
- # Upstream support has been available since 4.2
- if not self._check_version(4, 2):
- raise RuntimeError("This bugzilla version does not support "
- "fetching component details.")
-
- comps = None
- if self._products is None:
- self._products = []
-
- def _find_comps():
- for p in self._products:
- if p["name"] != product:
- continue
- return p.get("components", None)
-
- comps = _find_comps()
- if comps is None:
- self.refresh_products(names=[product],
- include_fields=["name", "id", "components"])
- comps = _find_comps()
-
- if comps is None:
- raise ValueError("Unknown product '%s'" % product)
-
- # Convert to old style dictionary to maintain back compat
- # with original RH bugzilla call
- ret = []
- for comp in comps:
- row = {}
- row["component"] = comp["name"]
- row["initialqacontact"] = comp["default_qa_contact"]
- row["initialowner"] = comp["default_assigned_to"]
- row["description"] = comp["description"]
- ret.append(row)
- return ret
-
-
- ###################
- # getbug* methods #
- ###################
-
- # getbug_extra_fields: Extra fields that need to be explicitly
- # requested from Bug.get in order for the data to be returned. This
- # decides the difference between getbug() and getbugsimple().
- #
- # As of Dec 2012 it seems like only RH bugzilla actually has behavior
- # like this, for upstream bz it returns all info for every Bug.get()
- _getbug_extra_fields = []
- _supports_getbug_extra_fields = False
-
- def _getbugs(self, idlist, simple=False, permissive=True,
- include_fields=None, exclude_fields=None, extra_fields=None):
- '''
- Return a list of dicts of full bug info for each given bug id.
- bug ids that couldn't be found will return None instead of a dict.
-
- @simple: If True, don't ask for any large extra_fields.
- '''
- oldidlist = idlist
- idlist = []
- for i in oldidlist:
- try:
- idlist.append(int(i))
- except ValueError:
- # String aliases can be passed as well
- idlist.append(i)
-
- extra_fields = self._listify(extra_fields or [])
- if not simple:
- extra_fields += self._getbug_extra_fields
-
- getbugdata = {"ids": idlist}
- if permissive:
- getbugdata["permissive"] = 1
- if self.bz_ver_major >= 4:
- if include_fields:
- getbugdata["include_fields"] = self._listify(include_fields)
- if exclude_fields:
- getbugdata["exclude_fields"] = self._listify(exclude_fields)
- if self._supports_getbug_extra_fields:
- getbugdata["extra_fields"] = extra_fields
-
- log.debug("Calling Bug.get with: %s", getbugdata)
- r = self._proxy.Bug.get(getbugdata)
-
- if self.bz_ver_major >= 4:
- bugdict = dict([(b['id'], b) for b in r['bugs']])
- else:
- bugdict = dict([(b['id'], b['internals']) for b in r['bugs']])
-
- ret = []
- for i in idlist:
- found = None
- if i in bugdict:
- found = bugdict[i]
- else:
- # Need to map an alias
- for valdict in bugdict.values():
- if i in valdict.get("alias", []):
- found = valdict
- break
-
- ret.append(found)
-
- return ret
-
- def _getbug(self, objid, simple=False,
- include_fields=None, exclude_fields=None, extra_fields=None):
- '''Return a dict of full bug info for the given bug id'''
- return self._getbugs([objid], simple=simple, permissive=False,
- include_fields=include_fields, exclude_fields=exclude_fields,
- extra_fields=extra_fields)[0]
-
- def getbug(self, objid,
- include_fields=None, exclude_fields=None, extra_fields=None):
- '''Return a Bug object with the full complement of bug data
- already loaded.'''
- data = self._getbug(objid, include_fields=include_fields,
- exclude_fields=exclude_fields, extra_fields=extra_fields)
- return _Bug(self, dict=data, autorefresh=self.bug_autorefresh)
-
- def getbugs(self, idlist,
- include_fields=None, exclude_fields=None, extra_fields=None):
- '''Return a list of Bug objects with the full complement of bug data
- already loaded. If there's a problem getting the data for a given id,
- the corresponding item in the returned list will be None.'''
- data = self._getbugs(idlist, include_fields=include_fields,
- exclude_fields=exclude_fields, extra_fields=extra_fields)
- return [(b and _Bug(self, dict=b,
- autorefresh=self.bug_autorefresh)) or None
- for b in data]
-
- # Since for so long getbugsimple was just getbug, I don't think we can
- # remove any fields without possibly causing a slowdown for some
- # existing users. Just have this API mean 'don't ask for the extra
- # big stuff'
- def getbugsimple(self, objid):
- '''Return a Bug object given bug id, populated with simple info'''
- return _Bug(self,
- dict=self._getbug(objid, simple=True),
- autorefresh=self.bug_autorefresh)
-
- def getbugssimple(self, idlist):
- '''Return a list of Bug objects for the given bug ids, populated with
- simple info. As with getbugs(), if there's a problem getting the data
- for a given bug ID, the corresponding item in the returned list will
- be None.'''
- return [(b and _Bug(self, dict=b,
- autorefresh=self.bug_autorefresh)) or None
- for b in self._getbugs(idlist, simple=True)]
-
-
- #################
- # query methods #
- #################
-
- def _convert_include_field_list(self, _in):
- if not _in:
- return _in
-
- for newname, oldname in self._get_api_aliases():
- if oldname in _in:
- _in.remove(oldname)
- if newname not in _in:
- _in.append(newname)
- return _in
-
- def build_query(self,
- product=None,
- component=None,
- version=None,
- long_desc=None,
- bug_id=None,
- short_desc=None,
- cc=None,
- assigned_to=None,
- reporter=None,
- qa_contact=None,
- status=None,
- blocked=None,
- dependson=None,
- keywords=None,
- keywords_type=None,
- url=None,
- url_type=None,
- status_whiteboard=None,
- status_whiteboard_type=None,
- fixed_in=None,
- fixed_in_type=None,
- flag=None,
- alias=None,
- qa_whiteboard=None,
- devel_whiteboard=None,
- boolean_query=None,
- bug_severity=None,
- priority=None,
- target_milestone=None,
- emailtype=None,
- booleantype=None,
- include_fields=None,
- quicksearch=None,
- savedsearch=None,
- savedsearch_sharer_id=None,
- sub_component=None,
- tags=None):
- """
- Build a query string from passed arguments. Will handle
- query parameter differences between various bugzilla versions.
-
- Most of the parameters should be self explanatory. However
- if you want to perform a complex query, and easy way is to
- create it with the bugzilla web UI, copy the entire URL it
- generates, and pass it to the static method
-
- Bugzilla.url_to_query
-
- Then pass the output to Bugzilla.query()
- """
- ignore = emailtype
- ignore = booleantype
- ignore = include_fields
-
- for key, val in [
- ('fixed_in', fixed_in),
- ('blocked', blocked),
- ('dependson', dependson),
- ('flag', flag),
- ('qa_whiteboard', qa_whiteboard),
- ('devel_whiteboard', devel_whiteboard),
- ('alias', alias),
- ('boolean_query', boolean_query),
- ('long_desc', long_desc),
- ('quicksearch', quicksearch),
- ('savedsearch', savedsearch),
- ('sharer_id', savedsearch_sharer_id),
- ('sub_component', sub_component),
- ]:
- if val is not None:
- raise RuntimeError("'%s' search not supported by this "
- "bugzilla" % key)
-
- query = {
- "product": self._listify(product),
- "component": self._listify(component),
- "version": version,
- "id": bug_id,
- "short_desc": short_desc,
- "bug_status": status,
- "keywords": keywords,
- "keywords_type": keywords_type,
- "bug_file_loc": url,
- "bug_file_loc_type": url_type,
- "status_whiteboard": status_whiteboard,
- "status_whiteboard_type": status_whiteboard_type,
- "fixed_in_type": fixed_in_type,
- "bug_severity": bug_severity,
- "priority": priority,
- "target_milestone": target_milestone,
- "assigned_to": assigned_to,
- "cc": cc,
- "qa_contact": qa_contact,
- "reporter": reporter,
- "tag": self._listify(tags),
- }
-
- # Strip out None elements in the dict
- for k, v in query.copy().items():
- if v is None:
- del(query[k])
- return query
-
- def _query(self, query):
- # This is kinda redundant now, but various scripts call
- # _query with their own assembled dictionaries, so don't
- # drop this lest we needlessly break those users
- log.debug("Calling Bug.search with: %s", query)
- return self._proxy.Bug.search(query)
-
- def query(self, query):
- '''Query bugzilla and return a list of matching bugs.
- query must be a dict with fields like those in in querydata['fields'].
- Returns a list of Bug objects.
- Also see the _query() method for details about the underlying
- implementation.
- '''
- r = self._query(query)
- log.debug("Query returned %s bugs", len(r['bugs']))
- return [_Bug(self, dict=b,
- autorefresh=self.bug_autorefresh) for b in r['bugs']]
-
- def simplequery(self, product, version='', component='',
- string='', matchtype='allwordssubstr'):
- '''Convenience method - query for bugs filed against the given
- product, version, and component whose comments match the given string.
- matchtype specifies the type of match to be done. matchtype may be
- any of the types listed in querydefaults['long_desc_type_list'], e.g.:
- ['allwordssubstr', 'anywordssubstr', 'substring', 'casesubstring',
- 'allwords', 'anywords', 'regexp', 'notregexp']
- Return value is the same as with query().
- '''
- q = {
- 'product': product,
- 'version': version,
- 'component': component,
- 'long_desc': string,
- 'long_desc_type': matchtype
- }
- return self.query(q)
-
- def pre_translation(self, query):
- '''In order to keep the API the same, Bugzilla4 needs to process the
- query and the result. This also applies to the refresh() function
- '''
- pass
-
- def post_translation(self, query, bug):
- '''In order to keep the API the same, Bugzilla4 needs to process the
- query and the result. This also applies to the refresh() function
- '''
- pass
-
- def bugs_history(self, bug_ids):
- '''
- Experimental. Gets the history of changes for
- particular bugs in the database.
- '''
- return self._proxy.Bug.history({'ids': bug_ids})
-
- #######################################
- # Methods for modifying existing bugs #
- #######################################
-
- # Bug() also has individual methods for many ops, like setassignee()
-
- def update_bugs(self, ids, updates):
- """
- A thin wrapper around bugzilla Bug.update(). Used to update all
- values of an existing bug report, as well as add comments.
-
- The dictionary passed to this function should be generated with
- build_update(), otherwise we cannot guarantee back compatibility.
- """
- tmp = updates.copy()
- tmp["ids"] = self._listify(ids)
-
- log.debug("Calling Bug.update with: %s", tmp)
- return self._proxy.Bug.update(tmp)
-
- def update_flags(self, idlist, flags):
- '''
- Updates the flags associated with a bug report.
- Format of flags is:
- [{"name": "needinfo", "status": "+", "requestee": "foo at bar.com"},
- {"name": "devel_ack", "status": "-"}, ...]
- '''
- d = {"ids": self._listify(idlist), "updates": flags}
- log.debug("Calling Flag.update with: %s", d)
- return self._proxy.Flag.update(d)
-
- def update_tags(self, idlist, tags_add=None, tags_remove=None):
- '''
- Updates the 'tags' field for a bug.
- '''
- tags = {}
- if tags_add:
- tags["add"] = self._listify(tags_add)
- if tags_remove:
- tags["remove"] = self._listify(tags_remove)
-
- d = {
- "ids": self._listify(idlist),
- "tags": tags,
- }
-
- log.debug("Calling Bug.update_tags with: %s", d)
- return self._proxy.Bug.update_tags(d)
-
-
- def build_update(self,
- alias=None,
- assigned_to=None,
- blocks_add=None,
- blocks_remove=None,
- blocks_set=None,
- depends_on_add=None,
- depends_on_remove=None,
- depends_on_set=None,
- cc_add=None,
- cc_remove=None,
- is_cc_accessible=None,
- comment=None,
- comment_private=None,
- component=None,
- deadline=None,
- dupe_of=None,
- estimated_time=None,
- groups_add=None,
- groups_remove=None,
- keywords_add=None,
- keywords_remove=None,
- keywords_set=None,
- op_sys=None,
- platform=None,
- priority=None,
- product=None,
- qa_contact=None,
- is_creator_accessible=None,
- remaining_time=None,
- reset_assigned_to=None,
- reset_qa_contact=None,
- resolution=None,
- see_also_add=None,
- see_also_remove=None,
- severity=None,
- status=None,
- summary=None,
- target_milestone=None,
- target_release=None,
- url=None,
- version=None,
- whiteboard=None,
- work_time=None,
- fixed_in=None,
- qa_whiteboard=None,
- devel_whiteboard=None,
- internal_whiteboard=None,
- sub_component=None):
- # pylint: disable=W0221
- # Argument number differs from overridden method
- # Base defines it with *args, **kwargs, so we don't have to maintain
- # the master argument list in 2 places
- ret = {}
-
- # These are only supported for rhbugzilla
- for key, val in [
- ("fixed_in", fixed_in),
- ("devel_whiteboard", devel_whiteboard),
- ("qa_whiteboard", qa_whiteboard),
- ("internal_whiteboard", internal_whiteboard),
- ("sub_component", sub_component),
- ]:
- if val is not None:
- raise ValueError("bugzilla instance does not support "
- "updating '%s'" % key)
-
- def s(key, val, convert=None):
- if val is None:
- return
- if convert:
- val = convert(val)
- ret[key] = val
-
- def add_dict(key, add, remove, _set=None, convert=None):
- if add is remove is _set is None:
- return
-
- def c(val):
- val = self._listify(val)
- if convert:
- val = [convert(v) for v in val]
- return val
-
- newdict = {}
- if add is not None:
- newdict["add"] = c(add)
- if remove is not None:
- newdict["remove"] = c(remove)
- if _set is not None:
- newdict["set"] = c(_set)
- ret[key] = newdict
-
-
- s("alias", alias)
- s("assigned_to", assigned_to)
- s("is_cc_accessible", is_cc_accessible, bool)
- s("component", component)
- s("deadline", deadline)
- s("dupe_of", dupe_of, int)
- s("estimated_time", estimated_time, int)
- s("op_sys", op_sys)
- s("platform", platform)
- s("priority", priority)
- s("product", product)
- s("qa_contact", qa_contact)
- s("is_creator_accessible", is_creator_accessible, bool)
- s("remaining_time", remaining_time, float)
- s("reset_assigned_to", reset_assigned_to, bool)
- s("reset_qa_contact", reset_qa_contact, bool)
- s("resolution", resolution)
- s("severity", severity)
- s("status", status)
- s("summary", summary)
- s("target_milestone", target_milestone)
- s("target_release", target_release)
- s("url", url)
- s("version", version)
- s("whiteboard", whiteboard)
- s("work_time", work_time, float)
-
- add_dict("blocks", blocks_add, blocks_remove, blocks_set,
- convert=int)
- add_dict("depends_on", depends_on_add, depends_on_remove,
- depends_on_set, convert=int)
- add_dict("cc", cc_add, cc_remove)
- add_dict("groups", groups_add, groups_remove)
- add_dict("keywords", keywords_add, keywords_remove, keywords_set)
- add_dict("see_also", see_also_add, see_also_remove)
-
- if comment is not None:
- ret["comment"] = {"comment": comment}
- if comment_private:
- ret["comment"]["is_private"] = comment_private
-
- return ret
-
-
- ########################################
- # Methods for working with attachments #
- ########################################
-
- def _attachment_uri(self, attachid):
- '''Returns the URI for the given attachment ID.'''
- att_uri = self.url.replace('xmlrpc.cgi', 'attachment.cgi')
- att_uri = att_uri + '?id=%s' % attachid
- return att_uri
-
- def attachfile(self, idlist, attachfile, description, **kwargs):
- '''
- Attach a file to the given bug IDs. Returns the ID of the attachment
- or raises XMLRPC Fault if something goes wrong.
-
- attachfile may be a filename (which will be opened) or a file-like
- object, which must provide a 'read' method. If it's not one of these,
- this method will raise a TypeError.
- description is the short description of this attachment.
-
- Optional keyword args are as follows:
- file_name: this will be used as the filename for the attachment.
- REQUIRED if attachfile is a file-like object with no
- 'name' attribute, otherwise the filename or .name
- attribute will be used.
- comment: An optional comment about this attachment.
- is_private: Set to True if the attachment should be marked private.
- is_patch: Set to True if the attachment is a patch.
- content_type: The mime-type of the attached file. Defaults to
- application/octet-stream if not set. NOTE that text
- files will *not* be viewable in bugzilla unless you
- remember to set this to text/plain. So remember that!
-
- Returns the list of attachment ids that were added. If only one
- attachment was added, we return the single int ID for back compat
- '''
- if isinstance(attachfile, str):
- f = open(attachfile)
- elif hasattr(attachfile, 'read'):
- f = attachfile
- else:
- raise TypeError("attachfile must be filename or file-like object")
-
- # Back compat
- if "contenttype" in kwargs:
- kwargs["content_type"] = kwargs.pop("contenttype")
- if "ispatch" in kwargs:
- kwargs["is_patch"] = kwargs.pop("ispatch")
- if "isprivate" in kwargs:
- kwargs["is_private"] = kwargs.pop("isprivate")
- if "filename" in kwargs:
- kwargs["file_name"] = kwargs.pop("filename")
-
- kwargs['summary'] = description
-
- data = f.read()
- if not isinstance(data, bytes):
- data = data.encode(locale.getpreferredencoding())
- kwargs['data'] = Binary(data)
-
- kwargs['ids'] = self._listify(idlist)
-
- if 'file_name' not in kwargs and hasattr(f, "name"):
- kwargs['file_name'] = os.path.basename(f.name)
- if 'content_type' not in kwargs:
- ctype = _detect_filetype(getattr(f, "name", None))
- if not ctype:
- ctype = 'application/octet-stream'
- kwargs['content_type'] = ctype
-
- ret = self._proxy.Bug.add_attachment(kwargs)
-
- if "attachments" in ret:
- # Up to BZ 4.2
- ret = [int(k) for k in ret["attachments"].keys()]
- elif "ids" in ret:
- # BZ 4.4+
- ret = ret["ids"]
-
- if type(ret) is list and len(ret) == 1:
- ret = ret[0]
- return ret
-
-
- def openattachment(self, attachid):
- '''Get the contents of the attachment with the given attachment ID.
- Returns a file-like object.'''
-
- def get_filename(headers):
- import re
-
- match = re.search(
- r'^.*filename="?(.*)"$',
- headers.get('content-disposition', '')
- )
-
- # default to attchid if no match was found
- return match.group(1) if match else attachid
-
- att_uri = self._attachment_uri(attachid)
-
- response = requests.get(att_uri, cookies=self._cookiejar, stream=True)
-
- ret = BytesIO()
- for chunk in response.iter_content(chunk_size=1024):
- if chunk:
- ret.write(chunk)
- ret.name = get_filename(response.headers)
-
- # Hooray, now we have a file-like object with .read() and .name
- ret.seek(0)
- return ret
-
- def updateattachmentflags(self, bugid, attachid, flagname, **kwargs):
- '''
- Updates a flag for the given attachment ID.
- Optional keyword args are:
- status: new status for the flag ('-', '+', '?', 'X')
- requestee: new requestee for the flag
- '''
- update = {
- 'name': flagname,
- 'attach_id': int(attachid),
- }
- update.update(kwargs.items())
-
- result = self._proxy.Flag.update({
- 'ids': [int(bugid)],
- 'updates': [update]})
- return result['flag_updates'][str(bugid)]
-
-
- #####################
- # createbug methods #
- #####################
-
- createbug_required = ('product', 'component', 'summary', 'version',
- 'description')
-
- def build_createbug(self,
- product=None,
- component=None,
- version=None,
- summary=None,
- description=None,
- comment_private=None,
- blocks=None,
- cc=None,
- assigned_to=None,
- keywords=None,
- depends_on=None,
- groups=None,
- op_sys=None,
- platform=None,
- priority=None,
- qa_contact=None,
- resolution=None,
- severity=None,
- status=None,
- target_milestone=None,
- target_release=None,
- url=None,
- sub_component=None):
-
- localdict = {}
- if blocks:
- localdict["blocks"] = self._listify(blocks)
- if cc:
- localdict["cc"] = self._listify(cc)
- if depends_on:
- localdict["depends_on"] = self._listify(depends_on)
- if groups:
- localdict["groups"] = self._listify(groups)
- if keywords:
- localdict["keywords"] = self._listify(keywords)
- if description:
- localdict["description"] = description
- if comment_private:
- localdict["comment_is_private"] = True
-
- # Most of the machinery and formatting here is the same as
- # build_update, so reuse that as much as possible
- ret = self.build_update(product=product, component=component,
- version=version, summary=summary, op_sys=op_sys,
- platform=platform, priority=priority, qa_contact=qa_contact,
- resolution=resolution, severity=severity, status=status,
- target_milestone=target_milestone,
- target_release=target_release, url=url,
- assigned_to=assigned_to, sub_component=sub_component)
-
- ret.update(localdict)
- return ret
-
- def _validate_createbug(self, *args, **kwargs):
- # Previous API required users specifying keyword args that mapped
- # to the XMLRPC arg names. Maintain that bad compat, but also allow
- # receiving a single dictionary like query() does
- if kwargs and args:
- raise BugzillaError("createbug: cannot specify positional "
- "args=%s with kwargs=%s, must be one or the "
- "other." % (args, kwargs))
- if args:
- if len(args) > 1 or type(args[0]) is not dict:
- raise BugzillaError("createbug: positional arguments only "
- "accept a single dictionary.")
- data = args[0]
- else:
- data = kwargs
-
- # If we're getting a call that uses an old fieldname, convert it to the
- # new fieldname instead.
- for newname, oldname in self._get_api_aliases():
- if (newname in self.createbug_required and
- newname not in data and
- oldname in data):
- data[newname] = data.pop(oldname)
-
- # Back compat handling for check_args
- if "check_args" in data:
- del(data["check_args"])
-
- return data
-
- def createbug(self, *args, **kwargs):
- '''
- Create a bug with the given info. Returns a new Bug object.
- Check bugzilla API documentation for valid values, at least
- product, component, summary, version, and description need to
- be passed.
- '''
- data = self._validate_createbug(*args, **kwargs)
- log.debug("Calling Bug.create with: %s", data)
- rawbug = self._proxy.Bug.create(data)
- return _Bug(self, bug_id=rawbug["id"],
- autorefresh=self.bug_autorefresh)
-
-
- ##############################
- # Methods for handling Users #
- ##############################
-
- def _getusers(self, ids=None, names=None, match=None):
- '''Return a list of users that match criteria.
-
- :kwarg ids: list of user ids to return data on
- :kwarg names: list of user names to return data on
- :kwarg match: list of patterns. Returns users whose real name or
- login name match the pattern.
- :raises XMLRPC Fault: Code 51: if a Bad Login Name was sent to the
- names array.
- Code 304: if the user was not authorized to see user they
- requested.
- Code 505: user is logged out and can't use the match or ids
- parameter.
-
- Available in Bugzilla-3.4+
- '''
- params = {}
- if ids:
- params['ids'] = self._listify(ids)
- if names:
- params['names'] = self._listify(names)
- if match:
- params['match'] = self._listify(match)
- if not params:
- raise BugzillaError('_get() needs one of ids, '
- ' names, or match kwarg.')
-
- log.debug("Calling User.get with: %s", params)
- return self._proxy.User.get(params)
-
- def getuser(self, username):
- '''Return a bugzilla User for the given username
-
- :arg username: The username used in bugzilla.
- :raises XMLRPC Fault: Code 51 if the username does not exist
- :returns: User record for the username
- '''
- ret = self.getusers(username)
- return ret and ret[0]
-
- def getusers(self, userlist):
- '''Return a list of Users from bugzilla.
-
- :userlist: List of usernames to lookup
- :returns: List of User records
- '''
- userobjs = [_User(self, **rawuser) for rawuser in
- self._getusers(names=userlist).get('users', [])]
-
- # Return users in same order they were passed in
- ret = []
- for u in userlist:
- for uobj in userobjs[:]:
- if uobj.email == u:
- userobjs.remove(uobj)
- ret.append(uobj)
- break
- ret += userobjs
- return ret
-
-
- def searchusers(self, pattern):
- '''Return a bugzilla User for the given list of patterns
-
- :arg pattern: List of patterns to match against.
- :returns: List of User records
- '''
- return [_User(self, **rawuser) for rawuser in
- self._getusers(match=pattern).get('users', [])]
-
- def createuser(self, email, name='', password=''):
- '''Return a bugzilla User for the given username
-
- :arg email: The email address to use in bugzilla
- :kwarg name: Real name to associate with the account
- :kwarg password: Password to set for the bugzilla account
- :raises XMLRPC Fault: Code 501 if the username already exists
- Code 500 if the email address isn't valid
- Code 502 if the password is too short
- Code 503 if the password is too long
- :return: User record for the username
- '''
- self._proxy.User.create(email, name, password)
- return self.getuser(email)
-
- def updateperms(self, user, action, groups):
- '''
- A method to update the permissions (group membership) of a bugzilla
- user.
-
- :arg user: The e-mail address of the user to be acted upon. Can
- also be a list of emails.
- :arg action: add, remove, or set
- :arg groups: list of groups to be added to (i.e. ['fedora_contrib'])
- '''
- groups = self._listify(groups)
- if action == "rem":
- action = "remove"
- if action not in ["add", "remove", "set"]:
- raise BugzillaError("Unknown user permission action '%s'" % action)
-
- update = {
- "names": self._listify(user),
- "groups": {
- action: groups,
- }
- }
-
- log.debug("Call User.update with: %s", update)
- return self._proxy.User.update(update)
-
-
- ######################
- # Deprecated methods #
- ######################
-
- def initcookiefile(self, cookiefile=None):
- '''
- Deprecated: Set self.cookiefile instead.
- '''
- if not cookiefile:
- cookiefile = os.path.expanduser('~/.bugzillacookies')
- self.cookiefile = cookiefile
-
-
- def adduser(self, user, name):
- '''Deprecated: Use createuser() instead.
-
- A method to create a user in Bugzilla. Takes the following:
-
- user: The email address of the user to create
- name: The full name of the user to create
- '''
- self.createuser(user, name)
-
- def getqueryinfo(self, force_refresh=False):
- ignore = force_refresh
- raise RuntimeError("getqueryinfo is deprecated and the "
- "information is not provided by any modern bugzilla.")
- querydata = property(getqueryinfo)
- querydefaults = property(getqueryinfo)
diff --git a/ciabot/bugzilla/bug.py b/ciabot/bugzilla/bug.py
deleted file mode 100644
index 80d9720..0000000
--- a/ciabot/bugzilla/bug.py
+++ /dev/null
@@ -1,517 +0,0 @@
-# base.py - the base classes etc. for a Python interface to bugzilla
-#
-# Copyright (C) 2007, 2008, 2009, 2010 Red Hat Inc.
-# Author: Will Woods <wwoods at redhat.com>
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the
-# Free Software Foundation; either version 2 of the License, or (at your
-# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
-# the full text of the license.
-
-import locale
-import sys
-
-from bugzilla import log
-
-
-class _Bug(object):
- '''A container object for a bug report. Requires a Bugzilla instance -
- every Bug is on a Bugzilla, obviously.
- Optional keyword args:
- dict=DICT - populate attributes with the result of a getBug() call
- bug_id=ID - if dict does not contain bug_id, this is required before
- you can read any attributes or make modifications to this
- bug.
- '''
- def __init__(self, bugzilla, bug_id=None, dict=None, autorefresh=True):
- # pylint: disable=redefined-builtin
- # API had pre-existing issue that we can't change ('dict' usage)
-
- self.bugzilla = bugzilla
- self._bug_fields = []
- self.autorefresh = autorefresh
-
- if bug_id:
- if not dict:
- dict = {}
- dict["id"] = bug_id
-
- if dict:
- log.debug("Bug(%s)", sorted(dict.keys()))
- self._update_dict(dict)
-
- self.weburl = bugzilla.url.replace('xmlrpc.cgi',
- 'show_bug.cgi?id=%i' % self.bug_id)
-
- def __str__(self):
- '''Return a simple string representation of this bug
-
- This is available only for compatibility. Using 'str(bug)' and
- 'print(bug)' is not recommended because of potential encoding issues.
- Please use unicode(bug) where possible.
- '''
- if hasattr(sys.version_info, "major") and sys.version_info.major >= 3:
- return self.__unicode__()
- else:
- return self.__unicode__().encode(
- locale.getpreferredencoding(), 'replace')
-
- def __unicode__(self):
- '''Return a simple unicode string representation of this bug'''
- return u"#%-6s %-10s - %s - %s" % (self.bug_id, self.bug_status,
- self.assigned_to, self.summary)
-
- def __repr__(self):
- return '<Bug #%i on %s at %#x>' % (self.bug_id, self.bugzilla.url,
- id(self))
-
- def __getattr__(self, name):
- refreshed = False
- while True:
- if refreshed and name in self.__dict__:
- # If name was in __dict__ to begin with, __getattr__ would
- # have never been called.
- return self.__dict__[name]
-
- # pylint: disable=protected-access
- aliases = self.bugzilla._get_bug_aliases()
- # pylint: enable=protected-access
-
- for newname, oldname in aliases:
- if name == oldname and newname in self.__dict__:
- return self.__dict__[newname]
-
- # Doing dir(bugobj) does getattr __members__/__methods__,
- # don't refresh for those
- if name.startswith("__") and name.endswith("__"):
- break
-
- if refreshed or not self.autorefresh:
- break
-
- log.info("Bug %i missing attribute '%s' - doing implicit "
- "refresh(). This will be slow, if you want to avoid "
- "this, properly use query/getbug include_fields, and "
- "set bugzilla.bug_autorefresh = False to force failure.",
- self.bug_id, name)
-
- # We pass the attribute name to getbug, since for something like
- # 'attachments' which downloads lots of data we really want the
- # user to opt in.
- self.refresh(extra_fields=[name])
- refreshed = True
-
- raise AttributeError("Bug object has no attribute '%s'" % name)
-
- def refresh(self, include_fields=None, exclude_fields=None,
- extra_fields=None):
- '''
- Refresh the bug with the latest data from bugzilla
- '''
- # pylint: disable=protected-access
- r = self.bugzilla._getbug(self.bug_id,
- include_fields=include_fields, exclude_fields=exclude_fields,
- extra_fields=self._bug_fields + (extra_fields or []))
- # pylint: enable=protected-access
- self._update_dict(r)
- reload = refresh
-
- def _update_dict(self, newdict):
- '''
- Update internal dictionary, in a way that ensures no duplicate
- entries are stored WRT field aliases
- '''
- if self.bugzilla:
- self.bugzilla.post_translation({}, newdict)
-
- # pylint: disable=protected-access
- aliases = self.bugzilla._get_bug_aliases()
- # pylint: enable=protected-access
-
- for newname, oldname in aliases:
- if oldname not in newdict:
- continue
-
- if newname not in newdict:
- newdict[newname] = newdict[oldname]
- elif newdict[newname] != newdict[oldname]:
- log.debug("Update dict contained differing alias values "
- "d[%s]=%s and d[%s]=%s , dropping the value "
- "d[%s]", newname, newdict[newname], oldname,
- newdict[oldname], oldname)
- del(newdict[oldname])
-
- for key in newdict.keys():
- if key not in self._bug_fields:
- self._bug_fields.append(key)
- self.__dict__.update(newdict)
-
- if 'id' not in self.__dict__ and 'bug_id' not in self.__dict__:
- raise TypeError("Bug object needs a bug_id")
-
-
- ##################
- # pickle helpers #
- ##################
-
- def __getstate__(self):
- ret = {}
- for key in self._bug_fields:
- ret[key] = self.__dict__[key]
- return ret
-
- def __setstate__(self, vals):
- self._bug_fields = []
- self.bugzilla = None
- self._update_dict(vals)
-
-
- #####################
- # Modify bug status #
- #####################
-
- def setstatus(self, status, comment=None, private=False,
- private_in_it=False, nomail=False):
- '''
- Update the status for this bug report.
- Commonly-used values are ASSIGNED, MODIFIED, and NEEDINFO.
-
- To change bugs to CLOSED, use .close() instead.
- '''
- ignore = private_in_it
- ignore = nomail
-
- vals = self.bugzilla.build_update(status=status,
- comment=comment,
- comment_private=private)
- log.debug("setstatus: update=%s", vals)
-
- return self.bugzilla.update_bugs(self.bug_id, vals)
-
- def close(self, resolution, dupeid=None, fixedin=None,
- comment=None, isprivate=False,
- private_in_it=False, nomail=False):
- '''Close this bug.
- Valid values for resolution are in bz.querydefaults['resolution_list']
- For bugzilla.redhat.com that's:
- ['NOTABUG', 'WONTFIX', 'DEFERRED', 'WORKSFORME', 'CURRENTRELEASE',
- 'RAWHIDE', 'ERRATA', 'DUPLICATE', 'UPSTREAM', 'NEXTRELEASE',
- 'CANTFIX', 'INSUFFICIENT_DATA']
- If using DUPLICATE, you need to set dupeid to the ID of the other bug.
- If using WORKSFORME/CURRENTRELEASE/RAWHIDE/ERRATA/UPSTREAM/NEXTRELEASE
- you can (and should) set 'new_fixed_in' to a string representing the
- version that fixes the bug.
- You can optionally add a comment while closing the bug. Set 'isprivate'
- to True if you want that comment to be private.
- '''
- ignore = private_in_it
- ignore = nomail
-
- vals = self.bugzilla.build_update(comment=comment,
- comment_private=isprivate,
- resolution=resolution,
- dupe_of=dupeid,
- fixed_in=fixedin,
- status="CLOSED")
- log.debug("close: update=%s", vals)
-
- return self.bugzilla.update_bugs(self.bug_id, vals)
-
-
- #####################
- # Modify bug emails #
- #####################
-
- def setassignee(self, assigned_to=None, reporter=None,
- qa_contact=None, comment=None):
- '''
- Set any of the assigned_to or qa_contact fields to a new
- bugzilla account, with an optional comment, e.g.
- setassignee(assigned_to='wwoods at redhat.com')
- setassignee(qa_contact='wwoods at redhat.com', comment='wwoods QA ftw')
-
- You must set at least one of the two assignee fields, or this method
- will throw a ValueError.
-
- Returns [bug_id, mailresults].
- '''
- if reporter:
- raise ValueError("reporter can not be changed")
-
- if not (assigned_to or qa_contact):
- raise ValueError("You must set one of assigned_to "
- " or qa_contact")
-
- vals = self.bugzilla.build_update(assigned_to=assigned_to,
- qa_contact=qa_contact,
- comment=comment)
- log.debug("setassignee: update=%s", vals)
-
- return self.bugzilla.update_bugs(self.bug_id, vals)
-
- def addcc(self, cclist, comment=None):
- '''
- Adds the given email addresses to the CC list for this bug.
- cclist: list of email addresses (strings)
- comment: optional comment to add to the bug
- '''
- vals = self.bugzilla.build_update(comment=comment,
- cc_add=cclist)
- log.debug("addcc: update=%s", vals)
-
- return self.bugzilla.update_bugs(self.bug_id, vals)
-
- def deletecc(self, cclist, comment=None):
- '''
- Removes the given email addresses from the CC list for this bug.
- '''
- vals = self.bugzilla.build_update(comment=comment,
- cc_remove=cclist)
- log.debug("deletecc: update=%s", vals)
-
- return self.bugzilla.update_bugs(self.bug_id, vals)
-
-
- ###############
- # Add comment #
- ###############
-
- def addcomment(self, comment, private=False,
- timestamp=None, worktime=None, bz_gid=None):
- '''
- Add the given comment to this bug. Set private to True to mark this
- comment as private.
- '''
- ignore = timestamp
- ignore = bz_gid
- ignore = worktime
-
- vals = self.bugzilla.build_update(comment=comment,
- comment_private=private)
- log.debug("addcomment: update=%s", vals)
-
- return self.bugzilla.update_bugs(self.bug_id, vals)
-
-
- ##########################
- # Get/set bug whiteboard #
- ##########################
-
- def _dowhiteboard(self, text, which, action, comment, private):
- '''
- Update the whiteboard given by 'which' for the given bug.
- '''
- if which not in ["status", "qa", "devel", "internal"]:
- raise ValueError("Unknown whiteboard type '%s'" % which)
-
- if not which.endswith('_whiteboard'):
- which = which + '_whiteboard'
- if which == "status_whiteboard":
- which = "whiteboard"
-
- if action != 'overwrite':
- wb = getattr(self, which, '').strip()
- tags = wb.split()
-
- sep = " "
- for t in tags:
- if t.endswith(","):
- sep = ", "
-
- if action == 'prepend':
- text = text + sep + wb
- elif action == 'append':
- text = wb + sep + text
- else:
- raise ValueError("Unknown whiteboard action '%s'" % action)
-
- updateargs = {which: text}
- vals = self.bugzilla.build_update(comment=comment,
- comment_private=private,
- **updateargs)
- log.debug("_updatewhiteboard: update=%s", vals)
-
- self.bugzilla.update_bugs(self.bug_id, vals)
-
-
- def appendwhiteboard(self, text, which='status',
- comment=None, private=False):
- '''Append the given text (with a space before it) to the given
- whiteboard. Defaults to using status_whiteboard.'''
- self._dowhiteboard(text, which, "append", comment, private)
-
- def prependwhiteboard(self, text, which='status',
- comment=None, private=False):
- '''Prepend the given text (with a space following it) to the given
- whiteboard. Defaults to using status_whiteboard.'''
- self._dowhiteboard(text, which, "prepend", comment, private)
-
- def setwhiteboard(self, text, which='status',
- comment=None, private=False):
- '''Overwrites the contents of the given whiteboard with the given text.
- Defaults to using status_whiteboard.'''
- self._dowhiteboard(text, which, "overwrite", comment, private)
-
- def addtag(self, tag, which='status'):
- '''Adds the given tag to the given bug.'''
- whiteboard = self.getwhiteboard(which)
- if whiteboard:
- self.appendwhiteboard(tag, which)
- else:
- self.setwhiteboard(tag, which)
-
- def gettags(self, which='status'):
- '''Get a list of tags (basically just whitespace-split the given
- whiteboard)'''
- return self.getwhiteboard(which).split()
-
- def deltag(self, tag, which='status'):
- '''Removes the given tag from the given bug.'''
- tags = self.gettags(which)
- for t in tags:
- if t.strip(",") == tag:
- tags.remove(t)
- self.setwhiteboard(' '.join(tags), which)
-
-
- #####################
- # Get/Set bug flags #
- #####################
-
- def get_flag_type(self, name):
- """
- Return flag_type information for a specific flag
-
- Older RHBugzilla returned a lot more info here, but it was
- non-upstream and is now gone.
- """
- for t in self.flags:
- if t['name'] == name:
- return t
- return None
-
- def get_flags(self, name):
- """
- Return flag value information for a specific flag
- """
- ft = self.get_flag_type(name)
- if not ft:
- return None
-
- return [ft]
-
- def get_flag_status(self, name):
- """
- Return a flag 'status' field
-
- This method works only for simple flags that have only a 'status' field
- with no "requestee" info, and no multiple values. For more complex
- flags, use get_flags() to get extended flag value information.
- """
- f = self.get_flags(name)
- if not f:
- return None
-
- # This method works only for simple flags that have only one
- # value set.
- assert len(f) <= 1
-
- return f[0]['status']
-
-
- ########################
- # Experimental methods #
- ########################
-
- def get_history(self):
- '''
- Experimental. Get the history of changes for this bug.
- '''
- return self.bugzilla.bugs_history([self.bug_id])
-
- ######################
- # Deprecated methods #
- ######################
-
- def getwhiteboard(self, which='status'):
- '''
- Deprecated. Use bug.qa_whiteboard, bug.devel_whiteboard, etc.
- '''
- return getattr(self, "%s_whiteboard" % which)
-
- def updateflags(self, flags):
- '''
- Deprecated, use bugzilla.update_flags() directly
- '''
- flaglist = []
- for key, value in flags.items():
- flaglist.append({"name": key, "status": value})
- return self.bugzilla.update_flags(self.bug_id, flaglist)
-
-
-class _User(object):
- '''Container object for a bugzilla User.
-
- :arg bugzilla: Bugzilla instance that this User belongs to.
- Rest of the params come straight from User.get()
- '''
- def __init__(self, bugzilla, **kwargs):
- self.bugzilla = bugzilla
- self.__userid = kwargs.get('id')
- self.__name = kwargs.get('name')
-
- self.__email = kwargs.get('email', self.__name)
- self.__can_login = kwargs.get('can_login', False)
-
- self.real_name = kwargs.get('real_name', None)
- self.password = None
-
- self.groups = kwargs.get('groups', {})
- self.groupnames = []
- for g in self.groups:
- if "name" in g:
- self.groupnames.append(g["name"])
- self.groupnames.sort()
-
-
- ########################
- # Read-only attributes #
- ########################
-
- # We make these properties so that the user cannot set them. They are
- # unaffected by the update() method so it would be misleading to let them
- # be changed.
- @property
- def userid(self):
- return self.__userid
-
- @property
- def email(self):
- return self.__email
-
- @property
- def can_login(self):
- return self.__can_login
-
- # name is a key in some methods. Mark it dirty when we change it #
- @property
- def name(self):
- return self.__name
-
- def refresh(self):
- """
- Update User object with latest info from bugzilla
- """
- newuser = self.bugzilla.getuser(self.email)
- self.__dict__.update(newuser.__dict__)
-
- def updateperms(self, action, groups):
- '''
- A method to update the permissions (group membership) of a bugzilla
- user.
-
- :arg action: add, remove, or set
- :arg groups: list of groups to be added to (i.e. ['fedora_contrib'])
- '''
- self.bugzilla.updateperms(self.name, action, groups)
diff --git a/ciabot/bugzilla/bugzilla3.py b/ciabot/bugzilla/bugzilla3.py
deleted file mode 100644
index efacdea..0000000
--- a/ciabot/bugzilla/bugzilla3.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# bugzilla3.py - a Python interface to Bugzilla 3.x using xmlrpclib.
-#
-# Copyright (C) 2008, 2009 Red Hat Inc.
-# Author: Will Woods <wwoods at redhat.com>
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the
-# Free Software Foundation; either version 2 of the License, or (at your
-# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
-# the full text of the license.
-
-from bugzilla.base import BugzillaBase
-
-
-class Bugzilla3(BugzillaBase):
- bz_ver_major = 3
- bz_ver_minor = 0
-
-
-class Bugzilla32(Bugzilla3):
- bz_ver_minor = 2
-
-
-class Bugzilla34(Bugzilla32):
- bz_ver_minor = 4
-
-
-class Bugzilla36(Bugzilla34):
- bz_ver_minor = 6
-
- def _getbugfields(self):
- '''Get the list of valid fields for Bug objects'''
- r = self._proxy.Bug.fields({'include_fields': ['name']})
- return [f['name'] for f in r['fields']]
diff --git a/ciabot/bugzilla/bugzilla4.py b/ciabot/bugzilla/bugzilla4.py
deleted file mode 100644
index 7f5e127..0000000
--- a/ciabot/bugzilla/bugzilla4.py
+++ /dev/null
@@ -1,47 +0,0 @@
-#
-# Copyright (C) 2008-2012 Red Hat Inc.
-# Author: Michal Novotny <minovotn at redhat.com>
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the
-# Free Software Foundation; either version 2 of the License, or (at your
-# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
-# the full text of the license.
-
-from bugzilla.bugzilla3 import Bugzilla36
-
-
-class Bugzilla4(Bugzilla36):
- bz_ver_major = 4
- bz_ver_minor = 0
-
-
- #################
- # Query Methods #
- #################
-
- def build_query(self, **kwargs):
- query = Bugzilla36.build_query(self, **kwargs)
-
- # 'include_fields' only available for Bugzilla4+
- include_fields = self._convert_include_field_list(
- kwargs.pop('include_fields', None))
- if include_fields:
- if 'id' not in include_fields:
- include_fields.append('id')
- query["include_fields"] = include_fields
-
- exclude_fields = self._convert_include_field_list(
- kwargs.pop('exclude_fields', None))
- if exclude_fields:
- query["exclude_fields"] = exclude_fields
-
- return query
-
-
-class Bugzilla42(Bugzilla4):
- bz_ver_minor = 2
-
-
-class Bugzilla44(Bugzilla42):
- bz_ver_minor = 4
diff --git a/ciabot/bugzilla/rhbugzilla.py b/ciabot/bugzilla/rhbugzilla.py
deleted file mode 100644
index 4c6c7e6..0000000
--- a/ciabot/bugzilla/rhbugzilla.py
+++ /dev/null
@@ -1,368 +0,0 @@
-# rhbugzilla.py - a Python interface to Red Hat Bugzilla using xmlrpclib.
-#
-# Copyright (C) 2008-2012 Red Hat Inc.
-# Author: Will Woods <wwoods at redhat.com>
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the
-# Free Software Foundation; either version 2 of the License, or (at your
-# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
-# the full text of the license.
-
-
-from bugzilla import log
-from bugzilla.bugzilla4 import Bugzilla44 as _parent
-
-
-class RHBugzilla(_parent):
- '''
- Bugzilla class for connecting Red Hat's forked bugzilla instance,
- bugzilla.redhat.com
-
- Historically this class used many more non-upstream methods, but
- in 2012 RH started dropping most of its custom bits. By that time,
- upstream BZ had most of the important functionality.
-
- Much of the remaining code here is just trying to keep things operating
- in python-bugzilla back compatible manner.
-
- This class was written using bugzilla.redhat.com's API docs:
- https://bugzilla.redhat.com/docs/en/html/api/
- '''
-
- def __init__(self, *args, **kwargs):
- """
- @rhbz_back_compat: If True, convert parameters to the format they were
- in prior RHBZ upgrade in June 2012. Mostly this replaces lists
- with comma separated strings, and alters groups and flags.
- Default is False. Please don't use this in new code, just update
- your scripts.
- @multicall: Unused nowadays, will be removed in the future
- """
- # 'multicall' is no longer used, just ignore it
- multicall = kwargs.pop("multicall", None)
- self.rhbz_back_compat = bool(kwargs.pop("rhbz_back_compat", False))
-
- if multicall is not None:
- log.warn("multicall is unused and will be removed in a "
- "future release.")
-
- if self.rhbz_back_compat:
- log.warn("rhbz_back_compat will be removed in a future release.")
-
- _parent.__init__(self, *args, **kwargs)
-
- def _add_both_alias(newname, origname):
- self._add_field_alias(newname, origname, is_api=False)
- self._add_field_alias(origname, newname, is_bug=False)
-
- _add_both_alias('fixed_in', 'cf_fixed_in')
- _add_both_alias('qa_whiteboard', 'cf_qa_whiteboard')
- _add_both_alias('devel_whiteboard', 'cf_devel_whiteboard')
- _add_both_alias('internal_whiteboard', 'cf_internal_whiteboard')
-
- self._add_field_alias('component', 'components', is_bug=False)
- self._add_field_alias('version', 'versions', is_bug=False)
- self._add_field_alias('sub_component', 'sub_components', is_bug=False)
-
- # flags format isn't exactly the same but it's the closest approx
- self._add_field_alias('flags', 'flag_types')
-
- self._getbug_extra_fields = self._getbug_extra_fields + [
- "comments", "description",
- "external_bugs", "flags", "sub_components",
- "tags",
- ]
- self._supports_getbug_extra_fields = True
-
-
- ######################
- # Bug update methods #
- ######################
-
- def build_update(self, **kwargs):
- adddict = {}
-
- def pop(key, destkey):
- val = kwargs.pop(key, None)
- if val is None:
- return
- adddict[destkey] = val
-
- def get_sub_component():
- val = kwargs.pop("sub_component", None)
- if val is None:
- return
-
- if type(val) is not dict:
- component = self._listify(kwargs.get("component"))
- if not component:
- raise ValueError("component must be specified if "
- "specifying sub_component")
- val = {component[0]: val}
- adddict["sub_components"] = val
-
- pop("fixed_in", "cf_fixed_in")
- pop("qa_whiteboard", "cf_qa_whiteboard")
- pop("devel_whiteboard", "cf_devel_whiteboard")
- pop("internal_whiteboard", "cf_internal_whiteboard")
-
- get_sub_component()
-
- vals = _parent.build_update(self, **kwargs)
- vals.update(adddict)
-
- return vals
-
-
- #################
- # Query methods #
- #################
-
- def pre_translation(self, query):
- '''Translates the query for possible aliases'''
- old = query.copy()
-
- if 'bug_id' in query:
- if type(query['bug_id']) is not list:
- query['id'] = query['bug_id'].split(',')
- else:
- query['id'] = query['bug_id']
- del query['bug_id']
-
- if 'component' in query:
- if type(query['component']) is not list:
- query['component'] = query['component'].split(',')
-
- if 'include_fields' not in query and 'column_list' not in query:
- return
-
- if 'include_fields' not in query:
- query['include_fields'] = []
- if 'column_list' in query:
- query['include_fields'] = query['column_list']
- del query['column_list']
-
- # We need to do this for users here for users that
- # don't call build_query
- self._convert_include_field_list(query['include_fields'])
-
- if old != query:
- log.debug("RHBugzilla pretranslated query to: %s", query)
-
- def post_translation(self, query, bug):
- '''
- Convert the results of getbug back to the ancient RHBZ value
- formats
- '''
- ignore = query
-
- # RHBZ _still_ returns component and version as lists, which
- # deviates from upstream. Copy the list values to components
- # and versions respectively.
- if 'component' in bug and "components" not in bug:
- val = bug['component']
- bug['components'] = type(val) is list and val or [val]
- bug['component'] = bug['components'][0]
-
- if 'version' in bug and "versions" not in bug:
- val = bug['version']
- bug['versions'] = type(val) is list and val or [val]
- bug['version'] = bug['versions'][0]
-
- # sub_components isn't too friendly of a format, add a simpler
- # sub_component value
- if 'sub_components' in bug and 'sub_component' not in bug:
- val = bug['sub_components']
- bug['sub_component'] = ""
- if type(val) is dict:
- values = []
- for vallist in val.values():
- values += vallist
- bug['sub_component'] = " ".join(values)
-
- if not self.rhbz_back_compat:
- return
-
- if 'flags' in bug and type(bug["flags"]) is list:
... etc. - the rest is truncated
More information about the Libreoffice-commits
mailing list