text: #!/usr/bin/env python3
  # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
  
  #
  # NOTE: this actually needs to run under python2.4 and centos 5.x through python3 and centos 7.x
  # please manually test changes on centos5 boxes or you will almost certainly break things.
  #
  
  import sys
  import yum
  import signal
  import os
  sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'simplejson'))
  try: import json
  except ImportError: import simplejson as json
  import re
  from rpmUtils.miscutils import stringToVersion,compareEVR
  from rpmUtils.arch import getBaseArch, getArchList
  
  
  try: from yum.misc import string_to_prco_tuple
  except ImportError:
      # RHEL5 compat
      def string_to_prco_tuple(prcoString):
          prco_split = prcoString.split()
          n, f, v = prco_split
          (prco_e, prco_v, prco_r) = stringToVersion(v)
          return (n, f, (prco_e, prco_v, prco_r))
  
  # hack to work around https://github.com/chef/chef/issues/7126
  # see https://bugzilla.redhat.com/show_bug.cgi?id=1396248
  if not hasattr(yum.packages.FakeRepository, 'compare_providers_priority'):
      yum.packages.FakeRepository.compare_providers_priority = 99
  
  base = None
  
  def get_base():
      global base
      if base is None:
          base = yum.YumBase()
      setup_exit_handler()
      return base
  
  def versioncompare(versions):
      arch_list = getArchList()
      candidate_arch1 = versions[0].split(".")[-1]
      candidate_arch2 = versions[1].split(".")[-1]
  
      # The first version number passed to this method is always a valid nevra (the current version)
      # If the second version number looks like it does not contain a valid arch
      # then we'll chop the arch component (assuming it *is* a valid one) from the first version string
      # so we're only comparing the evr portions.
      if (candidate_arch2 not in arch_list) and (candidate_arch1 in arch_list):
         final_version1 = versions[0].replace("." + candidate_arch1,"")
      else:
         final_version1 = versions[0]
  
      final_version2 = versions[1]
  
      (e1, v1, r1) = stringToVersion(final_version1)
      (e2, v2, r2) = stringToVersion(final_version2)
  
      evr_comparison = compareEVR((e1, v1, r1), (e2, v2, r2))
      outpipe.write("%(e)s\n" % { 'e': evr_comparison })
      outpipe.flush()
  
  def install_only_packages(name):
      base = get_base()
      if name in base.conf.installonlypkgs:
        outpipe.write('True')
      else:
        outpipe.write('False')
      outpipe.flush()
  
  # python2.4 / centos5 compat
  try:
      any
  except NameError:
      def any(s):
          for v in s:
              if v:
                  return True
          return False
  
  def query(command):
      base = get_base()
  
      enabled_repos = base.repos.listEnabled()
  
      # Handle any repocontrols passed in with our options
  
      if 'repos' in command:
        for repo in command['repos']:
          if 'enable' in repo:
            base.repos.enableRepo(repo['enable'])
          if 'disable' in repo:
            base.repos.disableRepo(repo['disable'])
  
      args = { 'name': command['provides'] }
      do_nevra = False
      if 'epoch' in command:
          args['epoch'] = command['epoch']
          do_nevra = True
      if 'version' in command:
          args['ver'] = command['version']
          do_nevra = True
      if 'release' in command:
          args['rel'] = command['release']
          do_nevra = True
      if 'arch' in command:
          desired_arch = command['arch']
          args['arch'] = command['arch']
          do_nevra = True
      else:
          desired_arch = getBaseArch()
  
      obj = None
      if command['action'] == "whatinstalled":
          obj = base.rpmdb
      else:
          obj = base.pkgSack
  
      # if we are given "name == 1.2.3" then we must use the getProvides() API.
      #   - this means that we ignore arch and version properties when given prco tuples as a package_name
      #   - in order to fix this, something would have to happen where getProvides was called first and
      #     then the result was searchNevra'd.  please be extremely careful if attempting to fix that
      #     since searchNevra does not support prco tuples.
      if any(elem in command['provides'] for elem in r"<=>"):
          # handles flags (<, >, =, etc) and versions, but no wildcareds 
          pkgs = obj.getProvides(*string_to_prco_tuple(command['provides']))
      elif do_nevra:
          # now if we're given version or arch properties explicitly, then we do a SearchNevra.
          #  - this means that wildcard version in the package_name with an arch property will not work correctly
          #  - again don't try to fix this just by pushing bugs around in the code, you would need to call
          #    returnPackages and searchProvides and then apply the Nevra filters to those results.
          pkgs = obj.searchNevra(**args)
          if (command['action'] == "whatinstalled") and (not pkgs):
            pkgs = obj.searchNevra(name=args['name'], arch=desired_arch)
      else:
          pats = [command['provides']]
          pkgs = obj.returnPackages(patterns=pats)
  
          if not pkgs:
              # handles wildcards
              pkgs = obj.searchProvides(command['provides'])
  
      if not pkgs:
          outpipe.write(command['provides'].split().pop(0)+' nil nil\n')
          outpipe.flush()
      else:
          # make sure we picked the package with the highest version
          pkgs = base.bestPackagesFromList(pkgs,single_name=True)
          pkg = pkgs.pop(0)
          outpipe.write("%(n)s %(e)s:%(v)s-%(r)s %(a)s\n" % { 'n': pkg.name, 'e': pkg.epoch, 'v': pkg.version, 'r': pkg.release, 'a': pkg.arch })
          outpipe.flush()
  
      # Reset any repos we were passed in enablerepo/disablerepo to the original state in enabled_repos
      if 'repos' in command:
        for repo in command['repos']:
          if 'enable' in repo:
            if base.repos.getRepo(repo['enable']) not in enabled_repos:
              base.repos.disableRepo(repo['enable'])
          if 'disable' in repo:
            if base.repos.getRepo(repo['disable']) in enabled_repos:
              base.repos.enableRepo(repo['disable'])
  
  # the design of this helper is that it should try to be 'brittle' and fail hard and exit in order
  # to keep process tables clean.  additional error handling should probably be added to the retry loop
  # on the ruby side.
  def exit_handler(signal, frame):
      base.closeRpmDB()
      sys.exit(0)
  
  def setup_exit_handler():
      signal.signal(signal.SIGINT, exit_handler)
      signal.signal(signal.SIGHUP, exit_handler)
      signal.signal(signal.SIGPIPE, exit_handler)
      signal.signal(signal.SIGQUIT, exit_handler)
  
  if len(sys.argv) < 3:
    inpipe = sys.stdin
    outpipe = sys.stdout
  else:
    inpipe = os.fdopen(int(sys.argv[1]), "r")
    outpipe = os.fdopen(int(sys.argv[2]), "w")
  
  while 1:
      # kill self if we get orphaned (tragic)
      ppid = os.getppid()
      if ppid == 1:
          sys.exit(0)
      setup_exit_handler()
      line = inpipe.readline()
  
      try:
          command = json.loads(line)
      except ValueError, e:
          base.closeRpmDB()
          sys.exit(0)
  
      if command['action'] == "whatinstalled":
          query(command)
      elif command['action'] == "whatavailable":
          query(command)
      elif command['action'] == "versioncompare":
          versioncompare(command['versions'])
      elif command['action'] == "installonlypkgs":
           install_only_packages(command['package'])
      else:
          raise RuntimeError("bad command")
  