Blame bugzilla/_cli.py

Packit 436967
#!/usr/libexec/platform-python
Packit 436967
#
Packit 436967
# bugzilla - a commandline frontend for the python bugzilla module
Packit 436967
#
Packit 436967
# Copyright (C) 2007-2017 Red Hat Inc.
Packit 436967
# Author: Will Woods <wwoods@redhat.com>
Packit 436967
# Author: Cole Robinson <crobinso@redhat.com>
Packit 436967
#
Packit 436967
# This program is free software; you can redistribute it and/or modify it
Packit 436967
# under the terms of the GNU General Public License as published by the
Packit 436967
# Free Software Foundation; either version 2 of the License, or (at your
Packit 436967
# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
Packit 436967
# the full text of the license.
Packit 436967
Packit 436967
from __future__ import print_function
Packit 436967
Packit 436967
import locale
Packit 436967
from logging import getLogger, DEBUG, INFO, WARN, StreamHandler, Formatter
Packit 436967
import argparse
Packit 436967
import os
Packit 436967
import re
Packit 436967
import socket
Packit 436967
import sys
Packit 436967
import tempfile
Packit 436967
Packit 436967
# pylint: disable=import-error
Packit 436967
if sys.version_info[0] >= 3:
Packit 436967
    # pylint: disable=no-name-in-module,redefined-builtin
Packit 436967
    from xmlrpc.client import Fault, ProtocolError
Packit 436967
    from urllib.parse import urlparse
Packit 436967
    basestring = (str, bytes)
Packit 436967
else:
Packit 436967
    from xmlrpclib import Fault, ProtocolError
Packit 436967
    from urlparse import urlparse
Packit 436967
# pylint: enable=import-error
Packit 436967
Packit 436967
import requests.exceptions
Packit 436967
Packit 436967
import bugzilla
Packit 436967
Packit 436967
DEFAULT_BZ = 'https://bugzilla.redhat.com/xmlrpc.cgi'
Packit 436967
Packit 436967
format_field_re = re.compile("%{([a-z0-9_]+)(?::([^}]*))?}")
Packit 436967
Packit 436967
log = getLogger(bugzilla.__name__)
Packit 436967
Packit 436967
Packit 436967
################
Packit 436967
# Util helpers #
Packit 436967
################
Packit 436967
Packit 436967
def _is_unittest():
Packit 436967
    return bool(os.getenv("__BUGZILLA_UNITTEST"))
Packit 436967
Packit 436967
Packit 436967
def _is_unittest_debug():
Packit 436967
    return bool(os.getenv("__BUGZILLA_UNITTEST_DEBUG"))
Packit 436967
Packit 436967
Packit 436967
def to_encoding(ustring):
Packit 436967
    string = ''
Packit 436967
    if isinstance(ustring, basestring):
Packit 436967
        string = ustring
Packit 436967
    elif ustring is not None:
Packit 436967
        string = str(ustring)
Packit 436967
Packit 436967
    if sys.version_info[0] >= 3:
Packit 436967
        return string
Packit 436967
Packit 436967
    preferred = locale.getpreferredencoding()
Packit 436967
    if _is_unittest():
Packit 436967
        preferred = "UTF-8"
Packit 436967
    return string.encode(preferred, 'replace')
Packit 436967
Packit 436967
Packit 436967
def open_without_clobber(name, *args):
Packit 436967
    """
Packit 436967
    Try to open the given file with the given mode; if that filename exists,
Packit 436967
    try "name.1", "name.2", etc. until we find an unused filename.
Packit 436967
    """
Packit 436967
    fd = None
Packit 436967
    count = 1
Packit 436967
    orig_name = name
Packit 436967
    while fd is None:
Packit 436967
        try:
Packit 436967
            fd = os.open(name, os.O_CREAT | os.O_EXCL, 0o666)
Packit 436967
        except OSError as err:
Packit 436967
            if err.errno == os.errno.EEXIST:
Packit 436967
                name = "%s.%i" % (orig_name, count)
Packit 436967
                count += 1
Packit 436967
            else:
Packit 436967
                raise IOError(err.errno, err.strerror, err.filename)
Packit 436967
    fobj = open(name, *args)
Packit 436967
    if fd != fobj.fileno():
Packit 436967
        os.close(fd)
Packit 436967
    return fobj
Packit 436967
Packit 436967
Packit 436967
def get_default_url():
Packit 436967
    """
Packit 436967
    Grab a default URL from bugzillarc [DEFAULT] url=X
Packit 436967
    """
Packit 436967
    from bugzilla.base import _open_bugzillarc
Packit 436967
    cfg = _open_bugzillarc()
Packit 436967
    if cfg:
Packit 436967
        cfgurl = cfg.defaults().get("url", None)
Packit 436967
        if cfgurl is not None:
Packit 436967
            log.debug("bugzillarc: found cli url=%s", cfgurl)
Packit 436967
            return cfgurl
Packit 436967
    return DEFAULT_BZ
Packit 436967
Packit 436967
Packit 436967
def setup_logging(debug, verbose):
Packit 436967
    handler = StreamHandler(sys.stderr)
Packit 436967
    handler.setFormatter(Formatter(
Packit 436967
        "[%(asctime)s] %(levelname)s (%(module)s:%(lineno)d) %(message)s",
Packit 436967
        "%H:%M:%S"))
Packit 436967
    log.addHandler(handler)
Packit 436967
Packit 436967
    if debug:
Packit 436967
        log.setLevel(DEBUG)
Packit 436967
    elif verbose:
Packit 436967
        log.setLevel(INFO)
Packit 436967
    else:
Packit 436967
        log.setLevel(WARN)
Packit 436967
Packit 436967
    if _is_unittest_debug():
Packit 436967
        log.setLevel(DEBUG)
Packit 436967
Packit 436967
Packit 436967
##################
Packit 436967
# Option parsing #
Packit 436967
##################
Packit 436967
Packit 436967
def _setup_root_parser():
Packit 436967
    epilog = 'Try "bugzilla COMMAND --help" for command-specific help.'
Packit 436967
    p = argparse.ArgumentParser(epilog=epilog)
Packit 436967
Packit 436967
    default_url = get_default_url()
Packit 436967
Packit 436967
    # General bugzilla connection options
Packit 436967
    p.add_argument('--bugzilla', default=default_url,
Packit 436967
            help="bugzilla XMLRPC URI. default: %s" % default_url)
Packit 436967
    p.add_argument("--nosslverify", dest="sslverify",
Packit 436967
                 action="store_false", default=True,
Packit 436967
                 help="Don't error on invalid bugzilla SSL certificate")
Packit 436967
    p.add_argument('--cert',
Packit 436967
            help="client side certificate file needed by the webserver")
Packit 436967
Packit 436967
    p.add_argument('--login', action="store_true",
Packit 436967
        help='Run interactive "login" before performing the '
Packit 436967
             'specified command.')
Packit 436967
    p.add_argument('--username', help="Log in with this username")
Packit 436967
    p.add_argument('--password', help="Log in with this password")
Packit 436967
Packit 436967
    p.add_argument('--ensure-logged-in', action="store_true",
Packit 436967
        help="Raise an error if we aren't logged in to bugzilla. "
Packit 436967
             "Consider using this if you are depending on "
Packit 436967
             "cached credentials, to ensure that when they expire the "
Packit 436967
             "tool errors, rather than subtly change output.")
Packit 436967
    p.add_argument('--no-cache-credentials',
Packit 436967
        action='store_false', default=True, dest='cache_credentials',
Packit 436967
        help="Don't save any bugzilla cookies or tokens to disk, and "
Packit 436967
             "don't use any pre-existing credentials.")
