Dashboard
PHP:
7.4.33
OS:
Linux
User:
sortthru
/
/
usr
/
share
/
oracle-cloud-agent
/
plugins
/
osms
/
osms
📤 Upload
📝 New File
📁 New Folder
Close
Editing: yumutils.py
# Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. # Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/licenses/UPL. import logging import sys import time import datetime from rpm import RPMPROB_FILTER_OLDPACKAGE from rpm import labelCompare import yum import yum.Errors from yum.plugins import PluginYumExit sys.path.append("/usr/share/yum-cli") import callback sys.path.insert(0, '/usr/share/oracle-cloud-agent/plugins/osms') from osms import alxutils from osms import config from osms import utils from osms.i18n import sstr YUM_PID_FILE = '/var/run/yum.pid' YUM_LOCK_TIMEOUT = 900 YUM_LOCK_INTERVAL = 10 YUM_BASE = None class YumAction(yum.YumBase): def __init__(self): yum.YumBase.__init__(self) self.cfg = config.initUp2dateConfig() self.doConfigSetup(debuglevel=self.cfg["debug"]) self.cache_only = None _do_yum_lock(self) self.doTsSetup() self.doRpmDBSetup() self.doRepoSetup() self.doSackSetup() # Copied from yum/cli.py, more or less def doTransaction(self, p_obj): """takes care of package downloading, checking, user confirmation and actually RUNNING the transaction""" #allow downgrades to support rollbacks self.tsInfo.probFilterFlags.append(RPMPROB_FILTER_OLDPACKAGE) # Check which packages have to be downloaded downloadpkgs = [] do_erase = False; for txmbr in self.tsInfo.getMembers(): if txmbr.ts_state in ['i', 'u']: po = txmbr.po if po: downloadpkgs.append(po) elif txmbr.ts_state in ['e']: do_erase = True; #if we're not deleting, we should use the downloadpkgs if not do_erase: p_obj['packages'] = downloadpkgs logging.debug('Downloading Packages:') problems = self.downloadPkgs(downloadpkgs) if len(problems.keys()) > 0: errstring = '' errstring += 'Error Downloading Packages:\n' for key in problems.keys(): errors = yum.misc.unique(problems[key]) for error in errors: errstring += ' %s: %s\n' % (key, error) raise yum.Errors.YumBaseError(errstring) if self.cfg['retrieveOnly']: # We are configured to only download packages (rather than install/update) # # If all we were asked to do was install/update, skip rest of transaction # and return now. # # If all we were asked to do was remove packages, process the transaction. # # If we were asked to do a mixture of install/erase - then we can't do a # complete transaction, and we have to FAIL, rather than execute or show a # success. # if downloadpkgs and do_erase: # FAIL err = 'Mix of install/erase and "retrieveOnly" set - transaction FAILS' logging.debug(err) raise yum.Errors.YumBaseError(err) elif downloadpkgs and not do_erase: # download and exit logging.debug('Configured to "retrieveOnly" so skipping package install') return 0 else: # erase-only, continue logging.debug('Configured to "retrieveOnly" but only erase-commands - continuing') if self.cache_only: logging.debug('Just pre-caching packages, skipping package install') return 0 # Check GPG signatures if self.gpgsigcheck(downloadpkgs) != 0: return 1 logging.debug('Running Transaction Test') tsConf = {} for feature in ['diskspacecheck']: # more to come, I'm sure tsConf[feature] = getattr(self.conf, feature) # clean out the ts b/c we have to give it new paths to the rpms del self.ts self.initActionTs() # save our dsCallback out testcb = callback.RPMInstallCallback(output=0) testcb.tsInfo = self.tsInfo dscb = self.dsCallback self.dsCallback = None # dumb, dumb dumb dumb! self.populateTs(keepold=0) # sigh tserrors = self.ts.test(testcb, conf=tsConf) logging.debug('Finished Transaction Test') if len(tserrors) > 0: errstring = 'Transaction Check Error: ' for descr in tserrors: errstring += ' %s\n' % descr raise yum.Errors.YumBaseError(errstring) logging.debug('Transaction Test Succeeded') del self.ts self.initActionTs() # make a new, blank ts to populate self.populateTs(keepold=0) # populate the ts self.ts.check() #required for ordering self.ts.order() # order # put back our depcheck callback self.dsCallback = dscb logging.debug('Running Transaction') self.runTransaction(testcb) #log the transaction results self.log_successful_transaction(p_obj) # close things return 0 # Also taken from yum/cli.py def gpgsigcheck(self, pkgs): '''Perform GPG signature verification on the given packages, installing keys if possible Returns non-zero if execution should stop (user abort). Will raise YumBaseError if there's a problem ''' for po in pkgs: result, errmsg = self.sigCheckPkg(po) if result == 0: # Verified ok, or verify not req'd continue elif result == 1: # bz 433781 # If the package is a Red Hat pkg, try to install the key and see if it helps if self.isRepoUsingRedHatGPG(po): logging.debug("GPG check wasn't successful, will attempt to import key") self.getKeyForPackage(po, askcb=lambda x,y,z: True) logging.debug("GPG key import was good.") # if we got here, the key worked, otherwise an exception is thrown else: raise yum.Errors.YumBaseError('Refusing to automatically import keys when running unattended.') else: # Fatal error raise yum.Errors.YumBaseError(errmsg) return 0 def isRepoUsingRedHatGPG(self, po): goodValues = ["file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release"] repo = self.repos.getRepo(po.repoid) keyurls = repo.gpgkey if len(keyurls) < 1: return False for keyurl in keyurls: if keyurl not in goodValues: logging.debug( "keyurl = %s, isn't a known Red Hat key, so this " \ "will not be imported. Manually import this key " \ "or set gpgcheck=0 in the OSMS yum plugin configuration file" % (keyurl)) return False return True def getInstalledPkgObject(self, package_tup): installed = self.rpmdb.returnPackages() logging.debug("Searching for installed package to remove: %s" % str(package_tup)) exactmatch, matched, unmatched = yum.packages.parsePackages( installed, (package_tup), casematch=1) erases = yum.misc.unique(matched + exactmatch) if len(erases) >= 1: logging.debug("Found %d package(s) to remove" % len(erases)) return erases else: # TODO: we should just fail, I think logging.debug("Couldn't find packages to remove") return () def check_obsoletes(self, pkgkeys): """ Checks if packages are obsoleted by existing packages. If package is obsolete, throw yum error. """ nevra_dict = self._nevra_kwarg_parse(pkgkeys) pkgs = self.pkgSack.searchNevra(name=nevra_dict['name'],epoch=nevra_dict['epoch'], arch=nevra_dict['arch'], ver=nevra_dict['version'], rel=nevra_dict['release']) for po in pkgs: obsoleting_pkg = self._test_loop(po, self._pkg2obspkg) poprovtup = (po.name, 'EQ', (po.epoch, po.ver, po.release)) for pkg in self.rpmdb.searchNevra(name=obsoleting_pkg.name): if pkg.inPrcoRange('obsoletes', poprovtup): already_obs = pkg if already_obs: error_string = 'Package %s is obsoleted by %s which is already installed' % (po, already_obs) raise yum.Errors.YumBaseError(error_string) def log_successful_transaction(self, pkgs): """ Log packages and actions to yum log. """ action_key = pkgs['action'] actions = {'i': 'Installed', 'u': 'Updated', 'e': 'Erased'} try: with open(self._getConfig().logfile, 'a') as logfile: for pkg in pkgs['packages']: install_time = datetime.datetime.now() log_msg = '{0} OSMS Action - {1}: {2} \n' logfile.write(log_msg.format(install_time.strftime('%b %d %X'), actions[action_key], pkg)) #We don't want to return an error to the user if logging fails for some reason except IOError as err: logging.info("Error logging to yum log: {0}".format(err)) def add_transaction_data(self, transaction_data): """ Add packages to transaction. transaction_data is in format: { 'packages' : [ [['name', '1.0.0', '1', '', ''], 'e'], ... # name, versio, rel., epoch, arch, flag ]} where flag can be: i - install u - update e - remove r - rollback Note: install and update will check for dependecies and obsoletes and will install them if needed. Rollback do not check anything and will assume that state to which we are rolling back should be correct. """ for pkgtup, action in transaction_data['packages']: pkgkeys = { 'name' : pkgtup[0], 'epoch' : pkgtup[3], 'version' : pkgtup[1], 'release' : pkgtup[2], } if len(pkgtup) > 4: pkgkeys['arch'] = pkgtup[4] else: pkgtup.append('') pkgkeys['arch'] = None if action == 'u': self.update(**pkgkeys) elif action == 'i': transaction_len = len(self.install(**pkgkeys)) # if an empty transaction is returned, # this could be due to obsoleted package if transaction_len == 0: self.check_obsoletes(pkgkeys) elif action == 'r': # we are doing rollback, we want exact version # no dependecy check pkgs = self.pkgSack.searchNevra(name=pkgkeys['name'], epoch=pkgkeys['epoch'], arch=pkgkeys['arch'], ver=pkgkeys['version'], rel=pkgkeys['release']) if not pkgs: raise yum.Errors.YumBaseError( "Cannot find package %s:%s-%s-%s.%s in any of enabled repositories." % (pkgkeys['epoch'], pkgkeys['name'], pkgkeys['version'], pkgkeys['release'], pkgkeys['arch']) ) for po in pkgs: self.tsInfo.addInstall(po) elif action == 'e': package_tup = _yum_package_tup(pkgtup) packages = self.getInstalledPkgObject(package_tup) for package in packages: self.remove(package) else: assert False, "Unknown package transaction action." def get_yum_base(): global YUM_BASE if YUM_BASE is None: YUM_BASE = YumAction() return YUM_BASE def _yum_package_tup(package_tup): """ Create a yum-style package tuple from an rhn package tuple. Allowed styles: n, n.a, n-v-r, n-e:v-r.a, n-v, n-v-r.a, e:n-v-r.a Choose from the above styles to be compatible with yum.parsePackage """ n, v, r, e, a = package_tup[:] if not e: # set epoch to 0 as yum expects e = '0' if not a: pkginfo = '%s-%s-%s' % (n, v, r) else: pkginfo = '%s-%s:%s-%s.%s' % (n, e, v, r, a) return (pkginfo,) def package_remove(package_list): transaction_data = __make_transaction(package_list, 'e') return _runTransaction(transaction_data) def package_update(package_list, cache_only=None): yum_base = get_yum_base() err = None errmsgs = [] # Remove already installed packages from the list for package in package_list[:]: pkgkeys = { 'name' : package[0], 'epoch' : package[3], 'version' : package[1], 'release' : package[2], } if len(package) == 5 \ and package[1] == '' \ and package[2] == '' \ and package[3] == '' \ and package[4] == '' \ and yum_base.rpmdb.searchNevra(name=package[0]): logging.debug('Package %s is already installed' % package[0]) package_list.remove(package) continue if len(package) > 4: pkgkeys['arch'] = package[4] else: package.append('') pkgkeys['arch'] = None if pkgkeys['epoch'] == '': pkgkeys['epoch'] = None pkgs = yum_base.rpmdb.searchNevra(name=pkgkeys['name'], arch=pkgkeys['arch']) evr = yum.packages.PackageEVR(pkgkeys['epoch'], pkgkeys['version'], pkgkeys['release']) found = False for pkg in pkgs: current = pkg.returnEVR() currentEVR = (current.epoch, current.version, current.release) candidateEVR = (evr.epoch, evr.version, evr.release) compare = labelCompare(currentEVR, candidateEVR) if compare == 0: logging.debug('Package %s already installed' % _yum_package_tup(package)) package_list.remove(package) found = True break elif compare > 0: logging.debug('More recent version of package %s is already installed' % _yum_package_tup(package)) package_list.remove(package) found = True break if not found: available = yum_base.pkgSack.searchNevra( name=pkgkeys['name'], arch=pkgkeys['arch'], epoch=pkgkeys['epoch'], ver=pkgkeys['version'], rel=pkgkeys['release'] ) if not available: err = 'Package %s is not available for installation' % _yum_package_tup(package) logging.debug('E: %s', err ) package_list.remove(package) errmsgs.append(err) # Don't proceed further with empty list, # since this would result into an empty yum transaction if not package_list: if err: ret = (32, "Failed: Packages failed to install properly:\n" + '\n'.join(errmsgs), {'version': '1', 'name': "package_install_failure"}) else: ret = (0, "Requested packages already installed", {}) return ret transaction_data = __make_transaction(package_list, 'i') return _runTransaction(transaction_data, cache_only) def __make_transaction(package_list, action): """ Build transaction Data like _runTransaction would expect. This is a list of ((n,v,r,e,a), m) where m is either e, i, or u """ transaction_data = {} transaction_data['packages'] = [] #We don't care about this stuff. transaction_data['flags'] = [] transaction_data['vsflags'] = [] transaction_data['probFilterFlags'] = [] for package in package_list: transaction_data['packages'].append((package, action)) return transaction_data class RunTransactionCommand: def __init__(self, transaction_data): self.transaction_data = transaction_data def execute(self, yum_base): yum_base.add_transaction_data(self.transaction_data) def _runTransaction(transaction_data, cache_only=None): """ Run a tranaction on a group of packages. """ command = RunTransactionCommand(transaction_data) return _run_yum_action(command, cache_only) def runTransaction(transaction_data, cache_only=None): """ Run a transaction on a group of packages. This was historicaly meant as generic call, but is only called for rollback. Therefore we change all actions "i" (install) to "r" (rollback) where we will not check dependencies and obsoletes. """ if cache_only: return (0, "no-ops for caching", {}) for package_object in transaction_data['packages'][:]: [package, action] = package_object pkgkeys = { 'name' : package[0], 'version' : package[1], 'release' : package[2], 'epoch' : package[3], } if len(package) > 4: pkgkeys['arch'] = package[4] else: pkgkeys['arch'] = None if pkgkeys['arch'] == '': pkgkeys['arch'] = None if pkgkeys['epoch'] == '': pkgkeys['epoch'] = None package_exists = get_yum_base().rpmdb.searchNevra(name=pkgkeys['name'], epoch=pkgkeys['epoch'], arch=pkgkeys['arch'], ver=pkgkeys['version'], rel=pkgkeys['release']) # if we're deleting a package that no longer exists, noop if action == 'e' and not package_exists: transaction_data['packages'].remove(package_object) # if we're installing a package that is already installed, noop elif action == 'i' and package_exists: transaction_data['packages'].remove(package_object) # Don't proceed further with empty package list, # since this would result in an empty yum transaction if not transaction_data['packages']: return (0, "Requested package actions have already been performed.", {}) for index, data in enumerate(transaction_data['packages']): if data[1] == 'i': transaction_data['packages'][index][1] = 'r' return _runTransaction(transaction_data) class FullUpdateCommand: def execute(self, yum_base): yum_base.update() def package_full_update( cache_only=None): command = FullUpdateCommand() return _run_yum_action(command, cache_only) def _do_yum_lock(yum_base): elapsed_time = 0 while True: try: return yum_base.doLock(YUM_PID_FILE) except Exception as e: if 'Existing lock /var/run/yum.pid' not in str(e): raise e logging.info('Existing lock /var/run/yum.pid: wait for %s seconds, elapsed %s seconds', YUM_LOCK_INTERVAL, elapsed_time) time.sleep(YUM_LOCK_INTERVAL) elapsed_time += YUM_LOCK_INTERVAL if elapsed_time >= YUM_LOCK_TIMEOUT: raise e def _run_yum_action(command, cache_only=None): """ Do something with yum. command is an object with an 'execute' method taking yum_base, so we can apply different operations to yum_base. """ # TODO: Note to future programmers: # When this is running on python 2.5, # use the unified try/except/finally time_started = time.time() try: try: yum_base = get_yum_base() _do_yum_lock(yum_base) # Accumulate transaction data oldcount = len(yum_base.tsInfo) p_list = [] p_obj = {} # build package list for logs for package in command.transaction_data['packages']: p_list.append(_yum_package_tup(package[0])[0]) p_obj['action'] = package[1] p_obj['packages'] = p_list command.execute(yum_base) if not len(yum_base.tsInfo) > oldcount: raise yum.Errors.YumBaseError('empty transaction') # depSolving stage (result, resultmsgs) = yum_base.buildTransaction() if result == 1: # Fatal Error for msg in resultmsgs: logging.debug('Error: %s' % msg) raise yum.Errors.DepError(resultmsgs) elif result == 0 or result == 2: # Continue on pass else: # Unknown Error for msg in resultmsgs: logging.debug('Error: %s' % msg) raise yum.Errors.YumBaseError(resultmsgs) logging.debug("Dependencies Resolved") yum_base.cache_only=cache_only yum_base.doTransaction(p_obj) # for sending yum update tid to alx if utils.is_alx(): time_ended = time.time() tid = yum_base.history.last().tid exit_code = 0 finally: yum_base.closeRpmDB() yum_base.doUnlock(YUM_PID_FILE) except (yum.Errors.InstallError, yum.Errors.UpdateError) as e: message = u"Failed: Packages failed to install properly: %s" % e data = {} data['version'] = "1" data['name'] = "package_install_failure" return (32, message, data) except yum.Errors.RemoveError as e: data = {} data['version'] = 0 data['name'] = "rpmremoveerrors" return (15, u"%s" % e, data) except yum.Errors.DepError as e: message = u"Failed: packages requested raised dependency problems: %s" % e data = {} data["version"] = "1" data["name"] = "failed_deps" return (18, message, data) except (yum.Errors.YumBaseError, PluginYumExit) as e: message = u"Error while executing packages action: %s" % e status = 6, data = {} return (status, message, data) # send yum update tid to alx if utils.is_alx(): alxutils.send_yum_updates(alxutils.SUCCESS, exit_code, tid, time_started, time_ended) return (0, "Update Succeeded", {})
Save
Cancel