summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJosh Boyer <jwboyer@redhat.com>2012-05-07 15:32:41 -0400
committerJosh Boyer <jwboyer@redhat.com>2012-05-07 15:32:41 -0400
commit1536a5f6277b003424cac6966734d722e7496c8d (patch)
tree615cbfa4a9bf96da826a7f1386469deeaae2342c
downloadkoji-bisect-1536a5f6277b003424cac6966734d722e7496c8d.tar.gz
koji-bisect-1536a5f6277b003424cac6966734d722e7496c8d.tar.xz
koji-bisect-1536a5f6277b003424cac6966734d722e7496c8d.zip
Initial commit
-rwxr-xr-xkoji-bisect.py209
1 files changed, 209 insertions, 0 deletions
diff --git a/koji-bisect.py b/koji-bisect.py
new file mode 100755
index 0000000..8262d95
--- /dev/null
+++ b/koji-bisect.py
@@ -0,0 +1,209 @@
+#!/usr/bin/python
+
+import sys
+import ConfigParser
+import argparse
+import rpmUtils.miscutils
+import koji
+import os
+import bisect
+from functools import cmp_to_key
+
+class Options:
+ debug = True
+ server = None
+ weburl = None
+ pkgurl = None
+ topdir = None
+ cert = None
+ ca = None
+ serverca = None
+ authtype = None
+ noauth = None
+ user = None
+ runas = None
+
+def get_options():
+ global options
+ # load local config
+ defaults = {
+ 'server' : 'http://localhost/kojihub',
+ 'weburl' : 'http://localhost/koji',
+ 'pkgurl' : 'http://localhost/packages',
+ 'topdir' : '/mnt/koji',
+ 'max_retries' : None,
+ 'retry_interval': None,
+ 'anon_retry' : None,
+ 'offline_retry' : None,
+ 'offline_retry_interval' : None,
+ 'poll_interval': 5,
+ 'cert': '~/.koji/client.crt',
+ 'ca': '~/.koji/clientca.crt',
+ 'serverca': '~/.koji/serverca.crt',
+ 'authtype': None
+ }
+ # grab settings from /etc/koji.conf first, and allow them to be
+ # overridden by user config
+ progname = 'koji'
+ for configFile in ('/etc/koji.conf',):
+ if os.access(configFile, os.F_OK):
+ f = open(configFile)
+ config = ConfigParser.ConfigParser()
+ config.readfp(f)
+ f.close()
+ if config.has_section(progname):
+ for name, value in config.items(progname):
+ #note the defaults dictionary also serves to indicate which
+ #options *can* be set via the config file. Such options should
+ #not have a default value set in the option parser.
+ if defaults.has_key(name):
+ if name in ('anon_retry', 'offline_retry'):
+ defaults[name] = config.getboolean(progname, name)
+ elif name in ('max_retries', 'retry_interval',
+ 'offline_retry_interval', 'poll_interval'):
+ try:
+ defaults[name] = int(value)
+ except ValueError:
+ parser.error("value for %s config option must be a valid integer" % name)
+ assert False
+ else:
+ defaults[name] = value
+ for name, value in defaults.iteritems():
+ if getattr(options, name, None) is None:
+ # print '%s' % getattr(options, name, None)
+ setattr(options, name, value)
+ # print '%s' % getattr(options, name, None)
+ dir_opts = ('topdir', 'cert', 'ca', 'serverca')
+ for name in dir_opts:
+ # expand paths here, so we don't have to worry about it later
+ value = os.path.expanduser(getattr(options, name))
+ setattr(options, name, value)
+
+ #honor topdir
+ if options.topdir:
+ koji.BASEDIR = options.topdir
+ koji.pathinfo.topdir = options.topdir
+
+ return options
+
+def ensure_connection(session):
+ try:
+ ret = session.getAPIVersion()
+ except xmlrpclib.ProtocolError:
+ error(_("Error: Unable to connect to server"))
+ if ret != koji.API_VERSION:
+ warn(_("WARNING: The server is at API version %d and the client is at %d" % (ret, koji.API_VERSION)))
+
+def activate_session(session):
+ """Test and login the session is applicable"""
+ global options
+ if options.authtype == "noauth" or options.noauth:
+ #skip authentication
+ pass
+ elif options.authtype == "ssl" or os.path.isfile(options.cert) and options.authtype is None:
+ # authenticate using SSL client cert
+ session.ssl_login(options.cert, options.ca, options.serverca, proxyuser=options.runas)
+ elif options.authtype == "password" or options.user and options.authtype is None:
+ # authenticate using user/password
+ session.login()
+ if not options.noauth and options.authtype != "noauth" and not session.logged_in:
+ error(_("Unable to log in, no authentication methods available"))
+ ensure_connection(session)
+ if options.debug:
+ print "successfully connected to hub"
+
+# Everything above this line is all koji session bullshit that we shouldn't
+# have to copy, but do because koji doesn't put it in a damn module somewhere
+# ANGRY CODING
+
+def sort_builds(builds):
+ build_tuples=[]
+ pkg = builds[0]['package_name']
+
+ # compareEVR takes tuples consisting of epoch, version, release
+ # we need to split the 'nvr' string we got from koji up into that
+ # so we can get it sorted in RPM order
+ for build in builds:
+ bld = build['nvr'].split('-')
+ bld.insert(1,build['epoch'])
+ build_tuples.append(tuple(bld[1:]))
+
+ build_list = sorted(build_tuples, key=cmp_to_key(rpmUtils.miscutils.compareEVR))
+
+ # reassemble the builds now. Because sigh.
+ nvr_list = []
+ for build in build_list:
+ nvr_list.append(pkg + '-' + build[1] + '-' + build[2])
+ return nvr_list
+
+def filter_dist(builds, dist):
+ dist_builds = []
+ for build in builds:
+ if build['nvr'].endswith(args.dist):
+ dist_builds.append(build)
+ return dist_builds
+
+def get_args():
+ parser = argparse.ArgumentParser(description='Bisect koji builds')
+ parser.add_argument('--good', action='store', help='good kernel NVR')
+ parser.add_argument('--bad', action='store', help='bad kernel NVR')
+ parser.add_argument('--list', action='store_true', help='list of builds remaining')
+ parser.add_argument('--dist', action='store', help='disttag of specific release')
+
+ return parser.parse_args()
+
+def list_builds(builds):
+ for build in builds:
+ print build
+
+if __name__ == "__main__":
+ global options
+ options = Options()
+
+ options = get_options()
+
+ args = get_args()
+
+ session = koji.ClientSession(options.server)
+ activate_session(session)
+
+ all_builds = session.listBuilds(packageID=8, state=1)
+ print all_builds[0]
+
+ builds = all_builds
+ if args.dist:
+ builds = filter_dist(builds, args.dist)
+
+ build_list = sort_builds(builds)
+ builds_left = build_list
+
+ good_index = None
+ if args.good:
+ i = bisect.bisect_left(build_list, args.good)
+ if i != len(build_list) and build_list[i] == args.good:
+ print "Marking %s as good" % build_list[i]
+ good_index = i
+
+ bad_index = None
+ if args.bad:
+ i = bisect.bisect_left(build_list, args.bad)
+ if i != len(build_list) and build_list[i] == args.bad:
+ print "Marking %s as bad" % build_list[i]
+ bad_index = i
+
+ if good_index == None or bad_index == None:
+ if args.list:
+ list_builds(builds_left)
+ else:
+ print "Must have a good or bad index"
+ sys.exit(1)
+
+ if good_index < bad_index:
+ builds_left = build_list[good_index:bad_index]
+ elif good_index == bad_index:
+ print "Shit yo, is %s bad or good ?!" % build_list[good_index]
+ else:
+ builds_left = build_list[bad_index:good_index]
+
+ if args.list:
+ list_builds(builds_left)