%PDF- %PDF-
| Direktori : /usr/share/rhn/actions/ |
| Current File : //usr/share/rhn/actions/packages.py |
#
# Copyright (c) 1999--2012 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
import os
import sys
import time
import yum
import yum.Errors
from yum.plugins import PluginYumExit
sys.path.append("/usr/share/yum-cli")
import callback
sys.path.append("/usr/share/rhn/")
from up2date_client import up2dateLog
from up2date_client import config
from up2date_client import rpmUtils
from up2date_client import rhnPackageInfo
from rpm import RPMPROB_FILTER_OLDPACKAGE
log = up2dateLog.initLog()
YUM_PID_FILE = '/var/run/yum.pid'
# file used to keep track of the next time rhn_check
# is allowed to update the package list on the server
LAST_UPDATE_FILE="/var/lib/up2date/dbtimestamp"
# mark this module as acceptable
__rhnexport__ = [
'update',
'remove',
'refresh_list',
'fullUpdate',
'checkNeedUpdate',
'runTransaction',
'verify'
]
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
self.doLock()
self.doTsSetup()
self.doRpmDBSetup()
self.doRepoSetup()
self.doSackSetup()
# Copied from yum/cli.py, more or less
def doTransaction(self):
"""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 = []
for txmbr in self.tsInfo.getMembers():
if txmbr.ts_state in ['i', 'u']:
po = txmbr.po
if po:
downloadpkgs.append(po)
log.log_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, so
# skip rest of transaction work and return now.
log.log_debug('Configured to "retrieveOnly" so skipping package install')
return 0
if self.cache_only:
log.log_debug('Just pre-caching packages, skipping package install')
return 0
# Check GPG signatures
if self.gpgsigcheck(downloadpkgs) != 0:
return 1
log.log_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)
log.log_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
log.log_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
log.log_debug('Running Transaction')
self.runTransaction(testcb)
# 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):
log.log_debug("GPG check wasn't successful, will attempt to import key")
self.getKeyForPackage(po, askcb=lambda x,y,z: True)
log.log_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:
log.log_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 RHN yum plugin configuration file"
% (keyurl))
return False
return True
def getInstalledPkgObject(self, package_tup):
installed = self.rpmdb.returnPackages()
log.log_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:
log.log_debug("Found %d package(s) to remove" % len(erases))
return erases
else:
# TODO: we should just fail, I think
log.log_debug("Couldn't find packages to remove")
return ()
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':
self.install(**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."
# global module level reference to YumAction
yum_base = YumAction()
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 remove(package_list, cache_only=None):
"""We have been told that we should remove packages"""
if cache_only:
return (0, "no-ops for caching", {})
if type(package_list) != type([]):
return (13, "Invalid arguments passed to function", {})
log.log_debug("Called remove_packages", package_list)
transaction_data = __make_transaction(package_list, 'e')
return _runTransaction(transaction_data)
def update(package_list, cache_only=None):
"""We have been told that we should retrieve/install packages"""
if type(package_list) != type([]):
return (13, "Invalid arguments passed to function", {})
log.log_debug("Called update", package_list)
# 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]):
log.log_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'] = '0'
pkgs = yum_base.rpmdb.searchNevra(name=pkgkeys['name'],
epoch=pkgkeys['epoch'], arch=pkgkeys['arch'],
ver=pkgkeys['version'], rel=pkgkeys['release'])
if pkgs:
log.log_debug('Package %s already installed' \
% _yum_package_tup(package))
package_list.remove(package)
else:
found = False
evr = yum.packages.PackageEVR(pkgkeys['epoch'], \
pkgkeys['version'], pkgkeys['release'])
for pkg in yum_base.rpmdb.searchNevra(name=pkgkeys['name'], arch=pkgkeys['arch']):
if pkg.returnEVR() > evr:
found = True
if found:
log.log_debug('More recent version of package %s is already installed' \
% _yum_package_tup(package))
package_list.remove(package)
# Don't proceed further with empty list,
# since this would result into an empty yum transaction
if not package_list:
return (0, "Requested packages already installed", {})
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 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 fullUpdate(force=0, cache_only=None):
""" Update all packages on the system. """
#TODO: force doesn't mean anything for yum.
command = FullUpdateCommand()
return _run_yum_action(command, cache_only)
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
try:
try:
yum_base.doLock(YUM_PID_FILE)
# Accumulate transaction data
oldcount = len(yum_base.tsInfo)
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:
log.log_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:
log.log_debug('Error: %s' % msg)
raise yum.Errors.YumBaseError, resultmsgs
log.log_debug("Dependencies Resolved")
yum_base.cache_only=cache_only
yum_base.doTransaction()
finally:
yum_base.closeRpmDB()
yum_base.doUnlock(YUM_PID_FILE)
except (yum.Errors.InstallError, yum.Errors.UpdateError), e:
data = {}
data['version'] = "1"
data['name'] = "package_install_failure"
return (32, "Failed: Packages failed to install "\
"properly: %s" % str(e), data)
except yum.Errors.RemoveError, e:
data = {}
data['version'] = 0
data['name'] = "rpmremoveerrors"
return (15, "%s" % str(e), data)
except yum.Errors.DepError, e:
data = {}
data["version"] = "1"
data["name"] = "failed_deps"
return (18, "Failed: packages requested raised "\
"dependency problems: %s" % str(e), data)
except (yum.Errors.YumBaseError, PluginYumExit), e:
status = 6,
message = "Error while executing packages action: %s" % str(e)
data = {}
return (status, message, data)
return (0, "Update Succeeded", {})
# The following functions are the same as the old up2date ones.
def checkNeedUpdate(rhnsd=None, cache_only=None):
""" Check if the locally installed package list changed, if
needed the list is updated on the server
In case of error avoid pushing data to stay safe
"""
if cache_only:
return (0, "no-ops for caching", {})
data = {}
dbpath = "/var/lib/rpm"
cfg = config.initUp2dateConfig()
if cfg['dbpath']:
dbpath = cfg['dbpath']
RPM_PACKAGE_FILE="%s/Packages" % dbpath
try:
dbtime = os.stat(RPM_PACKAGE_FILE)[8] # 8 is st_mtime
except:
return (0, "unable to stat the rpm database", data)
try:
last = os.stat(LAST_UPDATE_FILE)[8]
except:
last = 0;
# Never update the package list more than once every 1/2 hour
if last >= (dbtime - 10):
return (0, "rpm database not modified since last update (or package "
"list recently updated)", data)
if last == 0:
try:
file = open(LAST_UPDATE_FILE, "w+")
file.close()
except:
return (0, "unable to open the timestamp file", data)
# call the refresh_list action with a argument so we know it's
# from rhnsd
return refresh_list(rhnsd=1)
def refresh_list(rhnsd=None, cache_only=None):
""" push again the list of rpm packages to the server """
if cache_only:
return (0, "no-ops for caching", {})
log.log_debug("Called refresh_rpmlist")
ret = None
try:
rhnPackageInfo.updatePackageProfile()
except:
print "ERROR: refreshing remote package list for System Profile"
return (20, "Error refreshing package list", {})
touch_time_stamp()
return (0, "rpmlist refreshed", {})
def touch_time_stamp():
try:
file_d = open(LAST_UPDATE_FILE, "w+")
file_d.close()
except:
return (0, "unable to open the timestamp file", {})
# Never update the package list more than once every hour.
t = time.time()
try:
os.utime(LAST_UPDATE_FILE, (t, t))
except:
return (0, "unable to set the time stamp on the time stamp file %s"
% LAST_UPDATE_FILE, {})
def verify(packages, cache_only=None):
log.log_debug("Called packages.verify")
if cache_only:
return (0, "no-ops for caching", {})
data = {}
data['name'] = "packages.verify"
data['version'] = 0
ret, missing_packages = rpmUtils.verifyPackages(packages)
data['verify_info'] = ret
if len(missing_packages):
data['name'] = "packages.verify.missing_packages"
data['version'] = 0
data['missing_packages'] = missing_packages
return(43, "packages requested to be verified are missing", data)
return (0, "packages verified", data)