#417 Python3 support for CLI + XMLRPC client
Merged 4 years ago by mikem. Opened 5 years ago by tkopecek.
tkopecek/koji python3-cli2-basic  into  master

file modified
+1 -1
@@ -1,5 +1,5 @@ 

  [run]

  

  omit =

-     /usr/lib/*

+     /usr/*

      tests/*

file added
+12
@@ -0,0 +1,12 @@ 

+ [run]

+ 

+ ; extra omissions for py3 for now

+ 

+ omit =

+     /usr/*

+     tests/*

+     hub/*

+     util/*

+     koji/ssl/*

+     koji/daemon.py

+     koji/tasks.py

file modified
+14 -2
@@ -66,9 +66,21 @@ 

  

  test:

  	coverage erase

- 	PYTHONPATH=hub/.:plugins/hub/.:plugins/builder/. nosetests --with-coverage --cover-package .

+ 	PYTHONPATH=hub/.:plugins/hub/.:plugins/builder/. coverage run \

+ 	    --source . /usr/bin/nosetests

+ 	coverage report

  	coverage html

- 	@echo Coverage report in htmlcov/index.html

+ 	@echo Full coverage report in htmlcov/index.html

+ 

+ test3:

+ 	coverage erase

+ 	PYTHONPATH=hub/.:plugins/hub/.:plugins/builder/. coverage3 run \

+ 	    --rcfile .coveragerc3 --source . \

+ 	    /usr/bin/nosetests-3 \

+ 	    tests/test_lib tests/test_cli

+ 	coverage report

+ 	coverage html

+ 	@echo Full coverage report in htmlcov/index.html

  

  subdirs:

  	for d in $(SUBDIRS); do make -C $$d; [ $$? = 0 ] || exit 1; done

file modified
+1 -1
@@ -15,5 +15,5 @@ 

  

  	mkdir -p $(DESTDIR)/usr/bin

  	install -p -m 755 $(FILES) $(DESTDIR)/usr/bin

- 	install -p -m 644 koji.conf $(DESTDIR)/etc/koji.conf

  	mkdir -p $(DESTDIR)/etc/koji.conf.d

+ 	install -p -m 644 koji.conf $(DESTDIR)/etc/koji.conf

file modified
+109 -75
@@ -24,7 +24,14 @@ 

  #       Mike Bonnet <mikeb@redhat.com>

  #       Cristian Balint <cbalint@redhat.com>

  

+ from __future__ import absolute_import

+ from __future__ import division

  import sys

+ from six.moves import range

+ from six.moves import zip

+ import six

+ from six.moves import filter

+ from six.moves import map

  try:

      import krbV

  except ImportError:  # pragma: no cover
@@ -40,7 +47,7 @@ 

          import simplejson as json

      except ImportError:

          json = None

- import ConfigParser

+ import six.moves.configparser

  import base64

  import dateutil.parser

  import errno
@@ -52,15 +59,14 @@ 

  import os

  import re

  import pprint

+ import pycurl

  import random

  import socket

  import stat

  import string

  import time

  import traceback

- import urlgrabber.grabber as grabber

- import urlgrabber.progress as progress

- import xmlrpclib

+ import six.moves.xmlrpc_client

  try:

      import libcomps

  except ImportError:  # pragma: no cover
@@ -110,6 +116,12 @@ 

      """Stub function for translation"""

      return args

  

+ def _printable_unicode(s):

+     if six.PY2:

+         return s.encode('utf-8')

+     else:

+         return s

+ 

  ARGMAP = {'None': None,

            'True': True,

            'False': False}
@@ -142,7 +154,7 @@ 

  def get_epilog_str(progname=None):

      if progname is None:

          progname = os.path.basename(sys.argv[0]) or 'koji'

-     categories_ordered=', '.join(sorted(['all'] + categories.keys()))

+     categories_ordered=', '.join(sorted(['all'] + list(categories.keys())))

      epilog_str = '''

  Try "%(progname)s --help" for help about global options

  Try "%(progname)s help" to get all available commands
@@ -237,12 +249,12 @@ 

      # load local config

      try:

          result = koji.read_config(options.profile, user_config=options.configFile)

-     except koji.ConfigurationError, e:

+     except koji.ConfigurationError as e:

          parser.error(e.args[0])

          assert False  # pragma: no cover

  

      # update options according to local config

-     for name, value in result.iteritems():

+     for name, value in six.iteritems(result):

          if getattr(options, name, None) is None:

              setattr(options, name, value)

  
@@ -275,7 +287,7 @@ 

  def ensure_connection(session):

      try:

          ret = session.getAPIVersion()

-     except xmlrpclib.ProtocolError:

+     except six.moves.xmlrpc_client.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)))
@@ -339,7 +351,7 @@ 

          error = None

          try:

              result = self.session.getTaskResult(self.id)

-         except (xmlrpclib.Fault,koji.GenericError),e:

+         except (six.moves.xmlrpc_client.Fault,koji.GenericError) as e:

              error = e

          if error is None:

              # print("%s: complete" % self.str())
@@ -448,7 +460,7 @@ 

              tasks[task_id] = TaskWatcher(task_id,session,quiet=quiet)

          while True:

              all_done = True

-             for task_id,task in tasks.items():

+             for task_id, task in list(tasks.items()):

                  changed = task.update()

                  if not task.is_done():

                      all_done = False
@@ -461,7 +473,7 @@ 

                          rv = 1

                  for child in session.getTaskChildren(task_id):

                      child_id = child['id']

-                     if not child_id in tasks.keys():

+                     if not child_id in list(tasks.keys()):

                          tasks[child_id] = TaskWatcher(child_id, session, task.level + 1, quiet=quiet)

                          tasks[child_id].update()

                          # If we found new children, go through the list again,
@@ -511,7 +523,7 @@ 

              output = list_task_output_all_volumes(session, task_id)

              # convert to list of (file, volume)

              files = []

-             for filename, volumes in output.iteritems():

+             for filename, volumes in six.iteritems(output):

                  files += [(filename, volume) for volume in volumes]

  

              if opts.log:
@@ -547,7 +559,7 @@ 

      """List task output with all volumes, or fake it"""

      try:

          return session.listTaskOutput(task_id, all_volumes=True)

-     except koji.GenericError, e:

+     except koji.GenericError as e:

          if 'got an unexpected keyword argument' not in str(e):

              raise

      # otherwise leave off the option and fake it
@@ -941,7 +953,7 @@ 

  def _running_in_bg():

      try:

          return (not os.isatty(0)) or (os.getpgrp() != os.tcgetpgrp(0))

-     except OSError, e:

+     except OSError as e:

          return True

  

  def handle_build(options, session, args):
@@ -1164,11 +1176,11 @@ 

          try:

              params = koji.util.parse_maven_param(build_opts.inis, scratch=build_opts.scratch,

                                                   section=build_opts.section)

-         except ValueError, e:

+         except ValueError as e:

              parser.error(e.args[0])

-         opts = params.values()[0]

+         opts = list(params.values())[0]

          if opts.pop('type', 'maven') != 'maven':

-             parser.error(_("Section %s does not contain a maven-build config") % params.keys()[0])

+             parser.error(_("Section %s does not contain a maven-build config") % list(params.keys())[0])

          source = opts.pop('scmurl')

      else:

          source = args[1]
@@ -1223,11 +1235,11 @@ 

          try:

              params = koji.util.parse_maven_param(build_opts.inis, scratch=build_opts.scratch,

                                                   section=build_opts.section)

-         except ValueError, e:

+         except ValueError as e:

              parser.error(e.args[0])

-         opts = params.values()[0]

+         opts = list(params.values())[0]

          if opts.get('type') != 'wrapper':

-             parser.error(_("Section %s does not contain a wrapper-rpm config") % params.keys()[0])

+             parser.error(_("Section %s does not contain a wrapper-rpm config") % list(params.keys())[0])

          url = opts['scmurl']

          package = opts['buildrequires'][0]

          target_info = session.getBuildTarget(target, strict=True)
@@ -1297,7 +1309,7 @@ 

              opts[key] = val

      try:

          builds = koji.util.parse_maven_chain(args[1:], scratch=opts.get('scratch'))

-     except ValueError, e:

+     except ValueError as e:

          parser.error(e.args[0])

      priority = None

      if build_opts.background:
@@ -1491,7 +1503,7 @@ 

          name = "%(tag_name)s-repo_%(repoid)s" % opts

      output = koji.genMockConfig(name, arch, **opts)

      if options.ofile:

-         fo = file(options.ofile, 'w')

+         fo = open(options.ofile, 'w')

          fo.write(output)

          fo.close()

      else:
@@ -1576,7 +1588,7 @@ 

  

  def linked_upload(localfile, path, name=None):

      """Link a file into the (locally writable) workdir, bypassing upload"""

-     old_umask = os.umask(002)

+     old_umask = os.umask(0o02)

      try:

          if name is None:

              name = os.path.basename(localfile)
@@ -1625,7 +1637,7 @@ 

              nvr = "%(name)s-%(version)s-%(release)s" % koji.parse_NVRA(data['sourcerpm'])

          to_import.setdefault(nvr,[]).append((path,data))

      builds_missing = False

-     nvrs = to_import.keys()

+     nvrs = list(to_import.keys())

      nvrs.sort()

      for nvr in nvrs:

          to_import[nvr].sort()
@@ -1672,7 +1684,7 @@ 

          sys.stdout.flush()

          try:

              session.importRPM(serverdir, os.path.basename(path))

-         except koji.GenericError, e:

+         except koji.GenericError as e:

              print(_("\nError importing: %s" % str(e).splitlines()[-1]))

              sys.stdout.flush()

          else:
@@ -1759,7 +1771,7 @@ 

          parser.error(_("Unable to find json module"))

          assert False  # pragma: no cover

      activate_session(session)

-     metadata = json.load(file(args[0], 'r'))

+     metadata = json.load(open(args[0], 'r'))

      if 'output' not in metadata:

          print(_("Metadata contains no output"))

          sys.exit(1)
@@ -1850,7 +1862,11 @@ 

                          }

              if pkg.type == libcomps.PACKAGE_TYPE_CONDITIONAL:

                  pkgopts['requires'] = pkg.requires

-             print("  Package: %s: %r" % (pkg.name, pkgopts))

+             for k in pkgopts.keys():

+                 if six.PY2 and isinstance(pkgopts[k], unicode):

+                     pkgopts[k] = str(pkgopts[k])

+             s_opts = ', '.join(["'%s': %r" % (k, pkgopts[k]) for k in sorted(list(pkgopts.keys()))])

+             print("  Package: %s: {%s}" % (pkg.name, s_opts))

              session.groupPackageListAdd(tag, group.id, pkg.name, force=force, **pkgopts)

          # libcomps does not support group dependencies

          # libcomps does not support metapkgs
@@ -1875,11 +1891,15 @@ 

                               ('optional', group.optional_packages),

                               ('conditional', group.conditional_packages)]:

              for pkg in pdata:

+                 #yum.comps does not support basearchonly

                  pkgopts = {'type' : ptype}

                  if ptype == 'conditional':

                      pkgopts['requires'] = pdata[pkg]

-                 #yum.comps does not support basearchonly

-                 print("  Package: %s: %r" % (pkg, pkgopts))

+                 for k in pkgopts.keys():

+                     if six.PY2 and isinstance(pkgopts[k], unicode):

+                         pkgopts[k] = str(pkgopts[k])

+                 s_opts = ', '.join(["'%s': %r" % (k, pkgopts[k]) for k in sorted(list(pkgopts.keys()))])

+                 print("  Package: %s: {%s}" % (pkg, s_opts))

                  session.groupPackageListAdd(tag, group.groupid, pkg, force=force, **pkgopts)

          #yum.comps does not support group dependencies

          #yum.comps does not support metapkgs
@@ -2024,11 +2044,11 @@ 

      #(with the modification that we check to see if the build was latest within

      #the last N days)

      if options.ignore_tag_file:

-         fo = file(options.ignore_tag_file)

+         fo = open(options.ignore_tag_file)

          options.ignore_tag.extend([line.strip() for line in fo.readlines()])

          fo.close()

      if options.protect_tag_file:

-         fo = file(options.protect_tag_file)

+         fo = open(options.protect_tag_file)

          options.protect_tag.extend([line.strip() for line in fo.readlines()])

          fo.close()

      if options.debug:
@@ -2087,7 +2107,7 @@ 

              #that the build was recently untagged from

              tags.setdefault(entry['tag_name'], 1)

          if options.debug:

-             print("Tags: %s" % tags.keys())

+             print("Tags: %s" % list(tags.keys()))

          for tag_name in tags:

              if tag_name == options.trashcan_tag:

                  if options.debug:
@@ -2119,7 +2139,7 @@ 

              timeline.sort()

              #find most recent creation entry for our build and crop there

              latest_ts = None

-             for i in xrange(len(timeline)-1, -1, -1):

+             for i in range(len(timeline)-1, -1, -1):

                  #searching in reverse cronological order

                  event_id, is_create, entry = timeline[i]

                  if entry['build_id'] == binfo['id'] and is_create:
@@ -2257,7 +2277,7 @@ 

          build_space = 0

          if not by_sig and options.debug:

              print("(build has no signatures)")

-         for sigkey, rpms in by_sig.iteritems():

+         for sigkey, rpms in six.iteritems(by_sig):

              mycount = 0

              archdirs = {}

              sigdirs = {}
@@ -2281,7 +2301,7 @@ 

                          print("Unlinking: %s" % signedpath)

                      try:

                          os.unlink(signedpath)

-                     except OSError, e:

+                     except OSError as e:

                          print("Error removing %s: %s" % (signedpath, e))

                          print("This script needs write access to %s" % koji.BASEDIR)

                          continue
@@ -2301,10 +2321,10 @@ 

                          print("Removing dir: %s" % dir)

                      try:

                          os.rmdir(dir)

-                     except OSError, e:

+                     except OSError as e:

                          print("Error removing %s: %s" % (signedpath, e))

              if len(sigdirs) == 1:

-                 dir = sigdirs.keys()[0]

+                 dir = list(sigdirs.keys())[0]

                  if options.test:

                      print("Would have removed dir: %s" % dir)

                  else:
@@ -2312,7 +2332,7 @@ 

                          print("Removing dir: %s" % dir)

                      try:

                          os.rmdir(dir)

-                     except OSError, e:

+                     except OSError as e:

                          print("Error removing %s: %s" % (signedpath, e))

              elif len(sigdirs) > 1:

                  print("Warning: more than one signature dir for %s: %r" % (sigkey, sigdirs))
@@ -2546,7 +2566,7 @@ 

          sys.stdout.write(_("importing %s... ") % nvr)

          try:

              session.importBuildInPlace(data)

-         except koji.GenericError, e:

+         except koji.GenericError as e:

              print(_("\nError importing: %s" % str(e).splitlines()[-1]))

              sys.stdout.flush()

          else:
@@ -3671,35 +3691,35 @@ 

                  dstgroups[group['name']] = group

          #construct to-do lists.

          paddlist = [] # list containing new packages to be added from src tag

-         for (package_name, pkg) in srcpkgs.iteritems():

+         for (package_name, pkg) in six.iteritems(srcpkgs):

              if package_name not in dstpkgs:

                  paddlist.append(pkg)

          paddlist.sort(key = lambda x: x['package_name'])

          pdellist = [] # list containing packages no more present in dst tag

-         for (package_name, pkg) in dstpkgs.iteritems():

+         for (package_name, pkg) in six.iteritems(dstpkgs):

              if package_name not in srcpkgs:

                  pdellist.append(pkg)

          pdellist.sort(key = lambda x: x['package_name'])

          baddlist = [] # list containing new builds to be added from src tag

-         for (nvr, lbld) in srclblds.iteritems():

+         for (nvr, lbld) in six.iteritems(srclblds):

              if nvr not in dstlblds:

                  baddlist.append(lbld)

          baddlist.sort(key = lambda x: x['package_name'])

          bdellist = [] # list containing new builds to be removed from src tag

-         for (nvr, lbld) in dstlblds.iteritems():

+         for (nvr, lbld) in six.iteritems(dstlblds):

              if nvr not in srclblds:

                  bdellist.append(lbld)

          bdellist.sort(key = lambda x: x['package_name'])

          gaddlist = [] # list containing new groups to be added from src tag

-         for (grpname, group) in srcgroups.iteritems():

+         for (grpname, group) in six.iteritems(srcgroups):

              if grpname not in dstgroups:

                  gaddlist.append(group)

          gdellist = [] # list containing groups to be removed from src tag

-         for (grpname, group) in dstgroups.iteritems():

+         for (grpname, group) in six.iteritems(dstgroups):

              if grpname not in srcgroups:

                  gdellist.append(group)

          grpchanges = {} # dict of changes to make in shared groups

-         for (grpname, group) in srcgroups.iteritems():

+         for (grpname, group) in six.iteritems(srcgroups):

              if grpname in dstgroups:

                  grpchanges[grpname] = {'adds':[], 'dels':[]}

                  # Store whether group is inherited or not
@@ -4041,15 +4061,15 @@ 

          if depth < currtag['currdepth']:

              outspacing = depth - outdepth

              sys.stdout.write(' ' * (outspacing * 3 - 1))

-             sys.stdout.write(u'\u2502'.encode('UTF-8'))

+             sys.stdout.write(_printable_unicode(u'\u2502'))

              outdepth = depth

  

      sys.stdout.write(' ' * ((currtag['currdepth'] - outdepth) * 3 - 1))

      if siblings:

-         sys.stdout.write(u'\u251c'.encode('UTF-8'))

+         sys.stdout.write(_printable_unicode(u'\u251c'))

      else:

-         sys.stdout.write(u'\u2514'.encode('UTF-8'))

-     sys.stdout.write(u'\u2500'.encode('UTF-8'))

+         sys.stdout.write(_printable_unicode(u'\u2514'))

+     sys.stdout.write(_printable_unicode(u'\u2500'))

      if reverse:

          sys.stdout.write('%(name)s (%(tag_id)i)\n' % currtag)

      else:
@@ -4148,7 +4168,7 @@ 

              assert False  # pragma: no cover

  

      tags = session.listTags(buildinfo.get('id',None), pkginfo.get('id',None))

-     tags.sort(lambda a,b: cmp(a['name'],b['name']))

+     tags.sort(key=lambda x: x['name'])

      #if options.verbose:

      #    fmt = "%(name)s [%(id)i] %(perm)s %(locked)s %(arches)s"

      if options.show_id:
@@ -4393,7 +4413,7 @@ 

          else:

              return '%s.name' % key

      if edit:

-         keys = x.keys()

+         keys = list(x.keys())

          keys.sort()

          y = other[-1]

          for key in keys:
@@ -4408,7 +4428,7 @@ 

                  continue

              print("    %s: %s -> %s" % (key, x[key], y[key]))

      elif create and options.verbose and table != 'tag_listing':

-         keys = x.keys()

+         keys = list(x.keys())

          keys.sort()

          # the table keys have already been represented in the base format string

          also_hidden = list(_table_keys[table])
@@ -4884,7 +4904,7 @@ 

              print("Include all Maven archives?: %s" % (info['maven_include_all'] and 'yes' or 'no'))

          if 'extra' in info:

              print("Tag options:")

-             keys = info['extra'].keys()

+             keys = list(info['extra'].keys())

              keys.sort()

              for key in keys:

                  print("  %s : %s" % (key, pprint.pformat(info['extra'][key])))
@@ -5437,7 +5457,7 @@ 

      if not repolist:

          priority = 5

      else:

-         priority = (repolist[-1]['priority'] + 7) / 5 * 5

+         priority = (repolist[-1]['priority'] + 7) // 5 * 5

          #at least 3 higher than current max and a multiple of 5

      return priority

  
@@ -5914,7 +5934,7 @@ 

          if not os.path.exists(task_options.config):

              parser.error(_("%s not found!" % task_options.config))

          section = 'image-build'

-         config = ConfigParser.ConfigParser()

+         config = six.moves.configparser.ConfigParser()

          conf_fd = open(task_options.config)

          config.readfp(conf_fd)

          conf_fd.close()
@@ -6524,7 +6544,7 @@ 

          if value is not None:

              taskopts[key] = value

      task_id = session.makeTask(method=args[0],

-                                arglist=map(arg_filter,args[1:]),

+                                arglist=list(map(arg_filter,args[1:])),

                                 **taskopts)

      print("Created task id %d" % task_id)

      if _running_in_bg() or not options.watch:
@@ -6732,7 +6752,7 @@ 

          # We want the latest build, not a specific build

          try:

              builds = session.listTagged(suboptions.latestfrom, latest=True, package=build, type=suboptions.type)

-         except koji.GenericError, data:

+         except koji.GenericError as data:

              print("Error finding latest build: %s" % data)

              return 1

          if not builds:
@@ -6813,15 +6833,29 @@ 

              url = pathinfo.build(info) + '/' + fname

              urls.append((url, os.path.basename(fname)))

  

-     if suboptions.quiet:

-         pg = None

-     else:

-         pg = progress.TextMeter()

+     def _progress(download_t, download_d, upload_t, upload_d):

+         if download_t == 0:

+             percent_done = 0.0

+         else:

+             percent_done = float(download_d)/float(download_t)

+         percent_done_str = "%02d%%" % (percent_done * 100)

+         data_done = _format_size(download_d)

+ 

+         sys.stdout.write("[% -36s] % 4s % 10s\r" % ('='*(int(percent_done * 36)), percent_done_str, data_done))

+         sys.stdout.flush()

  

      for url, relpath in urls:

          if '/' in relpath:

              koji.ensuredir(os.path.dirname(relpath))

-         grabber.urlgrab(url, filename=relpath, progress_obj=pg, text=relpath)

+         print(relpath)

+         c = pycurl.Curl()

+         c.setopt(c.URL, url)

+         c.setopt(c.WRITEDATA, open(relpath, 'wb'))

+         if not suboptions.quiet:

+             c.setopt(c.NOPROGRESS, False)

+             c.setopt(c.XFERINFOFUNCTION, _progress)

+         c.perform()

+         print('')

  

  

  def anon_handle_download_logs(options, session, args):
@@ -6861,7 +6895,7 @@ 

          full_filename = os.path.normpath(os.path.join(task_log_dir, FAIL_LOG))

          koji.ensuredir(os.path.dirname(full_filename))

          sys.stdout.write("Writing: %s\n" % full_filename)

-         file(full_filename, 'w').write(content)

+         open(full_filename, 'w').write(content)

  

      def download_log(task_log_dir, task_id, filename, blocksize=102400, volume=None):

          # Create directories only if there is any log file to write to
@@ -6874,11 +6908,11 @@ 

          contents = 'IGNORE ME!'

          if suboptions.cont and os.path.exists(full_filename):

              sys.stdout.write("Continuing: %s\n" % full_filename)

-             fd = file(full_filename, 'ab')

+             fd = open(full_filename, 'ab')

              offset = fd.tell()

          else:

              sys.stdout.write("Downloading: %s\n" % full_filename)

-             fd = file(full_filename, 'wb')

+             fd = open(full_filename, 'wb')

              offset = 0

          try:

              while contents:
@@ -6975,7 +7009,7 @@ 

          downloadable_tasks.append(base_task)

      else:

          subtasks = session.getTaskChildren(base_task_id)

-         downloadable_tasks.extend(filter(check_downloadable, subtasks))

+         downloadable_tasks.extend(list(filter(check_downloadable, subtasks)))

  

      # get files for download

  
@@ -7064,7 +7098,7 @@ 

              targets = session.getBuildTargets(destTagID=tag_info['id'])

              if targets:

                  maybe = {}.fromkeys([t['build_tag_name'] for t in targets])

-                 maybe = maybe.keys()

+                 maybe = list(maybe.keys())

                  maybe.sort()

                  print("Suggested tags: %s" % ', '.join(maybe))

              return 1
@@ -7321,7 +7355,7 @@ 

      if not u:

          print("Not authenticated")

          u = {'name' : 'anonymous user'}

-     print("%s, %s!" % (random.choice(greetings).encode('utf-8'), u["name"]))

+     print("%s, %s!" % (_printable_unicode(random.choice(greetings)), u["name"]))

      print("")

      print("You are using the hub at %s" % session.baseurl)

      authtype = u.get('authtype', getattr(session, 'authtype', None))
@@ -7379,7 +7413,7 @@ 

              kwargs['new_chroot'] = True

  

          task_id = session.runroot(tag, arch, command, **kwargs)

-     except koji.GenericError, e:

+     except koji.GenericError as e:

          if 'Invalid method' in str(e):

              print("* The runroot plugin appears to not be installed on the"

                    " koji hub.  Please contact the administrator.")
@@ -7455,7 +7489,7 @@ 

  

      try:

          task_id = session.saveFailedTree(br_id, opts.full)

-     except koji.GenericError, e:

+     except koji.GenericError as e:

          m = str(e)

          if 'Invalid method' in m:

              print(_("* The save_failed_tree plugin appears to not be "
@@ -7489,7 +7523,7 @@ 

      chosen = set(args)

      if options.admin:

          chosen.add('admin')

-     avail = set(categories.keys() + ['all'])

+     avail = set(list(categories.keys()) + ['all'])

      unavail = chosen - avail

      for arg in unavail:

          print("No such help category: %s" % arg)
@@ -7502,7 +7536,7 @@ 

  

  def list_commands(categories_chosen=None):

      if categories_chosen is None or "all" in categories_chosen:

-         categories_chosen = categories.keys()

+         categories_chosen = list(categories.keys())

      else:

          # copy list since we're about to modify it

          categories_chosen = list(categories_chosen)
@@ -7570,9 +7604,9 @@ 

                  session.krb_login(principal=options.principal, keytab=options.keytab, proxyuser=options.runas)

              else:

                  session.krb_login(proxyuser=options.runas)

-         except socket.error, e:

+         except socket.error as e:

              warn(_("Could not connect to Kerberos authentication service: %s") % e.args[1])

-         except Exception, e:

+         except Exception as e:

              if krbV is not None and isinstance(e, krbV.Krb5Error):

                  error(_("Kerberos authentication failed: %s (%s)") % (e.args[1], e.args[0]))

              else:

@@ -667,8 +667,8 @@ 

   * ``python-krbV``

   * ``python-mock``

   * ``python-simplejson``

-  * ``python-urlgrabber``

   * ``python-psycopg2``

+  * ``python-pycurl``

   * ``python-requests``

   * ``python-qpid-proton``

  

file modified
+1 -1
@@ -9884,7 +9884,7 @@ 

  

          headers = koji.get_header_fields(rpm_path, headers)

          for key, value in headers.items():

-             if isinstance(value, basestring):

+             if isinstance(value, six.string_types):

                  headers[key] = koji.fixEncoding(value, remove_nonprintable=True)

          return headers

  

file modified
+75 -13
@@ -1,4 +1,21 @@ 

- %{!?python_sitelib: %define python_sitelib %(%{__python} -c "from %distutils.sysconfig import get_python_lib; print(get_python_lib())")}

+ # Enable Python 3 builds for Fedora + EPEL >5

+ # NOTE: do **NOT** change 'epel' to 'rhel' here, as this spec is also

+ %if 0%{?fedora} || 0%{?epel} > 5

+ %bcond_without python3

+ # If the definition isn't available for python3_pkgversion, define it

+ %{?!python3_pkgversion:%global python3_pkgversion 3}

+ %else

+ %bcond_with python3

+ %endif

+ 

+ # Compatibility with RHEL. These macros have been added to EPEL but

+ # not yet to RHEL proper.

+ # https://bugzilla.redhat.com/show_bug.cgi?id=1307190

+ %{!?__python2: %global __python2 /usr/bin/python2}

+ %{!?python2_sitelib: %global python2_sitelib %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")}

+ %{!?python2_sitearch: %global python2_sitearch %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}

+ %{!?py2_build: %global py2_build %{expand: CFLAGS="%{optflags}" %{__python2} setup.py %{?py_setup_args} build --executable="%{__python2} -s"}}

+ %{!?py2_install: %global py2_install %{expand: CFLAGS="%{optflags}" %{__python2} setup.py %{?py_setup_args} install -O1 --skip-build --root %{buildroot}}}

  

  %if 0%{?fedora} >= 21 || 0%{?redhat} >= 7

  %global use_systemd 1
@@ -25,26 +42,57 @@ 

  Source: https://releases.pagure.org/koji/koji-%{version}.tar.bz2

  BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)

  BuildArch: noarch

- Requires: python-krbV >= 1.0.13

- Requires: rpm-python

- Requires: pyOpenSSL

- Requires: python-requests

- Requires: python-requests-kerberos

- Requires: python-urlgrabber

- Requires: python-dateutil

- BuildRequires: python

+ %if 0%{with python3}

+ Requires: python3-%{name} = %{version}-%{release}

+ Requires: python3-pycurl

+ Requires: python3-libcomps

+ %else

+ Requires: python2-%{name} = %{version}-%{release}

+ Requires: python2-pycurl

+ %if 0%{?fedora} || 0%{?rhel} >= 7

+ Requires: python2-libcomps

+ %endif

+ %endif

  %if %{use_systemd}

  BuildRequires: systemd

  BuildRequires: pkgconfig

  %endif

- %if 0%{?fedora} || 0%{?rhel} >= 7

- Requires: python-libcomps

- %endif

  

  %description

  Koji is a system for building and tracking RPMS.  The base package

  contains shared libraries and the command-line interface.

  

+ %package -n python2-%{name}

+ Summary: Build system tools python library

+ %{?python_provide:%python_provide python2-%{name}}

+ BuildRequires: python2-devel

+ Requires: python-krbV >= 1.0.13

+ Requires: rpm-python

+ Requires: pyOpenSSL

+ Requires: python-requests

+ Requires: python-requests-kerberos

+ Requires: python-dateutil

+ Requires: python-six

+ 

+ %description -n python2-%{name}

+ desc

+ 

+ %if 0%{with python3}

+ %package -n python3-%{name}

+ Summary: Build system tools python library

+ %{?python_provide:%python_provide python3-%{name}}

+ BuildRequires: python3-devel

+ Requires: python3-rpm

+ Requires: python3-pyOpenSSL

+ Requires: python3-requests

+ Requires: python3-requests-kerberos

+ Requires: python3-dateutil

+ Requires: python3-six

+ 

+ %description -n python3-%{name}

+ desc

+ %endif

+ 

  %package hub

  Summary: Koji XMLRPC interface

  Group: Applications/Internet
@@ -189,6 +237,12 @@ 

  %install

  rm -rf $RPM_BUILD_ROOT

  make DESTDIR=$RPM_BUILD_ROOT %{?install_opt} install

+ %if 0%{with python3}

+ cd koji

+ make DESTDIR=$RPM_BUILD_ROOT PYTHON=python3 %{?install_opt} install

+ # alter python interpreter in koji CLI

+ sed -i 's/\#\!\/usr\/bin\/python/\#\!\/usr\/bin\/python3/' $RPM_BUILD_ROOT/usr/bin/koji

+ %endif

  

  %clean

  rm -rf $RPM_BUILD_ROOT
@@ -196,11 +250,19 @@ 

  %files

  %defattr(-,root,root)

  %{_bindir}/*

- %{python_sitelib}/%{name}

  %config(noreplace) /etc/koji.conf

  %dir /etc/koji.conf.d

  %doc docs Authors COPYING LGPL

  

+ %files -n python2-%{name}

+ %defattr(-,root,root)

+ %{python2_sitelib}/%{name}

+ 

+ %if 0%{with python3}

+ %files -n python3-koji

+ %{python3_sitelib}/%{name}

+ %endif

+ 

  %files hub

  %defattr(-,root,root)

  %{_datadir}/koji-hub

file modified
+10 -4
@@ -1,9 +1,15 @@ 

- SUBDIRS = ssl

- 

  PYTHON=python

  PACKAGE = $(shell basename `pwd`)

- PYFILES = $(wildcard *.py)

- PYSCRIPTS = context.py

+ ifeq ($(PYTHON), python3)

+     # for python3 we fully support only basic library + CLI

+     PYFILES = __init__.py util.py

+     PYSCRIPTS =

+     SUBDIRS =

+ else

+     PYFILES = $(wildcard *.py)

+     PYSCRIPTS = context.py

+     SUBDIRS = ssl

+ endif

  PYVER := $(shell $(PYTHON) -c 'import sys; print("%.3s" % (sys.version))')

  PYSYSDIR := $(shell $(PYTHON) -c 'import sys; print(sys.prefix)')

  PYLIBDIR = $(PYSYSDIR)/lib/python$(PYVER)

file modified
+114 -96
@@ -21,19 +21,23 @@ 

  #       Mike McLean <mikem@redhat.com>

  #       Mike Bonnet <mikeb@redhat.com>

  

+ 

+ from __future__ import absolute_import

  import sys

+ from six.moves import range

+ from six.moves import zip

+ import six

+ krbV = None

  try:

      import krbV

  except ImportError:  # pragma: no cover

-     sys.stderr.write("Warning: Could not install krbV module. Kerberos support will be disabled.\n")

-     sys.stderr.flush()

+     pass

  import base64

  import datetime

- import ConfigParser

+ import six.moves.configparser

  import errno

- import exceptions

  from fnmatch import fnmatch

- import httplib

+ import six.moves.http_client

  import imp

  import logging

  import logging.handlers
@@ -69,15 +73,13 @@ 

  import tempfile

  import time

  import traceback

- import urllib

- import urllib2

- import urlparse

- import util

+ from . import util

  import warnings

- import xmlrpclib

+ import six.moves.xmlrpc_client

  import xml.sax

  import xml.sax.handler

- from xmlrpclib import loads, dumps, Fault

+ from six.moves.xmlrpc_client import loads, dumps, Fault

+ import six.moves.urllib

  

  PROFILE_MODULES = {}  # {module_name: module_instance}

  
@@ -87,7 +89,7 @@ 

  

  ## Constants ##

  

- RPM_HEADER_MAGIC = '\x8e\xad\xe8'

+ RPM_HEADER_MAGIC = six.b('\x8e\xad\xe8')

  RPM_TAG_HEADERSIGNATURES = 62

  RPM_TAG_FILEDIGESTALGO = 5011

  RPM_SIGTAG_PGP = 1002
@@ -261,6 +263,8 @@ 

  ## BEGIN kojikamid dup

  

  #Exceptions

+ PythonImportError = ImportError # will be masked by koji's one

+ 

  class GenericError(Exception):

      """Base class for our custom exceptions"""

      faultCode = 1000
@@ -408,7 +412,7 @@ 

              info['name'] = n

              info['desc'] = getattr(v, '__doc__', None)

              ret.append(info)

-     ret.sort(lambda a, b: cmp(a['faultCode'], b['faultCode']))

+     ret.sort(key=lambda x: x['faultCode'])

      return ret

  

  #functions for encoding/decoding optional arguments
@@ -444,7 +448,7 @@ 

      args, opts = decode_args(*args)

      if strict and len(names) < len(args):

          raise TypeError("Expecting at most %i arguments" % len(names))

-     ret = dict(zip(names, args))

+     ret = dict(list(zip(names, args)))

      ret.update(opts)

      return ret

  
@@ -460,7 +464,7 @@ 

  

  def decode_int(n):

      """If n is not an integer, attempt to convert it"""

-     if isinstance(n, (int, long)):

+     if isinstance(n, six.integer_types):

          return n

      #else

      return int(n)
@@ -471,7 +475,7 @@ 

      """Load xmlrpc data from a string, but catch faults"""

      try:

          return loads(s)

-     except Fault, f:

+     except Fault as f:

          return f

  

  ## BEGIN kojikamid dup
@@ -528,7 +532,7 @@ 

      """Convert a list of bytes to an integer (network byte order)"""

      sum = 0

      n = len(data)

-     for i in xrange(n):

+     for i in range(n):

          sum += data[i] << (8 * (n - i - 1))

      return sum

  
@@ -547,8 +551,8 @@ 

      f = filename or file object

      ofs = offset of the header

      """

-     if isinstance(f, (str, unicode)):

-         fo = file(f, 'rb')

+     if isinstance(f, six.string_types):

+         fo = open(f, 'rb')

      else:

          fo = f

      if ofs != None:
@@ -564,7 +568,7 @@ 

      # now read two 4-byte integers which tell us

      #  - # of index entries

      #  - bytes of data in header

-     data = [ord(x) for x in fo.read(8)]

+     data = [_ord(x) for x in fo.read(8)]

      il = multibyte(data[0:4])

      dl = multibyte(data[4:8])

  
@@ -577,7 +581,7 @@ 

      # add eight bytes for section header

      hdrsize = hdrsize + 8

  

-     if not isinstance(f, (str, unicode)):

+     if not isinstance(f, six.string_types):

          fo.close()

      return hdrsize

  
@@ -594,23 +598,23 @@ 

  

      def version(self):

          #fourth byte is the version

-         return ord(self.header[3])

+         return _ord(self.header[3])

  

      def _index(self):

          # read two 4-byte integers which tell us

          #  - # of index entries  (each 16 bytes long)

          #  - bytes of data in header

-         data = [ord(x) for x in self.header[8:12]]

+         data = [_ord(x) for x in self.header[8:12]]

          il = multibyte(data[:4])

          dl = multibyte(data[4:8])

  

          #read the index (starts at offset 16)

          index = {}

-         for i in xrange(il):

+         for i in range(il):

              entry = []

-             for j in xrange(4):

+             for j in range(4):

                  ofs = 16 + i*16 + j*4

-                 data = [ord(x) for x in self.header[ofs:ofs+4]]

+                 data = [_ord(x) for x in self.header[ofs:ofs+4]]

                  entry.append(multibyte(data))

              #print("Tag: %d, Type: %d, Offset: %x, Count: %d" % tuple(entry))

              index[entry[0]] = entry
@@ -627,11 +631,11 @@ 

          print("Store at offset %d (%0x)" % (store, store))

          #sort entries by offset, dtype

          #also rearrange: tag, dtype, offset, count -> offset, dtype, tag, count

-         order = sorted([(x[2], x[1], x[0], x[3]) for x in self.index.itervalues()])

+         order = sorted([(x[2], x[1], x[0], x[3]) for x in six.itervalues(self.index)])

          next = store

          #map some rpmtag codes

          tags = {}

-         for name, code in rpm.__dict__.iteritems():

+         for name, code in six.iteritems(rpm.__dict__):

              if name.startswith('RPMTAG_') and isinstance(code, int):

                  tags[code] = name[7:].lower()

          for entry in order:
@@ -653,15 +657,15 @@ 

                  next = pos

              elif dtype == 1:

                  #char

-                 for i in xrange(count):

+                 for i in range(count):

                      print("Char: %r" % self.header[pos])

                      pos += 1

                  next = pos

              elif dtype >= 2 and dtype <= 5:

                  #integer

                  n = 1 << (dtype - 2)

-                 for i in xrange(count):

-                     data = [ord(x) for x in self.header[pos:pos+n]]

+                 for i in range(count):

+                     data = [_ord(x) for x in self.header[pos:pos+n]]

                      print("%r" % data)

                      num = multibyte(data)

                      print("Int(%d): %d" % (n, num))
@@ -669,7 +673,7 @@ 

                  next = pos

              elif dtype == 6:

                  # string (null terminated)

-                 end = self.header.find('\0', pos)

+                 end = self.header.find(six.b('\0'), pos)

                  print("String(%d): %r" % (end-pos, self.header[pos:end]))

                  next = end + 1

              elif dtype == 7:
@@ -677,15 +681,15 @@ 

                  next = pos+count

              elif dtype == 8:

                  # string array

-                 for i in xrange(count):

-                     end = self.header.find('\0', pos)

+                 for i in range(count):

+                     end = self.header.find(six.b('\0'), pos)

                      print("String(%d): %r" % (end-pos, self.header[pos:end]))

                      pos = end + 1

                  next = pos

              elif dtype == 9:

                  # unicode string array

-                 for i in xrange(count):

-                     end = self.header.find('\0', pos)

+                 for i in range(count):

+                     end = self.header.find(six.b('\0'), pos)

                      print("i18n(%d): %r" % (end-pos, self.header[pos:end]))

                      pos = end + 1

                  next = pos
@@ -714,7 +718,7 @@ 

          if dtype >= 2 and dtype <= 5:

              n = 1 << (dtype - 2)

              # n-byte integer

-             data = [ord(x) for x in self.header[pos:pos+n]]

+             data = [_ord(x) for x in self.header[pos:pos+n]]

              return multibyte(data)

          elif dtype == 6:

              # string (null terminated)
@@ -738,7 +742,7 @@ 

  def rip_rpm_sighdr(src):

      """Rip the signature header out of an rpm"""

      (start, size) = find_rpm_sighdr(src)

-     fo = file(src, 'rb')

+     fo = open(src, 'rb')

      fo.seek(start, 0)

      sighdr = fo.read(size)

      fo.close()
@@ -749,15 +753,22 @@ 

      (start, size) = find_rpm_sighdr(src)

      start += size

      size = rpm_hdr_size(src, start)

-     fo = file(src, 'rb')

+     fo = open(src, 'rb')

      fo.seek(start, 0)

      hdr = fo.read(size)

      fo.close()

      return hdr

  

+ def _ord(s):

+     # in python2 it is char/str, while in py3 it is already int/bytes

+     if isinstance(s, int):

+         return s

+     else:

+         return ord(s)

+ 

  def __parse_packet_header(pgp_packet):

      """Parse pgp_packet header, return tag type and the rest of pgp_packet"""

-     byte0 = ord(pgp_packet[0])

+     byte0 = _ord(pgp_packet[0])

      if (byte0 & 0x80) == 0:

          raise ValueError('Not an OpenPGP packet')

      if (byte0 & 0x40) == 0:
@@ -771,12 +782,12 @@ 

              length = struct.unpack(fmt, pgp_packet[1:offset])[0]

      else:

          tag = byte0 & 0x3F

-         byte1 = ord(pgp_packet[1])

+         byte1 = _ord(pgp_packet[1])

          if byte1 < 192:

              length = byte1

              offset = 2

          elif byte1 < 224:

-             length = ((byte1 - 192) << 8) + ord(pgp_packet[2]) + 192

+             length = ((byte1 - 192) << 8) + _ord(pgp_packet[2]) + 192

              offset = 3

          elif byte1 == 255:

              length = struct.unpack('>I', pgp_packet[2:6])[0]
@@ -793,17 +804,17 @@ 

      """Parse v4 signature subpackets and return a list of issuer key IDs"""

      res = []

      while len(subs) > 0:

-         byte0 = ord(subs[0])

+         byte0 = _ord(subs[0])

          if byte0 < 192:

              length = byte0

              off = 1

          elif byte0 < 255:

-             length = ((byte0 - 192) << 8) + ord(subs[1]) + 192

+             length = ((byte0 - 192) << 8) + _ord(subs[1]) + 192

              off = 2

          else:

              length = struct.unpack('>I', subs[1:5])[0]

              off = 5

-         if ord(subs[off]) == 16:

+         if _ord(subs[off]) == 16:

              res.append(subs[off+1 : off+length])

          subs = subs[off+length:]

      return res
@@ -813,9 +824,9 @@ 

      (tag, sigpacket) = __parse_packet_header(sigpacket)

      if tag != 2:

          raise ValueError('Not a signature packet')

-     if ord(sigpacket[0]) == 0x03:

+     if _ord(sigpacket[0]) == 0x03:

          key_id = sigpacket[11:15]

-     elif ord(sigpacket[0]) == 0x04:

+     elif _ord(sigpacket[0]) == 0x04:

          sub_len = struct.unpack('>H', sigpacket[4:6])[0]

          off = 6 + sub_len

          key_ids = __subpacket_key_ids(sigpacket[6:off])
@@ -828,7 +839,7 @@ 

          key_id = key_ids[0][-4:]

      else:

          raise NotImplementedError(

-             'Unknown PGP signature packet version %s' % ord(sigpacket[0]))

+             'Unknown PGP signature packet version %s' % _ord(sigpacket[0]))

      return hex_string(key_id)

  

  def get_sighdr_key(sighdr):
@@ -848,8 +859,8 @@ 

      if dst is None:

          (fd, dst) = tempfile.mkstemp()

          os.close(fd)

-     src_fo = file(src, 'rb')

-     dst_fo = file(dst, 'wb')

+     src_fo = open(src, 'rb')

+     dst_fo = open(dst, 'wb')

      dst_fo.write(src_fo.read(start))

      dst_fo.write(sighdr)

      src_fo.seek(size, 1)
@@ -867,8 +878,8 @@ 

      if ts is None:

          ts = rpm.TransactionSet()

          ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES|rpm._RPMVSF_NODIGESTS)

-     if isinstance(f, (str, unicode)):

-         fo = file(f, "r")

+     if isinstance(f, six.string_types):

+         fo = open(f, "r")

      else:

          fo = f

      hdr = ts.hdrFromFdno(fo.fileno())
@@ -910,8 +921,14 @@ 

          # HACK: workaround for https://bugzilla.redhat.com/show_bug.cgi?id=991329

          if result is None:

              result = []

-         elif isinstance(result, (int, long)):

+         elif isinstance(result, six.integer_types):

              result = [result]

+     if six.PY3 and isinstance(result, bytes):

+         try:

+             result = result.decode('utf-8')

+         except UnicodeDecodeError:

+             # typically signatures

+             pass

      return result

  

  
@@ -993,7 +1010,7 @@ 

              return False

  

  def _check_NVR(nvr):

-     if isinstance(nvr, basestring):

+     if isinstance(nvr, six.string_types):

          nvr = parse_NVR(nvr)

      if '-' in nvr['version']:

          raise GenericError('The "-" character not allowed in version field')
@@ -1022,7 +1039,7 @@ 

  

  

  def _check_NVRA(nvra):

-     if isinstance(nvra, basestring):

+     if isinstance(nvra, six.string_types):

              nvra = parse_NVRA(nvra)

      if '-' in nvra['version']:

          raise GenericError('The "-" character not allowed in version field')
@@ -1112,7 +1129,7 @@ 

      values = {}

      handler = POMHandler(values, fields)

      if path:

-         fd = file(path)

+         fd = open(path)

          contents = fd.read()

          fd.close()

  
@@ -1133,7 +1150,7 @@ 

          xml.sax.parseString(contents, handler)

  

      for field in fields:

-         if field not in values.keys():

+         if field not in list(values.keys()):

              raise GenericError('could not extract %s from POM: %s' % (field, (path or '<contents>')))

      return values

  
@@ -1174,7 +1191,7 @@ 

  

  def hex_string(s):

      """Converts a string to a string of hex digits"""

-     return ''.join(['%02x' % ord(x) for x in s])

+     return ''.join(['%02x' % _ord(x) for x in s])

  

  

  def make_groups_spec(grplist, name='buildsys-build', buildgroup=None):
@@ -1214,7 +1231,7 @@ 

              continue

          data.append("#Group: %s\n" % group_name)

          pkglist = list(group['packagelist'])

-         pkglist.sort(lambda a, b: cmp(a['package'], b['package']))

+         pkglist.sort(key=lambda x: x['package'])

          for pkg in pkglist:

              pkg_name = pkg['package']

              if pkg_name in seen_pkg:
@@ -1256,7 +1273,7 @@ 

  """]

      groups = list(groups)

      group_idx = dict([(g['name'], g) for g in groups])

-     groups.sort(lambda a, b: cmp(a['name'], b['name']))

+     groups.sort(key=lambda x: x['name'])

      for g in groups:

          group_id = g['name']

          name = g['display_name']
@@ -1283,7 +1300,7 @@ 

  """    <grouplist>

  """)

              grouplist = list(g['grouplist'])

-             grouplist.sort(lambda a, b: cmp(a['name'], b['name']))

+             grouplist.sort(key=lambda x: x['name'])

              for x in grouplist:

                  #['req_id','type','is_metapkg','name']

                  name = x['name']
@@ -1319,7 +1336,7 @@ 

  """)

          if g['packagelist']:

              packagelist = list(g['packagelist'])

-             packagelist.sort(lambda a, b: cmp(a['package'], b['package']))

+             packagelist.sort(key=lambda x: x['package'])

              for p in packagelist:

                  data.append(

  """      %s
@@ -1346,7 +1363,7 @@ 

  """      <!-- Expanding Group: %s -->

  """ % group_name)

                  pkglist = list(group['packagelist'])

-                 pkglist.sort(lambda a, b: cmp(a['package'], b['package']))

+                 pkglist.sort(key=lambda x: x['package'])

                  for pkg in pkglist:

                      pkg_name = pkg['package']

                      if pkg_name in seen_pkg:
@@ -1427,14 +1444,14 @@ 

      if opts.get('use_host_resolv', False) and os.path.exists('/etc/hosts'):

          # if we're setting up DNS,

          # also copy /etc/hosts from the host

-         etc_hosts = file('/etc/hosts')

+         etc_hosts = open('/etc/hosts')

          files['etc/hosts'] = etc_hosts.read()

          etc_hosts.close()

      mavenrc = ''

      if opts.get('maven_opts'):

          mavenrc = 'export MAVEN_OPTS="%s"\n' % ' '.join(opts['maven_opts'])

      if opts.get('maven_envs'):

-         for name, val in opts['maven_envs'].iteritems():

+         for name, val in six.iteritems(opts['maven_envs']):

              mavenrc += 'export %s="%s"\n' % (name, val)

      if mavenrc:

          files['etc/mavenrc'] = mavenrc
@@ -1497,10 +1514,10 @@ 

  """ % locals())

  

      parts.append("\n")

-     for key, value in config_opts.iteritems():

+     for key, value in six.iteritems(config_opts):

          parts.append("config_opts[%r] = %r\n" % (key, value))

      parts.append("\n")

-     for key, value in plugin_conf.iteritems():

+     for key, value in six.iteritems(plugin_conf):

          parts.append("config_opts['plugin_conf'][%r] = %r\n" % (key, value))

      parts.append("\n")

  
@@ -1508,14 +1525,14 @@ 

          # This line is REQUIRED for mock to work if bind_opts defined.

          parts.append("config_opts['internal_dev_setup'] = False\n")

          for key in bind_opts.keys():

-             for mnt_src, mnt_dest in bind_opts.get(key).iteritems():

+             for mnt_src, mnt_dest in six.iteritems(bind_opts.get(key)):

                  parts.append("config_opts['plugin_conf']['bind_mount_opts'][%r].append((%r, %r))\n" % (key, mnt_src, mnt_dest))

          parts.append("\n")

  

-     for key, value in macros.iteritems():

+     for key, value in six.iteritems(macros):

          parts.append("config_opts['macros'][%r] = %r\n" % (key, value))

      parts.append("\n")

-     for key, value in files.iteritems():

+     for key, value in six.iteritems(files):

          parts.append("config_opts['files'][%r] = %r\n" % (key, value))

  

      return ''.join(parts)
@@ -1561,7 +1578,7 @@ 

      on options"""

      if topurl:

          url = "%s/%s" % (topurl, relpath)

-         src = urllib2.urlopen(url)

+         src = six.moves.urllib.request.urlopen(url)

          fo = tempfile.TemporaryFile(dir=tempdir)

          shutil.copyfileobj(src, fo)

          src.close()
@@ -1578,7 +1595,7 @@ 

      configs = []

      try:

          conf_dir_contents = os.listdir(dir_name)

-     except OSError, exception:

+     except OSError as exception:

          if exception.errno != errno.ENOENT:

              raise

      else:
@@ -1659,7 +1676,7 @@ 

      got_conf = False

      for configFile in configs:

          f = open(configFile)

-         config = ConfigParser.ConfigParser()

+         config = six.moves.configparser.ConfigParser()

          config.readfp(f)

          f.close()

          if config.has_section(profile_name):
@@ -1754,7 +1771,7 @@ 

  

  class PathInfo(object):

      # ASCII numbers and upper- and lower-case letter for use in tmpdir()

-     ASCII_CHARS = [chr(i) for i in range(48, 58) + range(65, 91) + range(97, 123)]

+     ASCII_CHARS = [chr(i) for i in list(range(48, 58)) + list(range(65, 91)) + list(range(97, 123))]

  

      def __init__(self, topdir=None):

          self._topdir = topdir
@@ -1942,7 +1959,7 @@ 

              return True

          # else

          return False

-     if isinstance(e, httplib.BadStatusLine):

+     if isinstance(e, six.moves.http_client.BadStatusLine):

          return True

      if requests is not None:

          try:
@@ -1952,7 +1969,7 @@ 

                  e2 = getattr(e, 'args', [None])[0]

                  if isinstance(e2, requests.packages.urllib3.exceptions.ProtocolError):

                      e3 = getattr(e2, 'args', [None, None])[1]

-                     if isinstance(e3, httplib.BadStatusLine):

+                     if isinstance(e3, six.moves.http_client.BadStatusLine):

                          return True

                  if isinstance(e2, socket.error):

                      # same check as unwrapped socket error
@@ -2035,6 +2052,8 @@ 

          if self.rsession:

              self.rsession.close()

          if self.opts.get('use_old_ssl', False) or requests is None:

+             if not six.PY2:

+                 raise GenericError('use_old_ssl is only supported on python2')

              import koji.compatrequests

              self.rsession = koji.compatrequests.Session()

          else:
@@ -2086,7 +2105,7 @@ 

                  pass

  

          if not krbV:

-             raise exceptions.ImportError(

+             raise PythonImportError(

                  "Please install python-krbV to use kerberos."

              )

  
@@ -2137,7 +2156,7 @@ 

          # decode and decrypt the login info

          sinfo_priv = base64.decodestring(sinfo_enc)

          sinfo_str = ac.rd_priv(sinfo_priv)

-         sinfo = dict(zip(['session-id', 'session-key'], sinfo_str.split()))

+         sinfo = dict(list(zip(['session-id', 'session-key'], sinfo_str.split())))

  

          if not sinfo:

              self.logger.warn('No session info received')
@@ -2151,8 +2170,7 @@ 

          """Get the Kerberos principal of the server we're connecting

          to, based on baseurl."""

  

-         uri = urlparse.urlsplit(self.baseurl)

-         host, port = urllib.splitport(uri[1])

+         host = six.moves.urllib.parse.urlparse(self.baseurl).hostname

          if self.opts.get('krb_rdns', True):

              servername = socket.getfqdn(host)

          else:
@@ -2164,13 +2182,13 @@ 

  

      def gssapi_login(self, proxyuser=None):

          if not HTTPKerberosAuth:

-             raise exceptions.ImportError(

+             raise PythonImportError(

                  "Please install python-requests-kerberos to use GSSAPI."

              )

  

          # force https

          old_baseurl = self.baseurl

-         uri = urlparse.urlsplit(self.baseurl)

+         uri = six.moves.urllib.parse.urlsplit(self.baseurl)

          if uri[0] != 'https':

              self.baseurl = 'https://%s%s' % (uri[1], uri[2])

  
@@ -2214,7 +2232,7 @@ 

          # when API is changed

  

          # force https

-         uri = urlparse.urlsplit(self.baseurl)

+         uri = six.moves.urllib.parse.urlsplit(self.baseurl)

          if uri[0] != 'https':

              self.baseurl = 'https://%s%s' % (uri[1], uri[2])

  
@@ -2289,7 +2307,7 @@ 

              sinfo = self.sinfo.copy()

              sinfo['callnum'] = self.callnum

              self.callnum += 1

-             handler = "%s?%s" % (self.baseurl, urllib.urlencode(sinfo))

+             handler = "%s?%s" % (self.baseurl, six.moves.urllib.parse.urlencode(sinfo))

          elif name == 'sslLogin':

              handler = self.baseurl + '/ssllogin'

          else:
@@ -2308,7 +2326,7 @@ 

          for i in (0, 1):

              try:

                  return self._sendOneCall(handler, headers, request)

-             except Exception, e:

+             except Exception as e:

                  if i or not is_conn_error(e):

                      raise

                  self.logger.debug("Connection Error: %s", e)
@@ -2364,7 +2382,7 @@ 

          return ret

  

      def _read_xmlrpc_response(self, response):

-         p, u = xmlrpclib.getparser()

+         p, u = six.moves.xmlrpc_client.getparser()

          for chunk in response.iter_content(8192):

              if self.opts.get('debug_xmlrpc', False):

                  print("body: %r" % chunk)
@@ -2401,7 +2419,7 @@ 

                  # note that, for logged-in sessions the server should tell us (via a RetryError fault)

                  # if the call cannot be retried. For non-logged-in sessions, all calls should be read-only

                  # and hence retryable.

-                 except Fault, fault:

+                 except Fault as fault:

                      #try to convert the fault to a known exception

                      err = convertFault(fault)

                      if isinstance(err, ServerOffline):
@@ -2417,7 +2435,7 @@ 

                  except (SystemExit, KeyboardInterrupt):

                      #(depending on the python version, these may or may not be subclasses of Exception)

                      raise

-                 except Exception, e:

+                 except Exception as e:

                      tb_str = ''.join(traceback.format_exception(*sys.exc_info()))

                      self.new_session()

  
@@ -2490,7 +2508,7 @@ 

          if name is None:

              name = os.path.basename(localfile)

          self.logger.debug("Fast upload: %s to %s/%s", localfile, path, name)

-         fo = file(localfile, 'rb')

+         fo = open(localfile, 'rb')

          ofs = 0

          size = os.path.getsize(localfile)

          start = time.time()
@@ -2560,7 +2578,7 @@ 

              args['volume'] = volume

          size = len(chunk)

          self.callnum += 1

-         handler = "%s?%s" % (self.baseurl, urllib.urlencode(args))

+         handler = "%s?%s" % (self.baseurl, six.moves.urllib.parse.urlencode(args))

          headers = [

              ('User-Agent', 'koji/1'),

              ("Content-Type", "application/octet-stream"),
@@ -2598,7 +2616,7 @@ 

          start = time.time()

          # XXX - stick in a config or something

          retries = 3

-         fo = file(localfile, "r")  #specify bufsize?

+         fo = open(localfile, "r")  #specify bufsize?

          totalsize = os.path.getsize(localfile)

          ofs = 0

          md5sum = md5_constructor()
@@ -2695,7 +2713,7 @@ 

              values = []

              data = {}

              record.message = record.getMessage()

-             for key, value in self.mapping.iteritems():

+             for key, value in six.iteritems(self.mapping):

                  value = str(value)

                  if value.find("%(asctime)") >= 0:

                      if self.formatter:
@@ -2895,7 +2913,7 @@ 

          return '%s (%s)' % (method, arch)

  

  CONTROL_CHARS = [chr(i) for i in range(32)]

- NONPRINTABLE_CHARS = ''.join([c for c in CONTROL_CHARS if c not in '\r\n\t'])

+ NONPRINTABLE_CHARS = six.b(''.join([c for c in CONTROL_CHARS if c not in '\r\n\t']))

  def removeNonprintable(value):

      # expects raw-encoded string, not unicode

      return value.translate(None, NONPRINTABLE_CHARS)
@@ -2907,9 +2925,9 @@ 

      encoded in the 'fallback' charset.

      """

      if not value:

-         return ''

+         return six.b('')

  

-     if isinstance(value, unicode):

+     if isinstance(value, six.text_type):

          # value is already unicode, so just convert it

          # to a utf8-encoded str

          s = value.encode('utf8')
@@ -2943,7 +2961,7 @@ 

              k = fixEncodingRecurse(k, fallback=fallback, remove_nonprintable=remove_nonprintable)

              ret[k] = v

          return ret

-     elif isinstance(value, unicode):

+     elif isinstance(value, six.text_type):

          if remove_nonprintable:

              return removeNonprintable(value.encode('utf8'))

          else:

file modified
+20 -9
@@ -19,14 +19,21 @@ 

  #       Mike McLean <mikem@redhat.com>

  #       Mike Bonnet <mikeb@redhat.com>

  

+ from __future__ import absolute_import

  import socket

  import string

  import random

  import base64

- import krbV

+ try:

+     import krbV

+ except ImportError:

+     krbV = None

  import koji

  import cgi      #for parse_qs

- from context import context

+ from .context import context

+ from six.moves import range

+ from six.moves import zip

+ import six

  

  # 1 - load session if provided

  #       - check uri for session id
@@ -76,7 +83,7 @@ 

          try:

              id = long(args['session-id'][0])

              key = args['session-key'][0]

-         except KeyError, field:

+         except KeyError as field:

              raise koji.AuthError('%s not specified in session args' % field)

          try:

              callnum = args['callnum'][0]
@@ -96,7 +103,7 @@ 

              'EXTRACT(EPOCH FROM update_time)': 'update_ts',

              'user_id': 'user_id',

              }

-         fields, aliases = zip(*fields.items())

+         fields, aliases = list(zip(*list(fields.items())))

          q = """

          SELECT %s FROM sessions

          WHERE id = %%(id)i
@@ -108,7 +115,7 @@ 

          row = c.fetchone()

          if not row:

              raise koji.AuthError('Invalid session or bad credentials')

-         session_data = dict(zip(aliases, row))

+         session_data = dict(list(zip(aliases, row)))

          #check for expiration

          if session_data['expired']:

              raise koji.AuthExpired('session "%i" has expired' % id)
@@ -146,7 +153,7 @@