From 79c36e81be4535b8e45bd33ac95a5b98b2951e37 Mon Sep 17 00:00:00 2001 From: Pavel Raiskup Date: Mon, 28 Aug 2023 22:38:32 +0200 Subject: [PATCH] podman: fallback to normal bootstrap When Podman isn't installed, or the image can not be downloaded, or the image isn't compatible with host arch - Mock newly falls-back to a normal 'dnf --installroot' when bootstrap_image_fallback=True (default). Closes: #1200 --- mock/docs/site-defaults.cfg | 7 ++++++ mock/py/mockbuild/buildroot.py | 44 +++++++++++++++++++++++++++------- mock/py/mockbuild/config.py | 1 + mock/py/mockbuild/podman.py | 20 ++++++++++------ 4 files changed, 57 insertions(+), 15 deletions(-) diff --git a/mock/docs/site-defaults.cfg b/mock/docs/site-defaults.cfg index b5051b415..f60456079 100644 --- a/mock/docs/site-defaults.cfg +++ b/mock/docs/site-defaults.cfg @@ -158,6 +158,13 @@ # options invalidate the effect of this option. #config_opts['bootstrap_image_ready'] = False +# If 'use_bootstrap_image' is True, Mock is instructed download the configured +# container image from image registry. This option controls the behavior when +# the image can not be downloaded. When set to False, Mock fails hard. When +# set to True, Mock falls-back to normal bootstrap chroot installation using +# package manager (e.g. using dnf --installroot). +#config_opts['bootstrap_image_fallback'] = True + # anything you specify with 'bootstrap_*' will be copied to bootstrap config # e.g. config_opts['bootstrap_system_yum_command'] = '/usr/bin/yum-deprecated' will become # config_opts['system_yum_command'] = '/usr/bin/yum-deprecated' for bootstrap config diff --git a/mock/py/mockbuild/buildroot.py b/mock/py/mockbuild/buildroot.py index 646a47d28..bdc013260 100644 --- a/mock/py/mockbuild/buildroot.py +++ b/mock/py/mockbuild/buildroot.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # vim: noai:ts=4:sw=4:expandtab +from contextlib import contextmanager import errno import fcntl import glob @@ -19,7 +20,7 @@ from . import uid from . import util from .exception import (BuildRootLocked, Error, ResultDirNotAccessible, - BadCmdline) + BadCmdline, BootstrapError) from .package_manager import package_manager from .trace_decorator import getLog, traceLog from .podman import Podman @@ -211,6 +212,39 @@ def _init_locked(self): # Detect what package manager to use. self.set_package_manager() + @traceLog() + def _load_from_container_image(self): + if not self.uses_bootstrap_image or self.chroot_was_initialized: + return + + class _FallbackException(Exception): + pass + + @contextmanager + def _fallback(message): + try: + yield + except BootstrapError as exc: + if not self.config["image_fallback"]: + raise + raise _FallbackException( + f"{message}, falling back to bootstrap installation: {exc}" + ) from exc + + try: + with _fallback("Can't work with Podman"): + podman = Podman(self, self.bootstrap_image) + + with _fallback("Can't initialize from bootstrap image"): + podman.pull_image() + podman.cp(self.make_chroot_path(), self.config["tar_binary"]) + file_util.unlink_if_exists(os.path.join(self.make_chroot_path(), + "etc/rpm/macros.image-language-conf")) + except _FallbackException as exc: + getLog().warning("%s", exc) + self.use_bootstrap_image = False + + @traceLog() def _init(self, prebuild): @@ -229,13 +263,7 @@ def _init(self, prebuild): self.plugins.call_hooks('preinit') # intentionally we do not call bootstrap hook here - it does not have sense self.chroot_was_initialized = self.chroot_is_initialized() - if self.uses_bootstrap_image and not self.chroot_was_initialized: - podman = Podman(self, self.bootstrap_image) - podman.pull_image() - podman.cp(self.make_chroot_path(), self.config["tar_binary"]) - file_util.unlink_if_exists(os.path.join(self.make_chroot_path(), - "etc/rpm/macros.image-language-conf")) - + self._load_from_container_image() self._setup_dirs() # /dev is later overwritten by systemd-nspawn, but we need this for diff --git a/mock/py/mockbuild/config.py b/mock/py/mockbuild/config.py index 635bd7ed6..f4648d065 100644 --- a/mock/py/mockbuild/config.py +++ b/mock/py/mockbuild/config.py @@ -85,6 +85,7 @@ def setup_default_config_opts(): config_opts['use_bootstrap_image'] = True config_opts['bootstrap_image'] = 'fedora:latest' config_opts['bootstrap_image_ready'] = False + config_opts['bootstrap_image_fallback'] = True config_opts['internal_dev_setup'] = True diff --git a/mock/py/mockbuild/podman.py b/mock/py/mockbuild/podman.py index 20c46b085..b6979f757 100644 --- a/mock/py/mockbuild/podman.py +++ b/mock/py/mockbuild/podman.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # vim: noai:ts=4:sw=4:expandtab +import os import logging import subprocess from contextlib import contextmanager @@ -10,7 +11,7 @@ from mockbuild.exception import BootstrapError -def podman_check_native_image_architecture(image, logger=None): +def podman_check_native_image_architecture(image, logger=None, podman_binary=None): """ Return True if image's architecture is "native" for this host. Relates: @@ -19,10 +20,11 @@ def podman_check_native_image_architecture(image, logger=None): """ logger = logger or logging.getLogger() + podman = podman_binary or "/usr/bin/podman" logger.info("Checking that %s image matches host's architecture", image) - sys_check_cmd = ["podman", "version", "--format", "{{.OsArch}}"] - image_check_cmd = ["podman", "image", "inspect", - "--format", "{{.Os}}/{{.Architecture}}", image] + sys_check_cmd = [podman, "version", "--format", "{{.OsArch}}"] + image_check_cmd = [podman, "image", "inspect", + "--format", "{{.Os}}/{{.Architecture}}", image] def _podman_query(cmd): return subprocess.check_output(cmd, encoding="utf8").strip() @@ -46,6 +48,10 @@ class Podman: @traceLog() def __init__(self, buildroot, image): + self.podman_binary = "/usr/bin/podman" + if not os.path.exists(self.podman_binary): + raise BootstrapError(f"'{self.podman_binary}' not installed") + self.buildroot = buildroot self.image = image self.container_id = None @@ -56,7 +62,7 @@ def pull_image(self): """ pull the latest image """ logger = getLog() logger.info("Pulling image: %s", self.image) - cmd = ["podman", "pull", self.image] + cmd = [self.podman_binary, "pull", self.image] out, exit_status = util.do_with_status(cmd, env=self.buildroot.env, raiseExc=False, returnOutput=1) if exit_status: @@ -74,8 +80,8 @@ def mounted_image(self): bootstrap chroot directory. """ logger = getLog() - cmd_mount = ["podman", "image", "mount", self.image] - cmd_umount = ["podman", "image", "umount", self.image] + cmd_mount = [self.podman_binary, "image", "mount", self.image] + cmd_umount = [self.podman_binary, "image", "umount", self.image] result = subprocess.run(cmd_mount, capture_output=True, check=False, encoding="utf8") if result.returncode: message = "Podman mount failed: " + result.stderr