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(Test, module_framework.CommonFunctions):

    def setUp(self):

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

    def _process_mockcfg(self):

        profile_name = brtconfig.get_test_profile(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 profile_name not in mod_yaml["data"]["profiles"].keys():
            self.error("'%s' key was not found in 'profiles' section" % profile_name)

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

        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 _set_dnf_conf(self):
        filename = "/etc/dnf/dnf.conf"
        path = "/var/lib/mock/" + self.mock_root + "/root" + filename

        conf = "EOF\n"
        conf += "[main]\n"
        conf += "gpgcheck=1\n"
        conf += "installonly_limit=3\n"
        conf += "clean_requirements_on_remove=True\n"
        conf += "EOF\n"

        cmd = "sudo tee %s << %s" % (path, conf)
        self._run_command(cmd)


    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)

        self._set_dnf_conf()

        # 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

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

        docker_labels = brtconfig.get_docker_labels(self)
        #Dockerfile to use when building final image
        dockerfile = 'EOF\n'
        dockerfile += 'FROM %s\n' % img_scratch
        #Set default locale to C.utf8
        dockerfile += 'ENV LANG C.utf8\n'
        if docker_labels:
            for key in docker_labels.keys():
                dockerfile += 'LABEL %s="%s"\n' % (key, docker_labels[key])
        dockerfile += 'EOF\n'

        # Build final image with extra information from dockerfile
        self._run_command("docker build -t %s - << %s" %
                          (self.br_image_name, dockerfile))
        #Remove temporary image
        self._run_command("docker rmi %s" % img_scratch)

if __name__ == "__main__":
    main()