#!/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()