Packit 436967
Packit 436967
    p.add_argument('--cookiefile', default=None,
Packit 436967
            help="cookie file to use for bugzilla authentication")
Packit 436967
    p.add_argument('--tokenfile', default=None,
Packit 436967
            help="token file to use for bugzilla authentication")
Packit 436967
Packit 436967
    p.add_argument('--verbose', action='store_true',
Packit 436967
            help="give more info about what's going on")
Packit 436967
    p.add_argument('--debug', action='store_true',
Packit 436967
            help="output bunches of debugging info")
Packit 436967
    p.add_argument('--version', action='version',
Packit 436967
                   version=bugzilla.__version__)
Packit 436967
Packit 436967
    # Allow user to specify BZClass to initialize. Kinda weird for the
Packit 436967
    # CLI, I'd rather people file bugs about this so we can fix our detection.
Packit 436967
    # So hide it from the help output but keep it for back compat
Packit 436967
    p.add_argument('--bztype', default='auto', help=argparse.SUPPRESS)
Packit 436967
Packit 436967
    return p
Packit 436967
Packit 436967
Packit 436967
def _parser_add_output_options(p):
Packit 436967
    outg = p.add_argument_group("Output format options")
Packit 436967
    outg.add_argument('--full', action='store_const', dest='output',
Packit 436967
            const='full', default='normal',
Packit 436967
            help="output detailed bug info")
Packit 436967
    outg.add_argument('-i', '--ids', action='store_const', dest='output',
Packit 436967
            const='ids', help="output only bug IDs")
Packit 436967
    outg.add_argument('-e', '--extra', action='store_const',
Packit 436967
            dest='output', const='extra',
Packit 436967
            help="output additional bug information "
Packit 436967
                 "(keywords, Whiteboards, etc.)")
Packit 436967
    outg.add_argument('--oneline', action='store_const', dest='output',
Packit 436967
            const='oneline',
Packit 436967
            help="one line summary of the bug (useful for scripts)")
Packit 436967
    outg.add_argument('--raw', action='store_const', dest='output',
Packit 436967
            const='raw', help="raw output of the bugzilla contents")
Packit 436967
    outg.add_argument('--outputformat',
Packit 436967
            help="Print output in the form given. "
Packit 436967
                 "You can use RPM-style tags that match bug "
Packit 436967
                 "fields, e.g.: '%%{id}: %%{summary}'. See the man page "
Packit 436967
                 "section 'Output options' for more details.")
Packit 436967
Packit 436967
Packit 436967
def _parser_add_bz_fields(rootp, command):
Packit 436967
    cmd_new = (command == "new")
Packit 436967
    cmd_query = (command == "query")
Packit 436967
    cmd_modify = (command == "modify")
Packit 436967
    if cmd_new:
Packit 436967
        comment_help = "Set initial bug comment/description"
Packit 436967
    elif cmd_query:
Packit 436967
        comment_help = "Search all bug comments"
Packit 436967
    else:
Packit 436967
        comment_help = "Add new bug comment"
Packit 436967
Packit 436967
    p = rootp.add_argument_group("Standard bugzilla options")
Packit 436967
Packit 436967
    p.add_argument('-p', '--product', help="Product name")
Packit 436967
    p.add_argument('-v', '--version', help="Product version")
Packit 436967
    p.add_argument('-c', '--component', help="Component name")
Packit 436967
    p.add_argument('-t', '--summary', '--short_desc', help="Bug summary")
Packit 436967
    p.add_argument('-l', '--comment', '--long_desc', help=comment_help)
Packit 436967
    if not cmd_query:
Packit 436967
        p.add_argument("--comment-tag", action="append",
Packit 436967
                help="Comment tag for the new comment")
Packit 436967
    p.add_argument("--sub-component", action="append",
Packit 436967
        help="RHBZ sub component field")
Packit 436967
    p.add_argument('-o', '--os', help="Operating system")
Packit 436967
    p.add_argument('--arch', help="Arch this bug occurs on")
Packit 436967
    p.add_argument('-x', '--severity', help="Bug severity")
Packit 436967
    p.add_argument('-z', '--priority', help="Bug priority")
Packit 436967
    p.add_argument('--alias', help='Bug alias (name)')
Packit 436967
    p.add_argument('-s', '--status', '--bug_status',
Packit 436967
        help='Bug status (NEW, ASSIGNED, etc.)')
Packit 436967
    p.add_argument('-u', '--url', help="URL field")
Packit 436967
    p.add_argument('-m', '--target_milestone', help="Target milestone")
Packit 436967
    p.add_argument('--target_release', help="RHBZ Target release")
Packit 436967
Packit 436967
    p.add_argument('--blocked', action="append",
Packit 436967
        help="Bug IDs that this bug blocks")
Packit 436967
    p.add_argument('--dependson', action="append",
Packit 436967
        help="Bug IDs that this bug depends on")
Packit 436967
    p.add_argument('--keywords', action="append",
Packit 436967
        help="Bug keywords")
Packit 436967
    p.add_argument('--groups', action="append",
Packit 436967
        help="Which user groups can view this bug")
Packit 436967
Packit 436967
    p.add_argument('--cc', action="append", help="CC list")
Packit 436967
    p.add_argument('-a', '--assigned_to', '--assignee', help="Bug assignee")
Packit 436967
    p.add_argument('-q', '--qa_contact', help='QA contact')
Packit 436967
Packit 436967
    if not cmd_new:
Packit 436967
        p.add_argument('-f', '--flag', action='append',
Packit 436967
            help="Bug flags state. Ex:\n"
Packit 436967
                 "  --flag needinfo?\n"
Packit 436967
                 "  --flag dev_ack+ \n"
Packit 436967
                 "  clear with --flag needinfoX")
Packit 436967
        p.add_argument("--tags", action="append",
Packit 436967
                help="Tags/Personal Tags field.")
Packit 436967
Packit 436967
        p.add_argument('-w', "--whiteboard", '--status_whiteboard',
Packit 436967
            action="append", help='Whiteboard field')
Packit 436967
        p.add_argument("--devel_whiteboard", action="append",
Packit 436967
            help='RHBZ devel whiteboard field')
Packit 436967
        p.add_argument("--internal_whiteboard", action="append",
Packit 436967
            help='RHBZ internal whiteboard field')
Packit 436967
        p.add_argument("--qa_whiteboard", action="append",
Packit 436967
            help='RHBZ QA whiteboard field')
Packit 436967
        p.add_argument('-F', '--fixed_in',
Packit 436967
            help="RHBZ 'Fixed in version' field")
Packit 436967
Packit 436967
    # Put this at the end, so it sticks out more
Packit 436967
    p.add_argument('--field',
Packit 436967
        metavar="FIELD=VALUE", action="append", dest="fields",
Packit 436967
        help="Manually specify a bugzilla XMLRPC field. FIELD is "
Packit 436967
        "the raw name used by the bugzilla instance. For example if your "
Packit 436967
        "bugzilla instance has a custom field cf_my_field, do:\n"
Packit 436967
        "  --field cf_my_field=VALUE")
Packit 436967
Packit 436967
    # Used by unit tests, not for end user consumption
Packit 436967
    p.add_argument('--__test-return-result', action="store_true",
Packit 436967
        dest="test_return_result", help=argparse.SUPPRESS)
Packit 436967
Packit 436967
    if not cmd_modify:
Packit 436967
        _parser_add_output_options(rootp)
