def __init__(self, product, arch, dbo, templatedir=None, installpkgs=None, add_templates=None, add_template_vars=None): root = dbo.conf.installroot # use a copy of product so we can modify it locally product = product.copy() product.name = product.name.lower() self.vars = DataHolder(arch=arch, product=product, dbo=dbo, root=root, basearch=arch.basearch, libdir=arch.libdir) self.dbo = dbo self._runner = LoraxTemplateRunner(inroot=root, outroot=root, dbo=dbo, templatedir=templatedir) self.add_templates = add_templates or [] self.add_template_vars = add_template_vars or {} self._installpkgs = installpkgs or [] self._runner.defaults = self.vars self.dbo.reset()
def setUpClass(self): # Create 2 repositories with rpmfluff self.repo1_dir = tempfile.mkdtemp(prefix="lorax.test.repo.") makeFakeRPM(self.repo1_dir, "anaconda-core", 0, "0.0.1", "1") makeFakeRPM(self.repo1_dir, "exact", 0, "1.3.17", "1") makeFakeRPM(self.repo1_dir, "fake-milhouse", 0, "1.0.0", "1", ["/fake-milhouse/1.0.0-1"]) makeFakeRPM(self.repo1_dir, "fake-bart", 0, "1.0.0", "6") makeFakeRPM(self.repo1_dir, "fake-bart", 2, "1.13.0", "6") makeFakeRPM(self.repo1_dir, "fake-bart", 2, "2.3.0", "1") makeFakeRPM(self.repo1_dir, "fake-homer", 0, "0.4.0", "2") makeFakeRPM(self.repo1_dir, "lots-of-files", 0, "0.1.1", "1", [ "/lorax-files/file-one.txt", "/lorax-files/file-two.txt", "/lorax-files/file-three.txt" ]) makeFakeRPM(self.repo1_dir, "known-path", 0, "0.1.8", "1", ["/known-path/file-one.txt"]) os.system("createrepo_c " + self.repo1_dir) self.repo2_dir = tempfile.mkdtemp(prefix="lorax.test.repo.") makeFakeRPM(self.repo2_dir, "fake-milhouse", 0, "1.0.0", "4", ["/fake-milhouse/1.0.0-4"]) makeFakeRPM(self.repo2_dir, "fake-milhouse", 0, "1.0.7", "1", ["/fake-milhouse/1.0.7-1"]) makeFakeRPM(self.repo2_dir, "fake-milhouse", 0, "1.3.0", "1", ["/fake-milhouse/1.3.0-1"]) makeFakeRPM(self.repo2_dir, "fake-lisa", 0, "1.2.0", "1", ["/fake-lisa/1.2.0-1"]) makeFakeRPM(self.repo2_dir, "fake-lisa", 0, "1.1.4", "5", ["/fake-lisa/1.1.4-5"]) os.system("createrepo_c " + self.repo2_dir) self.repo3_dir = tempfile.mkdtemp(prefix="lorax.test.debug.repo.") makeFakeRPM(self.repo3_dir, "fake-marge", 0, "2.3.0", "1", ["/fake-marge/2.3.0-1"]) makeFakeRPM(self.repo3_dir, "fake-marge-debuginfo", 0, "2.3.0", "1", ["/fake-marge/file-one-debuginfo.txt"]) os.system("createrepo_c " + self.repo3_dir) # Get a dbo with just these repos # Setup a template runner self.root_dir = tempfile.mkdtemp(prefix="lorax.test.repo.") sources = [ "file://" + self.repo1_dir, "file://" + self.repo2_dir, "file://" + self.repo3_dir ] self.dnfbase = get_dnf_base_object(self.root_dir, sources, enablerepos=[], disablerepos=[]) self.runner = LoraxTemplateRunner( inroot=self.root_dir, outroot=self.root_dir, dbo=self.dnfbase, templatedir="./tests/pylorax/templates")
def __init__(self, product, arch, inroot, outroot, runtime, isolabel, domacboot=True, doupgrade=True, templatedir=None, add_templates=None, add_template_vars=None, workdir=None): # NOTE: if you pass an arg named "runtime" to a mako template it'll # clobber some mako internal variables - hence "runtime_img". self.vars = DataHolder(arch=arch, product=product, runtime_img=runtime, runtime_base=basename(runtime), inroot=inroot, outroot=outroot, basearch=arch.basearch, libdir=arch.libdir, isolabel=isolabel, udev=udev_escape, domacboot=domacboot, doupgrade=doupgrade, workdir=workdir, lower=string_lower) self._runner = LoraxTemplateRunner(inroot, outroot, templatedir=templatedir) self._runner.defaults = self.vars self.add_templates = add_templates or [] self.add_template_vars = add_template_vars or {} self.templatedir = templatedir self.treeinfo_data = None
class RuntimeBuilder(object): '''Builds the anaconda runtime image.''' def __init__(self, product, arch, dbo, templatedir=None, installpkgs=None, add_templates=None, add_template_vars=None): root = dbo.conf.installroot # use a copy of product so we can modify it locally product = product.copy() product.name = product.name.lower() self.vars = DataHolder(arch=arch, product=product, dbo=dbo, root=root, basearch=arch.basearch, libdir=arch.libdir) self.dbo = dbo self._runner = LoraxTemplateRunner(inroot=root, outroot=root, dbo=dbo, templatedir=templatedir) self.add_templates = add_templates or [] self.add_template_vars = add_template_vars or {} self._installpkgs = installpkgs or [] self._runner.defaults = self.vars self.dbo.reset() def _install_branding(self): release = None q = self.dbo.sack.query() a = q.available() for pkg in a.filter(provides='/etc/system-release'): if pkg.name.startswith('generic'): continue else: release = pkg.name break if not release: logger.error('could not get the release') return # release logger.info('got release: %s', release) self._runner.installpkg(release) # logos release, _suffix = release.split('-', 1) self._runner.installpkg('%s-logos' % release) def install(self): '''Install packages and do initial setup with runtime-install.tmpl''' self._install_branding() if len(self._installpkgs) > 0: self._runner.installpkg(*self._installpkgs) self._runner.run("runtime-install.tmpl") for tmpl in self.add_templates: self._runner.run(tmpl, **self.add_template_vars) def writepkglists(self, pkglistdir): '''debugging data: write out lists of package contents''' if not os.path.isdir(pkglistdir): os.makedirs(pkglistdir) q = self.dbo.sack.query() for pkgobj in q.installed(): with open(joinpaths(pkglistdir, pkgobj.name), "w") as fobj: for fname in pkgobj.files: fobj.write("{0}\n".format(fname)) def postinstall(self): '''Do some post-install setup work with runtime-postinstall.tmpl''' # copy configdir into runtime root beforehand configdir = joinpaths(self._runner.templatedir, "config_files") configdir_path = "tmp/config_files" fullpath = joinpaths(self.vars.root, configdir_path) if os.path.exists(fullpath): remove(fullpath) copytree(configdir, fullpath) self._runner.run("runtime-postinstall.tmpl", configdir=configdir_path) def cleanup(self): '''Remove unneeded packages and files with runtime-cleanup.tmpl''' self._runner.run("runtime-cleanup.tmpl") def verify(self): '''Ensure that contents of the installroot can run''' status = True ELF_MAGIC = b'\x7fELF' # Iterate over all files in /usr/bin and /usr/sbin # For ELF files, gather them into a list and we'll check them all at # the end. For files with a #!, check them as we go elf_files = [] usr_bin = Path(self.vars.root + '/usr/bin') usr_sbin = Path(self.vars.root + '/usr/sbin') for path in (str(x) for x in itertools.chain(usr_bin.iterdir(), usr_sbin.iterdir()) \ if x.is_file()): with open(path, "rb") as f: magic = f.read(4) if magic == ELF_MAGIC: # Save the path, minus the chroot prefix elf_files.append(path[len(self.vars.root):]) elif magic[:2] == b'#!': # Reopen the file as text and read the first line. # Open as latin-1 so that stray 8-bit characters don't make # things blow up. We only really care about ASCII parts. with open(path, "rt", encoding="latin-1") as f_text: # Remove the #!, split on space, and take the first part shabang = f_text.readline()[2:].split()[0] # Does the path exist? if not os.path.exists(self.vars.root + shabang): logger.error('%s, needed by %s, does not exist', shabang, path) status = False # Now, run ldd on all the ELF files # Just run ldd once on everything so it isn't logged a million times. # At least one thing in the list isn't going to be a dynamic executable, # so use execWithCapture to ignore the exit code. filename = '' for line in execWithCapture('ldd', elf_files, root=self.vars.root, log_output=False, filter_stderr=True).split('\n'): if line and not line[0].isspace(): # New filename header, strip the : at the end and save filename = line[:-1] elif 'not found' in line: logger.error('%s, needed by %s, not found', line.split()[0], filename) status = False return status def writepkgsizes(self, pkgsizefile): '''debugging data: write a big list of pkg sizes''' fobj = open(pkgsizefile, "w") getsize = lambda f: os.lstat(f).st_size if os.path.exists(f) else 0 q = self.dbo.sack.query() for p in sorted(q.installed()): pkgsize = sum( getsize(joinpaths(self.vars.root, f)) for f in p.files) fobj.write("{0.name}.{0.arch}: {1}\n".format(p, pkgsize)) def generate_module_data(self): root = self.vars.root moddir = joinpaths(root, "lib/modules/") for kver in os.listdir(moddir): ksyms = joinpaths(root, "boot/System.map-%s" % kver) logger.info("doing depmod and module-info for %s", kver) runcmd(["depmod", "-a", "-F", ksyms, "-b", root, kver]) generate_module_info(moddir + kver, outfile=moddir + "module-info") def create_runtime(self, outfile="/var/tmp/squashfs.img", compression="xz", compressargs=None, size=2): # make live rootfs image - must be named "LiveOS/rootfs.img" for dracut compressargs = compressargs or [] workdir = joinpaths(os.path.dirname(outfile), "runtime-workdir") os.makedirs(joinpaths(workdir, "LiveOS")) imgutils.mkrootfsimg(self.vars.root, joinpaths(workdir, "LiveOS/rootfs.img"), "Anaconda", size=size) # squash the live rootfs and clean up workdir imgutils.mksquashfs(workdir, outfile, compression, compressargs) remove(workdir) def finished(self): """ Done using RuntimeBuilder Close the dnf base object """ self.dbo.close()
class TreeBuilder(object): '''Builds the arch-specific boot images. inroot should be the installtree root (the newly-built runtime dir)''' def __init__(self, product, arch, inroot, outroot, runtime, isolabel, domacboot=True, doupgrade=True, templatedir=None, add_templates=None, add_template_vars=None, workdir=None): # NOTE: if you pass an arg named "runtime" to a mako template it'll # clobber some mako internal variables - hence "runtime_img". self.vars = DataHolder(arch=arch, product=product, runtime_img=runtime, runtime_base=basename(runtime), inroot=inroot, outroot=outroot, basearch=arch.basearch, libdir=arch.libdir, isolabel=isolabel, udev=udev_escape, domacboot=domacboot, doupgrade=doupgrade, workdir=workdir, lower=string_lower) self._runner = LoraxTemplateRunner(inroot, outroot, templatedir=templatedir) self._runner.defaults = self.vars self.add_templates = add_templates or [] self.add_template_vars = add_template_vars or {} self.templatedir = templatedir self.treeinfo_data = None @property def kernels(self): return findkernels(root=self.vars.inroot) def rebuild_initrds(self, add_args=None, backup="", prefix=""): '''Rebuild all the initrds in the tree. If backup is specified, each initrd will be renamed with backup as a suffix before rebuilding. If backup is empty, the existing initrd files will be overwritten. If suffix is specified, the existing initrd is untouched and a new image is built with the filename "${prefix}-${kernel.version}.img" If the initrd doesn't exist its name will be created based on the name of the kernel. ''' add_args = add_args or [] dracut = ["dracut", "--nomdadmconf", "--nolvmconf"] + add_args if not backup: dracut.append("--force") if not self.kernels: raise Exception("No kernels found, cannot rebuild_initrds") # Hush some dracut warnings. TODO: bind-mount proc in place? open(joinpaths(self.vars.inroot, "/proc/modules"), "w") for kernel in self.kernels: if prefix: idir = os.path.dirname(kernel.path) outfile = joinpaths(idir, prefix + '-' + kernel.version + '.img') elif hasattr(kernel, "initrd"): # If there is an existing initrd, use that outfile = kernel.initrd.path else: # Construct an initrd from the kernel name outfile = kernel.path.replace("vmlinuz-", "initrd-") + ".img" logger.info("rebuilding %s", outfile) if backup: initrd = joinpaths(self.vars.inroot, outfile) if os.path.exists(initrd): os.rename(initrd, initrd + backup) cmd = dracut + [outfile, kernel.version] logger.debug("cmd=%s", cmd) runcmd(cmd, root=self.vars.inroot) # ppc64 cannot boot images > 32MiB, check size and warn if self.vars.arch.basearch in ( "ppc64", "ppc64le") and os.path.exists(outfile): st = os.stat(outfile) if st.st_size > 32 * 1024 * 1024: logging.warning("ppc64 initrd %s is > 32MiB", outfile) os.unlink(joinpaths(self.vars.inroot, "/proc/modules")) def build(self): templatefile = templatemap[self.vars.arch.basearch] for tmpl in self.add_templates: self._runner.run(tmpl, **self.add_template_vars) self._runner.run(templatefile, kernels=self.kernels) self.treeinfo_data = self._runner.results.treeinfo self.implantisomd5() def implantisomd5(self): for _section, data in self.treeinfo_data.items(): if 'boot.iso' in data: iso = joinpaths(self.vars.outroot, data['boot.iso']) runcmd(["implantisomd5", iso]) @property def dracut_hooks_path(self): """ Return the path to the lorax dracut hooks scripts Use the configured share dir if it is setup, otherwise default to /usr/share/lorax/dracut_hooks """ if self.templatedir: return joinpaths(self.templatedir, "dracut_hooks") else: return "/usr/share/lorax/dracut_hooks" def copy_dracut_hooks(self, hooks): """ Copy the hook scripts in hooks into the installroot's /tmp/ and return a list of commands to pass to dracut when creating the initramfs hooks is a list of tuples with the name of the hook script and the target dracut hook directory (eg. [("99anaconda-copy-ks.sh", "/lib/dracut/hooks/pre-pivot")]) """ dracut_commands = [] for hook_script, dracut_path in hooks: src = joinpaths(self.dracut_hooks_path, hook_script) if not os.path.exists(src): logger.error("Missing lorax dracut hook script %s", (src)) continue dst = joinpaths(self.vars.inroot, "/tmp/", hook_script) copy2(src, dst) dracut_commands += [ "--include", joinpaths("/tmp/", hook_script), dracut_path ] return dracut_commands
class LoraxTemplateRunnerTestCase(unittest.TestCase): @classmethod def setUpClass(self): # Create 2 repositories with rpmfluff self.repo1_dir = tempfile.mkdtemp(prefix="lorax.test.repo.") makeFakeRPM(self.repo1_dir, "anaconda-core", 0, "0.0.1", "1") makeFakeRPM(self.repo1_dir, "exact", 0, "1.3.17", "1") makeFakeRPM(self.repo1_dir, "fake-milhouse", 0, "1.0.0", "1") makeFakeRPM(self.repo1_dir, "fake-bart", 2, "1.13.0", "6") makeFakeRPM(self.repo1_dir, "fake-homer", 0, "0.4.0", "2") makeFakeRPM(self.repo1_dir, "lots-of-files", 0, "0.1.1", "1", [ "/lorax-files/file-one.txt", "/lorax-files/file-two.txt", "/lorax-files/file-three.txt" ]) makeFakeRPM(self.repo1_dir, "known-path", 0, "0.1.8", "1", ["/known-path/file-one.txt"]) os.system("createrepo_c " + self.repo1_dir) self.repo2_dir = tempfile.mkdtemp(prefix="lorax.test.repo.") makeFakeRPM(self.repo2_dir, "fake-milhouse", 0, "1.3.0", "1") makeFakeRPM(self.repo2_dir, "fake-lisa", 0, "1.2.0", "1") os.system("createrepo_c " + self.repo2_dir) self.repo3_dir = tempfile.mkdtemp(prefix="lorax.test.debug.repo.") makeFakeRPM(self.repo3_dir, "fake-marge", 0, "2.3.0", "1", ["/fake-marge/file-one.txt"]) makeFakeRPM(self.repo3_dir, "fake-marge-debuginfo", 0, "2.3.0", "1", ["/fake-marge/file-one-debuginfo.txt"]) os.system("createrepo_c " + self.repo3_dir) # Get a dbo with just these repos # Setup a template runner self.root_dir = tempfile.mkdtemp(prefix="lorax.test.repo.") sources = [ "file://" + self.repo1_dir, "file://" + self.repo2_dir, "file://" + self.repo3_dir ] self.dnfbase = get_dnf_base_object(self.root_dir, sources, enablerepos=[], disablerepos=[]) self.runner = LoraxTemplateRunner( inroot=self.root_dir, outroot=self.root_dir, dbo=self.dnfbase, templatedir="./tests/pylorax/templates") @classmethod def tearDownClass(self): shutil.rmtree(self.repo1_dir) shutil.rmtree(self.repo2_dir) shutil.rmtree(self.root_dir) def test_00_runner_multi_repo(self): """Test installing packages with updates in a 2nd repo""" # If this does not raise an error it means that: # Installing a named package works (anaconda-core) # Installing a pinned package works (exact-1.3.17) # Installing a globbed set of package names from multiple repos works # removepkg removes a package's files # removefrom removes some, but not all, of a package's files # # These all need to be done in one template because run_pkg_transaction can only run once self.runner.run("install-test.tmpl") self.runner.run("install-remove-test.tmpl") self.assertFalse( os.path.exists(joinpaths(self.root_dir, "/known-path/file-one.txt"))) self.assertTrue( os.path.exists( joinpaths(self.root_dir, "/lorax-files/file-one.txt"))) self.assertFalse( os.path.exists( joinpaths(self.root_dir, "/lorax-files/file-two.txt"))) # Check the debug log self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/root/debug-pkgs.log"))) def test_install_file(self): """Test append, and install template commands""" self.runner.run("install-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/etc/lorax-test"))) with open(joinpaths(self.root_dir, "/etc/lorax-test")) as f: data = f.read() self.assertEqual(data, "TESTING LORAX TEMPLATES\n") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/etc/lorax-test-dest"))) def test_installimg(self): """Test installimg template command""" self.runner.run("installimg-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "images/product.img"))) def test_mkdir(self): """Test mkdir template command""" self.runner.run("mkdir-cmd.tmpl") self.assertTrue( os.path.isdir(joinpaths(self.root_dir, "/etc/lorax-mkdir"))) def test_replace(self): """Test append, and replace template command""" self.runner.run("replace-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/etc/lorax-replace"))) with open(joinpaths(self.root_dir, "/etc/lorax-replace")) as f: data = f.read() self.assertEqual(data, "Running 1.2.3 for lorax\n") def test_treeinfo(self): """Test treeinfo template command""" self.runner.run("treeinfo-cmd.tmpl") self.assertEqual(self.runner.results.treeinfo["images"]["boot.iso"], "images/boot.iso") def test_installkernel(self): """Test installkernel template command""" self.runner.run("installkernel-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/kernels/vmlinuz"))) self.assertEqual(self.runner.results.treeinfo["images"]["kernel"], "/kernels/vmlinuz") def test_installinitrd(self): """Test installinitrd template command""" self.runner.run("installinitrd-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/kernels/initrd.img"))) self.assertEqual(self.runner.results.treeinfo["images"]["initrd"], "/kernels/initrd.img") def test_installupgradeinitrd(self): """Test installupgraedinitrd template command""" self.runner.run("installupgradeinitrd-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/kernels/upgrade.img"))) self.assertEqual(self.runner.results.treeinfo["images"]["upgrade"], "/kernels/upgrade.img") def test_hardlink(self): """Test hardlink template command""" self.runner.run("hardlink-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/linked-file"))) self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/lorax-dir/lorax-file"))) def test_symlink(self): """Test symlink template command""" self.runner.run("symlink-cmd.tmpl") self.assertTrue( os.path.islink(joinpaths(self.root_dir, "/symlinked-file"))) def test_copy(self): """Test copy template command""" self.runner.run("copy-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/copied-file"))) def test_move(self): """Test move template command""" self.runner.run("move-cmd.tmpl") self.assertFalse( os.path.exists(joinpaths(self.root_dir, "/lorax-file"))) self.assertTrue(os.path.exists(joinpaths(self.root_dir, "/moved-file"))) def test_remove(self): """Test remove template command""" self.runner.run("remove-cmd.tmpl") self.assertFalse( os.path.exists(joinpaths(self.root_dir, "/lorax-file"))) def test_chmod(self): """Test chmod template command""" self.runner.run("chmod-cmd.tmpl") self.assertEqual( os.stat(joinpaths(self.root_dir, "/lorax-file")).st_mode, 0o100641) def test_runcmd(self): """Test runcmd template command""" self.runner.run("runcmd-cmd.tmpl", root=self.root_dir) self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/lorax-runcmd"))) def test_removekmod(self): """Test removekmod template command""" self.runner.run("removekmod-cmd.tmpl") self.assertTrue( os.path.exists( joinpaths(self.root_dir, "/lib/modules/1.2.3/kernel/drivers/video/bar1.ko"))) self.assertFalse( os.path.exists( joinpaths(self.root_dir, "/lib/modules/1.2.3/kernel/drivers/video/bar2.ko"))) self.assertFalse( os.path.exists( joinpaths(self.root_dir, "/lib/modules/1.2.3/kernel/sound/foo1.ko"))) self.assertFalse( os.path.exists( joinpaths(self.root_dir, "/lib/modules/1.2.3/kernel/sound/foo2.ko"))) def test_createaddrsize(self): """Test createaddrsize template command""" self.runner.run("createaddrsize-cmd.tmpl", root=self.root_dir) self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/initrd.addrsize"))) def test_systemctl(self): """Test systemctl template command""" self.runner.run("systemctl-cmd.tmpl") self.assertTrue( os.path.islink( joinpaths( self.root_dir, "/etc/systemd/system/multi-user.target.wants/foo.service")) ) def test_bad_template(self): """Test parsing a bad template""" with self.assertRaises(Exception): self.runner.run("bad-template.tmpl") def test_unknown_cmd(self): """Test a template with an unknown command""" with self.assertRaises(ValueError): self.runner.run("unknown-cmd.tmpl")
class RuntimeBuilder(object): '''Builds the anaconda runtime image.''' def __init__(self, product, arch, dbo, templatedir=None, installpkgs=None, add_templates=None, add_template_vars=None): root = dbo.conf.installroot # use a copy of product so we can modify it locally product = product.copy() product.name = product.name.lower() self.vars = DataHolder(arch=arch, product=product, dbo=dbo, root=root, basearch=arch.basearch, libdir=arch.libdir) self.dbo = dbo self._runner = LoraxTemplateRunner(inroot=root, outroot=root, dbo=dbo, templatedir=templatedir) self.add_templates = add_templates or [] self.add_template_vars = add_template_vars or {} self._installpkgs = installpkgs or [] self._runner.defaults = self.vars self.dbo.reset() def _install_branding(self): release = None q = self.dbo.sack.query() a = q.available() for pkg in a.filter(provides='/etc/system-release'): if pkg.name.startswith('generic'): continue else: release = pkg.name break if not release: logger.error('could not get the release') return # release logger.info('got release: %s', release) self._runner.installpkg(release) # logos release, _suffix = release.split('-', 1) self._runner.installpkg('%s-logos' % release) def install(self): '''Install packages and do initial setup with runtime-install.tmpl''' self._install_branding() if len(self._installpkgs) > 0: self._runner.installpkg(*self._installpkgs) self._runner.run("runtime-install.tmpl") for tmpl in self.add_templates: self._runner.run(tmpl, **self.add_template_vars) def writepkglists(self, pkglistdir): '''debugging data: write out lists of package contents''' if not os.path.isdir(pkglistdir): os.makedirs(pkglistdir) q = self.dbo.sack.query() for pkgobj in q.installed(): with open(joinpaths(pkglistdir, pkgobj.name), "w") as fobj: for fname in pkgobj.files: fobj.write("{0}\n".format(fname)) def postinstall(self): '''Do some post-install setup work with runtime-postinstall.tmpl''' # copy configdir into runtime root beforehand configdir = joinpaths(self._runner.templatedir,"config_files") configdir_path = "tmp/config_files" fullpath = joinpaths(self.vars.root, configdir_path) if os.path.exists(fullpath): remove(fullpath) copytree(configdir, fullpath) self._runner.run("runtime-postinstall.tmpl", configdir=configdir_path) def cleanup(self): '''Remove unneeded packages and files with runtime-cleanup.tmpl''' self._runner.run("runtime-cleanup.tmpl") def verify(self): '''Ensure that contents of the installroot can run''' status = True ELF_MAGIC = b'\x7fELF' # Iterate over all files in /usr/bin and /usr/sbin # For ELF files, gather them into a list and we'll check them all at # the end. For files with a #!, check them as we go elf_files = [] usr_bin = Path(self.vars.root + '/usr/bin') usr_sbin = Path(self.vars.root + '/usr/sbin') for path in (str(x) for x in itertools.chain(usr_bin.iterdir(), usr_sbin.iterdir()) \ if x.is_file()): with open(path, "rb") as f: magic = f.read(4) if magic == ELF_MAGIC: # Save the path, minus the chroot prefix elf_files.append(path[len(self.vars.root):]) elif magic[:2] == b'#!': # Reopen the file as text and read the first line. # Open as latin-1 so that stray 8-bit characters don't make # things blow up. We only really care about ASCII parts. with open(path, "rt", encoding="latin-1") as f_text: # Remove the #!, split on space, and take the first part shabang = f_text.readline()[2:].split()[0] # Does the path exist? if not os.path.exists(self.vars.root + shabang): logger.error('%s, needed by %s, does not exist', shabang, path) status = False # Now, run ldd on all the ELF files # Just run ldd once on everything so it isn't logged a million times. # At least one thing in the list isn't going to be a dynamic executable, # so use execWithCapture to ignore the exit code. filename = '' for line in execWithCapture('ldd', elf_files, root=self.vars.root, log_output=False, filter_stderr=True).split('\n'): if line and not line[0].isspace(): # New filename header, strip the : at the end and save filename = line[:-1] elif 'not found' in line: logger.error('%s, needed by %s, not found', line.split()[0], filename) status = False return status def writepkgsizes(self, pkgsizefile): '''debugging data: write a big list of pkg sizes''' fobj = open(pkgsizefile, "w") getsize = lambda f: os.lstat(f).st_size if os.path.exists(f) else 0 q = self.dbo.sack.query() for p in sorted(q.installed()): pkgsize = sum(getsize(joinpaths(self.vars.root,f)) for f in p.files) fobj.write("{0.name}.{0.arch}: {1}\n".format(p, pkgsize)) def generate_module_data(self): root = self.vars.root moddir = joinpaths(root, "lib/modules/") for kver in os.listdir(moddir): ksyms = joinpaths(root, "boot/System.map-%s" % kver) logger.info("doing depmod and module-info for %s", kver) runcmd(["depmod", "-a", "-F", ksyms, "-b", root, kver]) generate_module_info(moddir+kver, outfile=moddir+"module-info") def create_runtime(self, outfile="/var/tmp/squashfs.img", compression="xz", compressargs=None, size=2): # make live rootfs image - must be named "LiveOS/rootfs.img" for dracut compressargs = compressargs or [] workdir = joinpaths(os.path.dirname(outfile), "runtime-workdir") os.makedirs(joinpaths(workdir, "LiveOS")) imgutils.mkrootfsimg(self.vars.root, joinpaths(workdir, "LiveOS/rootfs.img"), "Anaconda", size=size) # squash the live rootfs and clean up workdir imgutils.mksquashfs(workdir, outfile, compression, compressargs) remove(workdir) def finished(self): """ Done using RuntimeBuilder Close the dnf base object """ self.dbo.close()
class TreeBuilder(object): '''Builds the arch-specific boot images. inroot should be the installtree root (the newly-built runtime dir)''' def __init__(self, product, arch, inroot, outroot, runtime, isolabel, domacboot=True, doupgrade=True, templatedir=None, add_templates=None, add_template_vars=None, workdir=None): # NOTE: if you pass an arg named "runtime" to a mako template it'll # clobber some mako internal variables - hence "runtime_img". self.vars = DataHolder(arch=arch, product=product, runtime_img=runtime, runtime_base=basename(runtime), inroot=inroot, outroot=outroot, basearch=arch.basearch, libdir=arch.libdir, isolabel=isolabel, udev=udev_escape, domacboot=domacboot, doupgrade=doupgrade, workdir=workdir, lower=string_lower) self._runner = LoraxTemplateRunner(inroot, outroot, templatedir=templatedir) self._runner.defaults = self.vars self.add_templates = add_templates or [] self.add_template_vars = add_template_vars or {} self.templatedir = templatedir self.treeinfo_data = None @property def kernels(self): return findkernels(root=self.vars.inroot) def rebuild_initrds(self, add_args=None, backup="", prefix=""): '''Rebuild all the initrds in the tree. If backup is specified, each initrd will be renamed with backup as a suffix before rebuilding. If backup is empty, the existing initrd files will be overwritten. If suffix is specified, the existing initrd is untouched and a new image is built with the filename "${prefix}-${kernel.version}.img" ''' add_args = add_args or [] dracut = ["dracut", "--nomdadmconf", "--nolvmconf"] + add_args if not backup: dracut.append("--force") kernels = [kernel for kernel in self.kernels if hasattr(kernel, "initrd")] if not kernels: raise Exception("No initrds found, cannot rebuild_initrds") # Hush some dracut warnings. TODO: bind-mount proc in place? open(joinpaths(self.vars.inroot,"/proc/modules"),"w") for kernel in kernels: if prefix: idir = os.path.dirname(kernel.initrd.path) outfile = joinpaths(idir, prefix+'-'+kernel.version+'.img') else: outfile = kernel.initrd.path logger.info("rebuilding %s", outfile) if backup: initrd = joinpaths(self.vars.inroot, outfile) os.rename(initrd, initrd + backup) cmd = dracut + [outfile, kernel.version] runcmd(cmd, root=self.vars.inroot) # ppc64 cannot boot images > 32MiB, check size and warn if self.vars.arch.basearch in ("ppc64", "ppc64le") and os.path.exists(outfile): st = os.stat(outfile) if st.st_size > 32 * 1024 * 1024: logging.warning("ppc64 initrd %s is > 32MiB", outfile) os.unlink(joinpaths(self.vars.inroot,"/proc/modules")) def build(self): templatefile = templatemap[self.vars.arch.basearch] for tmpl in self.add_templates: self._runner.run(tmpl, **self.add_template_vars) self._runner.run(templatefile, kernels=self.kernels) self.treeinfo_data = self._runner.results.treeinfo self.implantisomd5() def implantisomd5(self): for _section, data in self.treeinfo_data.items(): if 'boot.iso' in data: iso = joinpaths(self.vars.outroot, data['boot.iso']) runcmd(["implantisomd5", iso]) @property def dracut_hooks_path(self): """ Return the path to the lorax dracut hooks scripts Use the configured share dir if it is setup, otherwise default to /usr/share/lorax/dracut_hooks """ if self.templatedir: return joinpaths(self.templatedir, "dracut_hooks") else: return "/usr/share/lorax/dracut_hooks" def copy_dracut_hooks(self, hooks): """ Copy the hook scripts in hooks into the installroot's /tmp/ and return a list of commands to pass to dracut when creating the initramfs hooks is a list of tuples with the name of the hook script and the target dracut hook directory (eg. [("99anaconda-copy-ks.sh", "/lib/dracut/hooks/pre-pivot")]) """ dracut_commands = [] for hook_script, dracut_path in hooks: src = joinpaths(self.dracut_hooks_path, hook_script) if not os.path.exists(src): logger.error("Missing lorax dracut hook script %s", (src)) continue dst = joinpaths(self.vars.inroot, "/tmp/", hook_script) copy2(src, dst) dracut_commands += ["--include", joinpaths("/tmp/", hook_script), dracut_path] return dracut_commands
class RuntimeBuilder(object): '''Builds the anaconda runtime image.''' def __init__(self, product, arch, dbo, templatedir=None, installpkgs=None, excludepkgs=None, add_templates=None, add_template_vars=None, skip_branding=False): root = dbo.conf.installroot self.dbo = dbo self._runner = LoraxTemplateRunner(inroot=root, outroot=root, dbo=dbo, templatedir=templatedir) self.add_templates = add_templates or [] self.add_template_vars = add_template_vars or {} self._installpkgs = installpkgs or [] self._excludepkgs = excludepkgs or [] self.dbo.reset() # use a copy of product so we can modify it locally product = product.copy() product.name = product.name.lower() self._branding = self.get_branding(skip_branding, product) self.vars = DataHolder(arch=arch, product=product, dbo=dbo, root=root, basearch=arch.basearch, libdir=arch.libdir, branding=self._branding) self._runner.defaults = self.vars def get_branding(self, skip, product): """Select the branding from the available 'system-release' packages The *best* way to control this is to have a single package in the repo provide 'system-release' When there are more than 1 package it will: - Make a list of the available packages - If variant is set look for a package ending with lower(variant) and use that - If there are one or more non-generic packages, use the first one after sorting Returns the package names of the system-release and release logos package """ if skip: return DataHolder(release=None, logos=None) release = None q = self.dbo.sack.query() a = q.available() pkgs = sorted([p.name for p in a.filter(provides='system-release') if not p.name.startswith("generic")]) if not pkgs: logger.error("No system-release packages found, could not get the release") return DataHolder(release=None, logos=None) logger.debug("system-release packages: %s", pkgs) if product.variant: variant = [p for p in pkgs if p.endswith("-"+product.variant.lower())] if variant: release = variant[0] if not release: release = pkgs[0] # release logger.info('got release: %s', release) # logos uses the basename from release (fedora, redhat, centos, ...) logos, _suffix = release.split('-', 1) return DataHolder(release=release, logos=logos+"-logos") def install(self): '''Install packages and do initial setup with runtime-install.tmpl''' if self._branding.release: self._runner.installpkg(self._branding.release) if self._branding.logos: self._runner.installpkg(self._branding.logos) if len(self._installpkgs) > 0: self._runner.installpkg(*self._installpkgs) if len(self._excludepkgs) > 0: self._runner.removepkg(*self._excludepkgs) self._runner.run("runtime-install.tmpl") for tmpl in self.add_templates: self._runner.run(tmpl, **self.add_template_vars) def writepkglists(self, pkglistdir): '''debugging data: write out lists of package contents''' if not os.path.isdir(pkglistdir): os.makedirs(pkglistdir) q = self.dbo.sack.query() for pkgobj in q.installed(): with open(joinpaths(pkglistdir, pkgobj.name), "w") as fobj: for fname in pkgobj.files: fobj.write("{0}\n".format(fname)) def postinstall(self): '''Do some post-install setup work with runtime-postinstall.tmpl''' # copy configdir into runtime root beforehand configdir = joinpaths(self._runner.templatedir,"config_files") configdir_path = "tmp/config_files" fullpath = joinpaths(self.vars.root, configdir_path) if os.path.exists(fullpath): remove(fullpath) copytree(configdir, fullpath) self._runner.run("runtime-postinstall.tmpl", configdir=configdir_path) def cleanup(self): '''Remove unneeded packages and files with runtime-cleanup.tmpl''' self._runner.run("runtime-cleanup.tmpl") def verify(self): '''Ensure that contents of the installroot can run''' status = True ELF_MAGIC = b'\x7fELF' # Iterate over all files in /usr/bin and /usr/sbin # For ELF files, gather them into a list and we'll check them all at # the end. For files with a #!, check them as we go elf_files = [] usr_bin = Path(self.vars.root + '/usr/bin') usr_sbin = Path(self.vars.root + '/usr/sbin') for path in (str(x) for x in itertools.chain(usr_bin.iterdir(), usr_sbin.iterdir()) \ if x.is_file()): with open(path, "rb") as f: magic = f.read(4) if magic == ELF_MAGIC: # Save the path, minus the chroot prefix elf_files.append(path[len(self.vars.root):]) elif magic[:2] == b'#!': # Reopen the file as text and read the first line. # Open as latin-1 so that stray 8-bit characters don't make # things blow up. We only really care about ASCII parts. with open(path, "rt", encoding="latin-1") as f_text: # Remove the #!, split on space, and take the first part shabang = f_text.readline()[2:].split()[0] # Does the path exist? if not os.path.exists(self.vars.root + shabang): logger.error('%s, needed by %s, does not exist', shabang, path) status = False # Now, run ldd on all the ELF files # Just run ldd once on everything so it isn't logged a million times. # At least one thing in the list isn't going to be a dynamic executable, # so use execWithCapture to ignore the exit code. filename = '' for line in execWithCapture('ldd', elf_files, root=self.vars.root, log_output=False, filter_stderr=True).split('\n'): if line and not line[0].isspace(): # New filename header, strip the : at the end and save filename = line[:-1] elif 'not found' in line: logger.error('%s, needed by %s, not found', line.split()[0], filename) status = False return status def writepkgsizes(self, pkgsizefile): '''debugging data: write a big list of pkg sizes''' fobj = open(pkgsizefile, "w") getsize = lambda f: os.lstat(f).st_size if os.path.exists(f) else 0 q = self.dbo.sack.query() for p in sorted(q.installed()): pkgsize = sum(getsize(joinpaths(self.vars.root,f)) for f in p.files) fobj.write("{0.name}.{0.arch}: {1}\n".format(p, pkgsize)) def generate_module_data(self): root = self.vars.root moddir = joinpaths(root, "lib/modules/") for kernel in findkernels(root=root): ksyms = joinpaths(root, "boot/System.map-%s" % kernel.version) logger.info("doing depmod and module-info for %s", kernel.version) runcmd(["depmod", "-a", "-F", ksyms, "-b", root, kernel.version]) generate_module_info(moddir+kernel.version, outfile=moddir+"module-info") def create_squashfs_runtime(self, outfile="/var/tmp/squashfs.img", compression="xz", compressargs=None, size=2): """Create a plain squashfs runtime""" compressargs = compressargs or [] os.makedirs(os.path.dirname(outfile)) # squash the rootfs return imgutils.mksquashfs(self.vars.root, outfile, compression, compressargs) def create_ext4_runtime(self, outfile="/var/tmp/squashfs.img", compression="xz", compressargs=None, size=2): """Create a squashfs compressed ext4 runtime""" # make live rootfs image - must be named "LiveOS/rootfs.img" for dracut compressargs = compressargs or [] workdir = joinpaths(os.path.dirname(outfile), "runtime-workdir") os.makedirs(joinpaths(workdir, "LiveOS")) # Catch problems with the rootfs being too small and clearly log them try: imgutils.mkrootfsimg(self.vars.root, joinpaths(workdir, "LiveOS/rootfs.img"), "Anaconda", size=size) except CalledProcessError as e: if e.stdout and "No space left on device" in e.stdout: logger.error("The rootfs ran out of space with size=%d", size) raise # squash the live rootfs and clean up workdir rc = imgutils.mksquashfs(workdir, outfile, compression, compressargs) remove(workdir) return rc def finished(self): """ Done using RuntimeBuilder Close the dnf base object """ self.dbo.close()
class LoraxTemplateRunnerTestCase(unittest.TestCase): @classmethod def setUpClass(self): # Create 2 repositories with rpmfluff self.repo1_dir = tempfile.mkdtemp(prefix="lorax.test.repo.") makeFakeRPM(self.repo1_dir, "anaconda-core", 0, "0.0.1", "1") makeFakeRPM(self.repo1_dir, "exact", 0, "1.3.17", "1") makeFakeRPM(self.repo1_dir, "fake-milhouse", 0, "1.0.0", "1", ["/fake-milhouse/1.0.0-1"]) makeFakeRPM(self.repo1_dir, "fake-bart", 0, "1.0.0", "6") makeFakeRPM(self.repo1_dir, "fake-bart", 2, "1.13.0", "6") makeFakeRPM(self.repo1_dir, "fake-bart", 2, "2.3.0", "1") makeFakeRPM(self.repo1_dir, "fake-homer", 0, "0.4.0", "2") makeFakeRPM(self.repo1_dir, "lots-of-files", 0, "0.1.1", "1", [ "/lorax-files/file-one.txt", "/lorax-files/file-two.txt", "/lorax-files/file-three.txt" ]) makeFakeRPM(self.repo1_dir, "known-path", 0, "0.1.8", "1", ["/known-path/file-one.txt"]) os.system("createrepo_c " + self.repo1_dir) self.repo2_dir = tempfile.mkdtemp(prefix="lorax.test.repo.") makeFakeRPM(self.repo2_dir, "fake-milhouse", 0, "1.0.0", "4", ["/fake-milhouse/1.0.0-4"]) makeFakeRPM(self.repo2_dir, "fake-milhouse", 0, "1.0.7", "1", ["/fake-milhouse/1.0.7-1"]) makeFakeRPM(self.repo2_dir, "fake-milhouse", 0, "1.3.0", "1", ["/fake-milhouse/1.3.0-1"]) makeFakeRPM(self.repo2_dir, "fake-lisa", 0, "1.2.0", "1", ["/fake-lisa/1.2.0-1"]) makeFakeRPM(self.repo2_dir, "fake-lisa", 0, "1.1.4", "5", ["/fake-lisa/1.1.4-5"]) os.system("createrepo_c " + self.repo2_dir) self.repo3_dir = tempfile.mkdtemp(prefix="lorax.test.debug.repo.") makeFakeRPM(self.repo3_dir, "fake-marge", 0, "2.3.0", "1", ["/fake-marge/2.3.0-1"]) makeFakeRPM(self.repo3_dir, "fake-marge-debuginfo", 0, "2.3.0", "1", ["/fake-marge/file-one-debuginfo.txt"]) os.system("createrepo_c " + self.repo3_dir) # Get a dbo with just these repos # Setup a template runner self.root_dir = tempfile.mkdtemp(prefix="lorax.test.repo.") sources = [ "file://" + self.repo1_dir, "file://" + self.repo2_dir, "file://" + self.repo3_dir ] self.dnfbase = get_dnf_base_object(self.root_dir, sources, enablerepos=[], disablerepos=[]) self.runner = LoraxTemplateRunner( inroot=self.root_dir, outroot=self.root_dir, dbo=self.dnfbase, templatedir="./tests/pylorax/templates") @classmethod def tearDownClass(self): shutil.rmtree(self.repo1_dir) shutil.rmtree(self.repo2_dir) shutil.rmtree(self.root_dir) def test_pkgver_errors(self): """Test error states of _pkgver""" with self.assertRaises(RuntimeError) as e: self.runner._pkgver("=") self.assertEqual(str(e.exception), "Missing package name") with self.assertRaises(RuntimeError) as e: self.runner._pkgver("foopkg=") self.assertEqual(str(e.exception), "Missing version") with self.assertRaises(RuntimeError) as e: self.runner._pkgver("foopkg>1.0.0-1<1.0.6-1") self.assertEqual(str(e.exception), "Too many comparisons") def test_00_pkgver(self): """Test all the version comparison operators with pkgver""" matrix = [ ("fake-milhouse>=2.1.0-1", ""), # Not available ("fake-bart>=2:3.0.0-2", ""), # Not available ("fake-bart>2:1.13.0-6", "fake-bart-2:2.3.0-1"), ("fake-bart<2:1.13.0-6", "fake-bart-1.0.0-6"), ("fake-milhouse==1.3.0-1", "fake-milhouse-1.3.0-1"), ("fake-milhouse=1.3.0-1", "fake-milhouse-1.3.0-1"), ("fake-milhouse=1.0.0-4", "fake-milhouse-1.0.0-4"), ("fake-milhouse!=1.3.0-1", "fake-milhouse-1.0.7-1"), ("fake-milhouse<>1.3.0-1", "fake-milhouse-1.0.7-1"), ("fake-milhouse>1.0.0-4", "fake-milhouse-1.3.0-1"), ("fake-milhouse>=1.3.0", "fake-milhouse-1.3.0-1"), ("fake-milhouse>=1.0.7-1", "fake-milhouse-1.3.0-1"), ("fake-milhouse=>1.0.0-4", "fake-milhouse-1.3.0-1"), ("fake-milhouse<=1.0.0-4", "fake-milhouse-1.0.0-4"), ("fake-milhouse=<1.0.7-1", "fake-milhouse-1.0.7-1"), ("fake-milhouse<1.3.0", "fake-milhouse-1.0.7-1"), ("fake-milhouse<1.3.0-1", "fake-milhouse-1.0.7-1"), ("fake-milhouse<1.0.7-1", "fake-milhouse-1.0.0-4"), ] def nevra(pkg): if pkg.epoch: return "{}-{}:{}-{}".format(pkg.name, pkg.epoch, pkg.version, pkg.release) else: return "{}-{}-{}".format(pkg.name, pkg.version, pkg.release) print([nevra(p) for p in list(self.dnfbase.sack.query().available())]) for t in matrix: r = self.runner._pkgver(t[0]) if t[1]: self.assertTrue(len(r) > 0, t[0]) self.assertEqual(nevra(self.runner._pkgver(t[0])[0]), t[1], t[0]) else: self.assertEqual(r, [], t[0]) def test_01_runner_multi_repo(self): """Test installing packages with updates in a 2nd repo""" # If this does not raise an error it means that: # Installing a named package works (anaconda-core) # Installing a pinned package works (exact-1.3.17) # Installing a globbed set of package names from multiple repos works # Installing a package using version compare # removepkg removes a package's files # removefrom removes some, but not all, of a package's files # # These all need to be done in one template because run_pkg_transaction can only run once self.runner.run("install-test.tmpl") self.runner.run("install-remove-test.tmpl") def exists(p): return os.path.exists(joinpaths(self.root_dir, p)) self.assertFalse(exists("/known-path/file-one.txt")) self.assertTrue(exists("/lorax-files/file-one.txt")) self.assertFalse(exists("/lorax-files/file-two.txt")) self.assertTrue(exists("/fake-marge/2.3.0-1")) # Check the debug log self.assertTrue(exists("/root/debug-pkgs.log")) # Check package version installs self.assertTrue(exists("/fake-lisa/1.1.4-5")) self.assertFalse(exists("/fake-lisa/1.2.0-1")) self.assertTrue(exists("/fake-milhouse/1.3.0-1")) def test_install_file(self): """Test append, and install template commands""" self.runner.run("install-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/etc/lorax-test"))) with open(joinpaths(self.root_dir, "/etc/lorax-test")) as f: data = f.read() self.assertEqual(data, "TESTING LORAX TEMPLATES\n") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/etc/lorax-test-dest"))) def test_installimg(self): """Test installimg template command""" self.runner.run("installimg-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "images/product.img"))) def test_mkdir(self): """Test mkdir template command""" self.runner.run("mkdir-cmd.tmpl") self.assertTrue( os.path.isdir(joinpaths(self.root_dir, "/etc/lorax-mkdir"))) def test_replace(self): """Test append, and replace template command""" self.runner.run("replace-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/etc/lorax-replace"))) with open(joinpaths(self.root_dir, "/etc/lorax-replace")) as f: data = f.read() self.assertEqual(data, "Running 1.2.3 for lorax\n") def test_treeinfo(self): """Test treeinfo template command""" self.runner.run("treeinfo-cmd.tmpl") self.assertEqual(self.runner.results.treeinfo["images"]["boot.iso"], "images/boot.iso") def test_installkernel(self): """Test installkernel template command""" self.runner.run("installkernel-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/kernels/vmlinuz"))) self.assertEqual(self.runner.results.treeinfo["images"]["kernel"], "/kernels/vmlinuz") def test_installinitrd(self): """Test installinitrd template command""" self.runner.run("installinitrd-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/kernels/initrd.img"))) self.assertEqual(self.runner.results.treeinfo["images"]["initrd"], "/kernels/initrd.img") def test_installupgradeinitrd(self): """Test installupgraedinitrd template command""" self.runner.run("installupgradeinitrd-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/kernels/upgrade.img"))) self.assertEqual(self.runner.results.treeinfo["images"]["upgrade"], "/kernels/upgrade.img") def test_hardlink(self): """Test hardlink template command""" self.runner.run("hardlink-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/linked-file"))) self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/lorax-dir/lorax-file"))) def test_symlink(self): """Test symlink template command""" self.runner.run("symlink-cmd.tmpl") self.assertTrue( os.path.islink(joinpaths(self.root_dir, "/symlinked-file"))) def test_copy(self): """Test copy template command""" self.runner.run("copy-cmd.tmpl") self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/copied-file"))) def test_move(self): """Test move template command""" self.runner.run("move-cmd.tmpl") self.assertFalse( os.path.exists(joinpaths(self.root_dir, "/lorax-file"))) self.assertTrue(os.path.exists(joinpaths(self.root_dir, "/moved-file"))) def test_remove(self): """Test remove template command""" self.runner.run("remove-cmd.tmpl") self.assertFalse( os.path.exists(joinpaths(self.root_dir, "/lorax-file"))) def test_chmod(self): """Test chmod template command""" self.runner.run("chmod-cmd.tmpl") self.assertEqual( os.stat(joinpaths(self.root_dir, "/lorax-file")).st_mode, 0o100641) def test_runcmd(self): """Test runcmd template command""" self.runner.run("runcmd-cmd.tmpl", root=self.root_dir) self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/lorax-runcmd"))) def test_removekmod(self): """Test removekmod template command""" self.runner.run("removekmod-cmd.tmpl") self.assertTrue( os.path.exists( joinpaths(self.root_dir, "/lib/modules/1.2.3/kernel/drivers/video/bar1.ko"))) self.assertFalse( os.path.exists( joinpaths(self.root_dir, "/lib/modules/1.2.3/kernel/drivers/video/bar2.ko"))) self.assertFalse( os.path.exists( joinpaths(self.root_dir, "/lib/modules/1.2.3/kernel/sound/foo1.ko"))) self.assertFalse( os.path.exists( joinpaths(self.root_dir, "/lib/modules/1.2.3/kernel/sound/foo2.ko"))) def test_createaddrsize(self): """Test createaddrsize template command""" self.runner.run("createaddrsize-cmd.tmpl", root=self.root_dir) self.assertTrue( os.path.exists(joinpaths(self.root_dir, "/initrd.addrsize"))) def test_systemctl(self): """Test systemctl template command""" self.runner.run("systemctl-cmd.tmpl") self.assertTrue( os.path.islink( joinpaths( self.root_dir, "/etc/systemd/system/multi-user.target.wants/foo.service")) ) def test_bad_template(self): """Test parsing a bad template""" with self.assertRaises(Exception): self.runner.run("bad-template.tmpl") def test_unknown_cmd(self): """Test a template with an unknown command""" with self.assertRaises(ValueError): self.runner.run("unknown-cmd.tmpl")
class RuntimeBuilder(object): '''Builds the anaconda runtime image.''' def __init__(self, product, arch, dbo, templatedir=None, installpkgs=None, add_templates=None, add_template_vars=None): root = dbo.conf.installroot # use a copy of product so we can modify it locally product = product.copy() product.name = product.name.lower() self.vars = DataHolder(arch=arch, product=product, dbo=dbo, root=root, basearch=arch.basearch, libdir=arch.libdir) self.dbo = dbo self._runner = LoraxTemplateRunner(inroot=root, outroot=root, dbo=dbo, templatedir=templatedir) self.add_templates = add_templates or [] self.add_template_vars = add_template_vars or {} self._installpkgs = installpkgs or [] self._runner.defaults = self.vars self.dbo.reset() def _install_branding(self): release = None q = self.dbo.sack.query() a = q.available() for pkg in a.filter(provides='/etc/system-release'): if pkg.name.startswith('generic'): continue else: release = pkg.name break if not release: logger.error('could not get the release') return # release logger.info('got release: %s', release) self._runner.installpkg(release) # logos release, _suffix = release.split('-', 1) self._runner.installpkg('%s-logos' % release) def install(self): '''Install packages and do initial setup with runtime-install.tmpl''' self._install_branding() if len(self._installpkgs) > 0: self._runner.installpkg(*self._installpkgs) self._runner.run("runtime-install.tmpl") for tmpl in self.add_templates: self._runner.run(tmpl, **self.add_template_vars) def writepkglists(self, pkglistdir): '''debugging data: write out lists of package contents''' if not os.path.isdir(pkglistdir): os.makedirs(pkglistdir) q = self.dbo.sack.query() for pkgobj in q.installed(): with open(joinpaths(pkglistdir, pkgobj.name), "w") as fobj: for fname in pkgobj.files: fobj.write("{0}\n".format(fname)) def postinstall(self): '''Do some post-install setup work with runtime-postinstall.tmpl''' # copy configdir into runtime root beforehand configdir = joinpaths(self._runner.templatedir,"config_files") configdir_path = "tmp/config_files" fullpath = joinpaths(self.vars.root, configdir_path) if os.path.exists(fullpath): remove(fullpath) copytree(configdir, fullpath) self._runner.run("runtime-postinstall.tmpl", configdir=configdir_path) def cleanup(self): '''Remove unneeded packages and files with runtime-cleanup.tmpl''' self._runner.run("runtime-cleanup.tmpl") def writepkgsizes(self, pkgsizefile): '''debugging data: write a big list of pkg sizes''' fobj = open(pkgsizefile, "w") getsize = lambda f: os.lstat(f).st_size if os.path.exists(f) else 0 q = self.dbo.sack.query() for p in sorted(q.installed()): pkgsize = sum(getsize(joinpaths(self.vars.root,f)) for f in p.files) fobj.write("{0.name}.{0.arch}: {1}\n".format(p, pkgsize)) def generate_module_data(self): root = self.vars.root moddir = joinpaths(root, "lib/modules/") for kver in os.listdir(moddir): ksyms = joinpaths(root, "boot/System.map-%s" % kver) logger.info("doing depmod and module-info for %s", kver) runcmd(["depmod", "-a", "-F", ksyms, "-b", root, kver]) generate_module_info(moddir+kver, outfile=moddir+"module-info") def create_runtime(self, outfile="/var/tmp/squashfs.img", compression="xz", compressargs=None, size=2): # make live rootfs image - must be named "LiveOS/rootfs.img" for dracut compressargs = compressargs or [] workdir = joinpaths(os.path.dirname(outfile), "runtime-workdir") os.makedirs(joinpaths(workdir, "LiveOS")) imgutils.mkrootfsimg(self.vars.root, joinpaths(workdir, "LiveOS/rootfs.img"), "Anaconda", size=size) # squash the live rootfs and clean up workdir imgutils.mksquashfs(workdir, outfile, compression, compressargs) remove(workdir) def finished(self): """ Done using RuntimeBuilder Close the dnf base object """ self.dbo.close()