Blob Blame History Raw
#!/usr/bin/env python

import os
import subprocess
import re
import sys
import configparser
import tempfile

from avocado import main
from avocado import Test
from moduleframework import module_framework

import cleanup
import brtconfig


class BaseRuntimeSetupDocker(module_framework.CommonFunctions, Test):

    def setUp(self):

        self.mockcfg = brtconfig.get_mockcfg(self)
        self.br_image_name = brtconfig.get_docker_image_name(self)

    def _process_mockcfg(self):

        mockcfg = self.mockcfg

        mock_root = ''
        mockcfg_lines = []
        #Regex to get packages that are configured on mockcfg to be installed
        chroot_setup_pkg_regex = re.compile("config_opts\s*\[\s*'chroot_setup_cmd'\s*\]\s*="
                                            "\s*'install --setopt=tsflags=nodocs\s*(.*)\s*'")
        chroot_setup_pkgs = None
        with open(mockcfg, 'r') as mock_cfgfile:
            found_setup_cmd = False
            for line in mock_cfgfile:
                mockcfg_lines.append(line)
                if re.match("config_opts\s*\[\s*'root'\s*\]", line) is not None:
                    mock_root = line.split('=')[1].split("'")[1]
                if re.match("config_opts\s*\[\s*'chroot_setup_cmd'\s*\]", line) is not None:
                    found_setup_cmd = True
                    #Check if there are packages defined on chroot_setup_cmd
                    m = chroot_setup_pkg_regex.match(line)
                    if m:
                        chroot_setup_pkgs = sorted(m.group(1).split())
        if len(mock_root) == 0:
            self.error("mock configuration file %s does not specify mock root" %
                mockcfg)
        self.log.info("mock root: %s" % mock_root)
        self.mock_root = mock_root

        if not found_setup_cmd:
            self.error("mock configuration file %s does not define chroot_setup_cmd" % mockcfg)

        #Need to get all packages that need to be installed
        mod_yaml = self.getModulemdYamlconfig()
        if not mod_yaml:
            self.error("Could not read modulemd Yaml file")

        if "data" not in mod_yaml.keys():
            self.error("'data' key was not found in modulemd Yaml file")

        if "profiles" not in mod_yaml["data"].keys():
            self.error("'profiles' key was not found in 'data' section")

        if "container" not in mod_yaml["data"]["profiles"].keys():
            self.error("'container' key was not found in 'profiles' section")

        base_profile = mod_yaml["data"]["profiles"]["container"]
        if "rpms" not in base_profile.keys():
            self.error("'rpms' key was not found in 'container' profile")

        req_pkgs = base_profile["rpms"]
        if not req_pkgs:
            self.error("Could not find any package to be installed in the image")

        #Only update mockcfg if the list of packages changed
        if cmp(chroot_setup_pkgs, sorted(req_pkgs)):
            #Need to change chroot_setup_cmd line on mockcfg file
            setup_cmd = "install --setopt=tsflags=nodocs "
            setup_cmd += " ".join(req_pkgs)
            with open(mockcfg, 'w') as mock_cfgfile:
                for line in mockcfg_lines:
                    if re.match("config_opts\s*\[\s*'chroot_setup_cmd'\s*\]", line) is not None:
                        line = "config_opts['chroot_setup_cmd'] = '%s'\n" % setup_cmd
                    mock_cfgfile.write(line)

            #Test will exit with WARN to inform the config file has changed
            self.log.warning("List of packages to be installed by mock changed")

    def _run_command(self, cmd):
        try:
            cmd_output = subprocess.check_output(
                cmd, stderr=subprocess.STDOUT, shell=True)
        except subprocess.CalledProcessError as e:
            self.error("command '%s' returned exit status %d; output:\n%s" %
                       (e.cmd, e.returncode, e.output))
        else:
            self.log.info("command  '%s' succeeded with output:\n%s" %
                          (cmd, cmd_output))

    def _configure_mock_microdnf(self):
        """
        Configure mock chroot for microdnf so it carrys into the docker image
        """

        # fetch the dnf.conf file from the mock chroot that was conveniently
        # created based on the yum.conf value in the mock configuration file
        tmpdnfcfg = tempfile.NamedTemporaryFile(delete=False)
        self._run_command('mock -r %s --copyout /etc/dnf/dnf.conf %s' %
                          (self.mockcfg, tmpdnfcfg.name))

        with open(tmpdnfcfg.name, 'r') as dnffile:
            contents = dnffile.read()
        self.log.info(
            "Contents of original dnf.conf generated by mock:\n%s" % contents)

        # load the dnf.conf file and remove the [main] section so only the
        # repo section(s) remain
        config = configparser.ConfigParser()
        config.read(tmpdnfcfg.name)
        self.log.info("Found the following configuration section(s): %s" %
                      ' '.join(config.sections()))
        if 'main' in config:
            del config['main']

        # write out the cleaned up repo configuration
        tmpyumcfg = tempfile.NamedTemporaryFile(delete=False)
        with open(tmpyumcfg.name, 'w') as repofile:
            config.write(repofile, space_around_delimiters=False)

        with open(tmpyumcfg.name, 'r') as yumfile:
            contents = yumfile.read()
        self.log.info("Contents of revised yum repo config:\n%s" % contents)

        # copy the new yum repo configuration file into the mock chroot
        self._run_command(
            'mock -r %s --copyin %s /etc/yum.repos.d/build.repo' % (self.mockcfg, tmpyumcfg.name))
        self._run_command(
            'mock -r %s --chroot "chmod 644 /etc/yum.repos.d/build.repo"' % self.mockcfg)

        # remove the temporary files
        os.remove(tmpdnfcfg.name)
        os.remove(tmpyumcfg.name)

        # /etc/pki/rpm-gpg directory must exist or microdnf will explode
        self._run_command(
            'mock -r %s --chroot "mkdir -p -m=755 /etc/pki/rpm-gpg"' % self.mockcfg)

    def testCreateDockerImage(self):

        self._process_mockcfg()

        # Clean-up any old test artifacts (docker containers, image, mock root)
        # first:
        try:
            cleanup.cleanup_docker_and_mock(self.mockcfg, self.br_image_name)
        except:
            self.error("artifact cleanup failed")
        else:
            self.log.info("artifact cleanup successful")

        # Initialize chroot with mock
        self._run_command('mock -r %s --init' % self.mockcfg)

        # Configure mock chroot for microdnf so it carrys into the docker image
        self._configure_mock_microdnf()

        # check if "sudo" allows us to tar up the chroot without a password
        # Note: this must be configured in "sudoers" to work!
        tar_cmd = "tar -C /var/lib/mock/%s/root -c ." % self.mock_root
        try:
            cmd_output = subprocess.check_output(
                "sudo -n %s >/dev/null" % tar_cmd,
                stderr=subprocess.STDOUT, shell=True)
        except subprocess.CalledProcessError as e:
            # no luck using "sudo", warn and proceed as ordinary user without
            # it
            self.log.info("command '%s' returned exit status %d; output:\n%s" %
                          (e.cmd, e.returncode, e.output))
            self.log.warning("NO SUDO RIGHTS TO RUN COMMAND '%s' AS ROOT" %
                             tar_cmd)
            self.log.warning("GENERATED DOCKER IMAGE '%s' MAY BE INCOMPLETE!" %
                             self.br_image_name)
        else:
            # "sudo" works, so use it
            tar_cmd = "sudo -n " + tar_cmd

        # Import mock chroot as a docker image
        self._run_command("%s | docker import - %s" %
                          (tar_cmd, self.br_image_name))

if __name__ == "__main__":
    main()