Packit 436967
Packit 436967
Packit 436967
def _setup_action_new_parser(subparsers):
Packit 436967
    description = ("Create a new bug report. "
Packit 436967
        "--product, --component, --version, --summary, and --comment "
Packit 436967
        "must be specified. "
Packit 436967
        "Options that take multiple values accept comma separated lists, "
Packit 436967
        "including --cc, --blocks, --dependson, --groups, and --keywords.")
Packit 436967
    p = subparsers.add_parser("new", description=description)
Packit 436967
Packit 436967
    _parser_add_bz_fields(p, "new")
Packit 436967
Packit 436967
Packit 436967
def _setup_action_query_parser(subparsers):
Packit 436967
    description = ("List bug reports that match the given criteria. "
Packit 436967
        "Certain options can accept a comma separated list to query multiple "
Packit 436967
        "values, including --status, --component, --product, --version, --id.")
Packit 436967
    epilog = ("Note: querying via explicit command line options will only "
Packit 436967
        "get you so far. See the --from-url option for a way to use powerful "
Packit 436967
        "Web UI queries from the command line.")
Packit 436967
    p = subparsers.add_parser("query",
Packit 436967
        description=description, epilog=epilog)
Packit 436967
Packit 436967
    _parser_add_bz_fields(p, "query")
Packit 436967
Packit 436967
    g = p.add_argument_group("'query' specific options")
Packit 436967
    g.add_argument('-b', '--id', '--bug_id',
Packit 436967
        help="specify individual bugs by IDs, separated with commas")
Packit 436967
    g.add_argument('-r', '--reporter',
Packit 436967
        help="Email: search reporter email for given address")
Packit 436967
    g.add_argument('--quicksearch',
Packit 436967
        help="Search using bugzilla's quicksearch functionality.")
Packit 436967
    g.add_argument('--savedsearch',
Packit 436967
        help="Name of a bugzilla saved search. If you don't own this "
Packit 436967
            "saved search, you must passed --savedsearch_sharer_id.")
Packit 436967
    g.add_argument('--savedsearch-sharer-id',
Packit 436967
        help="Owner ID of the --savedsearch. You can get this ID from "
Packit 436967
            "the URL bugzilla generates when running the saved search "
Packit 436967
            "from the web UI.")
Packit 436967
Packit 436967
    # Keep this at the end so it sticks out more
Packit 436967
    g.add_argument('--from-url', metavar="WEB_QUERY_URL",
Packit 436967
        help="Make a working query via bugzilla's 'Advanced search' web UI, "
Packit 436967
             "grab the url from your browser (the string with query.cgi or "
Packit 436967
             "buglist.cgi in it), and --from-url will run it via the "
Packit 436967
             "bugzilla API. Don't forget to quote the string! "
Packit 436967
             "This only works for Bugzilla 5 and Red Hat bugzilla")
Packit 436967
Packit 436967
    # Deprecated options
Packit 436967
    p.add_argument('-E', '--emailtype', help=argparse.SUPPRESS)
Packit 436967
    p.add_argument('--components_file', help=argparse.SUPPRESS)
Packit 436967
    p.add_argument('-U', '--url_type',
Packit 436967
            help=argparse.SUPPRESS)
Packit 436967
    p.add_argument('-K', '--keywords_type',
Packit 436967
            help=argparse.SUPPRESS)
Packit 436967
    p.add_argument('-W', '--status_whiteboard_type',
Packit 436967
            help=argparse.SUPPRESS)
Packit 436967
    p.add_argument('-B', '--booleantype',
Packit 436967
            help=argparse.SUPPRESS)
Packit 436967
    p.add_argument('--boolean_query', action="append",
Packit 436967
            help=argparse.SUPPRESS)
Packit 436967
    p.add_argument('--fixed_in_type', help=argparse.SUPPRESS)
Packit 436967
Packit 436967
Packit 436967
def _setup_action_info_parser(subparsers):
Packit 436967
    description = ("List products or component information about the "
Packit 436967
        "bugzilla server.")
Packit 436967
    p = subparsers.add_parser("info", description=description)
Packit 436967
Packit 436967
    x = p.add_mutually_exclusive_group(required=True)
Packit 436967
    x.add_argument('-p', '--products', action='store_true',
Packit 436967
            help='Get a list of products')
Packit 436967
    x.add_argument('-c', '--components', metavar="PRODUCT",
Packit 436967
            help='List the components in the given product')
Packit 436967
    x.add_argument('-o', '--component_owners', metavar="PRODUCT",
Packit 436967
            help='List components (and their owners)')
Packit 436967
    x.add_argument('-v', '--versions', metavar="PRODUCT",
Packit 436967
            help='List the versions for the given product')
Packit 436967
    p.add_argument('--active-components', action="store_true",
Packit 436967
            help='Only show active components. Combine with --components*')
Packit 436967
Packit 436967
Packit 436967
Packit 436967
def _setup_action_modify_parser(subparsers):
Packit 436967
    usage = ("bugzilla modify [options] BUGID [BUGID...]\n"
Packit 436967
        "Fields that take multiple values have a special input format.\n"
Packit 436967
        "Append:    --cc=foo@example.com\n"
Packit 436967
        "Overwrite: --cc==foo@example.com\n"
Packit 436967
        "Remove:    --cc=-foo@example.com\n"
Packit 436967
        "Options that accept this format: --cc, --blocked, --dependson,\n"
Packit 436967
        "    --groups, --tags, whiteboard fields.")
Packit 436967
    p = subparsers.add_parser("modify", usage=usage)
Packit 436967
Packit 436967
    _parser_add_bz_fields(p, "modify")
Packit 436967
Packit 436967
    g = p.add_argument_group("'modify' specific options")
Packit 436967
    g.add_argument("ids", nargs="+", help="Bug IDs to modify")
Packit 436967
    g.add_argument('-k', '--close', metavar="RESOLUTION",
Packit 436967
        help='Close with the given resolution (WONTFIX, NOTABUG, etc.)')
Packit 436967
    g.add_argument('-d', '--dupeid', metavar="ORIGINAL",
Packit 436967
        help='ID of original bug. Implies --close DUPLICATE')
Packit 436967
    g.add_argument('--private', action='store_true', default=False,
Packit 436967
        help='Mark new comment as private')
Packit 436967
    g.add_argument('--reset-assignee', action="store_true",
Packit 436967
        help='Reset assignee to component default')
Packit 436967
    g.add_argument('--reset-qa-contact', action="store_true",
Packit 436967
        help='Reset QA contact to component default')
Packit 436967
Packit 436967
Packit 436967
def _setup_action_attach_parser(subparsers):
Packit 436967
    usage = """
Packit 436967
bugzilla attach --file=FILE --desc=DESC [--type=TYPE] BUGID [BUGID...]
Packit 436967
bugzilla attach --get=ATTACHID --getall=BUGID [...]
Packit 436967
bugzilla attach --type=TYPE BUGID [BUGID...]"""
Packit 436967
    description = "Attach files or download attachments."
Packit 436967
    p = subparsers.add_parser("attach", description=description, usage=usage)
Packit 436967
Packit 436967
    p.add_argument("ids", nargs="*", help="BUGID references")
Packit 436967
    p.add_argument('-f', '--file', metavar="FILENAME",
Packit 436967
            help='File to attach, or filename for data provided on stdin')
Packit 436967
    p.add_argument('-d', '--description', '--summary',
Packit 436967
            metavar="SUMMARY", dest='desc',
Packit 436967
            help="A short summary of the file being attached")
