#!/usr/bin/python # bugzilla - a commandline frontend for the python bugzilla module # # Copyright (C) 2007 Red Hat Inc. # Author: Will Woods # # 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 version = '0.1' # Change this once we're ready for actual use default_bz = 'https://partner-bugzilla.redhat.com/xmlrpc.cgi' def findcookie(): globs = ['~/.mozilla/firefox/*default*/cookies.txt'] for g in globs: cookiefiles = glob.glob(os.path.expanduser(g)) if cookiefiles: # return the first one we find. # TODO: find all cookiefiles, sort by age, use newest return cookiefiles[0] def setup_parser(): u = "usage: %prog info|query|new|modify [options]" p = optparse.OptionParser(usage=u) # General bugzilla connection options # TODO: OptionGroup p.add_option('--bugzilla',default=default_bz, help="bugzilla XMLRPC URI. default: %s" % default_bz) p.add_option('--user', help="username. Will attempt to use Firefox cookie if not specified.") p.add_option('--password', help="password. Will attempt to use Firefox cookie if not specified.") p.add_option('--cookiefile', help="cookie file to use for bugzilla authentication - default: %s" % findcookie()) return p def modify_parser(parser,action): p = parser if action == 'query': # TODO: product and version could default to current system # info (read from /etc/redhat-release?) p.add_option('-p','--product', help="product name (list with 'bugzilla info -p')") p.add_option('-v','--version', help="version string to search for") p.add_option('-c','--component', help="component name (list with 'bugzilla info -c PRODUCT')") p.add_option('-l','--long_desc', help="search inside bug comments") p.add_option('-s','--short_desc', help="search bug summaries") p.add_option('-o','--cc', help="search cc lists for given address") p.add_option('-a','--assigned_to', help="search for bugs assigned to this address") p.add_option('-b','--bug_id', help="specify individual bugs by IDs, separated with commas") p.add_option('-t','--bug_status',default="NEW,VERIFIED,ASSIGNED,NEEDINFO,ON_DEV,FAILS_QA,REOPENED", help="comma-separated list of bug statuses to accept") # output modifiers p.add_option('-f','--full',action='store_const',dest='output', const='full',default='normal',help="Give detailed bug info") p.add_option('-i','--ids',action='store_const',dest='output', const='ids',help="Just list bug IDs") 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}'") 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') return p if __name__ == '__main__': log = logging.getLogger("bugzilla") # Set up parser parser = setup_parser() # Get our action if len(sys.argv) > 1 and sys.argv[1] in ('info','query','new','modify'): action = sys.argv[1] else: parser.error("you must specify an action") # Add the action-specific args and such parser = modify_parser(parser,action) # Parse the commandline, woo (opt,args) = parser.parse_args() # Connect to bugzilla log.info('Connecting to %s',opt.bugzilla) bz=bugzilla.Bugzilla(url=opt.bugzilla) if opt.user and opt.password: bz.login(opt.user,opt.password) elif opt.cookiefile: bz.readcookiefile(opt.cookiefile) else: cookiefile = findcookie() if cookiefile: bz.readcookiefile(cookiefile) else: parser.error("Could not find a Firefox cookie file. Try --user/--password.") # And now we actually execute the given command 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': # shortcut for specific bug_ids if opt.bug_id: if ',' in opt.bug_id: buglist=bz.getbugs(opt.bug_id.split(',')) else: buglist=[bz.getbug(opt.bug_id)] else: # Construct the query from the list of queryable options q = dict() email_count = 1 for a in ('product','component','version','long_desc','bug_id', 'short_desc','cc','assigned_to','bug_status'): if hasattr(opt,a): i = getattr(opt,a) if i: if a in ('bug_status'): # list args q[a] = i.split(',').strip() elif a in ('cc','assigned_to'): # the email query fields are kind of weird - thanks # to Florian La Roche for figuring this bit out. # ex.: {'email1':'foo@bar.com','emailcc1':True} q['email%i' % email_count] = i q['email%s%i' % (a,email_count)] = True email_count = email_count + 1 else: q[a] = i buglist = bz.query(q) 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 b 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: print "* %s - %s (%s):\n%s\n" % (c['time'],c['name'],c['email'] or c['safe_email'],c['body']) elif opt.output == 'normal': for b in buglist: print b else: parser.error("opt.output was set to something weird.") else: print "Sorry - '%s' not implemented yet." % action