| Server IP : 182.53.201.61 / Your IP : 216.73.217.175 Web Server : Apache/2.2.15 (Fedora) System : Linux km10.dyndns.org 2.6.31.5-127.fc12.i686.PAE #1 SMP Sat Nov 7 21:25:57 EST 2009 i686 User : apache ( 48) PHP Version : 5.3.3 Disable Function : NONE MySQL : ON | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /usr/bin/ |
Upload File : |
#!/usr/bin/python
# bugzilla - a commandline frontend for the python bugzilla module
#
# Copyright (C) 2007,2008 Red Hat Inc.
# Author: Will Woods <wwoods@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 bugzilla, optparse
import os, sys, glob, re
import logging
import getpass
import bugzilla.util
import locale
version = '0.5.1'
default_bz = 'https://bugzilla.redhat.com/xmlrpc.cgi'
# Initial simple logging stuff
logging.basicConfig()
log = logging.getLogger("bugzilla")
if '--debug' in sys.argv:
log.setLevel(logging.DEBUG)
elif '--verbose' in sys.argv:
log.setLevel(logging.INFO)
cmdlist = ['info','query','new','modify','login']
def to_encoding(ustring):
if isinstance(ustring, basestring):
if isinstance(ustring, unicode):
return ustring.encode(locale.getpreferredencoding(), 'replace')
return ustring
return u''
def setup_parser():
u = "usage: %prog [global options] COMMAND [options]"
u += "\nCommands: %s" % ', '.join(cmdlist)
p = optparse.OptionParser(usage=u)
p.disable_interspersed_args()
# General bugzilla connection options
p.add_option('--bugzilla',default=default_bz,
help="bugzilla XMLRPC URI. default: %s" % default_bz)
p.add_option('--bztype',default='auto',
help="Bugzilla type. Autodetected if not set. "
"Available types: %s" % " ".join(bugzilla.classlist))
p.add_option('--user',
help="username")
p.add_option('--password',
help="password")
p.add_option('--cookiefile',
help="cookie file to use for bugzilla authentication")
p.add_option('--verbose',action='store_true',
help="give more info about what's going on")
p.add_option('--debug',action='store_true',
help="output bunches of debugging info")
return p
def setup_action_parser(action):
p = optparse.OptionParser(usage="usage: %%prog %s [options]" % action)
# TODO: product and version could default to current system
# info (read from /etc/redhat-release?)
if action == 'new':
p.add_option('-p','--product',
help="REQUIRED: product name (list with 'bugzilla info -p')")
p.add_option('-v','--version',
help="REQUIRED: product version")
p.add_option('-c','--component',
help="REQUIRED: component name (list with 'bugzilla info -c PRODUCT')")
p.add_option('-l','--comment',
help="REQUIRED: initial bug comment")
p.add_option('-s','--summary',dest='short_desc',
help="REQUIRED: bug summary")
p.add_option('-o','--os',default='Linux',dest='op_sys',
help="OPTIONAL: operating system (default: Linux)")
p.add_option('-a','--arch',default='All',dest='rep_platform',
help="OPTIONAL: arch this bug occurs on (default: All)")
p.add_option('--severity',default='medium',dest='bug_severity',
help="OPTIONAL: bug severity (default: medium)")
p.add_option('--priority',default='medium',dest='priority',
help="OPTIONAL: bug priority (default: medium)")
p.add_option('-u','--url',dest='bug_file_loc',default='http://',
help="OPTIONAL: URL for further bug info")
p.add_option('--cc',
help="OPTIONAL: add emails to initial CC list")
elif action == 'query':
# General bug metadata
p.add_option('-b','--bug_id',
help="specify individual bugs by IDs, separated with commas")
p.add_option('-p','--product',
help="product name, comma-separated (list with 'bugzilla info -p')")
p.add_option('-v','--version',
help="product version")
p.add_option('-c','--component',
help="component name(s), comma-separated (list with 'bugzilla info -c PRODUCT')")
p.add_option('--components_file',
help="list of component names from a file, one component per line (list with 'bugzilla info -c PRODUCT')")
p.add_option('-l','--long_desc',
help="search inside bug comments")
p.add_option('-m','--target_milestone',
help="search for a target milestone")
p.add_option('-s','--short_desc',
help="search bug summaries")
p.add_option('-t','--bug_status',default="ALL",
help="comma-separated list of bug statuses to accept [Default:all] [Available:NEW,ASSIGNED,NEEDINFO,ON_DEV,MODIFIED,POST,ON_QA,FAILS_QA,PASSES_QA,REOPENED,VERIFIED,RELEASE_PENDING,CLOSED]")
p.add_option('-x','--severity',
help="search severities, comma-separated")
p.add_option('-z','--priority',
help="search priorities, comma-separated")
# Email
p.add_option('-E','--emailtype',
help="Email: specify searching option for emails, ie. substring,notsubstring,exact,... [Default: substring]",default="substring")
p.add_option('-o','--cc',
help="Email: search cc lists for given address")
p.add_option('-r','--reporter',
help="Email: search reporter email for given address")
p.add_option('-a','--assigned_to',
help="Email: search for bugs assigned to this address")
p.add_option('-q','--qa_contact',
help="Email: search for bugs which have QA Contact assigned to this address")
# Strings
p.add_option('-u','--url',
help="search keywords field for given url")
p.add_option('-U','--url_type',
help="specify searching option for urls, ie. anywords,allwords,nowords")
p.add_option('-k','--keywords',
help="search keywords field for specified words")
p.add_option('-K','--keywords_type',
help="specify searching option for keywords, ie. anywords,allwords,nowords")
p.add_option('-w','--status_whiteboard',
help="search Status Whiteboard field for specified words")
p.add_option('-W','--status_whiteboard_type',
help="specify searching option for Status Whiteboard, ie. anywords,allwords,nowords")
# Boolean Charts
p.add_option('-B','--booleantype',
help="specify searching option for booleans, ie. substring,notsubstring,exact,... [Default: substring]",default="substring")
p.add_option('--boolean_query',
help="Boolean:Create your own query. Format: BooleanName-Condition-Parameter &/| ... . ie, keywords-substring-Partner & keywords-notsubstring-OtherQA")
p.add_option('--blocked',
help="Boolean:search for bugs that block this bug ID")
p.add_option('--dependson',
help="Boolean:search for bugs that depend on this bug ID")
p.add_option('--flag',
help="Boolean:search for bugs that have certain flag states present")
p.add_option('--qa_whiteboard',
help="Boolean:search for bugs that have certain QA Whiteboard text present")
p.add_option('--devel_whiteboard',
help="Boolean:search for bugs that have certain Devel Whiteboard text present")
p.add_option('--alias',
help="Boolean:search for bugs that have the provided alias")
p.add_option('--fixed_in',
help="search Status Whiteboard field for specified words")
# Precomposed queries
p.add_option('--from-url',
help="Use the query given by a query.cgi URL. (Use quotes!)")
elif action == 'info':
p.add_option('-p','--products',action='store_true',
help='Get a list of products')
p.add_option('-c','--components',metavar="PRODUCT",
help='List the components in the given product')
p.add_option('-o','--component_owners',metavar="PRODUCT",
help='List components (and their owners)')
p.add_option('-v','--versions',metavar="PRODUCT",
help='List the versions for the given product')
elif action == 'modify':
p.set_usage("usage: %prog modify [options] BUGID BUGID ...")
p.add_option('-l','--comment',
help='Add a comment')
# FIXME: check value for resolution
p.add_option('-k','--close',metavar="RESOLUTION",
help='Close with the given resolution')
p.add_option('-p','--private',action='store_true',default='false',
help='Mark as private')
p.add_option('-s','--status',
help='Change status of bug')
p.add_option('--assignee',
help='Assign bugzilla to assignee')
p.add_option('-f','--flag', action='append',
help='Update bugzilla flags with requested type, ie fedora-cvs?\
(Use a new option for each flag)')
p.add_option('--cc', action='append',
help='Add an email to the cc list')
# TODO: --keyword, --tag, --cc, ...
if action in ['new','query']:
# output modifiers
p.add_option('-f','--full',action='store_const',dest='output',
const='full',default='normal',help="output detailed bug info")
p.add_option('-i','--ids',action='store_const',dest='output',
const='ids',help="output only bug IDs")
p.add_option('-e','--extra',action='store_const',dest='output',
const='extra',help="output additional bug information (keywords, Whiteboards, etc.)")
p.add_option('--oneline', action='store_const', dest='output',
const='oneline',help="one line summary of the bug (useful for scripts)")
p.add_option('--outputformat',
help="Print output in the form given. You can use RPM-style "+
"tags that match bug fields, e.g.: '%{bug_id}: %{short_desc}'")
return p
def generate_man_page():
try:
from logilab.common.optik_ext import ManHelpFormatter
import datetime
except ImportError:
return
today = datetime.date.today()
datestr = today.strftime("%B %d, %Y")
manpage = \
'''.TH bugzilla 1 "%s" "version %s" "User Commands"
.SH NAME
bugzilla \- command-line interface to Bugzilla over XML-RPC
.SH SYNOPSIS
.B bugzilla
[\\fIoptions\\fR] [\\fIcommand\\fR] [\\fIcommand-options\\fR]
.SH DESCRIPTION
.PP
.BR bugzilla
is a command-line utility that allows access to the XML-RPC interface provided
by Bugzilla.
.PP
\\fIcommand\\fP is one of:
.br
.I \\fR * login - log into the given bugzilla instance
.br
.I \\fR * new - create a new bug
.br
.I \\fR * query - search for bugs matching given criteria
.br
.I \\fR * modify - modify existing bugs
.br
.I \\fR * info - get info about the given bugzilla instance
''' % (datestr,version)
manformatter = ManHelpFormatter()
parser = setup_parser()
parser.formatter = manformatter
opt_section = parser.format_option_help()
manpage += opt_section.replace("OPTIONS","GLOBAL OPTIONS")
for action in cmdlist:
action_parser = setup_action_parser(action)
action_parser.formatter = manformatter
opt_section = action_parser.format_option_help()
manpage += opt_section.replace("OPTIONS",
'\[oq]%s\[cq] OPTIONS' % action.upper())
manpage += \
'''.SH EXAMPLES
.TP
bugzilla query --bug_id 62037
.SH EXIT STATUS
.BR bugzilla
returns 1 if login fails or it is interrupted, and 0 otherwise.
.SH NOTES
Not everything that's exposed in the Web UI is exposed by XML-RPC, and not
everything that's exposed by XML-RPC is used by
.BR bugzilla .
.SH BUGS
Bugs? In a sub-1.0 release? Preposterous.
.SH AUTHOR
Will Woods <wwoods@redhat.com>'''
print manpage
def main():
# Set up parser for global args
parser = setup_parser()
# Parse the commandline, woo
(global_opt,args) = parser.parse_args()
# Get our action from these args
if len(args) and args[0] in cmdlist:
action = args.pop(0)
else:
parser.error("command must be one of: %s" % ','.join(cmdlist))
# Parse action-specific args
action_parser = setup_action_parser(action)
(opt, args) = action_parser.parse_args(args)
# Connect to bugzilla
log.info('Connecting to %s',global_opt.bugzilla)
if global_opt.bztype == 'auto':
bzclass = bugzilla.Bugzilla
log.info('Autodetecting Bugzilla type')
elif global_opt.bztype in bugzilla.classlist:
log.info('Using Bugzilla class %s' % global_opt.bztype)
bzclass = getattr(bugzilla,global_opt.bztype)
else:
parser.error("bztype must be one of: %s" % str(bugzilla.classlist))
bz=bzclass(url=global_opt.bugzilla)
# Handle 'login' action
if action == 'login':
if not global_opt.user:
sys.stdout.write('Username: ')
user = sys.stdin.readline()
global_opt.user = user.strip()
if not global_opt.password:
global_opt.password = getpass.getpass()
sys.stdout.write('Logging in... ')
# XXX NOTE: This will return success if you have a valid login cookie,
# even if you give a bad username and password. WEIRD.
if bz.login(global_opt.user,global_opt.password):
print 'Authorization cookie received.'
sys.exit(0)
else:
print 'failed.'
sys.exit(1)
# Set up authentication
if global_opt.user:
if not global_opt.password:
global_opt.password = getpass.getpass()
log.info('Using username/password for authentication')
bz.login(global_opt.user,global_opt.password)
else:
if global_opt.cookiefile:
bz.cookiefile = global_opt.cookiefile
cookiefile = bz.cookiefile
if os.path.exists(cookiefile):
log.info('Using cookies in %s for authentication', cookiefile)
else:
# FIXME check to see if .bugzillarc is in use
log.info('No authentication info provided.')
# And now we actually execute the given command
buglist = list() # save the results of query/new/modify here
if action == 'info':
if opt.products:
for k in sorted(bz.products):
print k
if opt.components:
for c in sorted(bz.getcomponents(opt.components)):
print c
if opt.component_owners:
component_details = bz.getcomponentsdetails(opt.component_owners)
for c in sorted(component_details):
print "%s: %s" % (c, component_details[c]['initialowner'])
if opt.versions:
for p in bz.querydata['product']:
if p['name'] == opt.versions:
for v in p['versions']:
print v
elif action == 'query':
# Construct the query from the list of queryable options
q = dict()
# Parse preconstructed queries.
u = getattr(opt,'from_url',None)
if u:
q = bugzilla.util.url_to_query(u)
# TODO: flags to save queries, list/perform saved queries
# TODO: use the given scheme/host for the bugzilla URL
email_count = 1
chart_id = 0
for a in ['product','component','components_file','version','long_desc','bug_id',
'short_desc','cc','assigned_to','reporter','qa_contact','bug_status',
'blocked','dependson','keywords','keywords_type','url','url_type','status_whiteboard',
'status_whiteboard_type','fixed_in','fixed_in_type','flag','alias','qa_whiteboard',
'devel_whiteboard','boolean_query','severity','priority','target_milestone']:
if hasattr(opt,a):
i = getattr(opt,a)
if i:
if a == 'bug_status': # list args
# FIXME: statuses can differ between bugzilla instances..
if i == 'ALL':
# leaving this out should return bugs of any status
pass
elif i == 'DEV':
# Alias for all development bug statuses
q[a] = 'NEW,ASSIGNED,NEEDINFO,ON_DEV,MODIFIED,POST,REOPENED'.split(',')
elif i == 'QE':
# Alias for all QE relevant bug statuses
q[a] = 'ASSIGNED,ON_QA,FAILS_QA,PASSES_QA'.split(',')
elif i == 'EOL':
# Alias for EndOfLife bug statuses
q[a] = 'VERIFIED,RELEASE_PENDING,CLOSED'.split(',')
else:
q[a] = i.split(',')
elif a in ['cc','assigned_to','reporter','qa_contact']:
# Emails
# ex.: {'email1':'foo@bar.com','emailcc1':True}
q['email%i' % email_count] = i
q['email%s%i' % (a,email_count)] = True
q['emailtype%i' % email_count] = opt.emailtype
email_count += 1
elif a == 'components_file':
# Components slurped in from file (one component per line)
# This can be made more robust
arr = []
f = open (i, 'r')
for line in f.readlines():
line = line.rstrip("\n")
arr.append(line)
q['component'] = ",".join(arr)
elif a in ['keywords','keywords_type','url','url_type','status_whiteboard',
'status_whiteboard_type','severity','priority']:
if a == 'url':
q['bug_file_loc'] = i
elif a == 'url_type':
q['bug_file_loc_type'] = i
else:
q['%s' % a] = i
elif a in ['fixed_in','blocked','dependson','flag','qa_whiteboard','devel_whiteboard','alias']:
# Boolean Charts
if a == 'flag':
# Flags have strange parameter name
q['field%i-0-0' % chart_id] = 'flagtypes.name'
elif a == 'devel_whiteboard':
q['field%i-0-0' % chart_id] = 'cf_devel_whiteboard'
else:
q['field%i-0-0' % chart_id] = a
q['value%i-0-0' % chart_id] = i
q['type%i-0-0' % chart_id] = opt.booleantype
chart_id += 1
elif a == 'boolean_query':
# Custom Boolean Chart query
# Format: BooleanName-Condition-Parameter &/| BooleanName-Condition-Parameter &/| ...
# ie, keywords-substring-Partner | keywords-notsubstring-PartnerVerified & keywords-notsubstring-OtherQA
chart_id = 0
and_count = 0
or_count = 0
# Manually specified boolean query
x = i.split(' ')
for par in x :
if par.find('&') != -1:
and_count += 1
elif par.find('|') != -1:
or_count += 1
elif par.find('-') != -1:
args = par.split('-')
q['field%i-%i-%i' % (chart_id,and_count,or_count)] = args[0]
q['type%i-%i-%i' % (chart_id,and_count,or_count)] = args[1]
q['value%i-%i-%i' % (chart_id,and_count,or_count)] = args[2]
else:
parser.error('Malformed boolean query: %s' % i)
else:
q[a] = i
log.debug("bz.query: %s", q)
buglist = bz.query(q)
elif action == 'new':
data = dict()
required=['product','component','version','short_desc','comment',
'rep_platform','bug_severity','op_sys','bug_file_loc','priority']
optional=['cc']
for a in required + optional:
i = getattr(opt,a)
if i:
data[a] = i
for k in required:
if k not in data:
parser.error('Missing required argument: %s' % k)
log.debug("bz.createbug(%s)", data)
b = bz.createbug(**data)
b.refresh()
buglist = [b]
elif action == 'modify':
bugid_list = []
for a in args:
if ',' in a:
for b in a.split(','):
bugid_list.append(b)
else:
bugid_list.append(a)
# Surely there's a simpler way to do that..
# bail out if no bugs were given
if not bugid_list:
parser.error('No bug IDs given (maybe you forgot an argument somewhere?)')
# Iterate over a list of Bug objects
# FIXME: this should totally use some multicall magic
buglist = bz.getbugssimple(bugid_list)
for id,bug in zip(bugid_list,buglist):
if not bug:
log.error(" failed to load bug %s" % id)
continue
log.debug("modifying bug %s" % bug.bug_id)
if opt.status:
log.debug(" set status: %s" % opt.status)
bug.setstatus(opt.status, opt.comment, private=opt.private)
elif opt.close:
log.debug(" close %s" % opt.close)
bug.close(opt.close,comment=opt.comment,isprivate=opt.private)
elif opt.assignee:
log.debug(" assignee %s" % opt.assignee)
bug.setassignee(assigned_to=opt.assignee,comment=opt.comment)
elif opt.flag:
log.debug(" flag %s" % opt.flag)
flags=dict()
for f in opt.flag:
(flagname,status) = (f[:-1],f[-1])
flags[flagname]=status
bug.updateflags(flags)
elif opt.cc:
ccs=[]
for c in opt.cc:
log.debug(" cc'ing %s" % opt.cc)
ccs.append(c)
bug.addcc(ccs, comment=opt.comment)
#this has to be last and signifies a stand-alone comment
elif opt.comment:
log.debug(" add comment: %s" % opt.comment)
bug.addcomment(opt.comment, opt.private)
else:
print "Sorry - '%s' not implemented yet." % action
# If we're doing new/query/modify, output our results
if action in ['new','query']:
if opt.outputformat:
format_field_re = re.compile("%{[a-z0-9_]+}")
def bug_field(matchobj):
fieldname = matchobj.group()[2:-1]
return str(getattr(b,fieldname))
for b in buglist:
print format_field_re.sub(bug_field,opt.outputformat)
elif opt.output == 'ids':
for b in buglist:
print b.bug_id
elif opt.output == 'full':
fullbuglist = bz.getbugs([b.bug_id for b in buglist])
for b in fullbuglist:
print to_encoding(u'#%s %s - %s - %s' %
(b.bug_id, b.bug_status, b.assigned_to, b.short_desc))
if b.cc: print "CC: %s" % " ".join(b.cc)
if b.blocked: print "Blocked: %s" % " ".join([str(i) for i in b.blocked])
if b.dependson: print "Depends: %s" % " ".join([str(i) for i in b.dependson])
for c in b.longdescs:
if 'email' in c:
email = c['email']
elif 'safe_email' in c:
email = c['safe_email']
else:
email = 'Unknown'
print to_encoding(u"* %s - %s (%s):\n%s\n" % (c['time'],
c['author'], email, c['body']))
elif opt.output == 'normal':
for b in buglist:
print to_encoding(u'#%s %s - %s - %s' %
(b.bug_id, b.bug_status, b.assigned_to, b.short_desc))
elif opt.output == 'extra':
print "Grabbing 'extra' bug information. This could take a moment."
fullbuglist = bz.getbugs([b.bug_id for b in buglist])
for b in fullbuglist:
print to_encoding(u'#%s %s - %s - %s' %
(b.bug_id, b.bug_status, b.assigned_to, b.short_desc))
if hasattr(b, 'keywords') and b.keywords:
print to_encoding(u" +Keywords: %s" % b.keywords)
if hasattr(b, 'qa_whiteboard') and b.qa_whiteboard:
print to_encoding(u" +QA Whiteboard: %s" % b.qa_whiteboard)
if hasattr(b, 'status_whiteboard') and b.status_whiteboard:
print to_encoding(u" +Status Whiteboard: %s" %
b.status_whiteboard)
if hasattr(b, 'devel_whiteboard') and b.devel_whiteboard:
print to_encoding(u" +Devel Whiteboard: %s" %
b.devel_whiteboard)
print "\nBugs listed: ",len(buglist)
elif opt.output == 'oneline':
fullbuglist = bz.getbugs([b.bug_id for b in buglist])
for b in fullbuglist:
flags=''
cve=''
#grab all the flags that are set
for i in b.flag_types:
for s in i['flags']:
if s['status']: flags += i['name'] + ":" + str(s['status']) + " "
#grab CVEs by searching the keywords and grabbing that bugzilla
if b.keywords.find("Security") != -1:
for bl in b.blocked:
cvebug = bz.getbug(bl)
for cb in cvebug.alias:
if cb.find("CVE") != -1:
cve += cb + " "
print to_encoding(u"#%s %8s %22s %s\t[%s] %s %s" %
(b.bug_id, b.bug_status, b.assigned_to, b.component,
b.target_milestone, flags, cve))
else:
parser.error("opt.output was set to something weird.")
if __name__ == '__main__':
try:
if '--generate-man' in sys.argv:
generate_man_page()
else:
main()
except KeyboardInterrupt:
print "\ninterrupted."
sys.exit(1)