Packit 436967
    p.add_argument('-t', '--type', metavar="MIMETYPE",
Packit 436967
            help="Mime-type for the file being attached")
Packit 436967
    p.add_argument('-g', '--get', metavar="ATTACHID", action="append",
Packit 436967
            default=[], help="Download the attachment with the given ID")
Packit 436967
    p.add_argument("--getall", "--get-all", metavar="BUGID", action="append",
Packit 436967
            default=[], help="Download all attachments on the given bug")
Packit 436967
    p.add_argument('-l', '--comment', '--long_desc',
Packit 436967
            help="Add comment with attachment")
Packit 436967
Packit 436967
Packit 436967
def _setup_action_login_parser(subparsers):
Packit 436967
    usage = 'bugzilla login [username [password]]'
Packit 436967
    description = "Log into bugzilla and save a login cookie or token."
Packit 436967
    p = subparsers.add_parser("login", description=description, usage=usage)
Packit 436967
    p.add_argument("pos_username", nargs="?", help="Optional username",
Packit 436967
            metavar="username")
Packit 436967
    p.add_argument("pos_password", nargs="?", help="Optional password",
Packit 436967
            metavar="password")
Packit 436967
Packit 436967
Packit 436967
def setup_parser():
Packit 436967
    rootparser = _setup_root_parser()
Packit 436967
    subparsers = rootparser.add_subparsers(dest="command")
Packit 436967
    subparsers.required = True
Packit 436967
    _setup_action_new_parser(subparsers)
Packit 436967
    _setup_action_query_parser(subparsers)
Packit 436967
    _setup_action_info_parser(subparsers)
Packit 436967
    _setup_action_modify_parser(subparsers)
Packit 436967
    _setup_action_attach_parser(subparsers)
Packit 436967
    _setup_action_login_parser(subparsers)
Packit 436967
    return rootparser
Packit 436967
Packit 436967
Packit 436967
####################
Packit 436967
# Command routines #
Packit 436967
####################
Packit 436967
Packit 436967
def _merge_field_opts(query, opt, parser):
Packit 436967
    # Add any custom fields if specified
Packit 436967
    if opt.fields is None:
Packit 436967
        return
Packit 436967
Packit 436967
    for f in opt.fields:
Packit 436967
        try:
Packit 436967
            f, v = f.split('=', 1)
Packit 436967
            query[f] = v
Packit 436967
        except Exception:
Packit 436967
            parser.error("Invalid field argument provided: %s" % (f))
Packit 436967
Packit 436967
Packit 436967
def _do_query(bz, opt, parser):
Packit 436967
    q = {}
Packit 436967
Packit 436967
    # Parse preconstructed queries.
Packit 436967
    u = opt.from_url
Packit 436967
    if u:
Packit 436967
        q = bz.url_to_query(u)
Packit 436967
Packit 436967
    if opt.components_file:
Packit 436967
        # Components slurped in from file (one component per line)
Packit 436967
        # This can be made more robust
Packit 436967
        clist = []
Packit 436967
        f = open(opt.components_file, 'r')
Packit 436967
        for line in f.readlines():
Packit 436967
            line = line.rstrip("\n")
Packit 436967
            clist.append(line)
Packit 436967
        opt.component = clist
Packit 436967
Packit 436967
    if opt.status:
Packit 436967
        val = opt.status
Packit 436967
        stat = val
Packit 436967
        if val == 'ALL':
Packit 436967
            # leaving this out should return bugs of any status
Packit 436967
            stat = None
Packit 436967
        elif val == 'DEV':
Packit 436967
            # Alias for all development bug statuses
Packit 436967
            stat = ['NEW', 'ASSIGNED', 'NEEDINFO', 'ON_DEV',
Packit 436967
                'MODIFIED', 'POST', 'REOPENED']
Packit 436967
        elif val == 'QE':
Packit 436967
            # Alias for all QE relevant bug statuses
Packit 436967
            stat = ['ASSIGNED', 'ON_QA', 'FAILS_QA', 'PASSES_QA']
Packit 436967
        elif val == 'EOL':
Packit 436967
            # Alias for EndOfLife bug statuses
Packit 436967
            stat = ['VERIFIED', 'RELEASE_PENDING', 'CLOSED']
Packit 436967
        elif val == 'OPEN':
Packit 436967
            # non-Closed statuses
Packit 436967
            stat = ['NEW', 'ASSIGNED', 'MODIFIED', 'ON_DEV', 'ON_QA',
Packit 436967
                'VERIFIED', 'RELEASE_PENDING', 'POST']
Packit 436967
        opt.status = stat
Packit 436967
Packit 436967
    # Convert all comma separated list parameters to actual lists,
Packit 436967
    # which is what bugzilla wants
Packit 436967
    # According to bugzilla docs, any parameter can be a list, but
Packit 436967
    # let's only do this for options we explicitly mention can be
Packit 436967
    # comma separated.
Packit 436967
    for optname in ["severity", "id", "status", "component",
Packit 436967
                    "priority", "product", "version"]:
Packit 436967
        val = getattr(opt, optname, None)
Packit 436967
        if not isinstance(val, str):
Packit 436967
            continue
Packit 436967
        setattr(opt, optname, val.split(","))
Packit 436967
Packit 436967
    include_fields = None
Packit 436967
    if opt.output == 'raw':
Packit 436967
        # 'raw' always does a getbug() call anyways, so just ask for ID back
Packit 436967
        include_fields = ['id']
Packit 436967
Packit 436967
    elif opt.outputformat:
Packit 436967
        include_fields = []
Packit 436967
        for fieldname, rest in format_field_re.findall(opt.outputformat):
Packit 436967
            if fieldname == "whiteboard" and rest:
Packit 436967
                fieldname = rest + "_" + fieldname
Packit 436967
            elif fieldname == "flag":
Packit 436967
                fieldname = "flags"
Packit 436967
            elif fieldname == "cve":
Packit 436967
                fieldname = ["keywords", "blocks"]
Packit 436967
            elif fieldname == "__unicode__":
Packit 436967
                # Needs to be in sync with bug.__unicode__
Packit 436967
                fieldname = ["id", "status", "assigned_to", "summary"]
Packit 436967
Packit 436967
            flist = isinstance(fieldname, list) and fieldname or [fieldname]
Packit 436967
            for f in flist:
Packit 436967
                if f not in include_fields:
Packit 436967
                    include_fields.append(f)
Packit 436967
Packit 436967
    if include_fields is not None:
Packit 436967
        include_fields.sort()
