crazytime: replace installtree with runtimebuilder
This commit is contained in:
parent
46e18c3781
commit
905e05159d
1265
share/ramdisk.ltmpl
1265
share/ramdisk.ltmpl
File diff suppressed because it is too large
Load Diff
@ -1,19 +1,5 @@
|
|||||||
FIXME:
|
FIXME:
|
||||||
- make TemplateRunner handle ramdisk.tmpl and installtree
|
|
||||||
- DONE: rewrite ramdisk.tmpl a bit
|
|
||||||
- DONE: install -> installpkg
|
|
||||||
- DONE: remove -> removepkg
|
|
||||||
- DONE: remove --path -> remove
|
|
||||||
- DONE: module takes multiple args
|
|
||||||
- DONE: rewrite installtree as template
|
|
||||||
- DONE: split installtree into postinstall/cleanup
|
|
||||||
- add TemplateRunner commands
|
|
||||||
- installpkg, removepkg, module
|
|
||||||
- also do_installpkg, do_removepkg
|
|
||||||
- or maybe run_transaction?
|
|
||||||
- handle [compression] conf keys (type, speed)
|
- handle [compression] conf keys (type, speed)
|
||||||
- run prelink on runtime image
|
|
||||||
- chroot ${root} /etc/cron.daily/prelink
|
|
||||||
|
|
||||||
|
|
||||||
Good ideas:
|
Good ideas:
|
||||||
@ -21,6 +7,8 @@ Good ideas:
|
|||||||
- update imgutils to be able to partition images
|
- update imgutils to be able to partition images
|
||||||
- via dmsetup stuff in anaconda.storage?
|
- via dmsetup stuff in anaconda.storage?
|
||||||
- allow alternate runtime images via conf key
|
- allow alternate runtime images via conf key
|
||||||
|
- run prelink on runtime image
|
||||||
|
- chroot ${root} /etc/cron.daily/prelink
|
||||||
|
|
||||||
|
|
||||||
Someday:
|
Someday:
|
||||||
|
@ -38,15 +38,13 @@ from base import BaseLoraxClass, DataHolder
|
|||||||
import output
|
import output
|
||||||
|
|
||||||
import yum
|
import yum
|
||||||
import yumhelper
|
|
||||||
import ltmpl
|
import ltmpl
|
||||||
|
|
||||||
import imgutils
|
import imgutils
|
||||||
import constants
|
import constants
|
||||||
from sysutils import *
|
from sysutils import *
|
||||||
|
|
||||||
from treebuilder import TreeBuilder
|
from treebuilder import RuntimeBuilder, TreeBuilder
|
||||||
from installtree import LoraxInstallTree
|
|
||||||
from buildstamp import BuildStamp
|
from buildstamp import BuildStamp
|
||||||
from treeinfo import TreeInfo
|
from treeinfo import TreeInfo
|
||||||
from discinfo import DiscInfo
|
from discinfo import DiscInfo
|
||||||
@ -158,7 +156,7 @@ class Lorax(BaseLoraxClass):
|
|||||||
|
|
||||||
# do we have all lorax required commands?
|
# do we have all lorax required commands?
|
||||||
self.lcmds = constants.LoraxRequiredCommands()
|
self.lcmds = constants.LoraxRequiredCommands()
|
||||||
# TODO: actually check for required commands
|
# TODO: actually check for required commands (runcmd etc)
|
||||||
|
|
||||||
# do we have a proper yum base object?
|
# do we have a proper yum base object?
|
||||||
logger.info("checking yum base object")
|
logger.info("checking yum base object")
|
||||||
@ -166,49 +164,25 @@ class Lorax(BaseLoraxClass):
|
|||||||
logger.critical("no yum base object")
|
logger.critical("no yum base object")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
logger.info("setting up yum helper")
|
logger.debug("using install root: {0}".format(ybo.installroot))
|
||||||
self.yum = yumhelper.LoraxYumHelper(ybo)
|
|
||||||
logger.debug("using install root: {0}".format(self.yum.installroot))
|
|
||||||
|
|
||||||
logger.info("setting up build architecture")
|
logger.info("setting up build architecture")
|
||||||
self.arch = ArchData(self.get_buildarch())
|
self.arch = ArchData(get_buildarch(ybo))
|
||||||
for attr in ('buildarch', 'basearch', 'libdir'):
|
for attr in ('buildarch', 'basearch', 'libdir'):
|
||||||
logger.debug("self.arch.%s = %s", attr, getattr(self.arch,attr))
|
logger.debug("self.arch.%s = %s", attr, getattr(self.arch,attr))
|
||||||
|
|
||||||
logger.info("setting up install tree")
|
|
||||||
self.installtree = LoraxInstallTree(self.yum, self.arch.libdir)
|
|
||||||
|
|
||||||
logger.info("setting up build parameters")
|
logger.info("setting up build parameters")
|
||||||
product = DataHolder(name=product, version=version, release=release,
|
product = DataHolder(name=product, version=version, release=release,
|
||||||
variant=variant, bugurl=bugurl, is_beta=is_beta)
|
variant=variant, bugurl=bugurl, is_beta=is_beta)
|
||||||
self.product = product
|
self.product = product
|
||||||
logger.debug("product data: %s" % product)
|
logger.debug("product data: %s" % product)
|
||||||
|
|
||||||
logger.info("parsing the runtime template")
|
templatedir = self.conf.get("lorax", "sharedir")
|
||||||
tfile = joinpaths(self.conf.get("lorax", "sharedir"),
|
rb = RuntimeBuilder(product, arch, self.outputdir, ybo, templatedir)
|
||||||
self.conf.get("templates", "ramdisk"))
|
|
||||||
|
|
||||||
# TODO: normalize with arch templates:
|
logger.info("installing runtime packages")
|
||||||
# tvars = dict(product=product, arch=arch)
|
rb.yum.conf.skip_broken = self.conf.getboolean("yum", "skipbroken")
|
||||||
tvars = { "basearch": self.arch.basearch,
|
rb.install()
|
||||||
"buildarch": self.arch.buildarch,
|
|
||||||
"libdir" : self.arch.libdir,
|
|
||||||
"product": self.product.name.lower() }
|
|
||||||
|
|
||||||
template = ltmpl.LoraxTemplate()
|
|
||||||
template.parse(tfile, tvars)
|
|
||||||
|
|
||||||
logger.info("creating tree directories")
|
|
||||||
for d in template.getdata("mkdir"):
|
|
||||||
os.makedirs(joinpaths(self.installtree.root, d))
|
|
||||||
|
|
||||||
# install packages
|
|
||||||
logger.info("getting list of required packages")
|
|
||||||
for package in template.getdata("install"):
|
|
||||||
self.installtree.yum.install(package)
|
|
||||||
|
|
||||||
skipbroken = self.conf.getboolean("yum", "skipbroken")
|
|
||||||
self.installtree.yum.process_transaction(skipbroken)
|
|
||||||
|
|
||||||
# write .buildstamp
|
# write .buildstamp
|
||||||
buildstamp = BuildStamp(self.product.name, self.product.version,
|
buildstamp = BuildStamp(self.product.name, self.product.version,
|
||||||
@ -219,55 +193,13 @@ class Lorax(BaseLoraxClass):
|
|||||||
logger.debug("saving pkglists to %s", self.workdir)
|
logger.debug("saving pkglists to %s", self.workdir)
|
||||||
dname = joinpaths(self.workdir, "pkglists")
|
dname = joinpaths(self.workdir, "pkglists")
|
||||||
os.makedirs(dname)
|
os.makedirs(dname)
|
||||||
for pkgname, pkgobj in self.installtree.yum.installed_packages.items():
|
for pkgobj in ybo.doPackageLists(pkgnarrow='installed').installed:
|
||||||
with open(joinpaths(dname, pkgname), "w") as fobj:
|
with open(joinpaths(dname, pkgobj.name), "w") as fobj:
|
||||||
for fname in pkgobj.filelist:
|
for fname in pkgobj.filelist:
|
||||||
fobj.write("{0}\n".format(fname))
|
fobj.write("{0}\n".format(fname))
|
||||||
|
|
||||||
logger.info("removing locales")
|
logger.info("doing post-install configuration")
|
||||||
self.installtree.remove_locales()
|
rb.postinstall()
|
||||||
|
|
||||||
logger.info("creating keymaps")
|
|
||||||
if self.arch.basearch not in ("s390", "s390x"):
|
|
||||||
self.installtree.create_keymaps(basearch=self.arch.basearch)
|
|
||||||
|
|
||||||
logger.info("creating screenfont")
|
|
||||||
self.installtree.create_screenfont(basearch=self.arch.basearch)
|
|
||||||
|
|
||||||
logger.info("moving stubs")
|
|
||||||
self.installtree.move_stubs()
|
|
||||||
|
|
||||||
logger.info("getting list of required modules")
|
|
||||||
# Need a list to pass to cleanup_kernel_modules, not a generator
|
|
||||||
modules = list(template.getdata("module"))
|
|
||||||
|
|
||||||
self.installtree.install_kernel_modules(modules)
|
|
||||||
|
|
||||||
logger.info("moving anaconda repos")
|
|
||||||
self.installtree.move_repos()
|
|
||||||
|
|
||||||
logger.info("creating depmod.conf")
|
|
||||||
self.installtree.create_depmod_conf()
|
|
||||||
|
|
||||||
# set up /sbin/init
|
|
||||||
if self.arch.basearch in ("s390", "s390x"):
|
|
||||||
self.installtree.setup_s390_init()
|
|
||||||
else:
|
|
||||||
self.installtree.setup_init()
|
|
||||||
# misc tree modifications
|
|
||||||
self.installtree.misc_tree_modifications()
|
|
||||||
|
|
||||||
# get config files
|
|
||||||
config_dir = joinpaths(self.conf.get("lorax", "sharedir"),
|
|
||||||
"config_files")
|
|
||||||
|
|
||||||
self.installtree.get_config_files(config_dir)
|
|
||||||
self.installtree.setup_sshd(config_dir)
|
|
||||||
if self.arch.basearch in ("s390", "s390x"):
|
|
||||||
self.installtree.generate_ssh_keys()
|
|
||||||
|
|
||||||
# get anaconda portions
|
|
||||||
self.installtree.get_anaconda_portions()
|
|
||||||
|
|
||||||
# write .discinfo
|
# write .discinfo
|
||||||
discinfo = DiscInfo(self.product.release, self.arch.basearch)
|
discinfo = DiscInfo(self.product.release, self.arch.basearch)
|
||||||
@ -277,12 +209,8 @@ class Lorax(BaseLoraxClass):
|
|||||||
installroot = joinpaths(self.workdir, "installroot")
|
installroot = joinpaths(self.workdir, "installroot")
|
||||||
linktree(self.installtree.root, installroot)
|
linktree(self.installtree.root, installroot)
|
||||||
|
|
||||||
logger.info("getting list of not required packages")
|
logger.info("cleaning unneeded files")
|
||||||
removepkgs = template.getdata("remove", mode="lines")
|
rb.clean()
|
||||||
self.installtree.remove_packages(removepkgs)
|
|
||||||
|
|
||||||
logger.info("cleaning up python files")
|
|
||||||
self.installtree.cleanup_python_files()
|
|
||||||
|
|
||||||
logger.info("creating the runtime image")
|
logger.info("creating the runtime image")
|
||||||
# TODO: different img styles / create_runtime implementations
|
# TODO: different img styles / create_runtime implementations
|
||||||
@ -293,13 +221,13 @@ class Lorax(BaseLoraxClass):
|
|||||||
logger.info("preparing to build output tree and boot images")
|
logger.info("preparing to build output tree and boot images")
|
||||||
treebuilder = TreeBuilder(self.product, self.arch,
|
treebuilder = TreeBuilder(self.product, self.arch,
|
||||||
installroot, self.outputdir,
|
installroot, self.outputdir,
|
||||||
templatedir=self.conf.get("lorax", "sharedir"))
|
templatedir)
|
||||||
|
|
||||||
# TODO: different image styles may do this part differently
|
# TODO: different image styles may do this part differently
|
||||||
logger.info("rebuilding initramfs images")
|
logger.info("rebuilding initramfs images")
|
||||||
treebuilder.rebuild_initrds(add_args=["--xz", "--add", "btrfs"])
|
treebuilder.rebuild_initrds(add_args=["--xz"])
|
||||||
|
|
||||||
# TODO: keep small initramfs for split initramfs/runtime media
|
# TODO: keep small initramfs for split initramfs/runtime media?
|
||||||
logger.info("adding runtime to initrds")
|
logger.info("adding runtime to initrds")
|
||||||
treebuilder.initrd_append(runtimedir)
|
treebuilder.initrd_append(runtimedir)
|
||||||
|
|
||||||
@ -313,9 +241,9 @@ class Lorax(BaseLoraxClass):
|
|||||||
treeinfo.add_section(section, data)
|
treeinfo.add_section(section, data)
|
||||||
treeinfo.write(joinpaths(self.outputdir, ".treeinfo"))
|
treeinfo.write(joinpaths(self.outputdir, ".treeinfo"))
|
||||||
|
|
||||||
def get_buildarch(self):
|
def get_buildarch(ybo):
|
||||||
# get architecture of the available anaconda package
|
# get architecture of the available anaconda package
|
||||||
_, available = self.yum.search("anaconda")
|
available = ybo.doPackageLists(patterns=["anaconda"]).available
|
||||||
|
|
||||||
if available:
|
if available:
|
||||||
anaconda = available.pop(0)
|
anaconda = available.pop(0)
|
||||||
|
@ -1,567 +0,0 @@
|
|||||||
#
|
|
||||||
# installtree.py
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Red Hat, Inc.
|
|
||||||
#
|
|
||||||
# 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, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
# Red Hat Author(s): Martin Gracik <mgracik@redhat.com>
|
|
||||||
#
|
|
||||||
|
|
||||||
import logging
|
|
||||||
logger = logging.getLogger("pylorax.installtree")
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import gzip
|
|
||||||
import re
|
|
||||||
import glob
|
|
||||||
import time
|
|
||||||
import subprocess
|
|
||||||
import operator
|
|
||||||
|
|
||||||
from base import BaseLoraxClass, DataHolder
|
|
||||||
import constants
|
|
||||||
from sysutils import *
|
|
||||||
|
|
||||||
|
|
||||||
class LoraxInstallTree(BaseLoraxClass):
|
|
||||||
|
|
||||||
def __init__(self, yum, libdir):
|
|
||||||
BaseLoraxClass.__init__(self)
|
|
||||||
self.yum = yum
|
|
||||||
self.root = self.yum.installroot
|
|
||||||
self.libdir = libdir
|
|
||||||
|
|
||||||
self.lcmds = constants.LoraxRequiredCommands()
|
|
||||||
|
|
||||||
def remove_locales(self):
|
|
||||||
chroot = lambda: os.chroot(self.root)
|
|
||||||
|
|
||||||
# get locales we need to keep
|
|
||||||
langtable = joinpaths(self.root, "usr/share/anaconda/lang-table")
|
|
||||||
if not os.path.exists(langtable):
|
|
||||||
logger.critical("could not find anaconda lang-table, exiting")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# remove unneeded locales from /usr/share/locale
|
|
||||||
with open(langtable, "r") as fobj:
|
|
||||||
langs = fobj.readlines()
|
|
||||||
|
|
||||||
langs = map(lambda l: l.split()[1], langs)
|
|
||||||
|
|
||||||
localedir = joinpaths(self.root, "usr/share/locale")
|
|
||||||
for fname in os.listdir(localedir):
|
|
||||||
fpath = joinpaths(localedir, fname)
|
|
||||||
if os.path.isdir(fpath) and fname not in langs:
|
|
||||||
shutil.rmtree(fpath)
|
|
||||||
|
|
||||||
# move the lang-table to etc
|
|
||||||
shutil.move(langtable, joinpaths(self.root, "etc"))
|
|
||||||
|
|
||||||
def create_keymaps(self, basearch):
|
|
||||||
keymaps = joinpaths(self.root, "etc/keymaps.gz")
|
|
||||||
|
|
||||||
# look for override
|
|
||||||
override = "keymaps-override-{0}".format(basearch)
|
|
||||||
override = joinpaths(self.root, "usr/share/anaconda", override)
|
|
||||||
if os.path.isfile(override):
|
|
||||||
logger.debug("using keymaps override")
|
|
||||||
shutil.move(override, keymaps)
|
|
||||||
else:
|
|
||||||
# create keymaps
|
|
||||||
cmd = [joinpaths(self.root, "usr/libexec/anaconda", "getkeymaps"),
|
|
||||||
basearch, keymaps, self.root]
|
|
||||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
|
||||||
proc.wait()
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def create_screenfont(self, basearch):
|
|
||||||
dst = joinpaths(self.root, "etc/screenfont.gz")
|
|
||||||
|
|
||||||
screenfont = "screenfont-{0}.gz".format(basearch)
|
|
||||||
screenfont = joinpaths(self.root, "usr/share/anaconda", screenfont)
|
|
||||||
if not os.path.isfile(screenfont):
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
shutil.move(screenfont, dst)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def move_stubs(self):
|
|
||||||
stubs = ("list-harddrives", "loadkeys", "mknod",
|
|
||||||
"raidstart", "raidstop")
|
|
||||||
|
|
||||||
for stub in stubs:
|
|
||||||
src = joinpaths(self.root, "usr/share/anaconda",
|
|
||||||
"{0}-stub".format(stub))
|
|
||||||
dst = joinpaths(self.root, "usr/bin", stub)
|
|
||||||
if os.path.isfile(src):
|
|
||||||
shutil.move(src, dst)
|
|
||||||
|
|
||||||
# move restart-anaconda
|
|
||||||
src = joinpaths(self.root, "usr/share/anaconda", "restart-anaconda")
|
|
||||||
dst = joinpaths(self.root, "usr/bin")
|
|
||||||
shutil.move(src, dst)
|
|
||||||
|
|
||||||
# move sitecustomize.py
|
|
||||||
pythonpath = joinpaths(self.root, "usr", self.libdir, "python?.?")
|
|
||||||
for path in glob.glob(pythonpath):
|
|
||||||
src = joinpaths(path, "site-packages/pyanaconda/sitecustomize.py")
|
|
||||||
dst = joinpaths(path, "site-packages")
|
|
||||||
shutil.move(src, dst)
|
|
||||||
|
|
||||||
def remove_packages(self, remove):
|
|
||||||
rdb = {}
|
|
||||||
order = []
|
|
||||||
for item in remove:
|
|
||||||
package = None
|
|
||||||
pattern = None
|
|
||||||
|
|
||||||
if item[0] == "--path":
|
|
||||||
# remove files
|
|
||||||
package = None
|
|
||||||
pattern = item[1]
|
|
||||||
else:
|
|
||||||
# remove package
|
|
||||||
package = item[0]
|
|
||||||
|
|
||||||
try:
|
|
||||||
pattern = item[1]
|
|
||||||
except IndexError:
|
|
||||||
pattern = None
|
|
||||||
|
|
||||||
if package not in rdb:
|
|
||||||
rdb[package] = [pattern]
|
|
||||||
order.append(package)
|
|
||||||
elif pattern not in rdb[package]:
|
|
||||||
rdb[package].append(pattern)
|
|
||||||
|
|
||||||
for package in order:
|
|
||||||
pattern_list = rdb[package]
|
|
||||||
logger.debug("{0}\t{1}".format(package, pattern_list))
|
|
||||||
self.yum.remove(package, pattern_list)
|
|
||||||
|
|
||||||
def cleanup_python_files(self):
|
|
||||||
for root, _, fnames in os.walk(self.root):
|
|
||||||
for fname in fnames:
|
|
||||||
if fname.endswith(".py"):
|
|
||||||
path = joinpaths(root, fname, follow_symlinks=False)
|
|
||||||
pyo, pyc = path + "o", path + "c"
|
|
||||||
if os.path.isfile(pyo):
|
|
||||||
os.unlink(pyo)
|
|
||||||
if os.path.isfile(pyc):
|
|
||||||
os.unlink(pyc)
|
|
||||||
|
|
||||||
os.symlink("/dev/null", pyc)
|
|
||||||
|
|
||||||
def move_modules(self):
|
|
||||||
shutil.move(joinpaths(self.root, "lib/modules"),
|
|
||||||
joinpaths(self.root, "modules"))
|
|
||||||
shutil.move(joinpaths(self.root, "lib/firmware"),
|
|
||||||
joinpaths(self.root, "firmware"))
|
|
||||||
|
|
||||||
os.symlink("../modules", joinpaths(self.root, "lib/modules"))
|
|
||||||
os.symlink("../firmware", joinpaths(self.root, "lib/firmware"))
|
|
||||||
|
|
||||||
def cleanup_kernel_modules(self, keepmodules, kernelver):
|
|
||||||
logger.info("cleaning up kernel modules for %s", kernelver)
|
|
||||||
moddir = joinpaths(self.root, "modules", kernelver)
|
|
||||||
fwdir = joinpaths(self.root, "firmware")
|
|
||||||
|
|
||||||
# expand required modules
|
|
||||||
modules = set()
|
|
||||||
pattern = re.compile(r"\.ko$")
|
|
||||||
|
|
||||||
for name in keepmodules:
|
|
||||||
if name.startswith("="):
|
|
||||||
group = name[1:]
|
|
||||||
if group in ("scsi", "ata"):
|
|
||||||
mpath = joinpaths(moddir, "modules.block")
|
|
||||||
elif group == "net":
|
|
||||||
mpath = joinpaths(moddir, "modules.networking")
|
|
||||||
else:
|
|
||||||
mpath = joinpaths(moddir, "modules.{0}".format(group))
|
|
||||||
|
|
||||||
if os.path.isfile(mpath):
|
|
||||||
with open(mpath, "r") as fobj:
|
|
||||||
for line in fobj:
|
|
||||||
module = pattern.sub("", line.strip())
|
|
||||||
modules.add(module)
|
|
||||||
else:
|
|
||||||
modules.add(name)
|
|
||||||
|
|
||||||
# resolve modules dependencies
|
|
||||||
moddep = joinpaths(moddir, "modules.dep")
|
|
||||||
with open(moddep, "r") as fobj:
|
|
||||||
lines = map(lambda line: line.strip(), fobj.readlines())
|
|
||||||
|
|
||||||
modpattern = re.compile(r"^.*/(?P<name>.*)\.ko:(?P<deps>.*)$")
|
|
||||||
deppattern = re.compile(r"^.*/(?P<name>.*)\.ko$")
|
|
||||||
unresolved = True
|
|
||||||
|
|
||||||
while unresolved:
|
|
||||||
unresolved = False
|
|
||||||
for line in lines:
|
|
||||||
match = modpattern.match(line)
|
|
||||||
modname = match.group("name")
|
|
||||||
if modname in modules:
|
|
||||||
# add the dependencies
|
|
||||||
for dep in match.group("deps").split():
|
|
||||||
match = deppattern.match(dep)
|
|
||||||
depname = match.group("name")
|
|
||||||
if depname not in modules:
|
|
||||||
unresolved = True
|
|
||||||
modules.add(depname)
|
|
||||||
|
|
||||||
# required firmware
|
|
||||||
firmware = set()
|
|
||||||
firmware.add("atmel_at76c504c-wpa.bin")
|
|
||||||
firmware.add("iwlwifi-3945-1.ucode")
|
|
||||||
firmware.add("iwlwifi-3945.ucode")
|
|
||||||
firmware.add("zd1211/zd1211_uph")
|
|
||||||
firmware.add("zd1211/zd1211_uphm")
|
|
||||||
firmware.add("zd1211/zd1211b_uph")
|
|
||||||
firmware.add("zd1211/zd1211b_uphm")
|
|
||||||
|
|
||||||
# remove not needed modules
|
|
||||||
for root, _, fnames in os.walk(moddir):
|
|
||||||
for fname in fnames:
|
|
||||||
path = os.path.join(root, fname)
|
|
||||||
name, ext = os.path.splitext(fname)
|
|
||||||
|
|
||||||
if ext == ".ko":
|
|
||||||
if name not in modules:
|
|
||||||
os.unlink(path)
|
|
||||||
logger.debug("removed module {0}".format(path))
|
|
||||||
else:
|
|
||||||
# get the required firmware
|
|
||||||
cmd = [self.lcmds.MODINFO, "-F", "firmware", path]
|
|
||||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
|
||||||
output = proc.stdout.read()
|
|
||||||
firmware |= set(output.split())
|
|
||||||
|
|
||||||
# remove not needed firmware
|
|
||||||
firmware = map(lambda fw: joinpaths(fwdir, fw), list(firmware))
|
|
||||||
for root, _, fnames in os.walk(fwdir):
|
|
||||||
for fname in fnames:
|
|
||||||
path = joinpaths(root, fname)
|
|
||||||
if path not in firmware:
|
|
||||||
os.unlink(path)
|
|
||||||
logger.debug("removed firmware {0}".format(path))
|
|
||||||
|
|
||||||
# get the modules paths
|
|
||||||
modpaths = {}
|
|
||||||
for root, _, fnames in os.walk(moddir):
|
|
||||||
for fname in fnames:
|
|
||||||
modpaths[fname] = joinpaths(root, fname)
|
|
||||||
|
|
||||||
# create the modules list
|
|
||||||
modlist = {}
|
|
||||||
for modtype, fname in (("scsi", "modules.block"),
|
|
||||||
("eth", "modules.networking")):
|
|
||||||
|
|
||||||
fname = joinpaths(moddir, fname)
|
|
||||||
with open(fname, "r") as fobj:
|
|
||||||
lines = map(lambda l: l.strip(), fobj.readlines())
|
|
||||||
lines = filter(lambda l: l, lines)
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
modname, ext = os.path.splitext(line)
|
|
||||||
if (line not in modpaths or
|
|
||||||
modname in ("floppy", "libiscsi", "scsi_mod")):
|
|
||||||
continue
|
|
||||||
|
|
||||||
cmd = [self.lcmds.MODINFO, "-F", "description", modpaths[line]]
|
|
||||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
|
||||||
output = proc.stdout.read()
|
|
||||||
|
|
||||||
try:
|
|
||||||
desc = output.splitlines()[0]
|
|
||||||
desc = desc.strip()[:65]
|
|
||||||
except IndexError:
|
|
||||||
desc = "{0} driver".format(modname)
|
|
||||||
|
|
||||||
info = '{0}\n\t{1}\n\t"{2}"\n'
|
|
||||||
info = info.format(modname, modtype, desc)
|
|
||||||
modlist[modname] = info
|
|
||||||
|
|
||||||
# write the module-info
|
|
||||||
moduleinfo = joinpaths(os.path.dirname(moddir), "module-info")
|
|
||||||
with open(moduleinfo, "w") as fobj:
|
|
||||||
fobj.write("Version 0\n")
|
|
||||||
for modname in sorted(modlist.keys()):
|
|
||||||
fobj.write(modlist[modname])
|
|
||||||
|
|
||||||
def compress_modules(self, kernelver):
|
|
||||||
logger.debug("compressing modules for %s", kernelver)
|
|
||||||
moddir = joinpaths(self.root, "modules", kernelver)
|
|
||||||
|
|
||||||
for root, _, fnames in os.walk(moddir):
|
|
||||||
for fname in filter(lambda f: f.endswith(".ko"), fnames):
|
|
||||||
path = os.path.join(root, fname)
|
|
||||||
with open(path, "rb") as fobj:
|
|
||||||
data = fobj.read()
|
|
||||||
|
|
||||||
gzipped = gzip.open("{0}.gz".format(path), "wb")
|
|
||||||
gzipped.write(data)
|
|
||||||
gzipped.close()
|
|
||||||
|
|
||||||
os.unlink(path)
|
|
||||||
|
|
||||||
def run_depmod(self, kernelver):
|
|
||||||
logger.debug("running depmod for %s", kernelver)
|
|
||||||
systemmap = "System.map-{0}".format(kernelver)
|
|
||||||
systemmap = joinpaths(self.root, "boot", systemmap)
|
|
||||||
|
|
||||||
cmd = [self.lcmds.DEPMOD, "-a", "-F", systemmap, "-b", self.root,
|
|
||||||
kernelver]
|
|
||||||
|
|
||||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
|
||||||
retcode = proc.wait()
|
|
||||||
if not retcode == 0:
|
|
||||||
logger.critical(proc.stdout.read())
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
moddir = joinpaths(self.root, "modules", kernelver)
|
|
||||||
|
|
||||||
# remove *map files
|
|
||||||
mapfiles = joinpaths(moddir, "*map")
|
|
||||||
for fpath in glob.glob(mapfiles):
|
|
||||||
os.unlink(fpath)
|
|
||||||
|
|
||||||
# remove build and source symlinks
|
|
||||||
for fname in ["build", "source"]:
|
|
||||||
os.unlink(joinpaths(moddir, fname))
|
|
||||||
|
|
||||||
def move_repos(self):
|
|
||||||
src = joinpaths(self.root, "etc/yum.repos.d")
|
|
||||||
dst = joinpaths(self.root, "etc/anaconda.repos.d")
|
|
||||||
shutil.move(src, dst)
|
|
||||||
|
|
||||||
def create_depmod_conf(self):
|
|
||||||
text = "search updates built-in\n"
|
|
||||||
|
|
||||||
with open(joinpaths(self.root, "etc/depmod.d/dd.conf"), "w") as fobj:
|
|
||||||
fobj.write(text)
|
|
||||||
|
|
||||||
def setup_s390_init(self):
|
|
||||||
# copy shutdown
|
|
||||||
src = joinpaths(self.root, "usr", self.libdir, "anaconda/shutdown")
|
|
||||||
dst = joinpaths(self.root, "sbin", "shutdown")
|
|
||||||
os.unlink(dst)
|
|
||||||
shutil.copy2(src, dst)
|
|
||||||
|
|
||||||
# copy linuxrc.s390
|
|
||||||
src = joinpaths(self.root, "usr/share/anaconda/linuxrc.s390")
|
|
||||||
dst = joinpaths(self.root, "sbin", "init")
|
|
||||||
os.unlink(dst)
|
|
||||||
shutil.copy2(src, dst)
|
|
||||||
|
|
||||||
def setup_init(self):
|
|
||||||
# replace init with anaconda init
|
|
||||||
src = joinpaths(self.root, "usr", self.libdir, "anaconda", "init")
|
|
||||||
dst = joinpaths(self.root, "sbin", "init")
|
|
||||||
os.unlink(dst)
|
|
||||||
shutil.copy2(src, dst)
|
|
||||||
|
|
||||||
# init symlinks
|
|
||||||
target = "/sbin/init"
|
|
||||||
name = joinpaths(self.root, "init")
|
|
||||||
os.symlink(target, name)
|
|
||||||
|
|
||||||
for fname in ["halt", "poweroff", "reboot"]:
|
|
||||||
name = joinpaths(self.root, "sbin", fname)
|
|
||||||
os.unlink(name)
|
|
||||||
os.symlink("init", name)
|
|
||||||
|
|
||||||
for fname in ["runlevel", "shutdown", "telinit"]:
|
|
||||||
name = joinpaths(self.root, "sbin", fname)
|
|
||||||
os.unlink(name)
|
|
||||||
|
|
||||||
def misc_tree_modifications(self):
|
|
||||||
# create resolv.conf
|
|
||||||
touch(joinpaths(self.root, "etc", "resolv.conf"))
|
|
||||||
|
|
||||||
# create a basic /bin/login script that'll automatically start up
|
|
||||||
# bash as a login shell. This is needed because we can't pass bash
|
|
||||||
# arguments from the agetty command line, and there's not really a
|
|
||||||
# better way to autologin root.
|
|
||||||
with open(joinpaths(self.root, "bin/login"), "w") as fobj:
|
|
||||||
fobj.write("#!/bin/bash\n")
|
|
||||||
fobj.write("exec -l /bin/bash\n")
|
|
||||||
|
|
||||||
def get_config_files(self, src_dir):
|
|
||||||
# anaconda needs to change a couple of the default gconf entries
|
|
||||||
gconf = joinpaths(self.root, "etc", "gconf", "gconf.xml.defaults")
|
|
||||||
|
|
||||||
# 0 - path, 1 - entry type, 2 - value
|
|
||||||
gconf_settings = \
|
|
||||||
[("/apps/metacity/general/button_layout", "string", ":"),
|
|
||||||
("/apps/metacity/general/action_right_click_titlebar",
|
|
||||||
"string", "none"),
|
|
||||||
("/apps/metacity/general/num_workspaces", "int", "1"),
|
|
||||||
("/apps/metacity/window_keybindings/close", "string", "disabled"),
|
|
||||||
("/apps/metacity/global_keybindings/run_command_window_screenshot",
|
|
||||||
"string", "disabled"),
|
|
||||||
("/apps/metacity/global_keybindings/run_command_screenshot",
|
|
||||||
"string", "disabled"),
|
|
||||||
("/apps/metacity/global_keybindings/switch_to_workspace_down",
|
|
||||||
"string", "disabled"),
|
|
||||||
("/apps/metacity/global_keybindings/switch_to_workspace_left",
|
|
||||||
"string", "disabled"),
|
|
||||||
("/apps/metacity/global_keybindings/switch_to_workspace_right",
|
|
||||||
"string", "disabled"),
|
|
||||||
("/apps/metacity/global_keybindings/switch_to_workspace_up",
|
|
||||||
"string", "disabled"),
|
|
||||||
("/desktop/gnome/interface/accessibility", "bool", "true"),
|
|
||||||
("/desktop/gnome/interface/at-spi-corba", "bool", "true")]
|
|
||||||
|
|
||||||
for path, entry_type, value in gconf_settings:
|
|
||||||
cmd = [self.lcmds.GCONFTOOL, "--direct",
|
|
||||||
"--config-source=xml:readwrite:{0}".format(gconf),
|
|
||||||
"-s", "-t", entry_type, path, value]
|
|
||||||
|
|
||||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
|
||||||
proc.wait()
|
|
||||||
|
|
||||||
# get rsyslog config
|
|
||||||
src = joinpaths(src_dir, "rsyslog.conf")
|
|
||||||
dst = joinpaths(self.root, "etc")
|
|
||||||
shutil.copy2(src, dst)
|
|
||||||
|
|
||||||
# get .bash_history
|
|
||||||
src = joinpaths(src_dir, ".bash_history")
|
|
||||||
dst = joinpaths(self.root, "root")
|
|
||||||
shutil.copy2(src, dst)
|
|
||||||
|
|
||||||
# get .profile
|
|
||||||
src = joinpaths(src_dir, ".profile")
|
|
||||||
dst = joinpaths(self.root, "root")
|
|
||||||
shutil.copy2(src, dst)
|
|
||||||
|
|
||||||
# get libuser.conf
|
|
||||||
src = joinpaths(src_dir, "libuser.conf")
|
|
||||||
dst = joinpaths(self.root, "etc")
|
|
||||||
shutil.copy2(src, dst)
|
|
||||||
|
|
||||||
# get selinux config
|
|
||||||
if os.path.exists(joinpaths(self.root, "etc/selinux/targeted")):
|
|
||||||
src = joinpaths(src_dir, "selinux.config")
|
|
||||||
dst = joinpaths(self.root, "etc/selinux", "config")
|
|
||||||
shutil.copy2(src, dst)
|
|
||||||
|
|
||||||
def setup_sshd(self, src_dir):
|
|
||||||
# get sshd config
|
|
||||||
src = joinpaths(src_dir, "sshd_config.anaconda")
|
|
||||||
dst = joinpaths(self.root, "etc", "ssh")
|
|
||||||
shutil.copy2(src, dst)
|
|
||||||
|
|
||||||
src = joinpaths(src_dir, "pam.sshd")
|
|
||||||
dst = joinpaths(self.root, "etc", "pam.d", "sshd")
|
|
||||||
shutil.copy2(src, dst)
|
|
||||||
|
|
||||||
dst = joinpaths(self.root, "etc", "pam.d", "login")
|
|
||||||
shutil.copy2(src, dst)
|
|
||||||
|
|
||||||
dst = joinpaths(self.root, "etc", "pam.d", "remote")
|
|
||||||
shutil.copy2(src, dst)
|
|
||||||
|
|
||||||
# enable root shell logins and
|
|
||||||
# 'install' account that starts anaconda on login
|
|
||||||
passwd = joinpaths(self.root, "etc", "passwd")
|
|
||||||
with open(passwd, "a") as fobj:
|
|
||||||
fobj.write("sshd:x:74:74:Privilege-separated "
|
|
||||||
"SSH:/var/empty/sshd:/sbin/nologin\n")
|
|
||||||
fobj.write("install:x:0:0:root:/root:/sbin/loader\n")
|
|
||||||
|
|
||||||
shadow = joinpaths(self.root, "etc", "shadow")
|
|
||||||
with open(shadow, "w") as fobj:
|
|
||||||
fobj.write("root::14438:0:99999:7:::\n")
|
|
||||||
fobj.write("install::14438:0:99999:7:::\n")
|
|
||||||
|
|
||||||
# change permissions
|
|
||||||
chmod_(shadow, 400)
|
|
||||||
|
|
||||||
def generate_ssh_keys(self):
|
|
||||||
logger.info("generating SSH1 RSA host key")
|
|
||||||
rsa1 = joinpaths(self.root, "etc/ssh/ssh_host_key")
|
|
||||||
cmd = [self.lcmds.SSHKEYGEN, "-q", "-t", "rsa1", "-f", rsa1,
|
|
||||||
"-C", "", "-N", ""]
|
|
||||||
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
|
||||||
p.wait()
|
|
||||||
|
|
||||||
logger.info("generating SSH2 RSA host key")
|
|
||||||
rsa2 = joinpaths(self.root, "etc/ssh/ssh_host_rsa_key")
|
|
||||||
cmd = [self.lcmds.SSHKEYGEN, "-q", "-t", "rsa", "-f", rsa2,
|
|
||||||
"-C", "", "-N", ""]
|
|
||||||
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
|
||||||
p.wait()
|
|
||||||
|
|
||||||
logger.info("generating SSH2 DSA host key")
|
|
||||||
dsa = joinpaths(self.root, "etc/ssh/ssh_host_dsa_key")
|
|
||||||
cmd = [self.lcmds.SSHKEYGEN, "-q", "-t", "dsa", "-f", dsa,
|
|
||||||
"-C", "", "-N", ""]
|
|
||||||
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
|
||||||
p.wait()
|
|
||||||
|
|
||||||
# change key file permissions
|
|
||||||
for key in [rsa1, rsa2, dsa]:
|
|
||||||
chmod_(key, 0600)
|
|
||||||
chmod_(key + ".pub", 0644)
|
|
||||||
|
|
||||||
|
|
||||||
def get_anaconda_portions(self):
|
|
||||||
src = joinpaths(self.root, "usr", self.libdir, "anaconda", "loader")
|
|
||||||
dst = joinpaths(self.root, "sbin")
|
|
||||||
shutil.copy2(src, dst)
|
|
||||||
|
|
||||||
src = joinpaths(self.root, "usr/share/anaconda", "loader.tr")
|
|
||||||
dst = joinpaths(self.root, "etc")
|
|
||||||
shutil.move(src, dst)
|
|
||||||
|
|
||||||
src = joinpaths(self.root, "usr/libexec/anaconda", "auditd")
|
|
||||||
dst = joinpaths(self.root, "sbin")
|
|
||||||
shutil.copy2(src, dst)
|
|
||||||
|
|
||||||
def compress(self, outfile, type="xz", speed="9"):
|
|
||||||
chdir = lambda: os.chdir(self.root)
|
|
||||||
start = time.time()
|
|
||||||
|
|
||||||
find = subprocess.Popen([self.lcmds.FIND, "."], stdout=subprocess.PIPE,
|
|
||||||
preexec_fn=chdir)
|
|
||||||
|
|
||||||
cpio = subprocess.Popen([self.lcmds.CPIO,
|
|
||||||
"--quiet", "-H", "newc", "-o"],
|
|
||||||
stdin=find.stdout, stdout=subprocess.PIPE,
|
|
||||||
preexec_fn=chdir)
|
|
||||||
|
|
||||||
compressed = subprocess.Popen([type, "-%s" % speed], stdin=cpio.stdout,
|
|
||||||
stdout=open(outfile, "wb"))
|
|
||||||
|
|
||||||
logger.debug("compressing")
|
|
||||||
rc = compressed.wait()
|
|
||||||
|
|
||||||
elapsed = time.time() - start
|
|
||||||
|
|
||||||
return True, elapsed
|
|
||||||
|
|
||||||
def install_kernel_modules(self, keepmodules):
|
|
||||||
self.move_modules()
|
|
||||||
for kernel in os.listdir(joinpaths(self.root, "modules")):
|
|
||||||
self.cleanup_kernel_modules(keepmodules, kernel)
|
|
||||||
self.compress_modules(kernel)
|
|
||||||
self.run_depmod(kernel)
|
|
@ -21,153 +21,12 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger("pylorax.yumhelper")
|
logger = logging.getLogger("pylorax.yumhelper")
|
||||||
|
import sys, os, re
|
||||||
import sys
|
import yum, yum.callbacks, yum.rpmtrans
|
||||||
import os
|
|
||||||
import fnmatch
|
|
||||||
import glob
|
|
||||||
import shutil
|
|
||||||
import re
|
|
||||||
|
|
||||||
import yum
|
|
||||||
import yum.callbacks
|
|
||||||
import yum.rpmtrans
|
|
||||||
|
|
||||||
import output
|
import output
|
||||||
from sysutils import joinpaths
|
|
||||||
|
|
||||||
|
|
||||||
class LoraxYumHelper(object):
|
|
||||||
|
|
||||||
def __init__(self, ybo):
|
|
||||||
self.ybo = ybo
|
|
||||||
|
|
||||||
# create our own installroot, the pungi one may be poluted
|
|
||||||
installroot = joinpaths(self.ybo.conf.installroot, "installroot")
|
|
||||||
os.makedirs(installroot)
|
|
||||||
self.ybo.conf.installroot = installroot
|
|
||||||
|
|
||||||
self.installroot = self.ybo.conf.installroot
|
|
||||||
self.installed_packages = self.get_packages("installed")
|
|
||||||
|
|
||||||
def install(self, pattern):
|
|
||||||
try:
|
|
||||||
self.ybo.install(name=pattern)
|
|
||||||
except yum.Errors.InstallError:
|
|
||||||
try:
|
|
||||||
self.ybo.install(pattern=pattern)
|
|
||||||
except yum.Errors.InstallError as e:
|
|
||||||
msg = "cannot install {0}: {1}"
|
|
||||||
logger.error(msg.format(pattern, e))
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def remove(self, package, pattern_list):
|
|
||||||
if package:
|
|
||||||
pkgobj = self.installed_packages.get(package)
|
|
||||||
if not pkgobj:
|
|
||||||
msg = "cannot erase {0}: Package not installed"
|
|
||||||
logger.error(msg.format(package))
|
|
||||||
return False
|
|
||||||
|
|
||||||
# match every file if no pattern specified
|
|
||||||
if None in pattern_list:
|
|
||||||
if len(pattern_list) > 1:
|
|
||||||
msg = "redundant patterns specified, " \
|
|
||||||
"removing whole package {0}"
|
|
||||||
logger.warning(msg.format(pkgobj.name))
|
|
||||||
|
|
||||||
pattern_list = ["*"]
|
|
||||||
|
|
||||||
logger.debug("erasing package {0}".format(pkgobj.name))
|
|
||||||
|
|
||||||
total = len(pkgobj.filelist)
|
|
||||||
newline = False
|
|
||||||
count = 0
|
|
||||||
for n, fname in enumerate(pkgobj.filelist, start=1):
|
|
||||||
msg = "[{0:3.0f}%] erasing <b>{1.ui_envra}</b>\r"
|
|
||||||
msg = msg.format(float(n) / float(total) * 100, pkgobj)
|
|
||||||
output.LoraxOutput().write(msg)
|
|
||||||
newline = True
|
|
||||||
|
|
||||||
for pattern in pattern_list:
|
|
||||||
if fnmatch.fnmatch(fname, pattern):
|
|
||||||
fullpath = joinpaths(self.installroot, fname)
|
|
||||||
if (os.path.islink(fullpath) or
|
|
||||||
os.path.isfile(fullpath)):
|
|
||||||
|
|
||||||
os.unlink(fullpath)
|
|
||||||
logger.debug("removed {0}".format(fullpath))
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
if newline:
|
|
||||||
output.LoraxOutput().write("\n")
|
|
||||||
|
|
||||||
if not count:
|
|
||||||
msg = "no files matched patterns {0}"
|
|
||||||
logger.warning(msg.format(pattern_list))
|
|
||||||
|
|
||||||
else:
|
|
||||||
for pattern in pattern_list:
|
|
||||||
msg = "erasing files matching pattern {0}"
|
|
||||||
logger.info(msg.format(pattern))
|
|
||||||
|
|
||||||
fullpattern = joinpaths(self.installroot, pattern)
|
|
||||||
count = 0
|
|
||||||
for fname in glob.glob(fullpattern):
|
|
||||||
# if there are symlinks, we could already removed the file
|
|
||||||
if not os.path.exists(fname):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if os.path.islink(fname) or os.path.isfile(fname):
|
|
||||||
os.unlink(fname)
|
|
||||||
else:
|
|
||||||
shutil.rmtree(fname)
|
|
||||||
|
|
||||||
logger.debug("removed {0}".format(fname))
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
if not count:
|
|
||||||
msg = "no files matched pattern {0}"
|
|
||||||
logger.error(msg.format(pattern))
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def process_transaction(self, skip_broken=False):
|
|
||||||
# skip broken
|
|
||||||
self.ybo.conf.skip_broken = skip_broken
|
|
||||||
self.ybo.buildTransaction()
|
|
||||||
|
|
||||||
self.ybo.repos.setProgressBar(LoraxDownloadCallback())
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.ybo.processTransaction(callback=LoraxTransactionCallback(),
|
|
||||||
rpmDisplay=LoraxRpmCallback())
|
|
||||||
except yum.Errors.YumRPMCheckError as e:
|
|
||||||
logger.error("yum transaction error: {0}".format(e))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
self.ybo.closeRpmDB()
|
|
||||||
|
|
||||||
self.installed_packages = self.get_packages("installed")
|
|
||||||
|
|
||||||
def search(self, pattern):
|
|
||||||
pl = self.ybo.doPackageLists(patterns=[pattern])
|
|
||||||
return pl.installed, pl.available
|
|
||||||
|
|
||||||
def get_packages(self, ptype="available"):
|
|
||||||
if ptype not in ("available", "installed"):
|
|
||||||
raise TypeError
|
|
||||||
|
|
||||||
pl = self.ybo.doPackageLists(pkgnarrow=ptype)
|
|
||||||
|
|
||||||
d = {}
|
|
||||||
for pkgobj in getattr(pl, ptype):
|
|
||||||
d[pkgobj.name] = pkgobj
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
__all__ = ['LoraxDownloadCallback', 'LoraxTransactionCallback',
|
||||||
|
'LoraxRpmCallback']
|
||||||
|
|
||||||
class LoraxDownloadCallback(yum.callbacks.DownloadBaseCallback):
|
class LoraxDownloadCallback(yum.callbacks.DownloadBaseCallback):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user