updating RPM directory
Klaus Steden
klaus.steden at thomson.net
Wed Aug 3 17:43:42 UTC 2005
> I think I've asked before, but can't seem to find that email.
>
> Does anyone have a script to update an existing directory with newer
> rpms from a given folder?
>
> For example, I have an RPM folder with customizations, and I want to
> update the RPMS from a directory that has update 1 plus newer erratas?
> Does anyone have such a script, before I try to write one?
>
Try this. I can't claim ownership or authorship of it - someone on this list
wrote it, but I've been using it for the past two months. It's a life-saver.
Klaus
-------------- next part --------------
#!/usr/bin/python
#
# update_release.py 2.0 - merge rpm updates and third party rpms
# into a RedHat distro and check for
# dependencies/conflicts problems
# Copyright (C) 2003 - Zouhir Hafidi (Zouhir.Hafidi at agat.univ-lille1.fr)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Usage (some examples):
# - get help:
# update_release.py -h
# - run in test mode and use current dir as a base dir
# update_release.py -n
# update_release.py -n -v
# - run in test mode and use /export/install/redhat/8.0 as a base dir
# update_release.py -n -d /export/install/redhat/8.0
# update_release.py -n -v -d /export/install/redhat/8.0
# - use current dir as a base dir. The installation dir and updates dir
# are respectively by default updated_release/RedHat/RPMS/ and updates/
# update_release.py
# update_release.py -v
# - use /export/install/redhat/8.0 as a base dir. The installation dir and
# updates dir are respectively by default updated_release/RedHat/RPMS/ and updates/
# update_release.py -d /export/install/redhat/8.0
# update_release.py -v -d /export/install/redhat/8.0
# - use /export/install/redhat/8.0 as a base dir and only check for
# dependencies/conflicts under updated_release/RedHat/RPMS
# update_release.py -c -d /export/install/redhat/8.0
# update_release.py -c -v -d /export/install/redhat/8.0
# - use /export/install/redhat/8.0 as a base dir and only check for
# dependencies/conflicts under some/relative/dir
# update_release.py -c -d /export/install/redhat/8.0 -i some/relative/dir
# update_release.py -c -v -d /export/install/redhat/8.0 -i some/relative/dir
import os
import sys
import getopt
import commands
import rpm
import string
import shutil
def usage():
print "Usage: %s [-h] | [[-n] [-v] [-d dir] [-i dir] [-u dir] [-b branch] [-c] [-H]]" % sys.argv[0]
print " -h|--help print this message"
print " -n|--dryrun dry run mode (show a trace of what would be done)"
print " -v|--verbose print more information when running"
print " -d dir|--basedir=dir base dir (current dir by default)"
print " -i dir|--installdir=dir installation dir (updated_release/RedHat/RPMS/ by default)"
print " -u dir|--updatesdir=dir updates dir (updates/ by default)"
print " -b branch|--branch=vers RedHat version (i.e. Fedora, RedHat)"
print " -c|--checkonly check for dependencies/conflicts in the installation dir"
print " -H|--nohdlist don't regenerate hdlists"
# Description:
# construct a dependency string (this function comes from anaconda)
# Argument(s):
# - rpm name
# - rpm version
# - dependency flags
# Return value(s):
# - dependency string
def formatRequire (name, version, flags):
string = name
if flags:
if flags & (rpm.RPMSENSE_LESS | rpm.RPMSENSE_GREATER | rpm.RPMSENSE_EQUAL):
string = string + " "
if flags & rpm.RPMSENSE_LESS:
string = string + "<"
if flags & rpm.RPMSENSE_GREATER:
string = string + ">"
if flags & rpm.RPMSENSE_EQUAL:
string = string + "="
string = string + " %s" % version
return string
# Description:
# construct a dictionary containing the headers of a set of rpms.
# If a same rpm exists with different versions, then take into
# account only the recent one. Source rpms are not processed.
# Argument(s):
# - a list of rpms filenames
# Return value(s):
# - a dictionary of the form:
# {(rpm_name, rpm_arch):(rpm_header, rpm_filename), ...}
# - a list containing source rpms and old rpms
def getRpmsHeaders(rpms):
headers = {}
bad_rpms = []
ts = rpm.TransactionSet("/", ~(rpm._RPMVSF_NOSIGNATURES))
ts.closeDB()
for rpm_file in rpms:
fdno = os.open(rpm_file, os.O_RDONLY)
header = ts.hdrFromFdno(fdno)
os.close(fdno)
if string.find(rpm_file, "debuginfo") >= 0:
bad_rpms.append(rpm_file)
if verbose_flag:
print " skip %s which is a debug package" % os.path.basename(rpm_file)
elif header[rpm.RPMTAG_SOURCEPACKAGE]:
bad_rpms.append(rpm_file)
if verbose_flag:
print " skip %s which is a source package" % os.path.basename(rpm_file)
else:
key = (header[rpm.RPMTAG_NAME], header[rpm.RPMTAG_ARCH])
if headers.has_key(key):
cmp = rpm.versionCompare(headers[key][0], header)
if not cmp in [-1, 1]:
print " rpm.versionCompare() returned error code %d while comparing %s and %s" % (cmp, os.path.basename(headers[key][1]), os.path.basename(rpm_file))
sys.exit(cmp)
if cmp == -1:
bad_rpms.append(headers[key][1])
if verbose_flag:
print " use %s instead of %s" % (os.path.basename(rpm_file), os.path.basename(headers[key][1]))
headers[key] = (header, rpm_file)
else:
bad_rpms.append(rpm_file)
if verbose_flag:
print " skip %s which is older than %s" % (os.path.basename(rpm_file), os.path.basename(headers[key][1]))
else:
headers[key] = (header, rpm_file)
return (headers, bad_rpms)
# Description:
# delete unwanted entries from a list of rpms filenames
# Argument(s):
# - list of rpms filenames
# - list of entries to delete
# Return value(s):
# None
def skipRpms(rpms_list, skip_list):
for item in skip_list:
del rpms_list[rpms_list.index(item)]
# Description:
# check if a given set of rpms put together doesn't have any
# dependencies nor conflicts problems
# Argument(s):
# - a dictionary of the form:
# {(rpm_name, rpm_arch):(rpm_header, rpm_filename), ...}
# Return value(s):
# - a list of errors if any
def checkDepsAndConflicts(headers):
ts = rpm.TransactionSet("/", ~(rpm._RPMVSF_NOSIGNATURES))
ts.closeDB()
for key in headers.keys():
ts.addInstall(headers[key][0], key, 'i')
return ts.check()
def main():
# Processing command line arguments
try:
opts, args = getopt.getopt(sys.argv[1:], 'hnvd:i:u:b:cHG', \
["help", "dryrun", "verbose", "basedir=", "installdir=", \
"updatesdir=", "branch=", "checkonly", "nohdlist", "genhdlist"])
except getopt.GetoptError:
usage()
sys.exit(1)
if args != []:
usage()
sys.exit(1)
global verbose_flag
verbose_flag = 0
dryrun_flag = 0
base_dir = os.getcwd()
# updated_release_dir = "updated_release/RedHat/RPMS"
updated_release_dir = "Fedora/RPMS"
updates_dir = "updates"
branch = "Fedora"
checkonly_flag = 0
nohdlist_flag = 0
genhdlist_flag = 0
for o, a in opts:
if o in ("-h", "--help"):
usage()
sys.exit()
if o in ("-n", "--dryrun"):
dryrun_flag = 1
if o in ("-v", "--verbose"):
verbose_flag = 1
if o in ("-d", "--basedir"):
base_dir = a
if o in ("-i", "--installdir"):
updated_release_dir = a
if o in ("-u", "--updatesdir"):
updates_dir = a
if o in ("-b", "--branch"):
branch = a
if o in ("-c", "--checkonly"):
checkonly_flag = 1
if o in ("-H", "--nohdlist"):
nohdlist_flag = 1
if o in ("-G", "--genhdlist"):
genhdlist_flag = 1
# Some initializations
updated_release_dir = os.path.join(base_dir, updated_release_dir)
updates_dir = os.path.join(base_dir, updates_dir)
old_rpms_dir = os.path.join(base_dir, "old_rpms")
regenerate_hdlist = 0
if dryrun_flag:
verbose_flag = 1
# The updated release dir must exist
if not os.path.isdir(updated_release_dir):
print "can't access %s, nothing to do" % updated_release_dir
sys.exit(2)
if verbose_flag:
print "processing rpms under %s ..." % updated_release_dir
# Get all rpm names under updated_release_dir
exit_status, output = commands.getstatusoutput("find %s -name '*\.rpm' | grep -v '.src.rpm'" % updated_release_dir)
if exit_status != 0:
sys.exit(3)
updated_release_rpms = output.split()
# Get headers of all rpms under updated_release_dir
updated_release_headers, bad_rpms = getRpmsHeaders(updated_release_rpms)
if bad_rpms != []:
print " ERROR: the following rpms are either source rpms or older rpms. Please remove them:"
for rpm_file in bad_rpms:
print " %s" % rpm_file
sys.exit(4)
# Before merging, check if the current updated release dir contains
# any dependencies or conflicts problems and exit if so
errors = checkDepsAndConflicts(updated_release_headers)
if errors:
print "ERROR: the installation tree already contains conflicts and/or dependencies problems"
for ((name, version, release), (reqname, reqversion), \
flags, suggest, sense) in errors:
if sense==rpm.RPMDEP_SENSE_REQUIRES:
print " depcheck: package %s needs %s" % ( name, formatRequire(reqname, reqversion, flags))
elif sense==rpm.RPMDEP_SENSE_CONFLICTS:
print " depcheck: package %s conflicts with %s" % (name, reqname)
sys.exit(4)
if verbose_flag:
print "done"
if checkonly_flag:
if errors:
print "dependencies/conflicts problems exist in %s" % updated_release_dir
else:
print "no dependencies/conflicts problems in %s" % updated_release_dir
sys.exit()
# The updates dir must exist
if not os.path.isdir(updates_dir):
print "can't access %s, nothing to do" % updates_dir
sys.exit(2)
# If necessary, create the dir to put old rpms into
if not os.path.isdir(old_rpms_dir):
os.mkdir(old_rpms_dir)
if verbose_flag:
print "processing rpms under %s ..." % updates_dir
# Get all rpm names under updates_dir
exit_status, output = commands.getstatusoutput("find %s -follow -name '*\.rpm' | grep -v '.src.rpm'" % updates_dir)
if exit_status != 0:
sys.exit(3)
updates_rpms = output.split()
# Get headers of all rpms under updates_dir
updates_headers, bad_rpms = getRpmsHeaders(updates_rpms)
# Ignore unwanted rpms
skipRpms(updates_rpms, bad_rpms)
if bad_rpms != []:
print " WARNING: the following rpms are either source rpms or older rpms. I will ignore them:"
for rpm_file in bad_rpms:
print " %s" % rpm_file
if verbose_flag:
print "done"
# Take the entries in the updates_headers dictionary and merge them
# with the entries in the updated_release_headers dictionary:
# - for every entry in the updates_headers dictionary
# - if a corresponding entry with an old version exists
# in the updated_release_headers dictionary then drop it
# in the old_rpms_headers dictionary and replace it with
# the new one
# - if there is no corresponding entry with an old version
# then simply add the new one in the updated_release_headers
# dictionary
old_rpms_headers = {}
for key in updates_headers.keys():
if updated_release_headers.has_key(key):
cmp = rpm.versionCompare(updated_release_headers[key][0], updates_headers[key][0])
if cmp == 1:
if verbose_flag:
print "skip %s which is older than %s" % (os.path.basename(updates_headers[key][1]), os.path.basename(updated_release_headers[key][1]))
elif cmp == 0:
if verbose_flag:
print "skip %s which is the same as %s" % (os.path.basename(updates_headers[key][1]), os.path.basename(updated_release_headers[key][1]))
elif cmp == -1:
if verbose_flag:
print "exchange %s with %s" % (os.path.basename(updated_release_headers[key][1]), os.path.basename(updates_headers[key][1]))
old_rpms_headers[key] = updated_release_headers[key]
updated_release_headers[key] = updates_headers[key]
else:
print " rpm.versionCompare() returned error code %d while comparing %s and %s" % (cmp, os.path.basename(updated_release_headers[key][1]), os.path.basename(updates_headers[key][1]))
sys.exit(cmp)
else:
updated_release_headers[key] = updates_headers[key]
if verbose_flag:
print "add %s" % os.path.basename(updates_headers[key][1])
# At this point, the merging process is done and the updated_release_headers
# dictionary should be up to date. Before applying the changes to the updated
# release dir, we must make sure that there's no dependencies nor conflicts
# problems. If so, we do a "backtracking" on the headers which make problems
# until a state without any dependencies/conflicts is reached.
while 1:
deps_and_conflicts = 0
errors = checkDepsAndConflicts(updated_release_headers)
if errors:
# Construct a list containing the names of the rpms that
# make problems. If a same rpm exists for different
# architectures, then all of them will not be used.
# NOTE: we don't know how to discard only problematic rpms
# since the architecture is not reported by the check()
# method (is there a fix ?)
if verbose_flag:
print "merging will result in the following conflicts and/or dependencies problems:"
name_list = []
for ((name, version, release), (reqname, reqversion), \
flags, suggest, sense) in errors:
if sense==rpm.RPMDEP_SENSE_REQUIRES:
if verbose_flag:
print " depcheck: package %s needs %s" % ( name, formatRequire(reqname, reqversion, flags))
deps_and_conflicts = 1
if not name in name_list:
name_list.append(name)
elif sense==rpm.RPMDEP_SENSE_CONFLICTS:
if verbose_flag:
print " depcheck: package %s conflicts with %s" % (name, reqname)
deps_and_conflicts = 1
if not name in name_list:
name_list.append(name)
# Discard entries with dependencies/conflicts problems in
# the updated_release_headers dictionary
if verbose_flag:
print "backtracking:"
for (name, arch) in updated_release_headers.keys():
if name in name_list :
if os.path.dirname(updated_release_headers[(name, arch)][1]) == updated_release_dir:
print "CRITICAL ERROR: attempt to discard %s" % updated_release_headers[(name, arch)][1]
sys.exit(4)
if verbose_flag:
print " discard %s" % updated_release_headers[(name, arch)][1]
del updated_release_headers[(name, arch)]
# Put back those entries which have been replaced by recent
# entries which resulted in dependencies/conflicts problems
for (name, arch) in old_rpms_headers.keys():
if name in name_list :
if verbose_flag:
print " reuse %s" % old_rpms_headers[(name, arch)][1]
updated_release_headers[(name, arch)] = old_rpms_headers[(name, arch)]
del old_rpms_headers[(name, arch)]
# The infinite loop *should* be broken after some iterations
if deps_and_conflicts == 0:
break
# At this point, the updated_release_headers dictionary contains
# entries with the rpms filenames that should be in the updated
# release dir so that we have no dependencies/conflicts problems.
# The old_rpms_headers dictionary contains entries with the rpms
# filenames that should be moved from the updated release dir to
# the old rpms dir. Just reflect these changes on the directories
# themselves
for key in updated_release_headers.keys():
if os.path.dirname(updated_release_headers[key][1]) == updated_release_dir:
continue
regenerate_hdlist = 1
if not dryrun_flag:
# os.link(updated_release_headers[key][1], \
shutil.copy2(updated_release_headers[key][1], \
os.path.join(updated_release_dir, os.path.basename(updated_release_headers[key][1])))
else:
print "should",
if key in old_rpms_headers.keys():
print "update %s with %s" % (os.path.basename(old_rpms_headers[key][1]), \
os.path.basename(updated_release_headers[key][1]))
if not dryrun_flag:
os.rename(old_rpms_headers[key][1], \
os.path.join(old_rpms_dir, os.path.basename(old_rpms_headers[key][1])))
else:
print "add %s" % os.path.basename(updated_release_headers[key][1])
# Regenerate hdlists if necessary
if genhdlist_flag or ((not nohdlist_flag) and regenerate_hdlist):
if not dryrun_flag:
os.system("/usr/lib/anaconda-runtime/genhdlist --withnumbers --productpath %s %s/../.." % (branch, updated_release_dir) )
os.system("PYTHONPATH=/usr/lib/anaconda /usr/lib/anaconda-runtime/pkgorder %s/../.. i386 %s > pkgorder" % (updated_release_dir, branch) )
os.system("/usr/lib/anaconda-runtime/genhdlist --withnumbers --productpath %s --fileorder pkgorder %s/../.." % (branch, updated_release_dir) )
else:
if verbose_flag:
print "should",
if verbose_flag:
print "regenerate hdlist"
if __name__ == "__main__":
main()
More information about the Kickstart-list
mailing list