Packit 436967
Packit 436967
    built_query = bz.build_query(
Packit 436967
        product=opt.product or None,
Packit 436967
        component=opt.component or None,
Packit 436967
        sub_component=opt.sub_component or None,
Packit 436967
        version=opt.version or None,
Packit 436967
        reporter=opt.reporter or None,
Packit 436967
        bug_id=opt.id or None,
Packit 436967
        short_desc=opt.summary or None,
Packit 436967
        long_desc=opt.comment or None,
Packit 436967
        cc=opt.cc or None,
Packit 436967
        assigned_to=opt.assigned_to or None,
Packit 436967
        qa_contact=opt.qa_contact or None,
Packit 436967
        status=opt.status or None,
Packit 436967
        blocked=opt.blocked or None,
Packit 436967
        dependson=opt.dependson or None,
Packit 436967
        keywords=opt.keywords or None,
Packit 436967
        keywords_type=opt.keywords_type or None,
Packit 436967
        url=opt.url or None,
Packit 436967
        url_type=opt.url_type or None,
Packit 436967
        status_whiteboard=opt.whiteboard or None,
Packit 436967
        status_whiteboard_type=opt.status_whiteboard_type or None,
Packit 436967
        fixed_in=opt.fixed_in or None,
Packit 436967
        fixed_in_type=opt.fixed_in_type or None,
Packit 436967
        flag=opt.flag or None,
Packit 436967
        alias=opt.alias or None,
Packit 436967
        qa_whiteboard=opt.qa_whiteboard or None,
Packit 436967
        devel_whiteboard=opt.devel_whiteboard or None,
Packit 436967
        boolean_query=opt.boolean_query or None,
Packit 436967
        bug_severity=opt.severity or None,
Packit 436967
        priority=opt.priority or None,
Packit 436967
        target_release=opt.target_release or None,
Packit 436967
        target_milestone=opt.target_milestone or None,
Packit 436967
        emailtype=opt.emailtype or None,
Packit 436967
        booleantype=opt.booleantype or None,
Packit 436967
        include_fields=include_fields,
Packit 436967
        quicksearch=opt.quicksearch or None,
Packit 436967
        savedsearch=opt.savedsearch or None,
Packit 436967
        savedsearch_sharer_id=opt.savedsearch_sharer_id or None,
Packit 436967
        tags=opt.tags or None)
Packit 436967
Packit 436967
    _merge_field_opts(built_query, opt, parser)
Packit 436967
Packit 436967
    built_query.update(q)
Packit 436967
    q = built_query
Packit 436967
Packit 436967
    if not q:
Packit 436967
        parser.error("'query' command requires additional arguments")
Packit 436967
    if opt.test_return_result:
Packit 436967
        return q
Packit 436967
    return bz.query(q)
Packit 436967
Packit 436967
Packit 436967
def _do_info(bz, opt):
Packit 436967
    """
Packit 436967
    Handle the 'info' subcommand
Packit 436967
    """
Packit 436967
    # All these commands call getproducts internally, so do it up front
Packit 436967
    # with minimal include_fields for speed
Packit 436967
    def _filter_components(compdetails):
Packit 436967
        ret = {}
Packit 436967
        for k, v in compdetails.items():
Packit 436967
            if v.get("is_active", True):
Packit 436967
                ret[k] = v
Packit 436967
        return ret
Packit 436967
Packit 436967
    productname = (opt.components or opt.component_owners or opt.versions)
Packit 436967
    include_fields = ["name", "id"]
Packit 436967
    fastcomponents = (opt.components and not opt.active_components)
Packit 436967
    if opt.versions:
Packit 436967
        include_fields += ["versions"]
Packit 436967
    if opt.component_owners:
Packit 436967
        include_fields += [
Packit 436967
            "components.default_assigned_to",
Packit 436967
            "components.name",
Packit 436967
        ]
Packit 436967
    if (opt.active_components and
Packit 436967
        any(["components" in i for i in include_fields])):
Packit 436967
        include_fields += ["components.is_active"]
Packit 436967
Packit 436967
    bz.refresh_products(names=productname and [productname] or None,
Packit 436967
            include_fields=include_fields)
Packit 436967
Packit 436967
    if opt.products:
Packit 436967
        for name in sorted([p["name"] for p in bz.getproducts()]):
Packit 436967
            print(name)
Packit 436967
Packit 436967
    elif fastcomponents:
Packit 436967
        for name in sorted(bz.getcomponents(productname)):
Packit 436967
            print(name)
Packit 436967
Packit 436967
    elif opt.components:
Packit 436967
        details = bz.getcomponentsdetails(productname)
Packit 436967
        for name in sorted(_filter_components(details)):
Packit 436967
            print(name)
Packit 436967
Packit 436967
    elif opt.versions:
Packit 436967
        proddict = bz.getproducts()[0]
Packit 436967
        for v in proddict['versions']:
Packit 436967
            print(to_encoding(v["name"]))
Packit 436967
Packit 436967
    elif opt.component_owners:
Packit 436967
        details = bz.getcomponentsdetails(productname)
Packit 436967
        for c in sorted(_filter_components(details)):
Packit 436967
            print(to_encoding(u"%s: %s" % (c,
Packit 436967
                details[c]['default_assigned_to'])))
Packit 436967
Packit 436967
Packit 436967
def _convert_to_outputformat(output):
Packit 436967
    fmt = ""
Packit 436967
Packit 436967
    if output == "normal":
Packit 436967
        fmt = "%{__unicode__}"
Packit 436967
Packit 436967
    elif output == "ids":
Packit 436967
        fmt = "%{id}"
Packit 436967
Packit 436967
    elif output == 'full':
Packit 436967
        fmt += "%{__unicode__}\n"
Packit 436967
        fmt += "Component: %{component}\n"
Packit 436967
        fmt += "CC: %{cc}\n"
Packit 436967
        fmt += "Blocked: %{blocks}\n"
Packit 436967
        fmt += "Depends: %{depends_on}\n"
Packit 436967
        fmt += "%{comments}\n"
Packit 436967
Packit 436967
    elif output == 'extra':
Packit 436967
        fmt += "%{__unicode__}\n"
Packit 436967
        fmt += " +Keywords: %{keywords}\n"
Packit 436967
        fmt += " +QA Whiteboard: %{qa_whiteboard}\n"
Packit 436967
        fmt += " +Status Whiteboard: %{status_whiteboard}\n"
Packit 436967
        fmt += " +Devel Whiteboard: %{devel_whiteboard}\n"
Packit 436967
Packit 436967
    elif output == 'oneline':
Packit 436967
        fmt += "#%{bug_id} %{status} %{assigned_to} %{component}\t"
Packit 436967
        fmt += "[%{target_milestone}] %{flags} %{cve}"
Packit 436967
Packit 436967
    else:
Packit 436967
        raise RuntimeError("Unknown output type '%s'" % output)
Packit 436967
Packit 436967
    return fmt
Packit 436967
Packit 436967
Packit 436967
def _format_output(bz, opt, buglist):
Packit 436967
    if opt.output == 'raw':
Packit 436967
        buglist = bz.getbugs([b.bug_id for b in buglist])
Packit 436967
        for b in buglist:
Packit 436967
            print("Bugzilla %s: " % b.bug_id)
Packit 436967
            for attrname in sorted(b.__dict__):
Packit 436967
                print(to_encoding(u"ATTRIBUTE[%s]: %s" %
Packit 436967
                                  (attrname, b.__dict__[attrname])))
Packit 436967
            print("\n\n")
Packit 436967
        return
Packit 436967
Packit 436967
    def bug_field(matchobj):
Packit 436967
        # whiteboard and flag allow doing
Packit 436967
        #   %{whiteboard:devel} and %{flag:needinfo}
Packit 436967
        # That's what 'rest' matches
Packit 436967
        (fieldname, rest) = matchobj.groups()
Packit 436967
Packit 436967
        if fieldname == "whiteboard" and rest:
Packit 436967
            fieldname = rest + "_" + fieldname
Packit 436967
Packit 436967
        if fieldname == "flag" and rest:
Packit 436967
            val = b.get_flag_status(rest)
Packit 436967
Packit 436967
        elif fieldname == "flags" or fieldname == "flags_requestee":
Packit 436967
            tmpstr = []
Packit 436967
            for f in getattr(b, "flags", []):
Packit 436967
                requestee = f.get('requestee', "")
Packit 436967
                if fieldname == "flags":
Packit 436967
                    requestee = ""
Packit 436967
                if fieldname == "flags_requestee":
Packit 436967
                    if requestee == "":
Packit 436967
                        continue
Packit 436967
                    tmpstr.append("%s" % requestee)
