Ejemplo n.º 1
0
 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()
Ejemplo n.º 2
0
    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")
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
 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()
Ejemplo n.º 6
0
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()
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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")
Ejemplo n.º 9
0
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()
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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()
Ejemplo n.º 12
0
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")
Ejemplo n.º 13
0
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()