From 65ce2c69ff5d94ca9dc68f6159235fa0fb05d05d Mon Sep 17 00:00:00 2001 From: Will Woods Date: Thu, 30 Aug 2007 18:29:38 -0400 Subject: initial import --- bugzilla.py | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 bugzilla.py (limited to 'bugzilla.py') diff --git a/bugzilla.py b/bugzilla.py new file mode 100644 index 0000000..77107bf --- /dev/null +++ b/bugzilla.py @@ -0,0 +1,199 @@ +#!/usr/bin/python +# bugzilla.py - a Python interface to Bugzilla, using xmlrpclib. +# +# 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 xmlrpclib, urllib2, cookielib + +version = '0.1' +user_agent = 'bugzilla.py/%s (Python-urllib2/%s)' % \ + (version,urllib2.__version__) + +class Bugzilla(object): + def __init__(self,**kwargs): + # Settings the user might want to tweak + self.user = '' + self.password = '' + self.url = '' + # Bugzilla object state info that users shouldn't mess with + self._cookiejar = None + self._proxy = None + self._opener = None + if 'cookies' in kwargs: + self.readcookiefile(kwargs['cookies']) + if 'url' in kwargs: + self.connect(kwargs['url']) + if 'user' in kwargs: + self.user = kwargs['user'] + if 'password' in kwargs: + self.password = kwargs['password'] + + def readcookiefile(self,cookiefile): + '''Read the given (Mozilla-style) cookie file and fill in the cookiejar, + allowing us to use the user's saved credentials to access bugzilla.''' + cj = cookielib.MozillaCookieJar() + cj.load(cookiefile) + self._cookiejar = cj + self._cookiejar.filename = cookiefile + + def connect(self,url): + '''Connect to the bugzilla instance with the given url.''' + # Set up the transport + if url.startswith('https'): + self._transport = SafeCookieTransport() + else: + self._transport = CookieTransport() + self._transport.user_agent = user_agent + self._transport.cookiejar = self._cookiejar or cookielib.CookieJar() + # Set up the proxy, using the transport + self._proxy = xmlrpclib.ServerProxy(url,self._transport) + # Set up the urllib2 opener (using the same cookiejar) + handler = urllib2.HTTPCookieProcessor(self._cookiejar) + self._opener = urllib2.build_opener(handler) + self._opener.addheaders = [('User-agent',user_agent)] + + def login(self,user,password): + '''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 a dict of user info. + + Note that it is not required to login before calling other methods; + you may just set user and password and call whatever methods you like. + ''' + self.user = user + self.password = password + try: + r = self._proxy.bugzilla.login(self.user,self.password) + except xmlrpclib.Fault, f: + r = False + return r + + # ARGLE this should use properties or do some kind of caching or something + def components(self,product): + '''Return a dict of components for the given product.''' + return self._proxy.bugzilla.getProdCompInfo(product, self.user, + self.password) + + def products(self): + '''Return a dict of product names and product descriptions.''' + return self._proxy.bugzilla.getProdInfo(self.user, self.password) + + def getbug(self,id): + '''Return a dict of full bug info for the given bug id''' + return self._proxy.bugzilla.getBug(id, self.user, self.password) + + def getbugsimple(self,id): + '''Return a short dict of simple bug info for the given bug id''' + return self._proxy.bugzilla.getBugSimple(id, self.user, self.password) + + # TODO: createbug, addcomment, attachfile, searchbugs + +class CookieTransport(xmlrpclib.Transport): + '''A subclass of xmlrpclib.Transport that supports cookies.''' + cookiejar = None + scheme = 'http' + + # Cribbed from xmlrpclib.Transport.send_user_agent + def send_cookies(self, connection, cookie_request): + if self.cookiejar is None: + self.cookiejar = cookielib.CookieJar() + elif self.cookiejar: + # Let the cookiejar figure out what cookies are appropriate + self.cookiejar.add_cookie_header(cookie_request) + # Pull the cookie headers out of the request object... + cookielist=list() + for h,v in cookie_request.header_items(): + if h.startswith('Cookie'): + cookielist.append([h,v]) + # ...and put them over the connection + for h,v in cookielist: + connection.putheader(h,v) + + # This is the same request() method from xmlrpclib.Transport, + # with a couple additions noted below + def request(self, host, handler, request_body, verbose=0): + h = self.make_connection(host) + if verbose: + h.set_debuglevel(1) + + # ADDED: construct the URL and Request object for proper cookie handling + request_url = "%s://%s/" % (self.scheme,host) + cookie_request = urllib2.Request(request_url) + + self.send_request(h,handler,request_body) + self.send_host(h,host) + self.send_cookies(h,cookie_request) # ADDED. creates cookiejar if None. + self.send_user_agent(h) + self.send_content(h,request_body) + + errcode, errmsg, headers = h.getreply() + + # ADDED: parse headers and get cookies here + # fake a response object that we can fill with the headers above + class CookieResponse: + def __init__(self,headers): self.headers = headers + def info(self): return self.headers + cookie_response = CookieResponse(headers) + # Okay, extract the cookies from the headers + self.cookiejar.extract_cookies(cookie_response,cookie_request) + # And write back any changes + self.cookiejar.save(self.cookiejar.filename) + + if errcode != 200: + raise xmlrpclib.ProtocolError( + host + handler, + errcode, errmsg, + headers + ) + + self.verbose = verbose + + try: + sock = h._conn.sock + except AttributeError: + sock = None + + return self._parse_response(h.getfile(), sock) + +class SafeCookieTransport(xmlrpclib.SafeTransport,CookieTransport): + '''SafeTransport subclass that supports cookies.''' + scheme = 'https' + request = CookieTransport.request + +import os, glob + +def find_firefox_cookiefile(): + cookieglob = os.path.expanduser('~/.mozilla/firefox/default.*/cookies.txt') + cookiefiles = glob.glob(cookieglob) + if cookiefiles: + # TODO return whichever is newest + return cookiefiles[0] + +def selftest(): + url = 'https://bugzilla.redhat.com/xmlrpc.cgi' + cookies = find_firefox_cookiefile() + public_bug = 1 + private_bug = 250666 + print "Woo, welcome to the bugzilla.py self-test." + print "Using bugzilla at " + url + print "Reading cookies from " + cookies + b = Bugzilla(url=url,cookies=cookies) + print "Reading product list" + print b.products() + print "Reading public bug (#%i)" % public_bug + print b.getbug(public_bug) + print "Reading private bug (#%i)" % private_bug + try: + print b.getbug(private_bug) + except xmlrpclib.Fault, e: + if 'NotPermitted' in e.faultString: + print "Failed: Not authorized." + else: + print "Failed: Unknown XMLRPC error: %s" % e -- cgit