Packit 436967
                else:
Packit 436967
                    tmpstr.append("%s%s%s" %
Packit 436967
                            (f['name'], f['status'], requestee))
Packit 436967
Packit 436967
            val = ",".join(tmpstr)
Packit 436967
Packit 436967
        elif fieldname == "cve":
Packit 436967
            cves = []
Packit 436967
            for key in getattr(b, "keywords", []):
Packit 436967
                # grab CVE from keywords and blockers
Packit 436967
                if key.find("Security") == -1:
Packit 436967
                    continue
Packit 436967
                for bl in b.blocks:
Packit 436967
                    cvebug = bz.getbug(bl)
Packit 436967
                    for cb in cvebug.alias:
Packit 436967
                        if cb.find("CVE") == -1:
Packit 436967
                            continue
Packit 436967
                        if cb.strip() not in cves:
Packit 436967
                            cves.append(cb)
Packit 436967
            val = ",".join(cves)
Packit 436967
Packit 436967
        elif fieldname == "comments":
Packit 436967
            val = ""
Packit 436967
            for c in getattr(b, "comments", []):
Packit 436967
                val += ("\n* %s - %s:\n%s\n" % (c['time'],
Packit 436967
                         c.get("creator", c.get("author", "")), c['text']))
Packit 436967
Packit 436967
        elif fieldname == "external_bugs":
Packit 436967
            val = ""
Packit 436967
            for e in getattr(b, "external_bugs", []):
Packit 436967
                url = e["type"]["full_url"].replace("%id%", e["ext_bz_bug_id"])
Packit 436967
                if not val:
Packit 436967
                    val += "\n"
Packit 436967
                val += "External bug: %s\n" % url
Packit 436967
Packit 436967
        elif fieldname == "__unicode__":
Packit 436967
            val = b.__unicode__()
Packit 436967
        else:
Packit 436967
            val = getattr(b, fieldname, "")
Packit 436967
Packit 436967
        vallist = isinstance(val, list) and val or [val]
Packit 436967
        val = ','.join([to_encoding(v) for v in vallist])
Packit 436967
Packit 436967
        return val
Packit 436967
Packit 436967
    for b in buglist:
Packit 436967
        print(format_field_re.sub(bug_field, opt.outputformat))
Packit 436967
Packit 436967
Packit 436967
def _parse_triset(vallist, checkplus=True, checkminus=True, checkequal=True,
Packit 436967
                  splitcomma=False):
Packit 436967
    add_val = []
Packit 436967
    rm_val = []
Packit 436967
    set_val = None
Packit 436967
Packit 436967
    def make_list(v):
Packit 436967
        if not v:
Packit 436967
            return []
Packit 436967
        if splitcomma:
Packit 436967
            return v.split(",")
Packit 436967
        return [v]
Packit 436967
Packit 436967
    for val in isinstance(vallist, list) and vallist or [vallist]:
Packit 436967
        val = val or ""
Packit 436967
Packit 436967
        if val.startswith("+") and checkplus:
Packit 436967
            add_val += make_list(val[1:])
Packit 436967
        elif val.startswith("-") and checkminus:
Packit 436967
            rm_val += make_list(val[1:])
Packit 436967
        elif val.startswith("=") and checkequal:
Packit 436967
            # Intentionally overwrite this
Packit 436967
            set_val = make_list(val[1:])
Packit 436967
        else:
Packit 436967
            add_val += make_list(val)
Packit 436967
Packit 436967
    return add_val, rm_val, set_val
Packit 436967
Packit 436967
Packit 436967
def _do_new(bz, opt, parser):
Packit 436967
    # Parse options that accept comma separated list
Packit 436967
    def parse_multi(val):
Packit 436967
        return _parse_triset(val, checkplus=False, checkminus=False,
Packit 436967
                             checkequal=False, splitcomma=True)[0]
Packit 436967
Packit 436967
    ret = bz.build_createbug(
Packit 436967
        blocks=parse_multi(opt.blocked) or None,
Packit 436967
        cc=parse_multi(opt.cc) or None,
Packit 436967
        component=opt.component or None,
Packit 436967
        depends_on=parse_multi(opt.dependson) or None,
Packit 436967
        description=opt.comment or None,
Packit 436967
        groups=parse_multi(opt.groups) or None,
Packit 436967
        keywords=parse_multi(opt.keywords) or None,
Packit 436967
        op_sys=opt.os or None,
Packit 436967
        platform=opt.arch or None,
Packit 436967
        priority=opt.priority or None,
Packit 436967
        product=opt.product or None,
Packit 436967
        severity=opt.severity or None,
Packit 436967
        summary=opt.summary or None,
Packit 436967
        url=opt.url or None,
Packit 436967
        version=opt.version or None,
Packit 436967
        assigned_to=opt.assigned_to or None,
Packit 436967
        qa_contact=opt.qa_contact or None,
Packit 436967
        sub_component=opt.sub_component or None,
Packit 436967
        alias=opt.alias or None,
Packit 436967
        comment_tags=opt.comment_tag or None,
Packit 436967
    )
Packit 436967
Packit 436967
    _merge_field_opts(ret, opt, parser)
Packit 436967
Packit 436967
    if opt.test_return_result:
Packit 436967
        return ret
Packit 436967
Packit 436967
    b = bz.createbug(ret)
Packit 436967
    b.refresh()
Packit 436967
    return [b]
Packit 436967
Packit 436967
Packit 436967
def _do_modify(bz, parser, opt):
Packit 436967
    bugid_list = [bugid for a in opt.ids for bugid in a.split(',')]
Packit 436967
Packit 436967
    add_wb, rm_wb, set_wb = _parse_triset(opt.whiteboard)
Packit 436967
    add_devwb, rm_devwb, set_devwb = _parse_triset(opt.devel_whiteboard)
Packit 436967
    add_intwb, rm_intwb, set_intwb = _parse_triset(opt.internal_whiteboard)
Packit 436967
    add_qawb, rm_qawb, set_qawb = _parse_triset(opt.qa_whiteboard)
Packit 436967
Packit 436967
    add_blk, rm_blk, set_blk = _parse_triset(opt.blocked, splitcomma=True)
Packit 436967
    add_deps, rm_deps, set_deps = _parse_triset(opt.dependson, splitcomma=True)
Packit 436967
    add_key, rm_key, set_key = _parse_triset(opt.keywords)
Packit 436967
    add_cc, rm_cc, ignore = _parse_triset(opt.cc,
Packit 436967
                                          checkplus=False,
Packit 436967
                                          checkequal=False)
Packit 436967
    add_groups, rm_groups, ignore = _parse_triset(opt.groups,
Packit 436967
                                                  checkequal=False,
Packit 436967
                                                  splitcomma=True)
Packit 436967
    add_tags, rm_tags, ignore = _parse_triset(opt.tags, checkequal=False)
Packit 436967
Packit 436967
    status = opt.status or None
Packit 436967
    if opt.dupeid is not None:
Packit 436967
        opt.close = "DUPLICATE"
Packit 436967
    if opt.close:
Packit 436967
        status = "CLOSED"
Packit 436967
Packit 436967
    flags = []
Packit 436967
    if opt.flag:
Packit 436967
        # Convert "foo+" to tuple ("foo", "+")
Packit 436967
        for f in opt.flag:
Packit 436967
            flags.append({"name": f[:-1], "status": f[-1]})
