Wrote a templating system for initrd creation.

A lot of stuff got changed and rewritten. Using a different approach now, so no point of tracking changes to the older commits.
This commit is contained in:
Martin Gracik 2009-06-04 15:36:56 +02:00
parent d3fd188841
commit b0b696d66d
17 changed files with 1265 additions and 446 deletions

9
etc/templates/initrd Normal file
View File

@ -0,0 +1,9 @@
# initrd template file
#
# supported variables: @initrd@
# @instroot@
:copy
# comment
@instroot@/bin/bash to @initrd@/bin
@instroot@/bin/mount to @initrd@/bin

View File

@ -1,26 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
#
# lorax # lorax
# Install image and tree support data generation tool.
#
# Copyright (C) 2008, 2009 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/>.
#
# Author(s): David Cantrell <dcantrell@redhat.com>
# Martin Gracik <mgracik@redhat.com>
#
import sys import sys
import os import os
@ -30,7 +9,7 @@ import pylorax
if __name__ == '__main__': if __name__ == '__main__':
version = '%s 0.1' % (os.path.basename(sys.argv[0]),) version = '%s 0.1' % os.path.basename(sys.argv[0])
usage = '%prog -p PRODUCT -v VERSION -r RELEASE -o OUTPUT REPOSITORY' usage = '%prog -p PRODUCT -v VERSION -r RELEASE -o OUTPUT REPOSITORY'
parser = OptionParser(usage=usage) parser = OptionParser(usage=usage)
@ -39,7 +18,8 @@ if __name__ == '__main__':
if os.path.isdir(value): if os.path.isdir(value):
setattr(parser.values, option.dest, value) setattr(parser.values, option.dest, value)
else: else:
parser.error('%s is not a directory' % (value,)) parser.error('%s is not a directory.' % value)
# XXX "options" should not be required # XXX "options" should not be required
# required # required
@ -65,7 +45,8 @@ if __name__ == '__main__':
metavar='URL', default='your distribution provided bug reporting tool.') metavar='URL', default='your distribution provided bug reporting tool.')
group.add_option('-u', '--updates', help='Path to find updated RPMS.', group.add_option('-u', '--updates', help='Path to find updated RPMS.',
metavar='PATHSPEC') metavar='PATHSPEC')
group.add_option('-m', '--mirrorlist', help='Mirror list repository, may be listed multiple times.', group.add_option('-m', '--mirrorlist',
help='Mirror list repository, may be listed multiple times.',
metavar='REPOSITORY', action='append', default=[]) metavar='REPOSITORY', action='append', default=[])
group.add_option('-c', '--confdir', help='Path to config files (default: /etc/lorax).', group.add_option('-c', '--confdir', help='Path to config files (default: /etc/lorax).',
metavar='PATHSPEC', action='callback', callback=check_dir, metavar='PATHSPEC', action='callback', callback=check_dir,
@ -89,10 +70,27 @@ if __name__ == '__main__':
sys.exit(0) sys.exit(0)
if not opts.product or not opts.version or not opts.release or not opts.output: if not opts.product or not opts.version or not opts.release or not opts.output:
parser.error('Missing a required argument.') parser.error('Missing required argument.')
if not args: if not args:
parser.error('Missing repository to use for image generation.') parser.error('Missing repository to use for image generation.')
lorax = pylorax.Lorax(repos=args, options=opts) config = pylorax.Config()
config.set(confdir=opts.confdir,
debug=opts.debug,
cleanup=opts.cleanup)
# required
config.set(product=opts.product,
version=opts.version,
release=opts.release,
outdir=opts.output,
repos=args)
# optional
config.set(variant=opts.variant,
bugurl=opts.bugurl,
updates=opts.updates,
mirrorlist=opts.mirrorlist)
lorax = pylorax.Lorax(config=config)
lorax.run() lorax.run()

View File

@ -1,25 +1,4 @@
# # __init__.py
# pylorax
# Install image and tree support data generation tool -- Python module.
#
# Copyright (C) 2008, 2009 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/>.
#
# Author(s): David Cantrell <dcantrell@redhat.com>
# Martin Gracik <mgracik@redhat.com>
#
import sys import sys
import os import os
@ -27,134 +6,146 @@ import shutil
import tempfile import tempfile
import time import time
import ConfigParser import ConfigParser
import re
import yum from config import Container
import rpmUtils import utils.rpmutil as rpmutil
import instroot
import images import images
from exceptions import LoraxError
class Lorax:
def __init__(self, repos, options):
self.repos = repos
# required class Config(Container):
self.product = options.product def __init__(self):
self.version = options.version config = ('confdir', 'datadir', 'tempdir', 'debug', 'cleanup')
self.release = options.release
self.output = options.output
# optional # options
self.debug = options.debug required = ('product', 'version', 'release', 'outdir', 'repos')
self.variant = options.variant optional = ('variant', 'bugurl', 'updates', 'mirrorlist')
self.bugurl = options.bugurl
self.updates = options.updates
self.mirrorlist = options.mirrorlist
self.confdir = options.confdir
self.cleanup = options.cleanup
self.conf = {} Container.__init__(self, config + required + optional)
if not self.confdir:
self.confdir = '/etc/lorax' # set defaults
self.conf['confdir'] = self.confdir self.set(confdir='/etc/lorax',
self.conf['datadir'] = '/usr/share/lorax' datadir='/usr/share/lorax',
self.conf['tmpdir'] = tempfile.gettempdir() tempdir=tempfile.mkdtemp(prefix='lorax.tmp.', dir=tempfile.gettempdir()),
debug=False,
cleanup=False)
self.set(product='',
version='',
release='',
outdir='',
repos=[])
self.set(variant='',
bugurl='',
updates='',
mirrorlist=[])
class Lorax(object):
def __init__(self, config):
assert isinstance(config, Config) == True
self.conf = config
# check if we have all required options
if not self.conf.repos:
raise LoraxError, 'missing repos'
if not self.conf.outdir:
raise LoraxError, 'missing outdir'
if not self.conf.product:
raise LoraxError, 'missing product'
if not self.conf.version:
raise LoraxError, 'missing version'
if not self.conf.release:
raise LoraxError, 'missing release'
self.yum = None
def run(self): def run(self):
"""run()
Generate install images.
"""
print('Collecting repos...') print('Collecting repos...')
self.repo, self.extrarepos = self.__collectRepos() self.collectRepos()
if not self.repo: # check if we have at least one valid repository
sys.stderr.write('No valid repository.\n') if not self.conf.repo:
sys.stderr.write('ERROR: no valid repository\n')
sys.exit(1) sys.exit(1)
print('Initializing directories...') print('Initializing directories...')
self.buildinstdir, self.treedir, self.cachedir = self.__initializeDirs() self.initDirs()
print('Writing yum configuration...') print('Initializing yum...')
self.yumconf = self.__writeYumConf() self.initYum()
print('Getting the build architecture...') print('Setting build architecture...')
self.buildarch = self.__getBuildArch() self.setBuildArch()
print('Creating install root tree...')
self.makeInstRoot()
print('Writing .treeinfo...') print('Writing .treeinfo...')
self.__writeTreeInfo() self.writeTreeInfo()
print('Writing .discinfo...') print('Writing .discinfo...')
self.__writeDiscInfo() self.writeDiscInfo()
print('Creating images...') print('Preparing the install tree...')
self.prepareInstRoot()
print('Creating the images...')
self.makeImages() self.makeImages()
if self.cleanup: if self.conf.cleanup:
print('Cleaning up...') print('Cleaning up...')
self.cleanUp() self.cleanUp()
def __collectRepos(self): def collectRepos(self):
"""_collectRepos()
Get the main repo (the first one) and then build a list of all remaining
repos in the list. Sanitize each repo URL for proper yum syntax.
"""
repolist = [] repolist = []
for repospec in self.repos: for repospec in self.conf.repos:
if repospec.startswith('/'): if repospec.startswith('/'):
repo = 'file://%s' % (repospec,) repo = 'file://%s' % repospec
print('Adding local repo:\n %s' % (repo,)) print('Adding local repo: %s' % repo)
repolist.append(repo) repolist.append(repo)
elif repospec.startswith('http://') or repospec.startswith('ftp://'): elif repospec.startswith('http://') or repospec.startswith('ftp://'):
print('Adding remote repo:\n %s' % (repospec,)) print('Adding remote repo: %s' % repospec)
repolist.append(repospec) repolist.append(repospec)
if not repolist: if not repolist:
return None, [] repo, extrarepos = None, []
else: else:
return repolist[0], repolist[1:] repo, extrarepos = repolist[0], repolist[1:]
def __initializeDirs(self): self.conf.addAttr(['repo', 'extrarepos'])
"""_initializeDirs() self.conf.set(repo=repo, extrarepos=extrarepos)
Create directories used for image generation. # remove repos attribute, to get a traceback, if we use it later
""" self.conf.delAttr('repos')
if not os.path.isdir(self.output): def initDirs(self):
os.makedirs(self.output, mode=0755) if not os.path.isdir(self.conf.outdir):
os.makedirs(self.conf.outdir, mode=0755)
self.conf['tmpdir'] = tempfile.mkdtemp('XXXXXX', 'lorax.tmp.', self.conf['tmpdir']) treedir = os.path.join(self.conf.tempdir, 'treedir', 'install')
buildinstdir = tempfile.mkdtemp('XXXXXX', 'buildinstall.tree.', self.conf['tmpdir']) cachedir = os.path.join(self.conf.tempdir, 'yumcache')
treedir = tempfile.mkdtemp('XXXXXX', 'treedir.', self.conf['tmpdir'])
cachedir = tempfile.mkdtemp('XXXXXX', 'yumcache.', self.conf['tmpdir'])
print('Working directories:') print('Working directories:')
print(' tmpdir = %s' % (self.conf['tmpdir'],)) print(' tempdir = %s' % self.conf.tempdir)
print(' buildinstdir = %s' % (buildinstdir,)) print(' treedir = %s' % treedir)
print(' treedir = %s' % (treedir,)) print(' cachedir = %s' % cachedir)
print(' cachedir = %s' % (cachedir,))
return buildinstdir, treedir, cachedir self.conf.addAttr(['treedir', 'cachedir'])
self.conf.set(treedir=treedir, cachedir=cachedir)
def __writeYumConf(self): def initYum(self):
"""_writeYumConf() yumconf = os.path.join(self.conf.tempdir, 'yum.conf')
Generate a temporary yum.conf file for image generation. Returns the path
to the temporary yum.conf file on success, None of failure.
"""
(fd, yumconf) = tempfile.mkstemp(prefix='yum.conf', dir=self.conf['tmpdir'])
f = os.fdopen(fd, 'w')
try:
f = open(yumconf, 'w')
except IOError:
sys.stderr.write('ERROR: Unable to write yum.conf file\n')
sys.exit(1)
else:
f.write('[main]\n') f.write('[main]\n')
f.write('cachedir=%s\n' % (self.cachedir,)) f.write('cachedir=%s\n' % self.conf.cachedir)
f.write('keepcache=0\n') f.write('keepcache=0\n')
f.write('gpgcheck=0\n') f.write('gpgcheck=0\n')
f.write('plugins=0\n') f.write('plugins=0\n')
@ -163,73 +154,63 @@ class Lorax:
f.write('[loraxrepo]\n') f.write('[loraxrepo]\n')
f.write('name=lorax repo\n') f.write('name=lorax repo\n')
f.write('baseurl=%s\n' % (self.repo,)) f.write('baseurl=%s\n' % self.conf.repo)
f.write('enabled=1\n\n') f.write('enabled=1\n\n')
for n, extra in enumerate(self.extrarepos, start=1): for n, extra in enumerate(self.conf.extrarepos, start=1):
f.write('[lorax-extrarepo-%d]\n' % (n,)) f.write('[lorax-extrarepo-%d]\n' % n)
f.write('name=lorax extra repo %d\n' % (n,)) f.write('name=lorax extra repo %d\n' % n)
f.write('baseurl=%s\n' % (extra,)) f.write('baseurl=%s\n' % extra)
f.write('enabled=1\n') f.write('enabled=1\n')
for n, mirror in enumerate(self.mirrorlist, start=1): for n, mirror in enumerate(self.conf.mirrorlist, start=1):
f.write('[lorax-mirrorlistrepo-%d]\n' % (n,)) f.write('[lorax-mirrorlistrepo-%d]\n' % n)
f.write('name=lorax mirrorlist repo %d\n' % (n,)) f.write('name=lorax mirrorlist repo %d\n' % n)
f.write('mirrorlist=%s\n' % (mirror,)) f.write('mirrorlist=%s\n' % mirror)
f.write('enabled=1\n') f.write('enabled=1\n')
f.close() f.close()
print('Wrote lorax yum configuration to %s' % (yumconf,))
return yumconf self.conf.addAttr('yumconf')
self.conf.set(yumconf=yumconf)
def __getBuildArch(self): self.yum = rpmutil.Yum(yumconf=self.conf.yumconf, installroot=self.conf.treedir)
"""_getBuildArch()
Query the configured yum repositories to determine our build architecture, # remove not needed options
which is the architecture of the anaconda package in the repositories. self.conf.delAttr(['repo', 'extrarepos', 'mirrorlist'])
This function is based on a subset of what repoquery(1) does. def setBuildArch(self):
""" unamearch = os.uname()[4]
uname_arch = os.uname()[4] self.conf.addAttr('buildarch')
self.conf.set(buildarch=unamearch)
if not self.yumconf or not os.path.isfile(self.yumconf):
sys.stderr.write('ERROR: yum.conf does not exist, defaulting to %s\n' % (uname_arch,))
return uname_arch
repoq = yum.YumBase()
repoq.doConfigSetup(self.yumconf)
anaconda = self.yum.find('anaconda')
try: try:
repoq.doRepoSetup() self.conf.set(buildarch=anaconda[0].arch)
except yum.Errors.RepoError: except:
sys.stderr.write('ERROR: cannot query yum repo, defaulting to %s\n' % (uname_arch,)) pass
return uname_arch
repoq.doSackSetup(rpmUtils.arch.getArchList()) # set the libdir
repoq.doTsSetup() self.conf.addAttr('libdir')
self.conf.set(libdir='lib')
# on 64-bit systems, make sure we use lib64 as the lib directory
if self.conf.buildarch.endswith('64') or self.conf.buildarch == 's390x':
self.conf.set(libdir='lib64')
ret_arch = None def writeTreeInfo(self, discnum=1, totaldiscs=1, packagedir=''):
for pkg in repoq.pkgSack.simplePkgList(): outfile = os.path.join(self.conf.outdir, '.treeinfo')
(n, a, e, v, r) = pkg
if n == 'anaconda':
ret_arch = a
break
if not ret_arch: # don't print anything instead of None if variant is not specified
ret_arch = uname_arch variant = ''
if self.conf.variant:
return ret_arch variant = self.conf.variant
def __writeTreeInfo(self, discnum=1, totaldiscs=1, packagedir=''):
outfile = os.path.join(self.output, '.treeinfo')
data = { 'timestamp': time.time(), data = { 'timestamp': time.time(),
'family': self.product, 'family': self.conf.product,
'version': self.version, 'version': self.conf.version,
'arch': self.buildarch, 'arch': self.conf.buildarch,
'variant': self.variant, 'variant': variant,
'discnum': str(discnum), 'discnum': str(discnum),
'totaldiscs': str(totaldiscs), 'totaldiscs': str(totaldiscs),
'packagedir': packagedir } 'packagedir': packagedir }
@ -241,6 +222,17 @@ class Lorax:
for key, value in data.items(): for key, value in data.items():
c.set(section, key, value) c.set(section, key, value)
section = 'images-%s' % self.conf.buildarch
c.add_section(section)
c.set(section, 'kernel', 'images/pxeboot/vmlinuz')
c.set(section, 'initrd', 'images/pxeboot/initrd.img')
# XXX actually create the boot iso somewhere, and set up this attribute
self.conf.addAttr('bootiso')
if self.conf.bootiso:
c.set(section, 'boot.iso', 'images/%s' % self.conf.bootiso)
try: try:
f = open(outfile, 'w') f = open(outfile, 'w')
except IOError: except IOError:
@ -250,47 +242,43 @@ class Lorax:
f.close() f.close()
return True return True
def __writeDiscInfo(self, discnum=0): def writeDiscInfo(self, discnum=0):
outfile = os.path.join(self.output, '.discinfo') outfile = os.path.join(self.conf.outdir, '.discinfo')
try: try:
f = open(outfile, 'w') f = open(outfile, 'w')
except IOError: except IOError:
return False return False
else: else:
f.write('%f\n' % (time.time(),)) f.write('%f\n' % time.time())
f.write('%s\n' % (self.release,)) f.write('%s\n' % self.conf.release)
f.write('%s\n' % (self.buildarch,)) f.write('%s\n' % self.conf.buildarch)
f.write('%d\n' % (discnum,)) f.write('%d\n' % discnum)
f.close() f.close()
return True return True
def makeInstRoot(self): def prepareInstRoot(self):
root = instroot.InstRoot(conf=self.conf, # XXX why do we need this?
yumconf=self.yumconf, os.symlink(os.path.join(os.path.sep, 'tmp'),
arch=self.buildarch, os.path.join(self.conf.treedir, 'var', 'lib', 'xkb'))
treedir=self.treedir,
updates=self.updates)
root.run()
# TODO
def makeImages(self): def makeImages(self):
pass i = images.Images(self.conf, self.yum)
i.run()
# XXX figure out where to put this
#def copyUpdates(self):
# if self.conf.updates and os.path.isdir(self.conf.updates):
# cp(os.path.join(self.conf.updates, '*'), self.conf.treedir)
# self.conf.delAttr('updates')
def cleanUp(self, trash=[]): def cleanUp(self, trash=[]):
"""cleanup([trash])
Given a list of things to remove, cleanUp() will remove them if it can.
Never fails, just tries to remove things and returns regardless of
failures removing things.
"""
for item in trash: for item in trash:
if os.path.isdir(item): if os.path.isdir(item):
shutil.rmtree(item, ignore_errors=True) shutil.rmtree(item, ignore_errors=True)
else: else:
os.unlink(item) os.unlink(item)
if os.path.isdir(self.conf['tmpdir']): # remove the whole lorax tempdir
shutil.rmtree(self.conf['tmpdir'], ignore_errors=True) if os.path.isdir(self.conf.tempdir):
shutil.rmtree(self.conf.tempdir, ignore_errors=True)

View File

@ -30,17 +30,18 @@ import fnmatch
import re import re
import fileinput import fileinput
import subprocess import subprocess
import pwd import pwd
import grp import grp
import tempfile
from fileutils import cp, mv from fileutils import cp, mv, touch
from yumutils import extract_rpm
import utils
sys.path.insert(0, '/usr/share/yum-cli')
import yummain
class InstRoot: class InstRoot:
"""InstRoot(conf, yumconf, arch, treedir, [updates=None]) """InstRoot(config, options, yum)
Create a instroot tree for the specified architecture. The list of Create a instroot tree for the specified architecture. The list of
packages to install are specified in the /etc/lorax/packages and packages to install are specified in the /etc/lorax/packages and
@ -58,20 +59,12 @@ class InstRoot:
returned or the program is aborted immediately. returned or the program is aborted immediately.
""" """
def __init__(self, conf, yumconf, arch, treedir, updates=None): def __init__(self, config, options, yum):
self.conf = conf self.conf = config
self.yumconf = yumconf self.opts = options
self.arch = arch self.yum = yum
self.treedir = treedir
self.updates = updates
self.libdir = 'lib' self.destdir = self.conf.treedir
# on 64-bit systems, make sure we use lib64 as the lib directory
if self.arch.endswith('64') or self.arch == 's390x':
self.libdir = 'lib64'
# the directory where the instroot will be created
self.destdir = os.path.join(self.treedir, 'install')
def run(self): def run(self):
"""run() """run()
@ -79,24 +72,21 @@ class InstRoot:
Generate the instroot tree and prepare it for building images. Generate the instroot tree and prepare it for building images.
""" """
if not os.path.isdir(self.destdir):
os.makedirs(self.destdir)
# build a list of packages to install
self.packages = self.__getPackageList() self.packages = self.__getPackageList()
# install the packages to the instroot
self.__installPackages() self.__installPackages()
# scrub instroot if not self.__installKernel():
self.__scrubInstRoot() sys.exit(1)
# XXX
#self.__scrubInstRoot()
def __getPackageList(self): def __getPackageList(self):
packages = set() packages = set()
packages_files = [] packages_files = []
packages_files.append(os.path.join(self.conf['confdir'], 'packages')) packages_files.append(os.path.join(self.conf.confdir, 'packages'))
packages_files.append(os.path.join(self.conf['confdir'], self.arch, 'packages')) packages_files.append(os.path.join(self.conf.confdir, self.opts.buildarch, 'packages'))
for pfile in packages_files: for pfile in packages_files:
if os.path.isfile(pfile): if os.path.isfile(pfile):
@ -120,29 +110,98 @@ class InstRoot:
return packages return packages
def __installPackages(self): def __installPackages(self):
# build the list of arguments to pass to yum # XXX i don't think this is needed
arglist = ['-c', self.yumconf]
arglist.append("--installroot=%s" % (self.destdir,))
arglist.extend(['install', '-y'])
arglist.extend(self.packages)
# do some prep work on the destdir before calling yum # do some prep work on the destdir before calling yum
os.makedirs(os.path.join(self.destdir, 'boot')) #os.makedirs(os.path.join(self.destdir, 'boot'))
os.makedirs(os.path.join(self.destdir, 'usr', 'sbin')) #os.makedirs(os.path.join(self.destdir, 'usr', 'sbin'))
os.makedirs(os.path.join(self.destdir, 'usr', 'lib', 'debug')) #os.makedirs(os.path.join(self.destdir, 'usr', 'lib', 'debug'))
os.makedirs(os.path.join(self.destdir, 'usr', 'src', 'debug')) #os.makedirs(os.path.join(self.destdir, 'usr', 'src', 'debug'))
os.makedirs(os.path.join(self.destdir, 'tmp')) #os.makedirs(os.path.join(self.destdir, 'tmp'))
os.makedirs(os.path.join(self.destdir, 'var', 'log')) #os.makedirs(os.path.join(self.destdir, 'var', 'log'))
os.makedirs(os.path.join(self.destdir, 'var', 'lib')) #os.makedirs(os.path.join(self.destdir, 'var', 'lib', 'yum'))
os.makedirs(os.path.join(self.destdir, 'var', 'lib', 'yum'))
# XXX maybe only this...
#os.makedirs(os.path.join(self.destdir, 'var', 'lib'))
os.symlink(os.path.join(os.path.sep, 'tmp'), os.path.join(self.destdir, 'var', 'lib', 'xkb')) os.symlink(os.path.join(os.path.sep, 'tmp'), os.path.join(self.destdir, 'var', 'lib', 'xkb'))
# XXX sort through yum errcodes and return False for actual bad things we care about self.yum.install(self.packages)
errcode = yummain.user_main(arglist, exit_code=False)
# copy updates to destdir # copy updates to treedir
if self.updates and os.path.isdir(self.updates): if self.opts.updates and os.path.isdir(self.opts.updates):
cp(os.path.join(self.updates, '*'), self.destdir) cp(os.path.join(self.opts.updates, '*'), self.destdir)
# XXX
def __installKernel(self):
arches = [self.opts.buildarch]
efiarch = []
kerneltags = ['kernel']
kernelxen = []
if self.opts.buildarch == 'ppc':
arches = ['ppc64', 'ppc']
elif self.opts.buildarch == 'i386':
arches = ['i586']
efiarch = ['ia32']
kerneltags = ['kernel', 'kernel-PAE']
kernelxen = ['kernel-PAE']
elif self.opts.buildarch == 'x86_64':
efiarch = ['x64']
elif self.opts.buildarch == 'ia64':
efiarch = ['ia64']
kpackages = self.yum.find(kerneltags)
if not kpackages:
sys.stderr.write('ERROR: Unable to find any kernel package\n')
return False
# create the modinfo file
(fd, modinfo) = tempfile.mkstemp(prefix='modinfo-%s.' % self.opts.buildarch,
dir=self.conf.tempdir)
self.conf.addAttr('modinfo')
self.conf.set(modinfo=modinfo)
for kernel in kpackages:
fn = self.yum.download(kernel)
kernelroot = os.path.join(self.conf.kernelbase, kernel.arch)
extract_rpm(fn, kernelroot)
os.unlink(fn)
# get vmlinuz and version
dir = os.path.join(kernelroot, 'boot')
if self.opts.buildarch == 'ia64':
dir = os.path.join(dir, 'efi', 'EFI', 'redhat')
vmlinuz = None
for file in os.listdir(dir):
if file.startswith('vmlinuz'):
vmlinuz = file
prefix, sep, version = file.partition('-')
if not vmlinuz:
sys.stderr.write('ERROR: vmlinuz file not found\n')
return False
modules_dir = os.path.join(kernelroot, 'lib', 'modules', version)
if not os.path.isdir(modules_dir):
sys.stderr.write('ERROR: modules directory not found\n')
return False
allmods = []
for file in os.listdir(modules_dir):
if file.endswith('.ko'):
allmods.append(os.path.join(modules_dir, file))
# install firmware
fpackages = self.yum.find('*firmware*')
for firmware in fpackages:
fn = self.yum.download(firmware)
extract_rpm(fn, kernelroot)
os.unlink(fn)
utils.genmodinfo(modules_dir, self.conf.modinfo)
return True
def __scrubInstRoot(self): def __scrubInstRoot(self):
self.__createConfigFiles() self.__createConfigFiles()
@ -155,35 +214,34 @@ class InstRoot:
self.__configureKmod() self.__configureKmod()
self.__moveAnacondaFiles() self.__moveAnacondaFiles()
self.__setShellLinks() self.__setShellLinks()
self.__moveBins() #self.__moveBins()
self.__removeUnwanted() self.__removeUnwanted()
self.__changeDestDirPermissions() self.__changeDestDirPermissions()
self.__createLDConfig() self.__createLDConfig()
self.__setBusyboxLinks() self.__setBusyboxLinks()
self.__strip() self.__strip()
self.__fixBrokenLinks() #self.__fixBrokenLinks()
def __createConfigFiles(self): def __createConfigFiles(self):
# create %gconf.xml # create %gconf.xml
dogtailconf = os.path.join(self.conf['datadir'], 'dogtail-%gconf.xml') dogtailconf = os.path.join(self.conf.datadir, 'dogtail-%gconf.xml')
if os.path.isfile(dogtailconf): if os.path.isfile(dogtailconf):
os.makedirs(os.path.join(self.destdir, '.gconf', 'desktop', 'gnome', 'interface')) os.makedirs(os.path.join(self.destdir, '.gconf', 'desktop', 'gnome', 'interface'))
f = open(os.path.join(self.destdir, '.gconf', 'desktop', '%gconf.xml'), 'w') touch(os.path.join(self.destdir, '.gconf', 'desktop', '%gconf.xml'))
f.close() touch(os.path.join(self.destdir, '.gconf', 'desktop', 'gnome', '%gconf.xml'))
f = open(os.path.join(self.destdir, '.gconf', 'desktop', 'gnome', '%gconf.xml'), 'w')
f.close()
dst = os.path.join(self.destdir, '.gconf', 'desktop', 'gnome', 'interface', '%gconf.xml') dst = os.path.join(self.destdir, '.gconf', 'desktop', 'gnome', 'interface', '%gconf.xml')
cp(dogtailconf, dst) cp(dogtailconf, dst)
# create selinux config # create selinux config
if os.path.isfile(os.path.join(self.destdir, 'etc', 'selinux', 'targeted')): if os.path.isfile(os.path.join(self.destdir, 'etc', 'selinux', 'targeted')):
selinuxconf = os.path.join(self.conf['datadir'], 'selinux-config') selinuxconf = os.path.join(self.conf.datadir, 'selinux-config')
if os.path.isfile(selinuxconf): if os.path.isfile(selinuxconf):
dst = os.path.join(self.destdir, 'etc', 'selinux', 'config') dst = os.path.join(self.destdir, 'etc', 'selinux', 'config')
cp(selinuxconf, dst) cp(selinuxconf, dst)
# create libuser.conf # create libuser.conf
libuserconf = os.path.join(self.conf['datadir'], 'libuser.conf') libuserconf = os.path.join(self.conf.datadir, 'libuser.conf')
if os.path.isfile(libuserconf): if os.path.isfile(libuserconf):
dst = os.path.join(self.destdir, 'etc', 'libuser.conf') dst = os.path.join(self.destdir, 'etc', 'libuser.conf')
cp(libuserconf, dst) cp(libuserconf, dst)
@ -265,7 +323,7 @@ class InstRoot:
shutil.rmtree(icon, ignore_errors=True) shutil.rmtree(icon, ignore_errors=True)
# remove engines we don't need # remove engines we don't need
tmp_path = os.path.join(self.destdir, 'usr', self.libdir, 'gtk-2.0') tmp_path = os.path.join(self.destdir, 'usr', self.opts.libdir, 'gtk-2.0')
if os.path.isdir(tmp_path): if os.path.isdir(tmp_path):
fnames = map(lambda fname: os.path.join(tmp_path, fname, 'engines'), os.listdir(tmp_path)) fnames = map(lambda fname: os.path.join(tmp_path, fname, 'engines'), os.listdir(tmp_path))
dnames = filter(lambda fname: os.path.isdir(fname), fnames) dnames = filter(lambda fname: os.path.isdir(fname), fnames)
@ -358,27 +416,27 @@ class InstRoot:
if not os.path.isdir(bootpath): if not os.path.isdir(bootpath):
os.makedirs(bootpath) os.makedirs(bootpath)
if self.arch == 'i386' or self.arch == 'x86_64': if self.opts.buildarch == 'i386' or self.opts.buildarch == 'x86_64':
for bootfile in os.listdir(os.path.join(self.destdir, 'boot')): for bootfile in os.listdir(os.path.join(self.destdir, 'boot')):
if bootfile.startswith('memtest'): if bootfile.startswith('memtest'):
src = os.path.join(self.destdir, 'boot', bootfile) src = os.path.join(self.destdir, 'boot', bootfile)
dst = os.path.join(bootpath, bootfile) dst = os.path.join(bootpath, bootfile)
cp(src, dst) cp(src, dst)
elif self.arch.startswith('sparc'): elif self.opts.buildarch.startswith('sparc'):
for bootfile in os.listdir(os.path.join(self.destdir, 'boot')): for bootfile in os.listdir(os.path.join(self.destdir, 'boot')):
if bootfile.endswith('.b'): if bootfile.endswith('.b'):
src = os.path.join(self.destdir, 'boot', bootfile) src = os.path.join(self.destdir, 'boot', bootfile)
dst = os.path.join(bootpath, bootfile) dst = os.path.join(bootpath, bootfile)
cp(src, dst) cp(src, dst)
elif self.arch.startswith('ppc'): elif self.opts.buildarch.startswith('ppc'):
src = os.path.join(self.destdir, 'boot', 'efika.forth') src = os.path.join(self.destdir, 'boot', 'efika.forth')
dst = os.path.join(bootpath, 'efika.forth') dst = os.path.join(bootpath, 'efika.forth')
cp(src, dst) cp(src, dst)
elif self.arch == 'alpha': elif self.opts.buildarch == 'alpha':
src = os.path.join(self.destdir, 'boot', 'bootlx') src = os.path.join(self.destdir, 'boot', 'bootlx')
dst = os.path.join(bootpath, 'bootlx') dst = os.path.join(bootpath, 'bootlx')
cp(src, dst) cp(src, dst)
elif self.arch == 'ia64': elif self.opts.buildarch == 'ia64':
src = os.path.join(self.destdir, 'boot', 'efi', 'EFI', 'redhat') src = os.path.join(self.destdir, 'boot', 'efi', 'EFI', 'redhat')
shutil.rmtree(bootpath, ignore_errors=True) shutil.rmtree(bootpath, ignore_errors=True)
cp(src, bootpath) cp(src, bootpath)
@ -435,6 +493,7 @@ class InstRoot:
os.symlink(busybox, sh) os.symlink(busybox, sh)
def __moveBins(self): def __moveBins(self):
# XXX why do we want to move everything to /usr when in mk-images we copy it back?
bin = os.path.join(self.destdir, 'bin') bin = os.path.join(self.destdir, 'bin')
sbin = os.path.join(self.destdir, 'sbin') sbin = os.path.join(self.destdir, 'sbin')
@ -518,49 +577,47 @@ class InstRoot:
def __createLDConfig(self): def __createLDConfig(self):
ldsoconf = os.path.join(self.destdir, 'etc', 'ld.so.conf') ldsoconf = os.path.join(self.destdir, 'etc', 'ld.so.conf')
f = open(ldsoconf, 'w') touch(ldsoconf)
f.close()
proc_dir = os.path.join(self.destdir, 'proc') proc_dir = os.path.join(self.destdir, 'proc')
if not os.path.isdir(proc_dir): if not os.path.isdir(proc_dir):
os.makedirs(proc_dir) os.makedirs(proc_dir)
# XXX isn't there a better way? os.system('mount -t proc proc %s' % proc_dir)
os.system('mount -t proc proc %s' % (proc_dir,))
f = open(ldsoconf, 'w') f = open(ldsoconf, 'w')
x11_libdir = os.path.join(self.destdir, 'usr', 'X11R6', self.libdir)
x11_libdir = os.path.join(self.destdir, 'usr', 'X11R6', self.opts.libdir)
if os.path.exists(x11_libdir): if os.path.exists(x11_libdir):
f.write('/usr/X11R6/%s\n' % (self.libdir,)) f.write('/usr/X11R6/%s\n' % self.opts.libdir)
f.write('/usr/kerberos/%s\n' % (self.libdir,))
f.write('/usr/kerberos/%s\n' % self.opts.libdir)
cwd = os.getcwd() cwd = os.getcwd()
os.chdir(self.destdir) os.chdir(self.destdir)
# XXX can't exit from os.chroot() :( os.system('/usr/sbin/chroot %s /sbin/ldconfig' % self.destdir)
os.system('/usr/sbin/chroot %s /usr/sbin/ldconfig' % (self.destdir,))
os.chdir(cwd) os.chdir(cwd)
if self.arch not in ('s390', 's390x'): if self.opts.buildarch not in ('s390', 's390x'):
os.unlink(os.path.join(self.destdir, 'usr', 'sbin', 'ldconfig')) os.unlink(os.path.join(self.destdir, 'sbin', 'ldconfig'))
os.unlink(os.path.join(self.destdir, 'etc', 'ld.so.conf')) os.unlink(os.path.join(self.destdir, 'etc', 'ld.so.conf'))
# XXX isn't there a better way? os.system('umount %s' % proc_dir)
os.system('umount %s' % (proc_dir,))
def __setBusyboxLinks(self): def __setBusyboxLinks(self):
src = os.path.join(self.destdir, 'usr', 'sbin', 'busybox.anaconda') src = os.path.join(self.destdir, 'sbin', 'busybox.anaconda')
dst = os.path.join(self.destdir, 'usr', 'bin', 'busybox') dst = os.path.join(self.destdir, 'bin', 'busybox')
mv(src, dst) mv(src, dst)
cwd = os.getcwd() cwd = os.getcwd()
os.chdir(os.path.join(self.destdir, 'usr', 'bin')) os.chdir(os.path.join(self.destdir, 'bin'))
busybox_process = subprocess.Popen(['./busybox'], stdout=subprocess.PIPE) busybox_process = subprocess.Popen(['./busybox'], stdout=subprocess.PIPE)
busybox_process.wait() busybox_process.wait()
if busybox_process.returncode: if busybox_process.returncode:
raise LoraxError, 'cannot run busybox' raise Error, 'busybox error'
busybox_output = busybox_process.stdout.readlines() busybox_output = busybox_process.stdout.readlines()
busybox_output = map(lambda line: line.strip(), busybox_output) busybox_output = map(lambda line: line.strip(), busybox_output)
@ -641,3 +698,10 @@ class InstRoot:
newtarget = re.sub(r'^\.\./\.\./%s/\(.*\)' % dir, r'\.\./%s/\1' % dir, target) newtarget = re.sub(r'^\.\./\.\./%s/\(.*\)' % dir, r'\.\./%s/\1' % dir, target)
if newtarget != target: if newtarget != target:
os.symlink(newtarget, link) os.symlink(newtarget, link)
def __makeAdditionalDirs(self):
os.makedirs(os.path.join(self.destdir, 'modules'))
os.makedirs(os.path.join(self.destdir, 'tmp'))
for dir in ('a', 'b', 'd', 'l', 's', 'v', 'x'):
os.makedirs(os.path.join(self.destdir, 'etc', 'terminfo', dir))
os.makedirs(os.path.join(self.destdir, 'var', 'lock', 'rpm'))

View File

@ -9,11 +9,7 @@ logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('pylorax') logger = logging.getLogger('pylorax')
def genmodinfo(path=None): def genmodinfo(path, output):
if not path:
release = os.uname()[2]
path = os.path.join('/lib/modules', release)
mods = {} mods = {}
for root, dirs, files in os.walk(path): for root, dirs, files in os.walk(path):
for file in files: for file in files:
@ -56,13 +52,14 @@ def genmodinfo(path=None):
modinfo = '%s\n\t%s\n\t"%s"\n' % (modname, modtype, desc) modinfo = '%s\n\t%s\n\t"%s"\n' % (modname, modtype, desc)
list[modtype][modname] = modinfo list[modtype][modname] = modinfo
# XXX i don't think we want to print it out now f = open(output, 'a')
print 'Version 0' f.write('Version 0\n')
for type in list: for type in list:
modlist = list[type].keys() modlist = list[type].keys()
modlist.sort() modlist.sort()
for m in modlist: for m in modlist:
print list[type][m] f.write('%s\n' %list[type][m])
f.close()
# XXX is the input a path to a file? # XXX is the input a path to a file?

View File

@ -0,0 +1,28 @@
# pylorax/actions/__init__.py
import os
def getActions():
actions = {}
root, actions_dir = os.path.split(os.path.dirname(__file__))
modules = set()
for filename in os.listdir(os.path.join(root, actions_dir)):
if filename.endswith('.py') and filename != '__init__.py':
basename, extension = os.path.splitext(filename)
modules.add(os.path.join('pylorax', actions_dir, basename).replace('/', '.'))
for module in modules:
print('Loading actions from %s' % module)
imported = __import__(module, globals(), locals(), [module], -1)
try:
commands = getattr(imported, 'COMMANDS')
except AttributeError:
continue
else:
for command, classname in commands.items():
print('Loaded: %s' % classname)
actions[command] = getattr(imported, classname)
return actions

View File

@ -0,0 +1,179 @@
# pylorax/actions/fileactions.py
from pylorax.base import LoraxAction
import os
import re
from pylorax.utils.fileutil import cp, mv, touch, edit, replace
COMMANDS = { 'copy': 'Copy',
'move': 'Move',
'link': 'Link',
'touch': 'Touch',
'edit': 'Edit',
'replace': 'Replace' }
def getFileName(string):
m = re.match(r'@instroot@(?P<file>.*)', string)
if m:
return m.group('file')
else:
return None
class Copy(LoraxAction):
REGEX = r'^(?P<src>.*?)\sto\s(?P<dst>.*?)(\smode\s(?P<mode>.*?))?$'
def __init__(self, **kwargs):
LoraxAction.__init__(self)
self._attrs['src'] = kwargs.get('src')
self._attrs['dst'] = kwargs.get('dst')
self._attrs['mode'] = kwargs.get('mode')
file = getFileName(self._attrs['src'])
if file:
self._attrs['install'] = file
def execute(self, verbose=False):
cp(src=self.src, dst=self.dst, mode=self.mode, verbose=verbose)
self._attrs['success'] = True
def getDeps(self):
return self._attrs['src']
@property
def src(self):
return self._attrs['src']
@property
def dst(self):
return self._attrs['dst']
@property
def mode(self):
return self._attrs['mode']
@property
def install(self):
return self._attrs.get('install')
class Move(Copy):
def execute(self, verbose=False):
mv(src=self.src, dst=self.dst, mode=self.mode, verbose=verbose)
self._attrs['success'] = True
class Link(LoraxAction):
REGEX = r'^(?P<name>.*?)\sto\s(?P<target>.*?)$'
def __init__(self, **kwargs):
LoraxAction.__init__(self)
self._attrs['name'] = kwargs.get('name')
self._attrs['target'] = kwargs.get('target')
file = getFileName(self._attrs['name'])
if file:
self._attrs['install'] = file
def execute(self, verbose=False):
os.symlink(self.name, self.target)
self._attrs['success'] = True
@property
def name(self):
return self._attrs['name']
@property
def target(self):
return self._attrs['target']
@property
def install(self):
return self._attrs['install']
class Touch(LoraxAction):
REGEX = r'^(?P<filename>.*?)$'
def __init__(self, **kwargs):
LoraxAction.__init__(self)
self._attrs['filename'] = kwargs.get('filename')
def execute(self, verbose=False):
touch(filename=self.filename, verbose=verbose)
self._attrs['success'] = True
@property
def filename(self):
return self._attrs['filename']
class Edit(Touch):
REGEX = r'^(?P<filename>.*?)\stext\s"(?P<text>.*?)"((?P<append>\sappend?))?$'
def __init__(self, **kwargs):
Touch.__init__(self, **kwargs)
self._attrs['text'] = kwargs.get('text')
append = kwargs.get('append', False)
if append:
self._attrs['append'] = True
else:
self._attrs['append'] = False
file = getFileName(self._attrs['filename'])
if file:
self._attrs['install'] = file
def execute(self, verbose=False):
edit(filename=self.filename, text=self.text, append=self.append, verbose=verbose)
self._attrs['success'] = True
@property
def text(self):
return self._attrs['text']
@property
def append(self):
return self._attrs['append']
@property
def install(self):
return self._attrs['install']
class Replace(Touch):
REGEX = r'^(?P<filename>.*?)\sfind\s"(?P<find>.*?)"\sreplace\s"(?P<replace>.*?)"$'
def __init__(self, **kwargs):
Touch.__init__(self, **kwargs)
self._attrs['find'] = kwargs.get('find')
self._attrs['replace'] = kwargs.get('replace')
file = getFileName(self._attrs['filename'])
if file:
self._attrs['install'] = file
def execute(self, verbose=False):
replace(filename=self.filename, find=self.find, replace=self.replace, verbose=verbose)
self._attrs['success'] = True
@property
def find(self):
return self._attrs['find']
@property
def replace(self):
return self._attrs['replace']
@property
def install(self):
return self._attrs['install']

43
src/pylorax/base.py Normal file
View File

@ -0,0 +1,43 @@
# pylorax/base.py
import commands
class LoraxAction(object):
REGEX = r'.*'
def __init__(self):
if self.__class__ is LoraxAction:
raise TypeError, 'LoraxAction is an abstract class'
self._attrs = {}
self._attrs['success'] = None
def __str__(self):
return '%s: %s' % (self.__class__.__name__, self._attrs)
def execute(self, verbose=False):
raise NotImplementedError, 'execute method not implemented for LoraxAction class'
@property
def success(self):
return self._attrs['success']
def seq(arg):
if type(arg) not in (type([]), type(())):
return [arg]
else:
return arg
def getConsoleSize():
err, output = commands.getstatusoutput('stty size')
if not err:
height, width = output.split()
else:
# set defaults
height, width = 24, 80
return int(height), int(width)

125
src/pylorax/config.py Normal file
View File

@ -0,0 +1,125 @@
# pylorax/config.py
import sys
from base import seq
import re
from exceptions import TemplateError
class Container(object):
def __init__(self, attrs=None):
self.__dict__['__internal'] = {}
self.__dict__['__internal']['attrs'] = set()
if attrs:
self.addAttr(attrs)
def __str__(self):
return str(self.__makeDict())
def __iter__(self):
return iter(self.__makeDict())
def __getitem__(self, attr):
self.__checkInternal(attr)
if attr not in self.__dict__:
raise AttributeError, "'Container' object has no attribute '%s'" % attr
return self.__dict__[attr]
def __setattr__(self, attr, value):
raise AttributeError, 'you cannot do that, use addAttr() and set() instead'
def __delattr__(self, attr):
raise AttributeError, 'you cannot do that, use delAttr() instead'
def addAttr(self, attrs):
for attr in filter(lambda attr: attr not in self.__dict__, seq(attrs)):
self.__checkInternal(attr)
self.__dict__[attr] = None
self.__dict__['__internal']['attrs'].add(attr)
def delAttr(self, attrs):
for attr in filter(lambda attr: attr in self.__dict__, seq(attrs)):
self.__checkInternal(attr)
del self.__dict__[attr]
self.__dict__['__internal']['attrs'].discard(attr)
def set(self, **kwargs):
unknown = set()
for attr, value in kwargs.items():
self.__checkInternal(attr)
if attr in self.__dict__:
self.__dict__[attr] = value
else:
unknown.add(attr)
return unknown
def __makeDict(self):
d = {}
for attr in self.__dict__['__internal']['attrs']:
d[attr] = self.__dict__[attr]
return d
def __checkInternal(self, attr):
if attr.startswith('__'):
raise AttributeError, 'do not mess with internal stuff'
class Template(object):
def __init__(self):
self._actions = []
def parse(self, filename, supported_actions):
try:
f = open(filename, 'r')
except IOError:
sys.stdout.write('ERROR: Unable to open the template file\n')
return False
else:
lines = f.readlines()
f.close()
active_action = ''
in_action = False
for lineno, line in enumerate(lines, start=1):
line = line.strip()
if not line or line.startswith('#'):
continue
if in_action and not line.startswith(':'):
# create the action object
regex = supported_actions[active_action].REGEX
m = re.match(regex, line)
if m:
new_action = supported_actions[active_action](**m.groupdict())
self._actions.append(new_action)
else:
# didn't match the regex
raise TemplateError, 'invalid action format "%s" on line %d' % (line, lineno)
if in_action and line.startswith(':'):
in_action = False
if not in_action and line.startswith(':'):
active_action = line[1:]
if active_action not in supported_actions:
raise TemplateError, 'unknown action "%s" on line %d' % (active_action, lineno)
else:
in_action = True
continue
return True
@property
def actions(self):
return self._actions

View File

@ -0,0 +1,7 @@
# pylorax/exceptions.py
class LoraxError(Exception):
pass
class TemplateError(Exception):
pass

View File

@ -1,50 +0,0 @@
import os
import shutil
import glob
def cp(src, dst, verbose=False):
for name in glob.iglob(src):
__copy(name, dst, verbose=verbose)
def mv(src, dst, verbose=False):
for name in glob.iglob(src):
__copy(name, dst, verbose=verbose, remove=True)
def __copy(src, dst, verbose=False, remove=False):
if not os.path.exists(src):
print('cannot stat "%s": No such file or directory' % (src,))
return
if os.path.isdir(dst):
basename = os.path.basename(src)
dst = os.path.join(dst, basename)
if os.path.isdir(src):
if os.path.isfile(dst):
print('omitting directory "%s"' % (src,))
return
if not os.path.isdir(dst):
os.makedirs(dst)
names = map(lambda name: os.path.join(src, name), os.listdir(src))
for name in names:
__copy(name, dst, verbose=verbose, remove=remove)
else:
if os.path.isdir(dst):
print('cannot overwrite directory "%s" with non-directory' % (dst,))
return
try:
if verbose:
print('copying "%s" to "%s"' % (src, dst))
shutil.copy2(src, dst)
except shutil.Error, IOError:
print('cannot copy "%s" to "%s"' % (src, dst))
else:
if remove:
if verbose:
print('removing "%s"' % (src,))
os.unlink(src)

View File

@ -1,102 +1,140 @@
# # pylorax/images.py
# pylorax images module
# Install image and tree support data generation tool -- Python module
#
import datetime import sys
import os
from utils.fileutil import cp, mv, rm, touch, replace
import initrd
class Images(): class Images(object):
def __init__(self, config, yum):
self.conf = config
self.yum = yum
def __init__(self, conf, yumconf, arch, imgdir, product, version, bugurl, output, noiso=False): # XXX don't see this used anywhere... maybe in some other script, have to check...
self.conf = conf #syslinux = os.path.join(self.conf.treedir, 'usr', 'lib', 'syslinux', 'syslinux-nomtools')
self.yumconf = yumconf #if not os.path.isfile(syslinux):
# print('WARNING: %s does not exist' % syslinux)
# syslinux = os.path.join(self.conf.treedir, 'usr', 'bin', 'syslinux')
# if not os.path.isfile(syslinux):
# print('ERROR: %s does not exist' % syslinux)
# sys.exit(1)
self.arch = arch def run(self):
self.imgdir = imgdir self.prepareBootTree()
self.product = product
self.version = version
self.bugurl = bugurl
self.output = output def __makeinitrd(self, dst, size=8192, loader='loader'):
self.noiso = noiso i = initrd.InitRD(self.conf, self.yum)
i.prepare()
i.processActions()
i.create(dst)
i.cleanUp()
now = datetime.datetime.now() def prepareBootTree(self):
self.imageuuid = now.strftime('%Y%m%d%H%M') + '.' + os.uname()[4] # install needed packages
self.yum.addPackages(['anaconda', 'anaconda-runtime', 'kernel', 'syslinux', 'memtest'])
self.yum.install()
self.initrdmods = self.__getModulesList() # create the destination directories
self.imgdir = os.path.join(self.conf.outdir, 'images')
if self.arch == 'sparc64': if os.path.exists(self.imgdir):
self.basearch = 'sparc' rm(self.imgdir)
else: self.pxedir = os.path.join(self.imgdir, 'pxeboot')
self.basearch = self.arch os.makedirs(self.imgdir)
os.makedirs(self.pxedir)
self.libdir = 'lib'
if self.arch == 'x86_64' or self.arch =='s390x':
self.libdir = 'lib64'
# explicit block size setting for some arches
# FIXME we compose ppc64-ish trees as ppc, so we have to set the "wrong" block size
# XXX i don't get this :)
self.crambs = []
if self.arch == 'sparc64':
self.crambs = ['--blocksize', '8192']
elif self.arch == 'sparc':
self.crambs = ['--blocksize', '4096']
self.__setUpDirectories()
def __getModulesList(self):
modules = set()
modules_files = []
modules_files.append(os.path.join(self.conf['confdir'], 'modules'))
modules_files.append(os.path.join(self.conf['confdir'], self.arch, 'modules'))
for pfile in modules_files:
if os.path.isfile(pfile):
f = open(pfile, 'r')
for line in f.readlines():
line = line.strip()
if not line or line.startswith('#'):
continue
if line.startswith('-'):
modules.discard(line[1:])
else:
modules.add(line)
# write the images/README
f = open(os.path.join(self.imgdir, 'README'), 'w')
f.write('This directory contains image files that can be used to create media\n'
'capable of starting the %s installation process.\n\n' % self.conf.product)
f.write('The boot.iso file is an ISO 9660 image of a bootable CD-ROM. It is useful\n'
'in cases where the CD-ROM installation method is not desired, but the\n'
'CD-ROM\'s boot speed would be an advantage.\n\n')
f.write('To use this image file, burn the file onto CD-R (or CD-RW) media as you\n'
'normally would.\n')
f.close() f.close()
modules = list(modules) # write the images/pxeboot/README
modules.sort() f = open(os.path.join(self.pxedir, 'README'), 'w')
f.write('The files in this directory are useful for booting a machine via PXE.\n\n')
f.write('The following files are available:\n')
f.write('vmlinuz - the kernel used for the installer\n')
f.write('initrd.img - an initrd with support for all install methods and\n')
f.write(' drivers supported for installation of %s\n' % self.conf.product)
f.close()
return modules # set up some dir variables for further use
anacondadir = os.path.join(self.conf.treedir, 'usr', 'lib', 'anaconda-runtime')
bootdiskdir = os.path.join(anacondadir, 'boot')
syslinuxdir = os.path.join(self.conf.treedir, 'usr', 'lib', 'syslinux')
def __setUpDirectories(self): isolinuxbin = os.path.join(syslinuxdir, 'isolinux.bin')
imagepath = os.path.join(self.output, 'images') if os.path.isfile(isolinuxbin):
fullmodpath = tempfile.mkdtemp('XXXXXX', 'instimagemods.', self.conf['tmpdir']) print('Creating the isolinux directory...')
finalfullmodpath = os.path.join(self.output, 'modules') self.isodir = os.path.join(self.conf.outdir, 'isolinux')
if os.path.exists(self.isodir):
rm(self.isodir)
os.makedirs(self.isodir)
kernelbase = tempfile.mkdtemp('XXXXXX', 'updboot.kernel.', self.conf['tmpdir']) # copy the isolinux.bin to isolinux dir
kernelname = 'vmlinuz' cp(isolinuxbin, self.isodir)
kerneldir = '/boot' # copy the syslinux.cfg to isolinux/isolinux.cfg
if self.arch == 'ia64': isolinuxcfg = os.path.join(self.isodir, 'isolinux.cfg')
kerneldir = os.path.join(kerneldir, 'efi', 'EFI', 'redhat') cp(os.path.join(bootdiskdir, 'syslinux.cfg'), isolinuxcfg)
for dir in [imagepath, fullmodpath, finalfullmodpath, kernelbase]: # set the product and version in isolinux.cfg
if os.path.isdir(dir): replace(isolinuxcfg, r'@PRODUCT@', self.conf.product)
shutil.rmtree(dir) replace(isolinuxcfg, r'@VERSION@', self.conf.version)
os.makedirs(dir)
self.imagepath = imagepath # copy the grub.conf to isolinux dir
self.fullmodpath = fullmodpath cp(os.path.join(bootdiskdir, 'grub.conf'), self.isodir)
self.finalfullmodpath = finalfullmodpath
self.kernelbase = kernelbase # create the initrd in isolinux dir
self.kernelname = kernelname initrd = os.path.join(self.isodir, 'initrd.img')
self.kerneldir = kerneldir self.__makeinitrd(initrd)
# copy the vmlinuz to isolinux dir
vmlinuz = os.path.join(self.conf.treedir, 'boot', 'vmlinuz-*')
cp(vmlinuz, os.path.join(self.isodir, 'vmlinuz'))
# copy the splash files to isolinux dir
vesasplash = os.path.join(anacondadir, 'syslinux-vesa-splash.jpg')
if os.path.isfile(vesasplash):
cp(vesasplash, os.path.join(self.isodir, 'splash.jpg'))
vesamenu = os.path.join(syslinuxdir, 'vesamenu.c32')
cp(vesamenu, self.isodir)
replace(isolinuxcfg, r'default linux', r'default vesamenu.c32')
replace(isolinuxcfg, r'prompt 1', r'#prompt 1')
else:
splashtools = os.path.join(anacondadir, 'splashtools.sh')
splashlss = os.path.join(bootdiskdir, 'splash.lss')
if os.path.isfile(splashtools):
os.system('%s %s %s' % (splashtools,
os.path.join(bootdiskdir, 'syslinux-splash.jpg'),
splashlss))
if os.path.isfile(splashlss):
cp(splashlss, self.isodir)
# copy the .msg files to isolinux dir
for file in os.listdir(bootdiskdir):
if file.endswith('.msg'):
cp(os.path.join(bootdiskdir, file), self.isodir)
replace(os.path.join(self.isodir, file), r'@VERSION@', self.conf.version)
# if present, copy the memtest to isolinux dir
# XXX search for it in bootdiskdir or treedir/install/boot ?
#cp(os.path.join(bootdiskdir, 'memtest*'), os.path.join(self.isodir, 'memtest'))
cp(os.path.join(self.conf.treedir, 'boot', 'memtest*'),
os.path.join(self.isodir, 'memtest'))
if os.path.isfile(os.path.join(self.isodir, 'memtest')):
f = open(isolinuxcfg, 'a')
f.write('label memtest86\n')
f.write(' menu label ^Memory test\n')
f.write(' kernel memtest\n')
f.write(' append -\n')
f.close()
else:
print('No isolinux binary found, skipping isolinux creation')

74
src/pylorax/initrd.py Normal file
View File

@ -0,0 +1,74 @@
# pylorax/initrd.py
import os
import re
import actions
from config import Template
from utils.libutil import LDD
from utils.fileutil import rm
class InitRD(object):
def __init__(self, config, yum):
self.conf = config
self.yum = yum
self.initrddir = os.path.join(self.conf.tempdir, 'initrd')
os.makedirs(self.initrddir)
# get supported actions
supported_actions = actions.getActions()
initrd_templates = []
initrd_templates.append(os.path.join(self.conf.confdir, 'templates', 'initrd'))
initrd_templates.append(os.path.join(self.conf.confdir, 'templates', self.conf.buildarch,
'initrd'))
self.template = Template()
for file in initrd_templates:
if os.path.isfile(file):
self.template.parse(file, supported_actions)
self.actions = []
def prepare(self):
# install needed packages
for action in filter(lambda action: hasattr(action, 'install'), self.template.actions):
self.yum.addPackages(action.install)
self.yum.install()
# get needed dependencies
ldd = LDD(libroot=os.path.join(self.conf.treedir, self.conf.libdir))
for action in filter(lambda action: hasattr(action, 'getDeps'), self.template.actions):
file = re.sub(r'@instroot@(?P<file>.*)', '%s\g<file>' % self.conf.treedir,
action.getDeps())
ldd.getDeps(file)
# resolve symlinks
ldd.getLinks()
# add dependencies to actions
for dep in ldd.deps:
kwargs = {}
kwargs['src'] = dep
kwargs['dst'] = re.sub(r'%s(?P<file>.*)' % self.conf.treedir,
'%s\g<file>' % self.initrddir,
dep)
new_action = actions.fileactions.Copy(**kwargs)
self.actions.append(new_action)
def processActions(self):
for action in self.template.actions:
action.execute()
for action in self.actions:
action.execute()
def create(self, dst):
os.system('find %s | cpio --quiet -c -o | gzip -9 > %s' % (self.initrddir, dst))
def cleanUp(self):
rm(self.initrddir)

View File

View File

@ -0,0 +1,104 @@
# pylorax/utils/fileutil.py
import sys
import os
import shutil
import glob
import fileinput
import re
def cp(src, dst, mode=None, verbose=False):
for name in glob.iglob(src):
__copy(name, dst, verbose=verbose)
if mode:
os.chmod(dst, mode)
def mv(src, dst, mode=None, verbose=False):
for name in glob.iglob(src):
__copy(name, dst, verbose=verbose, remove=True)
if mode:
os.chmod(dst, mode)
def rm(target, verbose=False):
if os.path.isdir(target):
if verbose:
print('removing directory "%s"' % target)
shutil.rmtree(target, ignore_errors=True)
else:
if verbose:
print('removing file "%s"' % target)
os.unlink(target)
def __copy(src, dst, verbose=False, remove=False):
if not os.path.exists(src):
sys.stderr.write('cannot stat "%s": No such file or directory\n' % src)
return
if os.path.isdir(dst):
basename = os.path.basename(src)
dst = os.path.join(dst, basename)
if os.path.isdir(src):
if os.path.isfile(dst):
sys.stderr.write('omitting directory "%s"\n' % src)
return
if not os.path.isdir(dst):
os.makedirs(dst)
names = map(lambda name: os.path.join(src, name), os.listdir(src))
for name in names:
__copy(name, dst, verbose=verbose, remove=remove)
else:
if os.path.isdir(dst):
sys.stderr.write('cannot overwrite directory "%s" with non-directory\n' % dst)
return
try:
if verbose:
print('copying "%s" to "%s"' % (src, dst))
shutil.copy2(src, dst)
except (shutil.Error, IOError) as why:
sys.stderr.write('cannot copy "%s" to "%s": %s\n' % (src, dst, why))
else:
if remove:
if verbose:
print('removing "%s"' % src)
os.unlink(src)
def touch(filename, verbose=False):
if os.path.exists(filename):
os.utime(filename, None)
return True
try:
f = open(filename, 'w')
except IOError:
return False
else:
f.close()
return True
def edit(filename, text, append=False, verbose=False):
mode = 'w'
if append:
mode = 'a'
try:
f = open(filename, mode)
except IOError:
return False
else:
f.write(text)
f.close()
return True
def replace(filename, find, replace, verbose=False):
fin = fileinput.input(filename, inplace=1)
for line in fin:
line = re.sub(find, replace, line)
sys.stdout.write(line)
fin.close()
return True

View File

@ -0,0 +1,57 @@
import sys
import os
import commands
import re
class LDD(object):
def __init__(self, libroot='/'):
f = open('/usr/bin/ldd', 'r')
for line in f.readlines():
line = line.strip()
if line.startswith('RTLDLIST='):
rtldlist, sep, ld_linux = line.partition('=')
break
f.close()
self._ldd = '%s --list --library-path %s' % (ld_linux, libroot)
self._deps = set()
def getDeps(self, filename):
rc, output = commands.getstatusoutput('%s %s' % (self._ldd, filename))
if rc:
return
lines = output.splitlines()
for line in lines:
line = line.strip()
m = re.match(r'^[a-zA-Z0-9.]*\s=>\s(?P<lib>[a-zA-Z0-9./]*)\s\(0x[0-9a-f]*\)$', line)
if m:
lib = m.group('lib')
if lib not in self._deps:
self._deps.add(lib)
self.getDeps(lib)
def getLinks(self):
targets = set()
for lib in self._deps:
if os.path.islink(lib):
targets.add(os.path.realpath(lib))
self._deps.update(targets)
@property
def deps(self):
return self._deps
if __name__ == '__main__':
ldd = LDD(libroot=sys.argv[2])
ldd.getDeps(sys.argv[1])
ldd.getLinks()
for dep in ldd.deps:
print dep

View File

@ -0,0 +1,158 @@
# pylorax/utils/rpmutil.py
import sys
import os
import stat
import yum
import urlgrabber
import shutil
import yum.callbacks
import yum.rpmtrans
from rpmUtils.miscutils import rpm2cpio
from cpioarchive import CpioArchive
from pylorax.base import seq, getConsoleSize
class Callback(yum.rpmtrans.SimpleCliCallBack):
def __init__(self):
yum.rpmtrans.SimpleCliCallBack.__init__(self)
self.height, self.width = getConsoleSize()
def event(self, package, action, te_current, te_total, ts_current, ts_total):
# XXX crazy output stuff
progress = float(te_current) / float(te_total)
percentage = int(progress * 100)
bar_length = 20
bar = int(percentage / (100/bar_length))
total_progress_str = '[%s/%s] ' % (ts_current, ts_total)
package_progress_str = ' [%s%s] %3d%%' % ('#' * bar, '-' * (bar_length - bar), percentage)
action_str = '%s %s' % (self.action[action], package)
chars_left = self.width - len(total_progress_str) - len(package_progress_str)
if len(action_str) > chars_left:
action_str = action_str[:chars_left-3]
action_str = action_str + '...'
else:
action_str = action_str + ' ' * (chars_left - len(action_str))
msg = total_progress_str + action_str + package_progress_str
sys.stdout.write(msg)
sys.stdout.write('\b' * len(msg))
if percentage == 100:
sys.stdout.write('\n')
sys.stdout.flush()
class Yum(object):
def __init__(self, yumconf='/etc/yum/yum.conf', installroot='/'):
self.yb = yum.YumBase()
self.yumconf = os.path.abspath(yumconf)
self.installroot = os.path.abspath(installroot)
self.yb.preconf.fn = self.yumconf
self.yb.preconf.root = self.installroot
self.yb._getConfig()
self.yb._getRpmDB()
self.yb._getRepos()
self.yb._getSacks()
def find(self, patterns):
pl = self.yb.doPackageLists(patterns=seq(patterns))
return pl.installed, pl.available
def isInstalled(self, pattern):
print('searching for package matching %s' % pattern)
pl = self.yb.doPackageLists(pkgnarrow='installed', patterns=[pattern])
print('found %s' % pl.installed)
return pl.installed
def download(self, packages):
for package in seq(packages):
print('Downloading package %s...' % package)
fn = urlgrabber.urlgrab(package.remote_url)
shutil.copy(fn, self.installroot)
return os.path.join(self.installroot, os.path.basename(fn))
def addPackages(self, patterns):
# FIXME don't add packages already installed
for pattern in seq(patterns):
installed = self.isInstalled(pattern)
if installed:
print 'Package %s already installed' % installed
return
print('Adding package matching %s...' % pattern)
try:
self.yb.install(name=pattern)
except yum.Errors.InstallError:
try:
self.yb.install(pattern=pattern)
except yum.Errors.InstallError:
sys.stderr.write('ERROR: No package matching %s available\n' % pattern)
def install(self):
self.yb.resolveDeps()
self.yb.buildTransaction()
cb = yum.callbacks.ProcessTransBaseCallback()
rpmcb = Callback()
self.yb.processTransaction(callback=cb, rpmDisplay=rpmcb)
def extract_rpm(rpmfile, destdir):
if not os.path.isdir(destdir):
os.makedirs(destdir)
rpm = os.open(rpmfile, os.O_RDONLY)
output = open(os.path.join(destdir, 'CONTENT.cpio'), 'w')
rpm2cpio(rpm, output)
output.close()
cwd = os.getcwd()
os.chdir(destdir)
cpio = CpioArchive(name=output.name)
for entry in cpio:
path = os.path.abspath(entry.name)
isdir = stat.S_ISDIR(entry.mode)
if isdir:
if not os.path.isdir(path):
os.makedirs(path)
else:
print('Extracting %s...' % entry.name)
dir = os.path.dirname(path)
if not os.path.isdir(dir):
os.makedirs(dir)
try:
f = open(path, 'w')
except IOError:
sys.stderr.write('ERROR: Unable to extract file %s\n' % path)
else:
f.write(entry.read())
f.close()
os.chmod(path, entry.mode)
os.chown(path, entry.uid, entry.gid)
cpio.close()
os.unlink(output.name)
os.chdir(cwd)
return True