Packit 436967
Packit 436967
    update = bz.build_update(
Packit 436967
        assigned_to=opt.assigned_to or None,
Packit 436967
        comment=opt.comment or None,
Packit 436967
        comment_private=opt.private or None,
Packit 436967
        component=opt.component or None,
Packit 436967
        product=opt.product or None,
Packit 436967
        blocks_add=add_blk or None,
Packit 436967
        blocks_remove=rm_blk or None,
Packit 436967
        blocks_set=set_blk,
Packit 436967
        url=opt.url or None,
Packit 436967
        cc_add=add_cc or None,
Packit 436967
        cc_remove=rm_cc or None,
Packit 436967
        depends_on_add=add_deps or None,
Packit 436967
        depends_on_remove=rm_deps or None,
Packit 436967
        depends_on_set=set_deps,
Packit 436967
        groups_add=add_groups or None,
Packit 436967
        groups_remove=rm_groups or None,
Packit 436967
        keywords_add=add_key or None,
Packit 436967
        keywords_remove=rm_key or None,
Packit 436967
        keywords_set=set_key,
Packit 436967
        op_sys=opt.os or None,
Packit 436967
        platform=opt.arch or None,
Packit 436967
        priority=opt.priority or None,
Packit 436967
        qa_contact=opt.qa_contact or None,
Packit 436967
        severity=opt.severity or None,
Packit 436967
        status=status,
Packit 436967
        summary=opt.summary or None,
Packit 436967
        version=opt.version or None,
Packit 436967
        reset_assigned_to=opt.reset_assignee or None,
Packit 436967
        reset_qa_contact=opt.reset_qa_contact or None,
Packit 436967
        resolution=opt.close or None,
Packit 436967
        target_release=opt.target_release or None,
Packit 436967
        target_milestone=opt.target_milestone or None,
Packit 436967
        dupe_of=opt.dupeid or None,
Packit 436967
        fixed_in=opt.fixed_in or None,
Packit 436967
        whiteboard=set_wb and set_wb[0] or None,
Packit 436967
        devel_whiteboard=set_devwb and set_devwb[0] or None,
Packit 436967
        internal_whiteboard=set_intwb and set_intwb[0] or None,
Packit 436967
        qa_whiteboard=set_qawb and set_qawb[0] or None,
Packit 436967
        sub_component=opt.sub_component or None,
Packit 436967
        alias=opt.alias or None,
Packit 436967
        flags=flags or None,
Packit 436967
        comment_tags=opt.comment_tag or None,
Packit 436967
    )
Packit 436967
Packit 436967
    # We make this a little convoluted to facilitate unit testing
Packit 436967
    wbmap = {
Packit 436967
        "whiteboard": (add_wb, rm_wb),
Packit 436967
        "internal_whiteboard": (add_intwb, rm_intwb),
Packit 436967
        "qa_whiteboard": (add_qawb, rm_qawb),
Packit 436967
        "devel_whiteboard": (add_devwb, rm_devwb),
Packit 436967
    }
Packit 436967
Packit 436967
    for k, v in wbmap.copy().items():
Packit 436967
        if not v[0] and not v[1]:
Packit 436967
            del(wbmap[k])
Packit 436967
Packit 436967
    _merge_field_opts(update, opt, parser)
Packit 436967
Packit 436967
    log.debug("update bug dict=%s", update)
Packit 436967
    log.debug("update whiteboard dict=%s", wbmap)
Packit 436967
Packit 436967
    if not any([update, wbmap, add_tags, rm_tags]):
Packit 436967
        parser.error("'modify' command requires additional arguments")
Packit 436967
Packit 436967
    if opt.test_return_result:
Packit 436967
        return (update, wbmap, add_tags, rm_tags)
Packit 436967
Packit 436967
    if add_tags or rm_tags:
Packit 436967
        ret = bz.update_tags(bugid_list,
Packit 436967
            tags_add=add_tags, tags_remove=rm_tags)
Packit 436967
        log.debug("bz.update_tags returned=%s", ret)
Packit 436967
    if update:
Packit 436967
        ret = bz.update_bugs(bugid_list, update)
Packit 436967
        log.debug("bz.update_bugs returned=%s", ret)
Packit 436967
Packit 436967
    if not wbmap:
Packit 436967
        return
Packit 436967
Packit 436967
    # Now for the things we can't blindly batch.
Packit 436967
    # Being able to prepend/append to whiteboards, which are just
Packit 436967
    # plain string values, is an old rhbz semantic that we try to maintain
Packit 436967
    # here. This is a bit weird for traditional bugzilla XMLRPC
Packit 436967
    log.debug("Adjusting whiteboard fields one by one")
Packit 436967
    for bug in bz.getbugs(bugid_list):
Packit 436967
        for wb, (add_list, rm_list) in wbmap.items():
Packit 436967
            for tag in add_list:
Packit 436967
                newval = getattr(bug, wb) or ""
Packit 436967
                if newval:
Packit 436967
                    newval += " "
Packit 436967
                newval += tag
Packit 436967
                bz.update_bugs([bug.id],
Packit 436967
                               bz.build_update(**{wb: newval}))
Packit 436967
Packit 436967
            for tag in rm_list:
Packit 436967
                newval = (getattr(bug, wb) or "").split()
Packit 436967
                for t in newval[:]:
Packit 436967
                    if t == tag:
Packit 436967
                        newval.remove(t)
Packit 436967
                bz.update_bugs([bug.id],
Packit 436967
                               bz.build_update(**{wb: " ".join(newval)}))
Packit 436967
Packit 436967
Packit 436967
def _do_get_attach(bz, opt):
Packit 436967
    for bug in bz.getbugs(opt.getall):
Packit 436967
        opt.get += bug.get_attachment_ids()
Packit 436967
Packit 436967
    for attid in set(opt.get):
Packit 436967
        att = bz.openattachment(attid)
Packit 436967
        outfile = open_without_clobber(att.name, "wb")
Packit 436967
        data = att.read(4096)
Packit 436967
        while data:
Packit 436967
            outfile.write(data)
Packit 436967
            data = att.read(4096)
Packit 436967
        print("Wrote %s" % outfile.name)
Packit 436967
Packit 436967
    return
Packit 436967
Packit 436967
Packit 436967
def _do_set_attach(bz, opt, parser):
Packit 436967
    if not opt.ids:
Packit 436967
        parser.error("Bug ID must be specified for setting attachments")
Packit 436967
Packit 436967
    if sys.stdin.isatty():
Packit 436967
        if not opt.file:
Packit 436967
            parser.error("--file must be specified")
Packit 436967
        fileobj = open(opt.file, "rb")
Packit 436967
    else:
Packit 436967
        # piped input on stdin
Packit 436967
        if not opt.desc:
Packit 436967
            parser.error("--description must be specified if passing "
Packit 436967
                         "file on stdin")
Packit 436967
Packit 436967
        fileobj = tempfile.NamedTemporaryFile(prefix="bugzilla-attach.")
Packit 436967
        data = sys.stdin.read(4096)
Packit 436967
Packit 436967
        while data:
Packit 436967
            fileobj.write(data.encode(locale.getpreferredencoding()))
Packit 436967
            data = sys.stdin.read(4096)
Packit 436967
        fileobj.seek(0)
Packit 436967
Packit 436967
    kwargs = {}
Packit 436967
    if opt.file:
Packit 436967
        kwargs["filename"] = os.path.basename(opt.file)
Packit 436967
    if opt.type:
Packit 436967
        kwargs["contenttype"] = opt.type
Packit 436967
    if opt.type in ["text/x-patch"]:
Packit 436967
        kwargs["ispatch"] = True
Packit 436967
    if opt.comment:
Packit 436967
        kwargs["comment"] = opt.comment
Packit 436967
    desc = opt.desc or os.path.basename(fileobj.name)
Packit 436967
Packit 436967
    # Upload attachments
Packit 436967
    for bugid in opt.ids:
Packit 436967
        attid = bz.attachfile(bugid, fileobj, desc, **kwargs)
Packit 436967
        print("Created attachment %i on bug %s" % (attid, bugid))
Packit 436967
Packit 436967
Packit 436967
#################
Packit 436967
# Main handling #
Packit 436967
#################
Packit 436967
Packit 436967
def _make_bz_instance(opt):
Packit 436967
    """
Packit 436967
    Build the Bugzilla instance we will use
Packit 436967
    """
Packit 436967
    if opt.bztype != 'auto':
Packit 436967
        log.info("Explicit --bztype is no longer supported, ignoring")
Packit 436967
Packit 436967
    cookiefile = None
Packit 436967
    tokenfile = None
Packit 436967
    if opt.cache_credentials:
Packit 436967
        cookiefile = opt.cookiefile or -1
Packit 436967
        tokenfile = opt.tokenfile or -1
Packit 436967
Packit 436967
    bz = bugzilla.Bugzilla(
Packit 436967
        url=opt.bugzilla,
Packit 436967
        cookiefile=cookiefile,
Packit 436967
        tokenfile=tokenfile,
Packit 436967
        sslverify=opt.sslverify,
Packit 436967
        cert=opt.cert)
Packit 436967
    return bz
Packit 436967
Packit 436967
Packit 436967
def _handle_login(opt, action, bz):
Packit 436967
    """
Packit 436967
    Handle all login related bits
Packit 436967
    """
Packit 436967
    is_login_command = (action == 'login')
Packit 436967
Packit 436967
    do_interactive_login = (is_login_command or
Packit 436967
        opt.login or opt.username or opt.password)
Packit 436967
    username = getattr(opt, "pos_username", None) or opt.username
Packit 436967
    password = getattr(opt, "pos_password", None) or opt.password
Packit 436967
Packit 436967
    try:
Packit 436967
        if do_interactive_login:
Packit 436967
            if bz.url:
Packit 436967
                print("Logging into %s" % urlparse(bz.url)[1])
Packit 436967
            bz.interactive_login(username, password)
Packit 436967
    except bugzilla.BugzillaError as e:
Packit 436967
        print(str(e))
Packit 436967
        sys.exit(1)
Packit 436967
Packit 436967
    if opt.ensure_logged_in and not bz.logged_in:
Packit 436967
        print("--ensure-logged-in passed but you aren't logged in to %s" %
Packit 436967
            bz.url)
Packit 436967
        sys.exit(1)
Packit 436967
Packit 436967
    if is_login_command:
Packit 436967
        msg = "Login successful."
Packit 436967
        if bz.cookiefile or bz.tokenfile:
Packit 436967
            msg = "Login successful, token cache updated."
Packit 436967
Packit 436967
        print(msg)
Packit 436967
        sys.exit(0)
Packit 436967
Packit 436967
Packit 436967
def _main(unittest_bz_instance):
Packit 436967
    parser = setup_parser()
Packit 436967
    opt = parser.parse_args()
Packit 436967
    action = opt.command
Packit 436967
    setup_logging(opt.debug, opt.verbose)
Packit 436967
Packit 436967
    log.debug("Launched with command line: %s", " ".join(sys.argv))
Packit 436967
    log.debug("Bugzilla module: %s", bugzilla)
Packit 436967
Packit 436967
    # Connect to bugzilla
Packit 436967
    log.info('Connecting to %s', opt.bugzilla)
Packit 436967
Packit 436967
    if unittest_bz_instance:
Packit 436967
        bz = unittest_bz_instance
Packit 436967
    else:
Packit 436967
        bz = _make_bz_instance(opt)
Packit 436967
Packit 436967
    # Handle login options
Packit 436967
    _handle_login(opt, action, bz)
Packit 436967
Packit 436967
Packit 436967
    ###########################
Packit 436967
    # Run the actual commands #
Packit 436967
    ###########################
Packit 436967
Packit 436967
    if hasattr(opt, "outputformat"):
Packit 436967
        if not opt.outputformat and opt.output not in ['raw', None]:
Packit 436967
            opt.outputformat = _convert_to_outputformat(opt.output)
Packit 436967
Packit 436967
    buglist = []
Packit 436967
    if action == 'info':
Packit 436967
        if not (opt.products or
Packit 436967
                opt.components or
Packit 436967
                opt.component_owners or
Packit 436967
                opt.versions):
Packit 436967
            parser.error("'info' command requires additional arguments")
Packit 436967
Packit 436967
        _do_info(bz, opt)
Packit 436967
Packit 436967
    elif action == 'query':
Packit 436967
        buglist = _do_query(bz, opt, parser)
Packit 436967
        if opt.test_return_result:
Packit 436967
            return buglist
Packit 436967
Packit 436967
    elif action == 'new':
Packit 436967
        buglist = _do_new(bz, opt, parser)
Packit 436967
        if opt.test_return_result:
Packit 436967
            return buglist
Packit 436967
Packit 436967
    elif action == 'attach':
Packit 436967
        if opt.get or opt.getall:
Packit 436967
            if opt.ids:
Packit 436967
                parser.error("Bug IDs '%s' not used for "
Packit 436967
                    "getting attachments" % opt.ids)
Packit 436967
            _do_get_attach(bz, opt)
Packit 436967
        else:
Packit 436967
            _do_set_attach(bz, opt, parser)
Packit 436967
Packit 436967
    elif action == 'modify':
Packit 436967
        modout = _do_modify(bz, parser, opt)
Packit 436967
        if opt.test_return_result:
Packit 436967
            return modout
Packit 436967
    else:
Packit 436967
        raise RuntimeError("Unexpected action '%s'" % action)
Packit 436967
Packit 436967
    # If we're doing new/query/modify, output our results
Packit 436967
    if action in ['new', 'query']:
Packit 436967
        _format_output(bz, opt, buglist)
Packit 436967
Packit 436967
Packit 436967
def main(unittest_bz_instance=None):
Packit 436967
    try:
Packit 436967
        try:
Packit 436967
            return _main(unittest_bz_instance)
Packit 436967
        except (Exception, KeyboardInterrupt):
Packit 436967
            log.debug("", exc_info=True)
Packit 436967
            raise
Packit 436967
    except (Fault, bugzilla.BugzillaError) as e:
Packit 436967
        print("\nServer error: %s" % str(e))
Packit 436967
        sys.exit(3)
Packit 436967
    except requests.exceptions.SSLError as e:
Packit 436967
        # Give SSL recommendations
Packit 436967
        print("SSL error: %s" % e)
Packit 436967
        print("\nIf you trust the remote server, you can work "
Packit 436967
              "around this error with:\n"
Packit 436967
              "  bugzilla --nosslverify ...")
Packit 436967
        sys.exit(4)
Packit 436967
    except (socket.error,
Packit 436967
            requests.exceptions.HTTPError,
Packit 436967
            requests.exceptions.ConnectionError,
Packit 436967
            ProtocolError) as e:
Packit 436967
        print("\nConnection lost/failed: %s" % str(e))
Packit 436967
        sys.exit(2)
Packit 436967
Packit 436967
Packit 436967
def cli():
Packit 436967
    try:
Packit 436967
        main()
Packit 436967
    except KeyboardInterrupt:
Packit 436967
        log.debug("", exc_info=True)
Packit 436967
        print("\nExited at user request.")
Packit 436967
        sys.exit(1)