kiwi.runtime_checker

   1# Copyright (c) 2015 SUSE Linux GmbH.  All rights reserved.
   2#
   3# This file is part of kiwi.
   4#
   5# kiwi is free software: you can redistribute it and/or modify
   6# it under the terms of the GNU General Public License as published by
   7# the Free Software Foundation, either version 3 of the License, or
   8# (at your option) any later version.
   9#
  10# kiwi is distributed in the hope that it will be useful,
  11# but WITHOUT ANY WARRANTY; without even the implied warranty of
  12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13# GNU General Public License for more details.
  14#
  15# You should have received a copy of the GNU General Public License
  16# along with kiwi.  If not, see <http://www.gnu.org/licenses/>
  17#
  18import os
  19import json
  20import re
  21import logging
  22from textwrap import dedent
  23from typing import (
  24    NamedTuple, Dict, List, Any
  25)
  26
  27# project
  28from kiwi.version import __version__
  29from io import StringIO
  30from kiwi.xml_description import XMLDescription
  31from kiwi.firmware import FirmWare
  32from kiwi.xml_state import XMLState
  33from kiwi.system.uri import Uri
  34from kiwi.defaults import Defaults
  35from kiwi.command import Command
  36from kiwi.path import Path
  37from kiwi.utils.command_capabilities import CommandCapabilities
  38from kiwi.runtime_config import RuntimeConfig
  39from kiwi.exceptions import (
  40    KiwiRuntimeError
  41)
  42
  43import kiwi.defaults as defaults
  44
  45dracut_module_type = NamedTuple(
  46    'dracut_module_type', [
  47        ('package', str),
  48        ('min_version', str)
  49    ]
  50)
  51
  52log = logging.getLogger('kiwi')
  53
  54
  55class RuntimeChecker:
  56    """
  57    **Implements build consistency checks at runtime**
  58    """
  59    def __init__(self, xml_state: XMLState) -> None:
  60        """
  61        The schema of an image description covers structure and syntax of
  62        the provided data. The RuntimeChecker provides methods to perform
  63        further semantic checks which allows to recognize potential build
  64        or boot problems early.
  65
  66        :param object xml_state: Instance of XMLState
  67        """
  68        self.xml_state = xml_state
  69
  70    def check_repositories_configured(self) -> None:
  71        """
  72        Verify that there are repositories configured
  73        """
  74        if not self.xml_state.get_repository_sections():
  75            raise KiwiRuntimeError(
  76                'No repositories configured'
  77            )
  78
  79    @staticmethod
  80    def check_target_dir_on_unsupported_filesystem(target_dir: str) -> None:
  81        """
  82        Raise if the given target dir does not reside on a
  83        filesystem that supports all important features like
  84        extended permissions(fscaps), ACLs or xattrs.
  85        """
  86        message = dedent('''\n
  87            Target root/image directory is lacking filesystem features
  88
  89            The filesystem {0} in the target path {1}
  90            does not support important features like extended permissions,
  91            ACLs or xattrs. The image build may fail or the resulting
  92            image misbehave.
  93        ''')
  94        target_dir = Path.first_exists(target_dir)
  95        stat = Command.run(['stat', '-f', '-c', '%T', target_dir])
  96        if stat:
  97            target_fs = stat.output.strip()
  98            supported_target_filesystem = (
  99                'btrfs',
 100                'ext2',
 101                'ext2/ext3',
 102                'ext3',
 103                'ext4',
 104                'overlayfs',
 105                'tmpfs',
 106                'xfs',
 107            )
 108            if target_fs not in supported_target_filesystem:
 109                raise KiwiRuntimeError(message.format(target_fs, target_dir))
 110
 111    def check_include_references_unresolvable(self) -> None:
 112        """
 113        Raise for still included <include> statements as not resolvable.
 114        The KIWI XSLT processing replaces the specified include
 115        directive(s) with the given file reference(s). If this action
 116        did not happen for example on nested includes, it can happen
 117        that they stay in the document as sort of waste.
 118        """
 119        message = dedent('''\n
 120            One ore more <include> statements are unresolvable
 121
 122            The following include references could not be resolved.
 123            Please verify the specified location(s) and/or delete
 124            the broken include directive(s) from the description.
 125            Please also note, nested includes from other include
 126            files are not supported:
 127
 128            {0}
 129        ''')
 130        include_files = \
 131            self.xml_state.get_include_section_reference_file_names()
 132        if include_files:
 133            raise KiwiRuntimeError(
 134                message.format(json.dumps(include_files, indent=4))
 135            )
 136
 137    def check_image_include_repos_publicly_resolvable(self) -> None:
 138        """
 139        Verify that all repos marked with the imageinclude attribute
 140        can be resolved into a http based web URL
 141        """
 142        message = dedent('''\n
 143            The use of imageinclude="true" in the repository definition
 144            for the Repository: {0}
 145            requires the repository to by publicly available. The source
 146            locator of the repository however indicates it is private to
 147            your local system. Therefore it can't be included into the
 148            system image repository configuration. Please define a publicly
 149            available repository in your image XML description.
 150        ''')
 151
 152        repository_sections = self.xml_state.get_repository_sections()
 153        for xml_repo in repository_sections:
 154            repo_marked_for_image_include = xml_repo.get_imageinclude()
 155
 156            if repo_marked_for_image_include:
 157                repo_source = xml_repo.get_source().get_path()
 158                repo_type = xml_repo.get_type()
 159                uri = Uri(repo_source, repo_type)
 160                if not uri.is_public():
 161                    raise KiwiRuntimeError(
 162                        message.format(repo_source)
 163                    )
 164
 165    @staticmethod
 166    def check_target_directory_not_in_shared_cache(target_dir: str) -> None:
 167        """
 168        The target directory must be outside of the kiwi shared cache
 169        directory in order to avoid busy mounts because kiwi bind mounts
 170        the cache directory into the image root tree to access host
 171        caching information
 172
 173        :param string target_dir: path name
 174        """
 175        message = dedent('''\n
 176            Target directory %s conflicts with kiwi's shared cache
 177            directory %s. This is going to create a busy loop mount.
 178            Please choose another target directory.
 179        ''')
 180
 181        shared_cache_location = Defaults.get_shared_cache_location()
 182
 183        target_dir_stack = os.path.abspath(
 184            os.path.normpath(target_dir)
 185        ).replace(os.sep + os.sep, os.sep).split(os.sep)
 186        if target_dir_stack[1:4] == shared_cache_location.split(os.sep):
 187            raise KiwiRuntimeError(
 188                message % (target_dir, shared_cache_location)
 189            )
 190
 191    def check_volume_label_used_with_lvm(self) -> None:
 192        """
 193        The optional volume label in a systemdisk setup is only
 194        effective if the LVM, logical volume manager system is
 195        used. In any other case where the filesystem itself offers
 196        volume management capabilities there are no extra filesystem
 197        labels which can be applied per volume
 198        """
 199        message = dedent('''\n
 200            Custom volume label setup used without LVM
 201
 202            The optional volume label in a systemdisk setup is only
 203            effective if the LVM, logical volume manager system is
 204            used. Your setup uses the {0} filesystem which itself
 205            offers volume management capabilities. Extra filesystem
 206            labels cannot be applied in this case.
 207
 208            If you want to force LVM over the {0} volume management
 209            system you can do so by specifying the following in
 210            your KIWI XML description:
 211
 212            <systemdisk ... preferlvm="true">
 213                <volume .../>
 214            </systemdisk>
 215        ''')
 216        volume_management = self.xml_state.get_volume_management()
 217        if volume_management != 'lvm':
 218            for volume in self.xml_state.get_volumes():
 219                if volume.label and volume.label != 'SWAP':
 220                    raise KiwiRuntimeError(
 221                        message.format(volume_management)
 222                    )
 223
 224    def check_partuuid_persistency_type_used_with_mbr(self) -> None:
 225        """
 226        The devicepersistency setting by-partuuid can only be
 227        used in combination with a partition table type that
 228        supports UUIDs. In any other case Linux creates artificial
 229        values for PTUUID and PARTUUID from the disk signature
 230        which can change without touching the actual partition
 231        table. We consider this unsafe and only allow the use
 232        of by-partuuid in combination with partition tables that
 233        actually supports it properly.
 234        """
 235        message = dedent('''\n
 236            devicepersistency={0!r} used with non UUID capable partition table
 237
 238            PTUUID and PARTUUID exists in the GUID (GPT) partition table.
 239            According to the firmware setting: {1!r}, the selected partition
 240            table type is: {2!r}. This table type does not natively support
 241            UUIDs. In such a case Linux creates artificial values for PTUUID
 242            and PARTUUID from the disk signature which can change without
 243            touching the actual partition table. This is considered unsafe
 244            and KIWI only allows the use of by-partuuid in combination with
 245            partition tables that actually supports UUIDs properly.
 246
 247            Please make sure to use one of the following firmware settings
 248            which leads to an image using an UUID capable partition table
 249            and therefore supporting consistent by-partuuid device names:
 250
 251            <type ... firmware="efi|uefi">
 252        ''')
 253        persistency_type = self.xml_state.build_type.get_devicepersistency()
 254        if persistency_type and persistency_type == 'by-partuuid':
 255            supported_table_types = ['gpt']
 256            firmware = FirmWare(self.xml_state)
 257            table_type = firmware.get_partition_table_type()
 258            if table_type not in supported_table_types:
 259                raise KiwiRuntimeError(
 260                    message.format(
 261                        persistency_type, firmware.firmware, table_type
 262                    )
 263                )
 264
 265    def check_swap_name_used_with_lvm(self) -> None:
 266        """
 267        The optional oem-swapname is only effective if used together
 268        with the LVM volume manager. A name for the swap space can
 269        only be set if it is created as a LVM volume. In any other
 270        case the name does not apply to the system
 271        """
 272        message = dedent('''\n
 273             Specified swap space name: {0} will not be used
 274
 275             The specified oem-swapname is used without the LVM volume
 276             manager. This means the swap space will be created as simple
 277             partition for which no name assignment can take place.
 278             The name specified in oem-swapname is used to give the
 279             LVM swap volume a name. Outside of LVM the setting is
 280             meaningless and should be removed.
 281
 282             Please delete the following setting from your image
 283             description:
 284
 285             <oem-swapname>{0}</oem-swapname>
 286        ''')
 287        volume_management = self.xml_state.get_volume_management()
 288        if volume_management != 'lvm':
 289            oemconfig = self.xml_state.get_build_type_oemconfig_section()
 290            if oemconfig and oemconfig.get_oem_swapname():
 291                raise KiwiRuntimeError(
 292                    message.format(oemconfig.get_oem_swapname()[0])
 293                )
 294
 295    def check_volume_setup_defines_reserved_labels(self) -> None:
 296        message = dedent('''\n
 297            Reserved label name used in LVM volume setup
 298
 299            The label setup for volume {0} uses the reserved label {1}.
 300            Reserved labels used by KIWI internally are {2}. Please
 301            choose another label name for this volume.
 302        ''')
 303        reserved_labels = [
 304            self.xml_state.build_type.get_rootfs_label() or 'ROOT',
 305            'SWAP', 'SPARE'
 306        ]
 307        volume_management = self.xml_state.get_volume_management()
 308        if volume_management == 'lvm':
 309            for volume in self.xml_state.get_volumes():
 310                # A swap volume is created implicitly if oem-swap is
 311                # requested. This volume detected via realpath set to
 312                # swap is skipped from the reserved label check as it
 313                # intentionally uses the reserved label named SWAP
 314                if volume.realpath != 'swap':
 315                    if volume.label and volume.label in reserved_labels:
 316                        raise KiwiRuntimeError(
 317                            message.format(
 318                                volume.name, volume.label, reserved_labels
 319                            )
 320                        )
 321
 322    def check_volume_setup_defines_multiple_fullsize_volumes(self) -> None:
 323        """
 324        The volume size specification 'all' makes this volume to
 325        take the rest space available on the system. It's only
 326        allowed to specify one all size volume
 327        """
 328        message = dedent('''\n
 329            Multiple all size volumes found but only one is allowed
 330
 331            The volume size specification 'all' makes this volume to
 332            take the rest space available on the system. It's only
 333            allowed to specify one all size volume
 334        ''')
 335        systemdisk_section = self.xml_state.get_build_type_system_disk_section()
 336        if systemdisk_section:
 337            all_size_volume_count = 0
 338            volumes = systemdisk_section.get_volume() or []
 339            for volume in volumes:
 340                size = volume.get_size() or volume.get_freespace()
 341                if size and 'all' in size:
 342                    all_size_volume_count += 1
 343            if all_size_volume_count > 1:
 344                raise KiwiRuntimeError(message)
 345
 346    def check_volume_setup_has_no_root_definition(self) -> None:
 347        """
 348        The root volume in a systemdisk setup is handled in a special
 349        way. It is not allowed to setup a custom name or mountpoint for
 350        the root volume. Therefore the size of the root volume can be
 351        setup via the @root volume name. This check looks up the volume
 352        setup and searches if there is a configuration for the '/'
 353        mountpoint which would cause the image build to fail
 354        """
 355        message = dedent('''\n
 356            Volume setup for "/" found. The size of the root volume
 357            must be specified via the @root volume name like the
 358            following example shows:
 359
 360            <volume name="@root" size="42G"/>
 361
 362            A custom name or mountpoint for the root volume is not
 363            allowed.
 364        ''')
 365        for volume in self.xml_state.get_volumes():
 366            if volume.mountpoint == '/':
 367                raise KiwiRuntimeError(message)
 368
 369    def check_container_tool_chain_installed(self) -> None:
 370        """
 371        When creating container images the specific tools are used in order
 372        to import and export OCI or Docker compatible images. This check
 373        searches for those tools to be installed in the build system and
 374        fails if it can't find them
 375        """
 376        message_tool_not_found = dedent('''\n
 377            Required tool {name} not found in caller environment
 378
 379            Creation of OCI or Docker images requires the tools {name} and
 380            skopeo to be installed on the build system. For SUSE based systems
 381            you can find the tools at:
 382
 383            http://download.opensuse.org/repositories/Virtualization:/containers
 384        ''')
 385        message_version_unsupported = dedent('''\n
 386            {name} tool found with unknown version
 387        ''')
 388        message_unknown_tool = dedent('''\n
 389            Unknown tool: {0}.
 390
 391            Please configure KIWI with an appropriate value (umoci or buildah).
 392            Consider this runtime configuration file syntax (/etc/kiwi.yml):
 393
 394            oci:
 395                - archive_tool: umoci | buildah
 396        ''')
 397
 398        expected_version = (0, 1, 0)
 399
 400        if self.xml_state.get_build_type_name() in ['docker', 'oci']:
 401            runtime_config = RuntimeConfig()
 402            tool_name = runtime_config.get_oci_archive_tool()
 403            if tool_name == 'buildah':
 404                oci_tools = ['buildah', 'skopeo']
 405            elif tool_name == 'umoci':
 406                oci_tools = ['umoci', 'skopeo']
 407            else:
 408                raise KiwiRuntimeError(message_unknown_tool.format(tool_name))
 409            for tool in oci_tools:
 410                if not Path.which(filename=tool, access_mode=os.X_OK):
 411                    raise KiwiRuntimeError(
 412                        message_tool_not_found.format(name=tool)
 413                    )
 414                elif not CommandCapabilities.check_version(
 415                    tool, expected_version, raise_on_error=False
 416                ):
 417                    raise KiwiRuntimeError(
 418                        message_version_unsupported.format(name=tool)
 419                    )
 420            self._check_multitag_support()
 421
 422    def _check_multitag_support(self) -> None:
 423        message = dedent('''\n
 424            Using additionaltags attribute requires skopeo tool to be
 425            capable to handle it, it must include the '--additional-tag'
 426            option for copy subcommand (check it running 'skopeo copy
 427            --help').\n
 428            It is known to be present since v0.1.30
 429        ''')
 430        if 'additional_names' in self.xml_state.get_container_config():
 431            if not CommandCapabilities.has_option_in_help(
 432                'skopeo', '--additional-tag', ['copy', '--help'],
 433                raise_on_error=False
 434            ):
 435                raise KiwiRuntimeError(message)
 436
 437    def check_luksformat_options_valid(self) -> None:
 438        """
 439        Options set via the luksformat element are passed along
 440        to the cryptsetup tool. Only options that are known to
 441        the tool should be allowed. Thus this runtime check looks
 442        up the provided option names if they exist in the cryptsetup
 443        version used on the build host
 444        """
 445        message = dedent('''\n
 446            Option {0!r} not found in cryptsetup
 447
 448            The Option {0!r} could not be found in the help output
 449            of the cryptsetup tool.
 450        ''')
 451        luksformat = self.xml_state.build_type.get_luksformat()
 452        if luksformat:
 453            for option in luksformat[0].get_option():
 454                argument = option.get_name()
 455                if not CommandCapabilities.has_option_in_help(
 456                    'cryptsetup', argument, ['--help'],
 457                    raise_on_error=False
 458                ):
 459                    raise KiwiRuntimeError(message.format(argument))
 460
 461    def check_appx_naming_conventions_valid(self) -> None:
 462        """
 463        When building wsl images there are some naming conventions that
 464        must be fulfilled to run the container on Microsoft Windows
 465        """
 466        launcher_pattern = r'[^\\]+(\.[Ee][Xx][Ee])$'
 467        message_container_launcher_invalid = dedent('''\n
 468            Invalid WSL launcher name: {0}
 469
 470            WSL launcher name must match the pattern: {1}
 471        ''')
 472        id_pattern = r'^[a-zA-Z0-9]+$'
 473        message_container_id_invalid = dedent('''\n
 474            Invalid WSL container application id: {0}
 475
 476            WSL container id must match the pattern: {1}
 477        ''')
 478        build_type = self.xml_state.get_build_type_name()
 479        container_config = self.xml_state.get_container_config()
 480        container_history = container_config.get('history') or {}
 481        if build_type == 'appx' and container_config:
 482            launcher = container_history.get('launcher')
 483            if launcher and not re.match(launcher_pattern, launcher):
 484                raise KiwiRuntimeError(
 485                    message_container_launcher_invalid.format(
 486                        launcher, launcher_pattern
 487                    )
 488                )
 489            application_id = container_history.get('application_id')
 490            if application_id and not re.match(id_pattern, application_id):
 491                raise KiwiRuntimeError(
 492                    message_container_id_invalid.format(
 493                        application_id, id_pattern
 494                    )
 495                )
 496
 497    def check_initrd_selection_required(self) -> None:
 498        """
 499        If the boot attribute is used without selecting kiwi
 500        as the initrd_system, the setting of the boot attribute
 501        will not have any effect. We assume that configurations
 502        which explicitly specify the boot attribute wants to use
 503        the custom kiwi initrd system and not dracut.
 504        """
 505        message_kiwi_initrd_system_not_selected = dedent('''\n
 506            Missing initrd_system selection for boot attribute
 507
 508            The selected boot="'{0}'" boot description indicates
 509            the custom kiwi initrd system should be used instead
 510            of dracut. If this is correct please explicitly request
 511            the kiwi initrd system as follows:
 512
 513            <type initrd_system="kiwi"/>
 514
 515            If this is not want you want and dracut should be used
 516            as initrd system, please delete the boot attribute
 517            as it is obsolete in this case.
 518        ''')
 519        initrd_system = self.xml_state.get_initrd_system()
 520        boot_image_reference = self.xml_state.build_type.get_boot()
 521        if initrd_system != 'kiwi' and boot_image_reference:
 522            raise KiwiRuntimeError(
 523                message_kiwi_initrd_system_not_selected.format(
 524                    boot_image_reference
 525                )
 526            )
 527
 528    def check_boot_description_exists(self) -> None:
 529        """
 530        If a kiwi initrd is used, a lookup to the specified boot
 531        description is done and fails early if it does not exist
 532        """
 533        message_no_boot_reference = dedent('''\n
 534            Boot description missing for '{0}' type
 535
 536            The selected '{1}' initrd_system requires a boot description
 537            reference. Please update your type setup as follows
 538
 539            <type image="{0}" boot="{0}boot/..."/>
 540
 541            A collection of custom boot descriptions can be found
 542            in the kiwi-boot-descriptions package
 543        ''')
 544        message_boot_description_not_found = dedent('''\n
 545            Boot description '{0}' not found
 546
 547            The selected boot description could not be found on
 548            the build host. A collection of custom boot descriptions
 549            can be found in the kiwi-boot-descriptions package
 550        ''')
 551        image_types_supporting_custom_boot_description = ['oem', 'pxe']
 552        build_type = self.xml_state.get_build_type_name()
 553        initrd_system = self.xml_state.get_initrd_system()
 554        if initrd_system == 'kiwi' and \
 555           build_type in image_types_supporting_custom_boot_description:
 556
 557            boot_image_reference = self.xml_state.build_type.get_boot()
 558            if not boot_image_reference:
 559                raise KiwiRuntimeError(
 560                    message_no_boot_reference.format(build_type, initrd_system)
 561                )
 562
 563            if not boot_image_reference[0] == os.sep:
 564                boot_image_reference = os.sep.join(
 565                    [
 566                        Defaults.get_boot_image_description_path(),
 567                        boot_image_reference
 568                    ]
 569                )
 570            if not os.path.exists(boot_image_reference):
 571                raise KiwiRuntimeError(
 572                    message_boot_description_not_found.format(
 573                        boot_image_reference
 574                    )
 575                )
 576
 577    def check_consistent_kernel_in_boot_and_system_image(self) -> None:
 578        """
 579        If a kiwi initrd is used, the kernel used to build the kiwi
 580        initrd and the kernel used in the system image must be the
 581        same in order to avoid an inconsistent boot setup
 582        """
 583        message = dedent('''\n
 584            Possible kernel mismatch between kiwi initrd and system image
 585
 586            The selected '{0}' boot image kernel is '{1}'. However this
 587            kernel package was not explicitly listed in the package list
 588            of the system image. Please fixup your system image
 589            description:
 590
 591            1) Add <package name="{1}"/> to your system XML description
 592
 593            2) Inherit kernel from system description to initrd via
 594               the custom kernel profile:
 595
 596               <type ... bootkernel="custom" .../>
 597
 598               <packages type="image"/>
 599                   <package name="desired-kernel" bootinclude="true"/>
 600               </packages>
 601        ''')
 602        boot_image_reference = self.xml_state.build_type.get_boot()
 603        boot_kernel_package_name = None
 604        if boot_image_reference:
 605            if not boot_image_reference[0] == '/':
 606                boot_image_reference = os.sep.join(
 607                    [
 608                        Defaults.get_boot_image_description_path(),
 609                        boot_image_reference
 610                    ]
 611                )
 612            boot_config_file = os.sep.join(
 613                [boot_image_reference, 'config.xml']
 614            )
 615            if os.path.exists(boot_config_file):
 616                boot_description = XMLDescription(
 617                    description=boot_config_file,
 618                    derived_from=self.xml_state.xml_data.description_dir
 619                )
 620                boot_kernel_profile = \
 621                    self.xml_state.build_type.get_bootkernel()
 622                if not boot_kernel_profile:
 623                    boot_kernel_profile = 'std'
 624                boot_xml_state = XMLState(
 625                    boot_description.load(), [boot_kernel_profile]
 626                )
 627                kernel_package_sections = []
 628                for packages_section in boot_xml_state.xml_data.get_packages():
 629                    # lookup package sections matching kernel profile in kiwi
 630                    # boot description. By definition this must be a packages
 631                    # section with a single profile name whereas the default
 632                    # profile name is 'std'. The section itself must contain
 633                    # one matching kernel package name for the desired
 634                    # architecture
 635                    if packages_section.get_profiles() == boot_kernel_profile:
 636                        for package in packages_section.get_package():
 637                            kernel_package_sections.append(package)
 638
 639                for package in kernel_package_sections:
 640                    if boot_xml_state.package_matches_host_architecture(
 641                            package
 642                    ):
 643                        boot_kernel_package_name = package.get_name()
 644
 645        if boot_kernel_package_name:
 646            # A kernel package name was found in the kiwi boot image
 647            # description. Let's check if this kernel is also used
 648            # in the system image
 649            image_package_names = self.xml_state.get_system_packages()
 650            if boot_kernel_package_name not in image_package_names:
 651                raise KiwiRuntimeError(
 652                    message.format(
 653                        self.xml_state.build_type.get_boot(),
 654                        boot_kernel_package_name
 655                    )
 656                )
 657
 658    def check_dracut_module_versions_compatible_to_kiwi(
 659        self, root_dir: str
 660    ) -> None:
 661        """
 662        KIWI images which makes use of kiwi dracut modules
 663        has to use module versions compatible with the version
 664        of this KIWI builder code base. This is important to avoid
 665        inconsistencies between the way how kiwi includes its own
 666        dracut modules and former version of those dracut modules
 667        which could be no longer compatible with the builder.
 668        Therefore this runtime check maintains a min_version constraint
 669        for which we know this KIWI builder to be compatible with.
 670        """
 671        message = dedent('''\n
 672            Incompatible dracut-kiwi module(s) found
 673
 674            The image was build with KIWI version={0}. The system
 675            root tree has the following dracut-kiwi-* module packages
 676            installed which are too old to work with this version of KIWI.
 677            Please make sure to use dracut-kiwi-* module packages
 678            which are >= than the versions listed below.
 679
 680            {1}
 681        ''')
 682        kiwi_dracut_modules = {
 683            '55kiwi-dump': dracut_module_type(
 684                'dracut-kiwi-oem-dump', '9.20.1'
 685            ),
 686            '55kiwi-live': dracut_module_type(
 687                'dracut-kiwi-live', '9.20.1'
 688            ),
 689            '55kiwi-overlay': dracut_module_type(
 690                'dracut-kiwi-overlay', '9.20.1'
 691            ),
 692            '55kiwi-repart': dracut_module_type(
 693                'dracut-kiwi-oem-repart', '9.20.1'
 694            ),
 695            '59kiwi-dump-reboot': dracut_module_type(
 696                'dracut-kiwi-oem-dump', '9.20.1'
 697            ),
 698            '59kiwi-lib': dracut_module_type(
 699                'dracut-kiwi-lib', '9.20.1'
 700            )
 701        }
 702        dracut_module_dir = os.sep.join(
 703            [root_dir, '/usr/lib/dracut/modules.d']
 704        )
 705        if not os.path.isdir(dracut_module_dir):
 706            # no dracut module dir present
 707            return
 708
 709        incompatible_modules = {}
 710        for module in os.listdir(dracut_module_dir):
 711            module_meta = kiwi_dracut_modules.get(module)
 712            if module_meta:
 713                module_version = self._get_dracut_module_version_from_pdb(
 714                    self.xml_state.get_package_manager(),
 715                    module_meta.package, root_dir
 716                )
 717                if module_version:
 718                    module_version_nr = tuple(
 719                        int(it) for it in module_version.split('.')
 720                    )
 721                    module_min_version_nr = tuple(
 722                        int(it) for it in module_meta.min_version.split('.')
 723                    )
 724                    if module_version_nr < module_min_version_nr:
 725                        incompatible_modules[
 726                            module_meta.package
 727                        ] = 'got:{0}, need:>={1}'.format(
 728                            module_version, module_meta.min_version
 729                        )
 730        if incompatible_modules:
 731            raise KiwiRuntimeError(
 732                message.format(__version__, incompatible_modules)
 733            )
 734
 735    def check_dracut_module_for_oem_install_in_package_list(self) -> None:
 736        """
 737        OEM images if configured to use dracut as initrd system
 738        and configured with one of the installiso, installstick
 739        or installpxe attributes requires the KIWI provided
 740        dracut-kiwi-oem-dump module to be installed at the time
 741        dracut is called. Thus this runtime check examines if the
 742        required package is part of the package list in the
 743        image description.
 744        """
 745        message = dedent('''\n
 746            Required dracut module package missing in package list
 747
 748            One of the packages '{0}' is required
 749            to build an installation image for the selected oem image type.
 750            Depending on your distribution, add the following in the
 751            <packages type="image"> section:
 752
 753            <package name="ONE_FROM_ABOVE"/>
 754        ''')
 755        meta = Defaults.get_runtime_checker_metadata()
 756        required_dracut_packages = meta['package_names']['dracut_oem_dump']
 757        initrd_system = self.xml_state.get_initrd_system()
 758        build_type = self.xml_state.get_build_type_name()
 759        if build_type == 'oem' and initrd_system == 'dracut':
 760            install_iso = self.xml_state.build_type.get_installiso()
 761            install_stick = self.xml_state.build_type.get_installstick()
 762            install_pxe = self.xml_state.build_type.get_installpxe()
 763            if install_iso or install_stick or install_pxe:
 764                package_names = \
 765                    self.xml_state.get_bootstrap_packages() + \
 766                    self.xml_state.get_system_packages()
 767                if not RuntimeChecker._package_in_list(
 768                    package_names, required_dracut_packages
 769                ):
 770                    raise KiwiRuntimeError(
 771                        message.format(required_dracut_packages)
 772                    )
 773
 774    def check_dracut_module_for_disk_oem_in_package_list(self) -> None:
 775        """
 776        OEM images if configured to use dracut as initrd system
 777        requires the KIWI provided dracut-kiwi-oem-repart module
 778        to be installed at the time dracut is called. Thus this
 779        runtime check examines if the required package is part of
 780        the package list in the image description.
 781        """
 782        message = dedent('''\n
 783            Required dracut module package missing in package list
 784
 785            One of the packages '{0}' is required
 786            for the selected oem image type. Depending on your distribution,
 787            add the following in the <packages type="image"> section:
 788
 789            <package name="ONE_FROM_ABOVE"/>
 790        ''')
 791        meta = Defaults.get_runtime_checker_metadata()
 792        required_dracut_packages = meta['package_names']['dracut_oem_repart']
 793        initrd_system = self.xml_state.get_initrd_system()
 794        disk_resize_requested = self.xml_state.get_oemconfig_oem_resize()
 795        build_type = self.xml_state.get_build_type_name()
 796        if build_type == 'oem' and initrd_system == 'dracut' and \
 797           disk_resize_requested:
 798            package_names = \
 799                self.xml_state.get_bootstrap_packages() + \
 800                self.xml_state.get_system_packages()
 801            if not RuntimeChecker._package_in_list(
 802                package_names, required_dracut_packages
 803            ):
 804                raise KiwiRuntimeError(
 805                    message.format(required_dracut_packages)
 806                )
 807
 808    def check_dracut_module_for_live_iso_in_package_list(self) -> None:
 809        """
 810        Live ISO images uses a dracut initrd to boot and requires
 811        the KIWI provided kiwi-live dracut module to be installed
 812        at the time dracut is called. Thus this runtime check
 813        examines if the required package is part of the package
 814        list in the image description.
 815        """
 816        message = dedent('''\n
 817            Required dracut module package missing in package list
 818
 819            One of the packages '{0}' is required
 820            for the selected live iso image type. Depending on your distribution,
 821            add the following in your <packages type="image"> section:
 822
 823            <package name="ONE_FROM_ABOVE"/>
 824        ''')
 825        meta = Defaults.get_runtime_checker_metadata()
 826        required_dracut_packages = meta['package_names']['dracut_live']
 827        type_name = self.xml_state.get_build_type_name()
 828        type_flag = self.xml_state.build_type.get_flags()
 829        if type_name == 'iso' and type_flag != 'dmsquash':
 830            package_names = \
 831                self.xml_state.get_bootstrap_packages() + \
 832                self.xml_state.get_system_packages()
 833            if not RuntimeChecker._package_in_list(
 834                package_names, required_dracut_packages
 835            ):
 836                raise KiwiRuntimeError(
 837                    message.format(required_dracut_packages)
 838                )
 839
 840    def check_dracut_module_for_disk_overlay_in_package_list(self) -> None:
 841        """
 842        Disk images configured to use a root filesystem overlay
 843        requires the KIWI provided kiwi-overlay dracut module to
 844        be installed at the time dracut is called. Thus this
 845        runtime check examines if the required package is part of
 846        the package list in the image description.
 847        """
 848        message = dedent('''\n
 849            Required dracut module package missing in package list
 850
 851            The package '{0}' is required
 852            for the selected overlayroot activated image type.
 853            Depending on your distribution, add the following in your
 854            <packages type="image"> section:
 855
 856            <package name="ONE_FROM_ABOVE"/>
 857        ''')
 858        initrd_system = self.xml_state.get_initrd_system()
 859        meta = Defaults.get_runtime_checker_metadata()
 860        required_dracut_packages = meta['package_names']['dracut_overlay']
 861        if initrd_system == 'dracut' and \
 862           self.xml_state.build_type.get_overlayroot():
 863            package_names = \
 864                self.xml_state.get_bootstrap_packages() + \
 865                self.xml_state.get_system_packages()
 866            if not RuntimeChecker._package_in_list(
 867                package_names, required_dracut_packages
 868            ):
 869                raise KiwiRuntimeError(
 870                    message.format(required_dracut_packages)
 871                )
 872
 873    def check_xen_uniquely_setup_as_server_or_guest(self) -> None:
 874        """
 875        If the image is classified to be used as Xen image, it can
 876        be either a Xen Server(dom0) or a Xen guest. The image
 877        configuration is checked if the information uniquely identifies
 878        the image as such
 879        """
 880        xen_message = dedent('''\n
 881            Inconsistent Xen setup found:
 882
 883            The use of the 'xen_server' or 'xen_loader' attributes indicates
 884            the target system for this image is Xen. However the image
 885            specifies both attributes at the same time which classifies
 886            the image to be both, a Xen Server(dom0) and a Xen guest at
 887            the same time, which is not supported.
 888
 889            Please cleanup your image description. Setup only one
 890            of 'xen_server' or 'xen_loader'.
 891        ''')
 892        ec2_message = dedent('''\n
 893            Inconsistent Amazon EC2 setup found:
 894
 895            The firmware setup indicates the target system for this image
 896            is Amazon EC2, which uses a Xen based virtualisation technology.
 897            Therefore the image must be classified as a Xen guest and can
 898            not be a Xen server as indicated by the 'xen_server' attribute
 899
 900            Please cleanup your image description. Delete the 'xen_server'
 901            attribute for images used with Amazon EC2.
 902        ''')
 903        if self.xml_state.is_xen_server() and self.xml_state.is_xen_guest():
 904            firmware = self.xml_state.build_type.get_firmware()
 905            ec2_firmware_names = Defaults.get_ec2_capable_firmware_names()
 906            if firmware and firmware in ec2_firmware_names:
 907                raise KiwiRuntimeError(ec2_message)
 908            else:
 909                raise KiwiRuntimeError(xen_message)
 910
 911    def check_mediacheck_installed(self) -> None:
 912        """
 913        If the image description enables the mediacheck attribute
 914        the required tools to run this check must be installed
 915        on the image build host
 916        """
 917        message_tool_not_found = dedent('''\n
 918            Required tool {name} not found in caller environment
 919
 920            The attribute 'mediacheck' is set to 'true' which requires
 921            the above tool to be installed on the build system
 922        ''')
 923        if self.xml_state.build_type.get_mediacheck() is True:
 924            tool = 'tagmedia'
 925            media_tagger = RuntimeConfig().get_iso_media_tag_tool()
 926            if media_tagger == 'checkmedia':
 927                tool = 'tagmedia'
 928            elif media_tagger == 'isomd5sum':
 929                tool = 'implantisomd5'
 930            if not Path.which(filename=tool, access_mode=os.X_OK):
 931                raise KiwiRuntimeError(
 932                    message_tool_not_found.format(name=tool)
 933                )
 934
 935    def check_image_version_provided(self) -> None:
 936        """
 937        Kiwi requires a <version> element to be specified as part
 938        of at least one <preferences> section.
 939        """
 940        message_missing_version = dedent('''\n
 941            No version is defined in any of the <preferences>
 942            sections. Please add
 943
 944                <version>image_version<version/>
 945
 946            inside the <preferences> section.
 947        ''')
 948
 949        if not self.xml_state.get_image_version():
 950            raise KiwiRuntimeError(message_missing_version)
 951
 952    def check_image_type_unique(self) -> None:
 953        """
 954        Verify that the selected image type is unique within
 955        the range of the configured types and profiles.
 956        """
 957        message = dedent('''\n
 958            Conflicting image type setup detected
 959
 960            The selected image type '{0}' in the {1} profile
 961            selection is not unique. There are the following type
 962            settings which overrides each other:
 963            {2}
 964            To solve this conflict please move the image type
 965            setup into its own profile and select them using
 966            the --profile option at call time.
 967        ''')
 968        image_type_sections = []
 969        type_dict: Dict[str, List[Any]] = {}
 970        for preferences in self.xml_state.get_preferences_sections():
 971            image_type_sections += preferences.get_type()
 972
 973        for image_type in image_type_sections:
 974            type_name = image_type.get_image()
 975            if type_dict.get(type_name):
 976                type_dict[type_name].append(image_type)
 977            else:
 978                type_dict[type_name] = [image_type]
 979
 980        for type_name, type_list in list(type_dict.items()):
 981            if len(type_list) > 1:
 982                type_export = StringIO()
 983                for image_type in type_list:
 984                    type_export.write(os.linesep)
 985                    image_type.export(type_export, 0)
 986                raise KiwiRuntimeError(
 987                    message.format(
 988                        type_name, self.xml_state.profiles or ['Default'],
 989                        type_export.getvalue()
 990                    )
 991                )
 992
 993    def check_efi_fat_image_has_correct_size(self) -> None:
 994        """
 995        Verify that the efifatimagesize does not exceed the max
 996        El Torito load size of 65535 * 512 bytes
 997        """
 998        message = dedent('''\n
 999            El Torito max load size exceeded
1000
1001            The configured efifatimagesize of '{0}MB' exceeds
1002            the El Torito max load size of 65535 * 512 bytes (~31MB).
1003        ''')
1004        fat_image_mbsize = int(
1005            self.xml_state.build_type
1006                .get_efifatimagesize() or defaults.EFI_FAT_IMAGE_SIZE
1007        )
1008        if fat_image_mbsize > 31:
1009            raise KiwiRuntimeError(
1010                message.format(fat_image_mbsize)
1011            )
1012
1013    def check_bootloader_env_compatible_with_loader(self) -> None:
1014        """
1015        If there is an environment section as part of the bootloader
1016        section, check if the selected loader supports custom
1017        environment blobs
1018        """
1019        message = dedent('''\n
1020            Selected loader does not support custom environments
1021
1022            The selected bootloader {} does not support custom
1023            environment settings. Please drop the <environment>
1024            setting from <bootloadersettings> for this loader
1025        ''')
1026        env_supported_loaders = [
1027            'grub2',
1028            'grub2_s390x_emu',
1029            'custom'
1030        ]
1031        bootloader = self.xml_state.get_build_type_bootloader_name()
1032        variable_list = self.xml_state.\
1033            get_build_type_bootloader_environment_variables()
1034        if variable_list and bootloader not in env_supported_loaders:
1035            raise KiwiRuntimeError(
1036                message.format(bootloader)
1037            )
1038
1039    @staticmethod
1040    def _package_in_list(
1041        package_list: List[str], search_list: List[str]
1042    ) -> str:
1043        result = ''
1044        for search in search_list:
1045            if search in package_list:
1046                result = search
1047                break
1048        return result
1049
1050    @staticmethod
1051    def _get_dracut_module_version_from_pdb(
1052        package_manager: str, package_name: str, root_dir: str
1053    ) -> str:
1054        tool = Defaults.get_default_packager_tool(package_manager)
1055        package_query = None
1056        package_manager_query = None
1057        package_version = ''
1058        if tool == 'rpm':
1059            package_manager_query = [
1060                'chroot', root_dir, tool, '-q', '--qf',
1061                '%{VERSION}', package_name
1062            ]
1063        elif tool == 'dpkg':
1064            package_manager_query = [
1065                'chroot', root_dir, 'dpkg-query', '-W', '-f',
1066                '${Version}', package_name
1067            ]
1068        if package_manager_query:
1069            try:
1070                package_query = Command.run(package_manager_query)
1071                if package_query:
1072                    package_version = package_query.output.split('-', 1)[0]
1073            except Exception as issue:
1074                log.debug(f'Package manager query failed with: {issue}')
1075        return package_version
class dracut_module_type(builtins.tuple):

dracut_module_type(package, min_version)

dracut_module_type(package: str, min_version: str)

Create new instance of dracut_module_type(package, min_version)

package: str

Alias for field number 0

min_version: str

Alias for field number 1

log = <Logger kiwi (DEBUG)>
class RuntimeChecker:
  56class RuntimeChecker:
  57    """
  58    **Implements build consistency checks at runtime**
  59    """
  60    def __init__(self, xml_state: XMLState) -> None:
  61        """
  62        The schema of an image description covers structure and syntax of
  63        the provided data. The RuntimeChecker provides methods to perform
  64        further semantic checks which allows to recognize potential build
  65        or boot problems early.
  66
  67        :param object xml_state: Instance of XMLState
  68        """
  69        self.xml_state = xml_state
  70
  71    def check_repositories_configured(self) -> None:
  72        """
  73        Verify that there are repositories configured
  74        """
  75        if not self.xml_state.get_repository_sections():
  76            raise KiwiRuntimeError(
  77                'No repositories configured'
  78            )
  79
  80    @staticmethod
  81    def check_target_dir_on_unsupported_filesystem(target_dir: str) -> None:
  82        """
  83        Raise if the given target dir does not reside on a
  84        filesystem that supports all important features like
  85        extended permissions(fscaps), ACLs or xattrs.
  86        """
  87        message = dedent('''\n
  88            Target root/image directory is lacking filesystem features
  89
  90            The filesystem {0} in the target path {1}
  91            does not support important features like extended permissions,
  92            ACLs or xattrs. The image build may fail or the resulting
  93            image misbehave.
  94        ''')
  95        target_dir = Path.first_exists(target_dir)
  96        stat = Command.run(['stat', '-f', '-c', '%T', target_dir])
  97        if stat:
  98            target_fs = stat.output.strip()
  99            supported_target_filesystem = (
 100                'btrfs',
 101                'ext2',
 102                'ext2/ext3',
 103                'ext3',
 104                'ext4',
 105                'overlayfs',
 106                'tmpfs',
 107                'xfs',
 108            )
 109            if target_fs not in supported_target_filesystem:
 110                raise KiwiRuntimeError(message.format(target_fs, target_dir))
 111
 112    def check_include_references_unresolvable(self) -> None:
 113        """
 114        Raise for still included <include> statements as not resolvable.
 115        The KIWI XSLT processing replaces the specified include
 116        directive(s) with the given file reference(s). If this action
 117        did not happen for example on nested includes, it can happen
 118        that they stay in the document as sort of waste.
 119        """
 120        message = dedent('''\n
 121            One ore more <include> statements are unresolvable
 122
 123            The following include references could not be resolved.
 124            Please verify the specified location(s) and/or delete
 125            the broken include directive(s) from the description.
 126            Please also note, nested includes from other include
 127            files are not supported:
 128
 129            {0}
 130        ''')
 131        include_files = \
 132            self.xml_state.get_include_section_reference_file_names()
 133        if include_files:
 134            raise KiwiRuntimeError(
 135                message.format(json.dumps(include_files, indent=4))
 136            )
 137
 138    def check_image_include_repos_publicly_resolvable(self) -> None:
 139        """
 140        Verify that all repos marked with the imageinclude attribute
 141        can be resolved into a http based web URL
 142        """
 143        message = dedent('''\n
 144            The use of imageinclude="true" in the repository definition
 145            for the Repository: {0}
 146            requires the repository to by publicly available. The source
 147            locator of the repository however indicates it is private to
 148            your local system. Therefore it can't be included into the
 149            system image repository configuration. Please define a publicly
 150            available repository in your image XML description.
 151        ''')
 152
 153        repository_sections = self.xml_state.get_repository_sections()
 154        for xml_repo in repository_sections:
 155            repo_marked_for_image_include = xml_repo.get_imageinclude()
 156
 157            if repo_marked_for_image_include:
 158                repo_source = xml_repo.get_source().get_path()
 159                repo_type = xml_repo.get_type()
 160                uri = Uri(repo_source, repo_type)
 161                if not uri.is_public():
 162                    raise KiwiRuntimeError(
 163                        message.format(repo_source)
 164                    )
 165
 166    @staticmethod
 167    def check_target_directory_not_in_shared_cache(target_dir: str) -> None:
 168        """
 169        The target directory must be outside of the kiwi shared cache
 170        directory in order to avoid busy mounts because kiwi bind mounts
 171        the cache directory into the image root tree to access host
 172        caching information
 173
 174        :param string target_dir: path name
 175        """
 176        message = dedent('''\n
 177            Target directory %s conflicts with kiwi's shared cache
 178            directory %s. This is going to create a busy loop mount.
 179            Please choose another target directory.
 180        ''')
 181
 182        shared_cache_location = Defaults.get_shared_cache_location()
 183
 184        target_dir_stack = os.path.abspath(
 185            os.path.normpath(target_dir)
 186        ).replace(os.sep + os.sep, os.sep).split(os.sep)
 187        if target_dir_stack[1:4] == shared_cache_location.split(os.sep):
 188            raise KiwiRuntimeError(
 189                message % (target_dir, shared_cache_location)
 190            )
 191
 192    def check_volume_label_used_with_lvm(self) -> None:
 193        """
 194        The optional volume label in a systemdisk setup is only
 195        effective if the LVM, logical volume manager system is
 196        used. In any other case where the filesystem itself offers
 197        volume management capabilities there are no extra filesystem
 198        labels which can be applied per volume
 199        """
 200        message = dedent('''\n
 201            Custom volume label setup used without LVM
 202
 203            The optional volume label in a systemdisk setup is only
 204            effective if the LVM, logical volume manager system is
 205            used. Your setup uses the {0} filesystem which itself
 206            offers volume management capabilities. Extra filesystem
 207            labels cannot be applied in this case.
 208
 209            If you want to force LVM over the {0} volume management
 210            system you can do so by specifying the following in
 211            your KIWI XML description:
 212
 213            <systemdisk ... preferlvm="true">
 214                <volume .../>
 215            </systemdisk>
 216        ''')
 217        volume_management = self.xml_state.get_volume_management()
 218        if volume_management != 'lvm':
 219            for volume in self.xml_state.get_volumes():
 220                if volume.label and volume.label != 'SWAP':
 221                    raise KiwiRuntimeError(
 222                        message.format(volume_management)
 223                    )
 224
 225    def check_partuuid_persistency_type_used_with_mbr(self) -> None:
 226        """
 227        The devicepersistency setting by-partuuid can only be
 228        used in combination with a partition table type that
 229        supports UUIDs. In any other case Linux creates artificial
 230        values for PTUUID and PARTUUID from the disk signature
 231        which can change without touching the actual partition
 232        table. We consider this unsafe and only allow the use
 233        of by-partuuid in combination with partition tables that
 234        actually supports it properly.
 235        """
 236        message = dedent('''\n
 237            devicepersistency={0!r} used with non UUID capable partition table
 238
 239            PTUUID and PARTUUID exists in the GUID (GPT) partition table.
 240            According to the firmware setting: {1!r}, the selected partition
 241            table type is: {2!r}. This table type does not natively support
 242            UUIDs. In such a case Linux creates artificial values for PTUUID
 243            and PARTUUID from the disk signature which can change without
 244            touching the actual partition table. This is considered unsafe
 245            and KIWI only allows the use of by-partuuid in combination with
 246            partition tables that actually supports UUIDs properly.
 247
 248            Please make sure to use one of the following firmware settings
 249            which leads to an image using an UUID capable partition table
 250            and therefore supporting consistent by-partuuid device names:
 251
 252            <type ... firmware="efi|uefi">
 253        ''')
 254        persistency_type = self.xml_state.build_type.get_devicepersistency()
 255        if persistency_type and persistency_type == 'by-partuuid':
 256            supported_table_types = ['gpt']
 257            firmware = FirmWare(self.xml_state)
 258            table_type = firmware.get_partition_table_type()
 259            if table_type not in supported_table_types:
 260                raise KiwiRuntimeError(
 261                    message.format(
 262                        persistency_type, firmware.firmware, table_type
 263                    )
 264                )
 265
 266    def check_swap_name_used_with_lvm(self) -> None:
 267        """
 268        The optional oem-swapname is only effective if used together
 269        with the LVM volume manager. A name for the swap space can
 270        only be set if it is created as a LVM volume. In any other
 271        case the name does not apply to the system
 272        """
 273        message = dedent('''\n
 274             Specified swap space name: {0} will not be used
 275
 276             The specified oem-swapname is used without the LVM volume
 277             manager. This means the swap space will be created as simple
 278             partition for which no name assignment can take place.
 279             The name specified in oem-swapname is used to give the
 280             LVM swap volume a name. Outside of LVM the setting is
 281             meaningless and should be removed.
 282
 283             Please delete the following setting from your image
 284             description:
 285
 286             <oem-swapname>{0}</oem-swapname>
 287        ''')
 288        volume_management = self.xml_state.get_volume_management()
 289        if volume_management != 'lvm':
 290            oemconfig = self.xml_state.get_build_type_oemconfig_section()
 291            if oemconfig and oemconfig.get_oem_swapname():
 292                raise KiwiRuntimeError(
 293                    message.format(oemconfig.get_oem_swapname()[0])
 294                )
 295
 296    def check_volume_setup_defines_reserved_labels(self) -> None:
 297        message = dedent('''\n
 298            Reserved label name used in LVM volume setup
 299
 300            The label setup for volume {0} uses the reserved label {1}.
 301            Reserved labels used by KIWI internally are {2}. Please
 302            choose another label name for this volume.
 303        ''')
 304        reserved_labels = [
 305            self.xml_state.build_type.get_rootfs_label() or 'ROOT',
 306            'SWAP', 'SPARE'
 307        ]
 308        volume_management = self.xml_state.get_volume_management()
 309        if volume_management == 'lvm':
 310            for volume in self.xml_state.get_volumes():
 311                # A swap volume is created implicitly if oem-swap is
 312                # requested. This volume detected via realpath set to
 313                # swap is skipped from the reserved label check as it
 314                # intentionally uses the reserved label named SWAP
 315                if volume.realpath != 'swap':
 316                    if volume.label and volume.label in reserved_labels:
 317                        raise KiwiRuntimeError(
 318                            message.format(
 319                                volume.name, volume.label, reserved_labels
 320                            )
 321                        )
 322
 323    def check_volume_setup_defines_multiple_fullsize_volumes(self) -> None:
 324        """
 325        The volume size specification 'all' makes this volume to
 326        take the rest space available on the system. It's only
 327        allowed to specify one all size volume
 328        """
 329        message = dedent('''\n
 330            Multiple all size volumes found but only one is allowed
 331
 332            The volume size specification 'all' makes this volume to
 333            take the rest space available on the system. It's only
 334            allowed to specify one all size volume
 335        ''')
 336        systemdisk_section = self.xml_state.get_build_type_system_disk_section()
 337        if systemdisk_section:
 338            all_size_volume_count = 0
 339            volumes = systemdisk_section.get_volume() or []
 340            for volume in volumes:
 341                size = volume.get_size() or volume.get_freespace()
 342                if size and 'all' in size:
 343                    all_size_volume_count += 1
 344            if all_size_volume_count > 1:
 345                raise KiwiRuntimeError(message)
 346
 347    def check_volume_setup_has_no_root_definition(self) -> None:
 348        """
 349        The root volume in a systemdisk setup is handled in a special
 350        way. It is not allowed to setup a custom name or mountpoint for
 351        the root volume. Therefore the size of the root volume can be
 352        setup via the @root volume name. This check looks up the volume
 353        setup and searches if there is a configuration for the '/'
 354        mountpoint which would cause the image build to fail
 355        """
 356        message = dedent('''\n
 357            Volume setup for "/" found. The size of the root volume
 358            must be specified via the @root volume name like the
 359            following example shows:
 360
 361            <volume name="@root" size="42G"/>
 362
 363            A custom name or mountpoint for the root volume is not
 364            allowed.
 365        ''')
 366        for volume in self.xml_state.get_volumes():
 367            if volume.mountpoint == '/':
 368                raise KiwiRuntimeError(message)
 369
 370    def check_container_tool_chain_installed(self) -> None:
 371        """
 372        When creating container images the specific tools are used in order
 373        to import and export OCI or Docker compatible images. This check
 374        searches for those tools to be installed in the build system and
 375        fails if it can't find them
 376        """
 377        message_tool_not_found = dedent('''\n
 378            Required tool {name} not found in caller environment
 379
 380            Creation of OCI or Docker images requires the tools {name} and
 381            skopeo to be installed on the build system. For SUSE based systems
 382            you can find the tools at:
 383
 384            http://download.opensuse.org/repositories/Virtualization:/containers
 385        ''')
 386        message_version_unsupported = dedent('''\n
 387            {name} tool found with unknown version
 388        ''')
 389        message_unknown_tool = dedent('''\n
 390            Unknown tool: {0}.
 391
 392            Please configure KIWI with an appropriate value (umoci or buildah).
 393            Consider this runtime configuration file syntax (/etc/kiwi.yml):
 394
 395            oci:
 396                - archive_tool: umoci | buildah
 397        ''')
 398
 399        expected_version = (0, 1, 0)
 400
 401        if self.xml_state.get_build_type_name() in ['docker', 'oci']:
 402            runtime_config = RuntimeConfig()
 403            tool_name = runtime_config.get_oci_archive_tool()
 404            if tool_name == 'buildah':
 405                oci_tools = ['buildah', 'skopeo']
 406            elif tool_name == 'umoci':
 407                oci_tools = ['umoci', 'skopeo']
 408            else:
 409                raise KiwiRuntimeError(message_unknown_tool.format(tool_name))
 410            for tool in oci_tools:
 411                if not Path.which(filename=tool, access_mode=os.X_OK):
 412                    raise KiwiRuntimeError(
 413                        message_tool_not_found.format(name=tool)
 414                    )
 415                elif not CommandCapabilities.check_version(
 416                    tool, expected_version, raise_on_error=False
 417                ):
 418                    raise KiwiRuntimeError(
 419                        message_version_unsupported.format(name=tool)
 420                    )
 421            self._check_multitag_support()
 422
 423    def _check_multitag_support(self) -> None:
 424        message = dedent('''\n
 425            Using additionaltags attribute requires skopeo tool to be
 426            capable to handle it, it must include the '--additional-tag'
 427            option for copy subcommand (check it running 'skopeo copy
 428            --help').\n
 429            It is known to be present since v0.1.30
 430        ''')
 431        if 'additional_names' in self.xml_state.get_container_config():
 432            if not CommandCapabilities.has_option_in_help(
 433                'skopeo', '--additional-tag', ['copy', '--help'],
 434                raise_on_error=False
 435            ):
 436                raise KiwiRuntimeError(message)
 437
 438    def check_luksformat_options_valid(self) -> None:
 439        """
 440        Options set via the luksformat element are passed along
 441        to the cryptsetup tool. Only options that are known to
 442        the tool should be allowed. Thus this runtime check looks
 443        up the provided option names if they exist in the cryptsetup
 444        version used on the build host
 445        """
 446        message = dedent('''\n
 447            Option {0!r} not found in cryptsetup
 448
 449            The Option {0!r} could not be found in the help output
 450            of the cryptsetup tool.
 451        ''')
 452        luksformat = self.xml_state.build_type.get_luksformat()
 453        if luksformat:
 454            for option in luksformat[0].get_option():
 455                argument = option.get_name()
 456                if not CommandCapabilities.has_option_in_help(
 457                    'cryptsetup', argument, ['--help'],
 458                    raise_on_error=False
 459                ):
 460                    raise KiwiRuntimeError(message.format(argument))
 461
 462    def check_appx_naming_conventions_valid(self) -> None:
 463        """
 464        When building wsl images there are some naming conventions that
 465        must be fulfilled to run the container on Microsoft Windows
 466        """
 467        launcher_pattern = r'[^\\]+(\.[Ee][Xx][Ee])$'
 468        message_container_launcher_invalid = dedent('''\n
 469            Invalid WSL launcher name: {0}
 470
 471            WSL launcher name must match the pattern: {1}
 472        ''')
 473        id_pattern = r'^[a-zA-Z0-9]+$'
 474        message_container_id_invalid = dedent('''\n
 475            Invalid WSL container application id: {0}
 476
 477            WSL container id must match the pattern: {1}
 478        ''')
 479        build_type = self.xml_state.get_build_type_name()
 480        container_config = self.xml_state.get_container_config()
 481        container_history = container_config.get('history') or {}
 482        if build_type == 'appx' and container_config:
 483            launcher = container_history.get('launcher')
 484            if launcher and not re.match(launcher_pattern, launcher):
 485                raise KiwiRuntimeError(
 486                    message_container_launcher_invalid.format(
 487                        launcher, launcher_pattern
 488                    )
 489                )
 490            application_id = container_history.get('application_id')
 491            if application_id and not re.match(id_pattern, application_id):
 492                raise KiwiRuntimeError(
 493                    message_container_id_invalid.format(
 494                        application_id, id_pattern
 495                    )
 496                )
 497
 498    def check_initrd_selection_required(self) -> None:
 499        """
 500        If the boot attribute is used without selecting kiwi
 501        as the initrd_system, the setting of the boot attribute
 502        will not have any effect. We assume that configurations
 503        which explicitly specify the boot attribute wants to use
 504        the custom kiwi initrd system and not dracut.
 505        """
 506        message_kiwi_initrd_system_not_selected = dedent('''\n
 507            Missing initrd_system selection for boot attribute
 508
 509            The selected boot="'{0}'" boot description indicates
 510            the custom kiwi initrd system should be used instead
 511            of dracut. If this is correct please explicitly request
 512            the kiwi initrd system as follows:
 513
 514            <type initrd_system="kiwi"/>
 515
 516            If this is not want you want and dracut should be used
 517            as initrd system, please delete the boot attribute
 518            as it is obsolete in this case.
 519        ''')
 520        initrd_system = self.xml_state.get_initrd_system()
 521        boot_image_reference = self.xml_state.build_type.get_boot()
 522        if initrd_system != 'kiwi' and boot_image_reference:
 523            raise KiwiRuntimeError(
 524                message_kiwi_initrd_system_not_selected.format(
 525                    boot_image_reference
 526                )
 527            )
 528
 529    def check_boot_description_exists(self) -> None:
 530        """
 531        If a kiwi initrd is used, a lookup to the specified boot
 532        description is done and fails early if it does not exist
 533        """
 534        message_no_boot_reference = dedent('''\n
 535            Boot description missing for '{0}' type
 536
 537            The selected '{1}' initrd_system requires a boot description
 538            reference. Please update your type setup as follows
 539
 540            <type image="{0}" boot="{0}boot/..."/>
 541
 542            A collection of custom boot descriptions can be found
 543            in the kiwi-boot-descriptions package
 544        ''')
 545        message_boot_description_not_found = dedent('''\n
 546            Boot description '{0}' not found
 547
 548            The selected boot description could not be found on
 549            the build host. A collection of custom boot descriptions
 550            can be found in the kiwi-boot-descriptions package
 551        ''')
 552        image_types_supporting_custom_boot_description = ['oem', 'pxe']
 553        build_type = self.xml_state.get_build_type_name()
 554        initrd_system = self.xml_state.get_initrd_system()
 555        if initrd_system == 'kiwi' and \
 556           build_type in image_types_supporting_custom_boot_description:
 557
 558            boot_image_reference = self.xml_state.build_type.get_boot()
 559            if not boot_image_reference:
 560                raise KiwiRuntimeError(
 561                    message_no_boot_reference.format(build_type, initrd_system)
 562                )
 563
 564            if not boot_image_reference[0] == os.sep:
 565                boot_image_reference = os.sep.join(
 566                    [
 567                        Defaults.get_boot_image_description_path(),
 568                        boot_image_reference
 569                    ]
 570                )
 571            if not os.path.exists(boot_image_reference):
 572                raise KiwiRuntimeError(
 573                    message_boot_description_not_found.format(
 574                        boot_image_reference
 575                    )
 576                )
 577
 578    def check_consistent_kernel_in_boot_and_system_image(self) -> None:
 579        """
 580        If a kiwi initrd is used, the kernel used to build the kiwi
 581        initrd and the kernel used in the system image must be the
 582        same in order to avoid an inconsistent boot setup
 583        """
 584        message = dedent('''\n
 585            Possible kernel mismatch between kiwi initrd and system image
 586
 587            The selected '{0}' boot image kernel is '{1}'. However this
 588            kernel package was not explicitly listed in the package list
 589            of the system image. Please fixup your system image
 590            description:
 591
 592            1) Add <package name="{1}"/> to your system XML description
 593
 594            2) Inherit kernel from system description to initrd via
 595               the custom kernel profile:
 596
 597               <type ... bootkernel="custom" .../>
 598
 599               <packages type="image"/>
 600                   <package name="desired-kernel" bootinclude="true"/>
 601               </packages>
 602        ''')
 603        boot_image_reference = self.xml_state.build_type.get_boot()
 604        boot_kernel_package_name = None
 605        if boot_image_reference:
 606            if not boot_image_reference[0] == '/':
 607                boot_image_reference = os.sep.join(
 608                    [
 609                        Defaults.get_boot_image_description_path(),
 610                        boot_image_reference
 611                    ]
 612                )
 613            boot_config_file = os.sep.join(
 614                [boot_image_reference, 'config.xml']
 615            )
 616            if os.path.exists(boot_config_file):
 617                boot_description = XMLDescription(
 618                    description=boot_config_file,
 619                    derived_from=self.xml_state.xml_data.description_dir
 620                )
 621                boot_kernel_profile = \
 622                    self.xml_state.build_type.get_bootkernel()
 623                if not boot_kernel_profile:
 624                    boot_kernel_profile = 'std'
 625                boot_xml_state = XMLState(
 626                    boot_description.load(), [boot_kernel_profile]
 627                )
 628                kernel_package_sections = []
 629                for packages_section in boot_xml_state.xml_data.get_packages():
 630                    # lookup package sections matching kernel profile in kiwi
 631                    # boot description. By definition this must be a packages
 632                    # section with a single profile name whereas the default
 633                    # profile name is 'std'. The section itself must contain
 634                    # one matching kernel package name for the desired
 635                    # architecture
 636                    if packages_section.get_profiles() == boot_kernel_profile:
 637                        for package in packages_section.get_package():
 638                            kernel_package_sections.append(package)
 639
 640                for package in kernel_package_sections:
 641                    if boot_xml_state.package_matches_host_architecture(
 642                            package
 643                    ):
 644                        boot_kernel_package_name = package.get_name()
 645
 646        if boot_kernel_package_name:
 647            # A kernel package name was found in the kiwi boot image
 648            # description. Let's check if this kernel is also used
 649            # in the system image
 650            image_package_names = self.xml_state.get_system_packages()
 651            if boot_kernel_package_name not in image_package_names:
 652                raise KiwiRuntimeError(
 653                    message.format(
 654                        self.xml_state.build_type.get_boot(),
 655                        boot_kernel_package_name
 656                    )
 657                )
 658
 659    def check_dracut_module_versions_compatible_to_kiwi(
 660        self, root_dir: str
 661    ) -> None:
 662        """
 663        KIWI images which makes use of kiwi dracut modules
 664        has to use module versions compatible with the version
 665        of this KIWI builder code base. This is important to avoid
 666        inconsistencies between the way how kiwi includes its own
 667        dracut modules and former version of those dracut modules
 668        which could be no longer compatible with the builder.
 669        Therefore this runtime check maintains a min_version constraint
 670        for which we know this KIWI builder to be compatible with.
 671        """
 672        message = dedent('''\n
 673            Incompatible dracut-kiwi module(s) found
 674
 675            The image was build with KIWI version={0}. The system
 676            root tree has the following dracut-kiwi-* module packages
 677            installed which are too old to work with this version of KIWI.
 678            Please make sure to use dracut-kiwi-* module packages
 679            which are >= than the versions listed below.
 680
 681            {1}
 682        ''')
 683        kiwi_dracut_modules = {
 684            '55kiwi-dump': dracut_module_type(
 685                'dracut-kiwi-oem-dump', '9.20.1'
 686            ),
 687            '55kiwi-live': dracut_module_type(
 688                'dracut-kiwi-live', '9.20.1'
 689            ),
 690            '55kiwi-overlay': dracut_module_type(
 691                'dracut-kiwi-overlay', '9.20.1'
 692            ),
 693            '55kiwi-repart': dracut_module_type(
 694                'dracut-kiwi-oem-repart', '9.20.1'
 695            ),
 696            '59kiwi-dump-reboot': dracut_module_type(
 697                'dracut-kiwi-oem-dump', '9.20.1'
 698            ),
 699            '59kiwi-lib': dracut_module_type(
 700                'dracut-kiwi-lib', '9.20.1'
 701            )
 702        }
 703        dracut_module_dir = os.sep.join(
 704            [root_dir, '/usr/lib/dracut/modules.d']
 705        )
 706        if not os.path.isdir(dracut_module_dir):
 707            # no dracut module dir present
 708            return
 709
 710        incompatible_modules = {}
 711        for module in os.listdir(dracut_module_dir):
 712            module_meta = kiwi_dracut_modules.get(module)
 713            if module_meta:
 714                module_version = self._get_dracut_module_version_from_pdb(
 715                    self.xml_state.get_package_manager(),
 716                    module_meta.package, root_dir
 717                )
 718                if module_version:
 719                    module_version_nr = tuple(
 720                        int(it) for it in module_version.split('.')
 721                    )
 722                    module_min_version_nr = tuple(
 723                        int(it) for it in module_meta.min_version.split('.')
 724                    )
 725                    if module_version_nr < module_min_version_nr:
 726                        incompatible_modules[
 727                            module_meta.package
 728                        ] = 'got:{0}, need:>={1}'.format(
 729                            module_version, module_meta.min_version
 730                        )
 731        if incompatible_modules:
 732            raise KiwiRuntimeError(
 733                message.format(__version__, incompatible_modules)
 734            )
 735
 736    def check_dracut_module_for_oem_install_in_package_list(self) -> None:
 737        """
 738        OEM images if configured to use dracut as initrd system
 739        and configured with one of the installiso, installstick
 740        or installpxe attributes requires the KIWI provided
 741        dracut-kiwi-oem-dump module to be installed at the time
 742        dracut is called. Thus this runtime check examines if the
 743        required package is part of the package list in the
 744        image description.
 745        """
 746        message = dedent('''\n
 747            Required dracut module package missing in package list
 748
 749            One of the packages '{0}' is required
 750            to build an installation image for the selected oem image type.
 751            Depending on your distribution, add the following in the
 752            <packages type="image"> section:
 753
 754            <package name="ONE_FROM_ABOVE"/>
 755        ''')
 756        meta = Defaults.get_runtime_checker_metadata()
 757        required_dracut_packages = meta['package_names']['dracut_oem_dump']
 758        initrd_system = self.xml_state.get_initrd_system()
 759        build_type = self.xml_state.get_build_type_name()
 760        if build_type == 'oem' and initrd_system == 'dracut':
 761            install_iso = self.xml_state.build_type.get_installiso()
 762            install_stick = self.xml_state.build_type.get_installstick()
 763            install_pxe = self.xml_state.build_type.get_installpxe()
 764            if install_iso or install_stick or install_pxe:
 765                package_names = \
 766                    self.xml_state.get_bootstrap_packages() + \
 767                    self.xml_state.get_system_packages()
 768                if not RuntimeChecker._package_in_list(
 769                    package_names, required_dracut_packages
 770                ):
 771                    raise KiwiRuntimeError(
 772                        message.format(required_dracut_packages)
 773                    )
 774
 775    def check_dracut_module_for_disk_oem_in_package_list(self) -> None:
 776        """
 777        OEM images if configured to use dracut as initrd system
 778        requires the KIWI provided dracut-kiwi-oem-repart module
 779        to be installed at the time dracut is called. Thus this
 780        runtime check examines if the required package is part of
 781        the package list in the image description.
 782        """
 783        message = dedent('''\n
 784            Required dracut module package missing in package list
 785
 786            One of the packages '{0}' is required
 787            for the selected oem image type. Depending on your distribution,
 788            add the following in the <packages type="image"> section:
 789
 790            <package name="ONE_FROM_ABOVE"/>
 791        ''')
 792        meta = Defaults.get_runtime_checker_metadata()
 793        required_dracut_packages = meta['package_names']['dracut_oem_repart']
 794        initrd_system = self.xml_state.get_initrd_system()
 795        disk_resize_requested = self.xml_state.get_oemconfig_oem_resize()
 796        build_type = self.xml_state.get_build_type_name()
 797        if build_type == 'oem' and initrd_system == 'dracut' and \
 798           disk_resize_requested:
 799            package_names = \
 800                self.xml_state.get_bootstrap_packages() + \
 801                self.xml_state.get_system_packages()
 802            if not RuntimeChecker._package_in_list(
 803                package_names, required_dracut_packages
 804            ):
 805                raise KiwiRuntimeError(
 806                    message.format(required_dracut_packages)
 807                )
 808
 809    def check_dracut_module_for_live_iso_in_package_list(self) -> None:
 810        """
 811        Live ISO images uses a dracut initrd to boot and requires
 812        the KIWI provided kiwi-live dracut module to be installed
 813        at the time dracut is called. Thus this runtime check
 814        examines if the required package is part of the package
 815        list in the image description.
 816        """
 817        message = dedent('''\n
 818            Required dracut module package missing in package list
 819
 820            One of the packages '{0}' is required
 821            for the selected live iso image type. Depending on your distribution,
 822            add the following in your <packages type="image"> section:
 823
 824            <package name="ONE_FROM_ABOVE"/>
 825        ''')
 826        meta = Defaults.get_runtime_checker_metadata()
 827        required_dracut_packages = meta['package_names']['dracut_live']
 828        type_name = self.xml_state.get_build_type_name()
 829        type_flag = self.xml_state.build_type.get_flags()
 830        if type_name == 'iso' and type_flag != 'dmsquash':
 831            package_names = \
 832                self.xml_state.get_bootstrap_packages() + \
 833                self.xml_state.get_system_packages()
 834            if not RuntimeChecker._package_in_list(
 835                package_names, required_dracut_packages
 836            ):
 837                raise KiwiRuntimeError(
 838                    message.format(required_dracut_packages)
 839                )
 840
 841    def check_dracut_module_for_disk_overlay_in_package_list(self) -> None:
 842        """
 843        Disk images configured to use a root filesystem overlay
 844        requires the KIWI provided kiwi-overlay dracut module to
 845        be installed at the time dracut is called. Thus this
 846        runtime check examines if the required package is part of
 847        the package list in the image description.
 848        """
 849        message = dedent('''\n
 850            Required dracut module package missing in package list
 851
 852            The package '{0}' is required
 853            for the selected overlayroot activated image type.
 854            Depending on your distribution, add the following in your
 855            <packages type="image"> section:
 856
 857            <package name="ONE_FROM_ABOVE"/>
 858        ''')
 859        initrd_system = self.xml_state.get_initrd_system()
 860        meta = Defaults.get_runtime_checker_metadata()
 861        required_dracut_packages = meta['package_names']['dracut_overlay']
 862        if initrd_system == 'dracut' and \
 863           self.xml_state.build_type.get_overlayroot():
 864            package_names = \
 865                self.xml_state.get_bootstrap_packages() + \
 866                self.xml_state.get_system_packages()
 867            if not RuntimeChecker._package_in_list(
 868                package_names, required_dracut_packages
 869            ):
 870                raise KiwiRuntimeError(
 871                    message.format(required_dracut_packages)
 872                )
 873
 874    def check_xen_uniquely_setup_as_server_or_guest(self) -> None:
 875        """
 876        If the image is classified to be used as Xen image, it can
 877        be either a Xen Server(dom0) or a Xen guest. The image
 878        configuration is checked if the information uniquely identifies
 879        the image as such
 880        """
 881        xen_message = dedent('''\n
 882            Inconsistent Xen setup found:
 883
 884            The use of the 'xen_server' or 'xen_loader' attributes indicates
 885            the target system for this image is Xen. However the image
 886            specifies both attributes at the same time which classifies
 887            the image to be both, a Xen Server(dom0) and a Xen guest at
 888            the same time, which is not supported.
 889
 890            Please cleanup your image description. Setup only one
 891            of 'xen_server' or 'xen_loader'.
 892        ''')
 893        ec2_message = dedent('''\n
 894            Inconsistent Amazon EC2 setup found:
 895
 896            The firmware setup indicates the target system for this image
 897            is Amazon EC2, which uses a Xen based virtualisation technology.
 898            Therefore the image must be classified as a Xen guest and can
 899            not be a Xen server as indicated by the 'xen_server' attribute
 900
 901            Please cleanup your image description. Delete the 'xen_server'
 902            attribute for images used with Amazon EC2.
 903        ''')
 904        if self.xml_state.is_xen_server() and self.xml_state.is_xen_guest():
 905            firmware = self.xml_state.build_type.get_firmware()
 906            ec2_firmware_names = Defaults.get_ec2_capable_firmware_names()
 907            if firmware and firmware in ec2_firmware_names:
 908                raise KiwiRuntimeError(ec2_message)
 909            else:
 910                raise KiwiRuntimeError(xen_message)
 911
 912    def check_mediacheck_installed(self) -> None:
 913        """
 914        If the image description enables the mediacheck attribute
 915        the required tools to run this check must be installed
 916        on the image build host
 917        """
 918        message_tool_not_found = dedent('''\n
 919            Required tool {name} not found in caller environment
 920
 921            The attribute 'mediacheck' is set to 'true' which requires
 922            the above tool to be installed on the build system
 923        ''')
 924        if self.xml_state.build_type.get_mediacheck() is True:
 925            tool = 'tagmedia'
 926            media_tagger = RuntimeConfig().get_iso_media_tag_tool()
 927            if media_tagger == 'checkmedia':
 928                tool = 'tagmedia'
 929            elif media_tagger == 'isomd5sum':
 930                tool = 'implantisomd5'
 931            if not Path.which(filename=tool, access_mode=os.X_OK):
 932                raise KiwiRuntimeError(
 933                    message_tool_not_found.format(name=tool)
 934                )
 935
 936    def check_image_version_provided(self) -> None:
 937        """
 938        Kiwi requires a <version> element to be specified as part
 939        of at least one <preferences> section.
 940        """
 941        message_missing_version = dedent('''\n
 942            No version is defined in any of the <preferences>
 943            sections. Please add
 944
 945                <version>image_version<version/>
 946
 947            inside the <preferences> section.
 948        ''')
 949
 950        if not self.xml_state.get_image_version():
 951            raise KiwiRuntimeError(message_missing_version)
 952
 953    def check_image_type_unique(self) -> None:
 954        """
 955        Verify that the selected image type is unique within
 956        the range of the configured types and profiles.
 957        """
 958        message = dedent('''\n
 959            Conflicting image type setup detected
 960
 961            The selected image type '{0}' in the {1} profile
 962            selection is not unique. There are the following type
 963            settings which overrides each other:
 964            {2}
 965            To solve this conflict please move the image type
 966            setup into its own profile and select them using
 967            the --profile option at call time.
 968        ''')
 969        image_type_sections = []
 970        type_dict: Dict[str, List[Any]] = {}
 971        for preferences in self.xml_state.get_preferences_sections():
 972            image_type_sections += preferences.get_type()
 973
 974        for image_type in image_type_sections:
 975            type_name = image_type.get_image()
 976            if type_dict.get(type_name):
 977                type_dict[type_name].append(image_type)
 978            else:
 979                type_dict[type_name] = [image_type]
 980
 981        for type_name, type_list in list(type_dict.items()):
 982            if len(type_list) > 1:
 983                type_export = StringIO()
 984                for image_type in type_list:
 985                    type_export.write(os.linesep)
 986                    image_type.export(type_export, 0)
 987                raise KiwiRuntimeError(
 988                    message.format(
 989                        type_name, self.xml_state.profiles or ['Default'],
 990                        type_export.getvalue()
 991                    )
 992                )
 993
 994    def check_efi_fat_image_has_correct_size(self) -> None:
 995        """
 996        Verify that the efifatimagesize does not exceed the max
 997        El Torito load size of 65535 * 512 bytes
 998        """
 999        message = dedent('''\n
1000            El Torito max load size exceeded
1001
1002            The configured efifatimagesize of '{0}MB' exceeds
1003            the El Torito max load size of 65535 * 512 bytes (~31MB).
1004        ''')
1005        fat_image_mbsize = int(
1006            self.xml_state.build_type
1007                .get_efifatimagesize() or defaults.EFI_FAT_IMAGE_SIZE
1008        )
1009        if fat_image_mbsize > 31:
1010            raise KiwiRuntimeError(
1011                message.format(fat_image_mbsize)
1012            )
1013
1014    def check_bootloader_env_compatible_with_loader(self) -> None:
1015        """
1016        If there is an environment section as part of the bootloader
1017        section, check if the selected loader supports custom
1018        environment blobs
1019        """
1020        message = dedent('''\n
1021            Selected loader does not support custom environments
1022
1023            The selected bootloader {} does not support custom
1024            environment settings. Please drop the <environment>
1025            setting from <bootloadersettings> for this loader
1026        ''')
1027        env_supported_loaders = [
1028            'grub2',
1029            'grub2_s390x_emu',
1030            'custom'
1031        ]
1032        bootloader = self.xml_state.get_build_type_bootloader_name()
1033        variable_list = self.xml_state.\
1034            get_build_type_bootloader_environment_variables()
1035        if variable_list and bootloader not in env_supported_loaders:
1036            raise KiwiRuntimeError(
1037                message.format(bootloader)
1038            )
1039
1040    @staticmethod
1041    def _package_in_list(
1042        package_list: List[str], search_list: List[str]
1043    ) -> str:
1044        result = ''
1045        for search in search_list:
1046            if search in package_list:
1047                result = search
1048                break
1049        return result
1050
1051    @staticmethod
1052    def _get_dracut_module_version_from_pdb(
1053        package_manager: str, package_name: str, root_dir: str
1054    ) -> str:
1055        tool = Defaults.get_default_packager_tool(package_manager)
1056        package_query = None
1057        package_manager_query = None
1058        package_version = ''
1059        if tool == 'rpm':
1060            package_manager_query = [
1061                'chroot', root_dir, tool, '-q', '--qf',
1062                '%{VERSION}', package_name
1063            ]
1064        elif tool == 'dpkg':
1065            package_manager_query = [
1066                'chroot', root_dir, 'dpkg-query', '-W', '-f',
1067                '${Version}', package_name
1068            ]
1069        if package_manager_query:
1070            try:
1071                package_query = Command.run(package_manager_query)
1072                if package_query:
1073                    package_version = package_query.output.split('-', 1)[0]
1074            except Exception as issue:
1075                log.debug(f'Package manager query failed with: {issue}')
1076        return package_version

Implements build consistency checks at runtime

RuntimeChecker(xml_state: kiwi.xml_state.XMLState)
60    def __init__(self, xml_state: XMLState) -> None:
61        """
62        The schema of an image description covers structure and syntax of
63        the provided data. The RuntimeChecker provides methods to perform
64        further semantic checks which allows to recognize potential build
65        or boot problems early.
66
67        :param object xml_state: Instance of XMLState
68        """
69        self.xml_state = xml_state

The schema of an image description covers structure and syntax of the provided data. The RuntimeChecker provides methods to perform further semantic checks which allows to recognize potential build or boot problems early.

Parameters
  • object xml_state: Instance of XMLState
xml_state
def check_repositories_configured(self) -> None:
71    def check_repositories_configured(self) -> None:
72        """
73        Verify that there are repositories configured
74        """
75        if not self.xml_state.get_repository_sections():
76            raise KiwiRuntimeError(
77                'No repositories configured'
78            )

Verify that there are repositories configured

@staticmethod
def check_target_dir_on_unsupported_filesystem(target_dir: str) -> None:
 80    @staticmethod
 81    def check_target_dir_on_unsupported_filesystem(target_dir: str) -> None:
 82        """
 83        Raise if the given target dir does not reside on a
 84        filesystem that supports all important features like
 85        extended permissions(fscaps), ACLs or xattrs.
 86        """
 87        message = dedent('''\n
 88            Target root/image directory is lacking filesystem features
 89
 90            The filesystem {0} in the target path {1}
 91            does not support important features like extended permissions,
 92            ACLs or xattrs. The image build may fail or the resulting
 93            image misbehave.
 94        ''')
 95        target_dir = Path.first_exists(target_dir)
 96        stat = Command.run(['stat', '-f', '-c', '%T', target_dir])
 97        if stat:
 98            target_fs = stat.output.strip()
 99            supported_target_filesystem = (
100                'btrfs',
101                'ext2',
102                'ext2/ext3',
103                'ext3',
104                'ext4',
105                'overlayfs',
106                'tmpfs',
107                'xfs',
108            )
109            if target_fs not in supported_target_filesystem:
110                raise KiwiRuntimeError(message.format(target_fs, target_dir))

Raise if the given target dir does not reside on a filesystem that supports all important features like extended permissions(fscaps), ACLs or xattrs.

def check_include_references_unresolvable(self) -> None:
112    def check_include_references_unresolvable(self) -> None:
113        """
114        Raise for still included <include> statements as not resolvable.
115        The KIWI XSLT processing replaces the specified include
116        directive(s) with the given file reference(s). If this action
117        did not happen for example on nested includes, it can happen
118        that they stay in the document as sort of waste.
119        """
120        message = dedent('''\n
121            One ore more <include> statements are unresolvable
122
123            The following include references could not be resolved.
124            Please verify the specified location(s) and/or delete
125            the broken include directive(s) from the description.
126            Please also note, nested includes from other include
127            files are not supported:
128
129            {0}
130        ''')
131        include_files = \
132            self.xml_state.get_include_section_reference_file_names()
133        if include_files:
134            raise KiwiRuntimeError(
135                message.format(json.dumps(include_files, indent=4))
136            )

Raise for still included statements as not resolvable. The KIWI XSLT processing replaces the specified include directive(s) with the given file reference(s). If this action did not happen for example on nested includes, it can happen that they stay in the document as sort of waste.

def check_image_include_repos_publicly_resolvable(self) -> None:
138    def check_image_include_repos_publicly_resolvable(self) -> None:
139        """
140        Verify that all repos marked with the imageinclude attribute
141        can be resolved into a http based web URL
142        """
143        message = dedent('''\n
144            The use of imageinclude="true" in the repository definition
145            for the Repository: {0}
146            requires the repository to by publicly available. The source
147            locator of the repository however indicates it is private to
148            your local system. Therefore it can't be included into the
149            system image repository configuration. Please define a publicly
150            available repository in your image XML description.
151        ''')
152
153        repository_sections = self.xml_state.get_repository_sections()
154        for xml_repo in repository_sections:
155            repo_marked_for_image_include = xml_repo.get_imageinclude()
156
157            if repo_marked_for_image_include:
158                repo_source = xml_repo.get_source().get_path()
159                repo_type = xml_repo.get_type()
160                uri = Uri(repo_source, repo_type)
161                if not uri.is_public():
162                    raise KiwiRuntimeError(
163                        message.format(repo_source)
164                    )

Verify that all repos marked with the imageinclude attribute can be resolved into a http based web URL

@staticmethod
def check_target_directory_not_in_shared_cache(target_dir: str) -> None:
166    @staticmethod
167    def check_target_directory_not_in_shared_cache(target_dir: str) -> None:
168        """
169        The target directory must be outside of the kiwi shared cache
170        directory in order to avoid busy mounts because kiwi bind mounts
171        the cache directory into the image root tree to access host
172        caching information
173
174        :param string target_dir: path name
175        """
176        message = dedent('''\n
177            Target directory %s conflicts with kiwi's shared cache
178            directory %s. This is going to create a busy loop mount.
179            Please choose another target directory.
180        ''')
181
182        shared_cache_location = Defaults.get_shared_cache_location()
183
184        target_dir_stack = os.path.abspath(
185            os.path.normpath(target_dir)
186        ).replace(os.sep + os.sep, os.sep).split(os.sep)
187        if target_dir_stack[1:4] == shared_cache_location.split(os.sep):
188            raise KiwiRuntimeError(
189                message % (target_dir, shared_cache_location)
190            )

The target directory must be outside of the kiwi shared cache directory in order to avoid busy mounts because kiwi bind mounts the cache directory into the image root tree to access host caching information

Parameters
  • string target_dir: path name
def check_volume_label_used_with_lvm(self) -> None:
192    def check_volume_label_used_with_lvm(self) -> None:
193        """
194        The optional volume label in a systemdisk setup is only
195        effective if the LVM, logical volume manager system is
196        used. In any other case where the filesystem itself offers
197        volume management capabilities there are no extra filesystem
198        labels which can be applied per volume
199        """
200        message = dedent('''\n
201            Custom volume label setup used without LVM
202
203            The optional volume label in a systemdisk setup is only
204            effective if the LVM, logical volume manager system is
205            used. Your setup uses the {0} filesystem which itself
206            offers volume management capabilities. Extra filesystem
207            labels cannot be applied in this case.
208
209            If you want to force LVM over the {0} volume management
210            system you can do so by specifying the following in
211            your KIWI XML description:
212
213            <systemdisk ... preferlvm="true">
214                <volume .../>
215            </systemdisk>
216        ''')
217        volume_management = self.xml_state.get_volume_management()
218        if volume_management != 'lvm':
219            for volume in self.xml_state.get_volumes():
220                if volume.label and volume.label != 'SWAP':
221                    raise KiwiRuntimeError(
222                        message.format(volume_management)
223                    )

The optional volume label in a systemdisk setup is only effective if the LVM, logical volume manager system is used. In any other case where the filesystem itself offers volume management capabilities there are no extra filesystem labels which can be applied per volume

def check_partuuid_persistency_type_used_with_mbr(self) -> None:
225    def check_partuuid_persistency_type_used_with_mbr(self) -> None:
226        """
227        The devicepersistency setting by-partuuid can only be
228        used in combination with a partition table type that
229        supports UUIDs. In any other case Linux creates artificial
230        values for PTUUID and PARTUUID from the disk signature
231        which can change without touching the actual partition
232        table. We consider this unsafe and only allow the use
233        of by-partuuid in combination with partition tables that
234        actually supports it properly.
235        """
236        message = dedent('''\n
237            devicepersistency={0!r} used with non UUID capable partition table
238
239            PTUUID and PARTUUID exists in the GUID (GPT) partition table.
240            According to the firmware setting: {1!r}, the selected partition
241            table type is: {2!r}. This table type does not natively support
242            UUIDs. In such a case Linux creates artificial values for PTUUID
243            and PARTUUID from the disk signature which can change without
244            touching the actual partition table. This is considered unsafe
245            and KIWI only allows the use of by-partuuid in combination with
246            partition tables that actually supports UUIDs properly.
247
248            Please make sure to use one of the following firmware settings
249            which leads to an image using an UUID capable partition table
250            and therefore supporting consistent by-partuuid device names:
251
252            <type ... firmware="efi|uefi">
253        ''')
254        persistency_type = self.xml_state.build_type.get_devicepersistency()
255        if persistency_type and persistency_type == 'by-partuuid':
256            supported_table_types = ['gpt']
257            firmware = FirmWare(self.xml_state)
258            table_type = firmware.get_partition_table_type()
259            if table_type not in supported_table_types:
260                raise KiwiRuntimeError(
261                    message.format(
262                        persistency_type, firmware.firmware, table_type
263                    )
264                )

The devicepersistency setting by-partuuid can only be used in combination with a partition table type that supports UUIDs. In any other case Linux creates artificial values for PTUUID and PARTUUID from the disk signature which can change without touching the actual partition table. We consider this unsafe and only allow the use of by-partuuid in combination with partition tables that actually supports it properly.

def check_swap_name_used_with_lvm(self) -> None:
266    def check_swap_name_used_with_lvm(self) -> None:
267        """
268        The optional oem-swapname is only effective if used together
269        with the LVM volume manager. A name for the swap space can
270        only be set if it is created as a LVM volume. In any other
271        case the name does not apply to the system
272        """
273        message = dedent('''\n
274             Specified swap space name: {0} will not be used
275
276             The specified oem-swapname is used without the LVM volume
277             manager. This means the swap space will be created as simple
278             partition for which no name assignment can take place.
279             The name specified in oem-swapname is used to give the
280             LVM swap volume a name. Outside of LVM the setting is
281             meaningless and should be removed.
282
283             Please delete the following setting from your image
284             description:
285
286             <oem-swapname>{0}</oem-swapname>
287        ''')
288        volume_management = self.xml_state.get_volume_management()
289        if volume_management != 'lvm':
290            oemconfig = self.xml_state.get_build_type_oemconfig_section()
291            if oemconfig and oemconfig.get_oem_swapname():
292                raise KiwiRuntimeError(
293                    message.format(oemconfig.get_oem_swapname()[0])
294                )

The optional oem-swapname is only effective if used together with the LVM volume manager. A name for the swap space can only be set if it is created as a LVM volume. In any other case the name does not apply to the system

def check_volume_setup_defines_reserved_labels(self) -> None:
296    def check_volume_setup_defines_reserved_labels(self) -> None:
297        message = dedent('''\n
298            Reserved label name used in LVM volume setup
299
300            The label setup for volume {0} uses the reserved label {1}.
301            Reserved labels used by KIWI internally are {2}. Please
302            choose another label name for this volume.
303        ''')
304        reserved_labels = [
305            self.xml_state.build_type.get_rootfs_label() or 'ROOT',
306            'SWAP', 'SPARE'
307        ]
308        volume_management = self.xml_state.get_volume_management()
309        if volume_management == 'lvm':
310            for volume in self.xml_state.get_volumes():
311                # A swap volume is created implicitly if oem-swap is
312                # requested. This volume detected via realpath set to
313                # swap is skipped from the reserved label check as it
314                # intentionally uses the reserved label named SWAP
315                if volume.realpath != 'swap':
316                    if volume.label and volume.label in reserved_labels:
317                        raise KiwiRuntimeError(
318                            message.format(
319                                volume.name, volume.label, reserved_labels
320                            )
321                        )
def check_volume_setup_defines_multiple_fullsize_volumes(self) -> None:
323    def check_volume_setup_defines_multiple_fullsize_volumes(self) -> None:
324        """
325        The volume size specification 'all' makes this volume to
326        take the rest space available on the system. It's only
327        allowed to specify one all size volume
328        """
329        message = dedent('''\n
330            Multiple all size volumes found but only one is allowed
331
332            The volume size specification 'all' makes this volume to
333            take the rest space available on the system. It's only
334            allowed to specify one all size volume
335        ''')
336        systemdisk_section = self.xml_state.get_build_type_system_disk_section()
337        if systemdisk_section:
338            all_size_volume_count = 0
339            volumes = systemdisk_section.get_volume() or []
340            for volume in volumes:
341                size = volume.get_size() or volume.get_freespace()
342                if size and 'all' in size:
343                    all_size_volume_count += 1
344            if all_size_volume_count > 1:
345                raise KiwiRuntimeError(message)

The volume size specification 'all' makes this volume to take the rest space available on the system. It's only allowed to specify one all size volume

def check_volume_setup_has_no_root_definition(self) -> None:
347    def check_volume_setup_has_no_root_definition(self) -> None:
348        """
349        The root volume in a systemdisk setup is handled in a special
350        way. It is not allowed to setup a custom name or mountpoint for
351        the root volume. Therefore the size of the root volume can be
352        setup via the @root volume name. This check looks up the volume
353        setup and searches if there is a configuration for the '/'
354        mountpoint which would cause the image build to fail
355        """
356        message = dedent('''\n
357            Volume setup for "/" found. The size of the root volume
358            must be specified via the @root volume name like the
359            following example shows:
360
361            <volume name="@root" size="42G"/>
362
363            A custom name or mountpoint for the root volume is not
364            allowed.
365        ''')
366        for volume in self.xml_state.get_volumes():
367            if volume.mountpoint == '/':
368                raise KiwiRuntimeError(message)

The root volume in a systemdisk setup is handled in a special way. It is not allowed to setup a custom name or mountpoint for the root volume. Therefore the size of the root volume can be setup via the @root volume name. This check looks up the volume setup and searches if there is a configuration for the '/' mountpoint which would cause the image build to fail

def check_container_tool_chain_installed(self) -> None:
370    def check_container_tool_chain_installed(self) -> None:
371        """
372        When creating container images the specific tools are used in order
373        to import and export OCI or Docker compatible images. This check
374        searches for those tools to be installed in the build system and
375        fails if it can't find them
376        """
377        message_tool_not_found = dedent('''\n
378            Required tool {name} not found in caller environment
379
380            Creation of OCI or Docker images requires the tools {name} and
381            skopeo to be installed on the build system. For SUSE based systems
382            you can find the tools at:
383
384            http://download.opensuse.org/repositories/Virtualization:/containers
385        ''')
386        message_version_unsupported = dedent('''\n
387            {name} tool found with unknown version
388        ''')
389        message_unknown_tool = dedent('''\n
390            Unknown tool: {0}.
391
392            Please configure KIWI with an appropriate value (umoci or buildah).
393            Consider this runtime configuration file syntax (/etc/kiwi.yml):
394
395            oci:
396                - archive_tool: umoci | buildah
397        ''')
398
399        expected_version = (0, 1, 0)
400
401        if self.xml_state.get_build_type_name() in ['docker', 'oci']:
402            runtime_config = RuntimeConfig()
403            tool_name = runtime_config.get_oci_archive_tool()
404            if tool_name == 'buildah':
405                oci_tools = ['buildah', 'skopeo']
406            elif tool_name == 'umoci':
407                oci_tools = ['umoci', 'skopeo']
408            else:
409                raise KiwiRuntimeError(message_unknown_tool.format(tool_name))
410            for tool in oci_tools:
411                if not Path.which(filename=tool, access_mode=os.X_OK):
412                    raise KiwiRuntimeError(
413                        message_tool_not_found.format(name=tool)
414                    )
415                elif not CommandCapabilities.check_version(
416                    tool, expected_version, raise_on_error=False
417                ):
418                    raise KiwiRuntimeError(
419                        message_version_unsupported.format(name=tool)
420                    )
421            self._check_multitag_support()

When creating container images the specific tools are used in order to import and export OCI or Docker compatible images. This check searches for those tools to be installed in the build system and fails if it can't find them

def check_luksformat_options_valid(self) -> None:
438    def check_luksformat_options_valid(self) -> None:
439        """
440        Options set via the luksformat element are passed along
441        to the cryptsetup tool. Only options that are known to
442        the tool should be allowed. Thus this runtime check looks
443        up the provided option names if they exist in the cryptsetup
444        version used on the build host
445        """
446        message = dedent('''\n
447            Option {0!r} not found in cryptsetup
448
449            The Option {0!r} could not be found in the help output
450            of the cryptsetup tool.
451        ''')
452        luksformat = self.xml_state.build_type.get_luksformat()
453        if luksformat:
454            for option in luksformat[0].get_option():
455                argument = option.get_name()
456                if not CommandCapabilities.has_option_in_help(
457                    'cryptsetup', argument, ['--help'],
458                    raise_on_error=False
459                ):
460                    raise KiwiRuntimeError(message.format(argument))

Options set via the luksformat element are passed along to the cryptsetup tool. Only options that are known to the tool should be allowed. Thus this runtime check looks up the provided option names if they exist in the cryptsetup version used on the build host

def check_appx_naming_conventions_valid(self) -> None:
462    def check_appx_naming_conventions_valid(self) -> None:
463        """
464        When building wsl images there are some naming conventions that
465        must be fulfilled to run the container on Microsoft Windows
466        """
467        launcher_pattern = r'[^\\]+(\.[Ee][Xx][Ee])$'
468        message_container_launcher_invalid = dedent('''\n
469            Invalid WSL launcher name: {0}
470
471            WSL launcher name must match the pattern: {1}
472        ''')
473        id_pattern = r'^[a-zA-Z0-9]+$'
474        message_container_id_invalid = dedent('''\n
475            Invalid WSL container application id: {0}
476
477            WSL container id must match the pattern: {1}
478        ''')
479        build_type = self.xml_state.get_build_type_name()
480        container_config = self.xml_state.get_container_config()
481        container_history = container_config.get('history') or {}
482        if build_type == 'appx' and container_config:
483            launcher = container_history.get('launcher')
484            if launcher and not re.match(launcher_pattern, launcher):
485                raise KiwiRuntimeError(
486                    message_container_launcher_invalid.format(
487                        launcher, launcher_pattern
488                    )
489                )
490            application_id = container_history.get('application_id')
491            if application_id and not re.match(id_pattern, application_id):
492                raise KiwiRuntimeError(
493                    message_container_id_invalid.format(
494                        application_id, id_pattern
495                    )
496                )

When building wsl images there are some naming conventions that must be fulfilled to run the container on Microsoft Windows

def check_initrd_selection_required(self) -> None:
498    def check_initrd_selection_required(self) -> None:
499        """
500        If the boot attribute is used without selecting kiwi
501        as the initrd_system, the setting of the boot attribute
502        will not have any effect. We assume that configurations
503        which explicitly specify the boot attribute wants to use
504        the custom kiwi initrd system and not dracut.
505        """
506        message_kiwi_initrd_system_not_selected = dedent('''\n
507            Missing initrd_system selection for boot attribute
508
509            The selected boot="'{0}'" boot description indicates
510            the custom kiwi initrd system should be used instead
511            of dracut. If this is correct please explicitly request
512            the kiwi initrd system as follows:
513
514            <type initrd_system="kiwi"/>
515
516            If this is not want you want and dracut should be used
517            as initrd system, please delete the boot attribute
518            as it is obsolete in this case.
519        ''')
520        initrd_system = self.xml_state.get_initrd_system()
521        boot_image_reference = self.xml_state.build_type.get_boot()
522        if initrd_system != 'kiwi' and boot_image_reference:
523            raise KiwiRuntimeError(
524                message_kiwi_initrd_system_not_selected.format(
525                    boot_image_reference
526                )
527            )

If the boot attribute is used without selecting kiwi as the initrd_system, the setting of the boot attribute will not have any effect. We assume that configurations which explicitly specify the boot attribute wants to use the custom kiwi initrd system and not dracut.

def check_boot_description_exists(self) -> None:
529    def check_boot_description_exists(self) -> None:
530        """
531        If a kiwi initrd is used, a lookup to the specified boot
532        description is done and fails early if it does not exist
533        """
534        message_no_boot_reference = dedent('''\n
535            Boot description missing for '{0}' type
536
537            The selected '{1}' initrd_system requires a boot description
538            reference. Please update your type setup as follows
539
540            <type image="{0}" boot="{0}boot/..."/>
541
542            A collection of custom boot descriptions can be found
543            in the kiwi-boot-descriptions package
544        ''')
545        message_boot_description_not_found = dedent('''\n
546            Boot description '{0}' not found
547
548            The selected boot description could not be found on
549            the build host. A collection of custom boot descriptions
550            can be found in the kiwi-boot-descriptions package
551        ''')
552        image_types_supporting_custom_boot_description = ['oem', 'pxe']
553        build_type = self.xml_state.get_build_type_name()
554        initrd_system = self.xml_state.get_initrd_system()
555        if initrd_system == 'kiwi' and \
556           build_type in image_types_supporting_custom_boot_description:
557
558            boot_image_reference = self.xml_state.build_type.get_boot()
559            if not boot_image_reference:
560                raise KiwiRuntimeError(
561                    message_no_boot_reference.format(build_type, initrd_system)
562                )
563
564            if not boot_image_reference[0] == os.sep:
565                boot_image_reference = os.sep.join(
566                    [
567                        Defaults.get_boot_image_description_path(),
568                        boot_image_reference
569                    ]
570                )
571            if not os.path.exists(boot_image_reference):
572                raise KiwiRuntimeError(
573                    message_boot_description_not_found.format(
574                        boot_image_reference
575                    )
576                )

If a kiwi initrd is used, a lookup to the specified boot description is done and fails early if it does not exist

def check_consistent_kernel_in_boot_and_system_image(self) -> None:
578    def check_consistent_kernel_in_boot_and_system_image(self) -> None:
579        """
580        If a kiwi initrd is used, the kernel used to build the kiwi
581        initrd and the kernel used in the system image must be the
582        same in order to avoid an inconsistent boot setup
583        """
584        message = dedent('''\n
585            Possible kernel mismatch between kiwi initrd and system image
586
587            The selected '{0}' boot image kernel is '{1}'. However this
588            kernel package was not explicitly listed in the package list
589            of the system image. Please fixup your system image
590            description:
591
592            1) Add <package name="{1}"/> to your system XML description
593
594            2) Inherit kernel from system description to initrd via
595               the custom kernel profile:
596
597               <type ... bootkernel="custom" .../>
598
599               <packages type="image"/>
600                   <package name="desired-kernel" bootinclude="true"/>
601               </packages>
602        ''')
603        boot_image_reference = self.xml_state.build_type.get_boot()
604        boot_kernel_package_name = None
605        if boot_image_reference:
606            if not boot_image_reference[0] == '/':
607                boot_image_reference = os.sep.join(
608                    [
609                        Defaults.get_boot_image_description_path(),
610                        boot_image_reference
611                    ]
612                )
613            boot_config_file = os.sep.join(
614                [boot_image_reference, 'config.xml']
615            )
616            if os.path.exists(boot_config_file):
617                boot_description = XMLDescription(
618                    description=boot_config_file,
619                    derived_from=self.xml_state.xml_data.description_dir
620                )
621                boot_kernel_profile = \
622                    self.xml_state.build_type.get_bootkernel()
623                if not boot_kernel_profile:
624                    boot_kernel_profile = 'std'
625                boot_xml_state = XMLState(
626                    boot_description.load(), [boot_kernel_profile]
627                )
628                kernel_package_sections = []
629                for packages_section in boot_xml_state.xml_data.get_packages():
630                    # lookup package sections matching kernel profile in kiwi
631                    # boot description. By definition this must be a packages
632                    # section with a single profile name whereas the default
633                    # profile name is 'std'. The section itself must contain
634                    # one matching kernel package name for the desired
635                    # architecture
636                    if packages_section.get_profiles() == boot_kernel_profile:
637                        for package in packages_section.get_package():
638                            kernel_package_sections.append(package)
639
640                for package in kernel_package_sections:
641                    if boot_xml_state.package_matches_host_architecture(
642                            package
643                    ):
644                        boot_kernel_package_name = package.get_name()
645
646        if boot_kernel_package_name:
647            # A kernel package name was found in the kiwi boot image
648            # description. Let's check if this kernel is also used
649            # in the system image
650            image_package_names = self.xml_state.get_system_packages()
651            if boot_kernel_package_name not in image_package_names:
652                raise KiwiRuntimeError(
653                    message.format(
654                        self.xml_state.build_type.get_boot(),
655                        boot_kernel_package_name
656                    )
657                )

If a kiwi initrd is used, the kernel used to build the kiwi initrd and the kernel used in the system image must be the same in order to avoid an inconsistent boot setup

def check_dracut_module_versions_compatible_to_kiwi(self, root_dir: str) -> None:
659    def check_dracut_module_versions_compatible_to_kiwi(
660        self, root_dir: str
661    ) -> None:
662        """
663        KIWI images which makes use of kiwi dracut modules
664        has to use module versions compatible with the version
665        of this KIWI builder code base. This is important to avoid
666        inconsistencies between the way how kiwi includes its own
667        dracut modules and former version of those dracut modules
668        which could be no longer compatible with the builder.
669        Therefore this runtime check maintains a min_version constraint
670        for which we know this KIWI builder to be compatible with.
671        """
672        message = dedent('''\n
673            Incompatible dracut-kiwi module(s) found
674
675            The image was build with KIWI version={0}. The system
676            root tree has the following dracut-kiwi-* module packages
677            installed which are too old to work with this version of KIWI.
678            Please make sure to use dracut-kiwi-* module packages
679            which are >= than the versions listed below.
680
681            {1}
682        ''')
683        kiwi_dracut_modules = {
684            '55kiwi-dump': dracut_module_type(
685                'dracut-kiwi-oem-dump', '9.20.1'
686            ),
687            '55kiwi-live': dracut_module_type(
688                'dracut-kiwi-live', '9.20.1'
689            ),
690            '55kiwi-overlay': dracut_module_type(
691                'dracut-kiwi-overlay', '9.20.1'
692            ),
693            '55kiwi-repart': dracut_module_type(
694                'dracut-kiwi-oem-repart', '9.20.1'
695            ),
696            '59kiwi-dump-reboot': dracut_module_type(
697                'dracut-kiwi-oem-dump', '9.20.1'
698            ),
699            '59kiwi-lib': dracut_module_type(
700                'dracut-kiwi-lib', '9.20.1'
701            )
702        }
703        dracut_module_dir = os.sep.join(
704            [root_dir, '/usr/lib/dracut/modules.d']
705        )
706        if not os.path.isdir(dracut_module_dir):
707            # no dracut module dir present
708            return
709
710        incompatible_modules = {}
711        for module in os.listdir(dracut_module_dir):
712            module_meta = kiwi_dracut_modules.get(module)
713            if module_meta:
714                module_version = self._get_dracut_module_version_from_pdb(
715                    self.xml_state.get_package_manager(),
716                    module_meta.package, root_dir
717                )
718                if module_version:
719                    module_version_nr = tuple(
720                        int(it) for it in module_version.split('.')
721                    )
722                    module_min_version_nr = tuple(
723                        int(it) for it in module_meta.min_version.split('.')
724                    )
725                    if module_version_nr < module_min_version_nr:
726                        incompatible_modules[
727                            module_meta.package
728                        ] = 'got:{0}, need:>={1}'.format(
729                            module_version, module_meta.min_version
730                        )
731        if incompatible_modules:
732            raise KiwiRuntimeError(
733                message.format(__version__, incompatible_modules)
734            )

KIWI images which makes use of kiwi dracut modules has to use module versions compatible with the version of this KIWI builder code base. This is important to avoid inconsistencies between the way how kiwi includes its own dracut modules and former version of those dracut modules which could be no longer compatible with the builder. Therefore this runtime check maintains a min_version constraint for which we know this KIWI builder to be compatible with.

def check_dracut_module_for_oem_install_in_package_list(self) -> None:
736    def check_dracut_module_for_oem_install_in_package_list(self) -> None:
737        """
738        OEM images if configured to use dracut as initrd system
739        and configured with one of the installiso, installstick
740        or installpxe attributes requires the KIWI provided
741        dracut-kiwi-oem-dump module to be installed at the time
742        dracut is called. Thus this runtime check examines if the
743        required package is part of the package list in the
744        image description.
745        """
746        message = dedent('''\n
747            Required dracut module package missing in package list
748
749            One of the packages '{0}' is required
750            to build an installation image for the selected oem image type.
751            Depending on your distribution, add the following in the
752            <packages type="image"> section:
753
754            <package name="ONE_FROM_ABOVE"/>
755        ''')
756        meta = Defaults.get_runtime_checker_metadata()
757        required_dracut_packages = meta['package_names']['dracut_oem_dump']
758        initrd_system = self.xml_state.get_initrd_system()
759        build_type = self.xml_state.get_build_type_name()
760        if build_type == 'oem' and initrd_system == 'dracut':
761            install_iso = self.xml_state.build_type.get_installiso()
762            install_stick = self.xml_state.build_type.get_installstick()
763            install_pxe = self.xml_state.build_type.get_installpxe()
764            if install_iso or install_stick or install_pxe:
765                package_names = \
766                    self.xml_state.get_bootstrap_packages() + \
767                    self.xml_state.get_system_packages()
768                if not RuntimeChecker._package_in_list(
769                    package_names, required_dracut_packages
770                ):
771                    raise KiwiRuntimeError(
772                        message.format(required_dracut_packages)
773                    )

OEM images if configured to use dracut as initrd system and configured with one of the installiso, installstick or installpxe attributes requires the KIWI provided dracut-kiwi-oem-dump module to be installed at the time dracut is called. Thus this runtime check examines if the required package is part of the package list in the image description.

def check_dracut_module_for_disk_oem_in_package_list(self) -> None:
775    def check_dracut_module_for_disk_oem_in_package_list(self) -> None:
776        """
777        OEM images if configured to use dracut as initrd system
778        requires the KIWI provided dracut-kiwi-oem-repart module
779        to be installed at the time dracut is called. Thus this
780        runtime check examines if the required package is part of
781        the package list in the image description.
782        """
783        message = dedent('''\n
784            Required dracut module package missing in package list
785
786            One of the packages '{0}' is required
787            for the selected oem image type. Depending on your distribution,
788            add the following in the <packages type="image"> section:
789
790            <package name="ONE_FROM_ABOVE"/>
791        ''')
792        meta = Defaults.get_runtime_checker_metadata()
793        required_dracut_packages = meta['package_names']['dracut_oem_repart']
794        initrd_system = self.xml_state.get_initrd_system()
795        disk_resize_requested = self.xml_state.get_oemconfig_oem_resize()
796        build_type = self.xml_state.get_build_type_name()
797        if build_type == 'oem' and initrd_system == 'dracut' and \
798           disk_resize_requested:
799            package_names = \
800                self.xml_state.get_bootstrap_packages() + \
801                self.xml_state.get_system_packages()
802            if not RuntimeChecker._package_in_list(
803                package_names, required_dracut_packages
804            ):
805                raise KiwiRuntimeError(
806                    message.format(required_dracut_packages)
807                )

OEM images if configured to use dracut as initrd system requires the KIWI provided dracut-kiwi-oem-repart module to be installed at the time dracut is called. Thus this runtime check examines if the required package is part of the package list in the image description.

def check_dracut_module_for_live_iso_in_package_list(self) -> None:
809    def check_dracut_module_for_live_iso_in_package_list(self) -> None:
810        """
811        Live ISO images uses a dracut initrd to boot and requires
812        the KIWI provided kiwi-live dracut module to be installed
813        at the time dracut is called. Thus this runtime check
814        examines if the required package is part of the package
815        list in the image description.
816        """
817        message = dedent('''\n
818            Required dracut module package missing in package list
819
820            One of the packages '{0}' is required
821            for the selected live iso image type. Depending on your distribution,
822            add the following in your <packages type="image"> section:
823
824            <package name="ONE_FROM_ABOVE"/>
825        ''')
826        meta = Defaults.get_runtime_checker_metadata()
827        required_dracut_packages = meta['package_names']['dracut_live']
828        type_name = self.xml_state.get_build_type_name()
829        type_flag = self.xml_state.build_type.get_flags()
830        if type_name == 'iso' and type_flag != 'dmsquash':
831            package_names = \
832                self.xml_state.get_bootstrap_packages() + \
833                self.xml_state.get_system_packages()
834            if not RuntimeChecker._package_in_list(
835                package_names, required_dracut_packages
836            ):
837                raise KiwiRuntimeError(
838                    message.format(required_dracut_packages)
839                )

Live ISO images uses a dracut initrd to boot and requires the KIWI provided kiwi-live dracut module to be installed at the time dracut is called. Thus this runtime check examines if the required package is part of the package list in the image description.

def check_dracut_module_for_disk_overlay_in_package_list(self) -> None:
841    def check_dracut_module_for_disk_overlay_in_package_list(self) -> None:
842        """
843        Disk images configured to use a root filesystem overlay
844        requires the KIWI provided kiwi-overlay dracut module to
845        be installed at the time dracut is called. Thus this
846        runtime check examines if the required package is part of
847        the package list in the image description.
848        """
849        message = dedent('''\n
850            Required dracut module package missing in package list
851
852            The package '{0}' is required
853            for the selected overlayroot activated image type.
854            Depending on your distribution, add the following in your
855            <packages type="image"> section:
856
857            <package name="ONE_FROM_ABOVE"/>
858        ''')
859        initrd_system = self.xml_state.get_initrd_system()
860        meta = Defaults.get_runtime_checker_metadata()
861        required_dracut_packages = meta['package_names']['dracut_overlay']
862        if initrd_system == 'dracut' and \
863           self.xml_state.build_type.get_overlayroot():
864            package_names = \
865                self.xml_state.get_bootstrap_packages() + \
866                self.xml_state.get_system_packages()
867            if not RuntimeChecker._package_in_list(
868                package_names, required_dracut_packages
869            ):
870                raise KiwiRuntimeError(
871                    message.format(required_dracut_packages)
872                )

Disk images configured to use a root filesystem overlay requires the KIWI provided kiwi-overlay dracut module to be installed at the time dracut is called. Thus this runtime check examines if the required package is part of the package list in the image description.

def check_xen_uniquely_setup_as_server_or_guest(self) -> None:
874    def check_xen_uniquely_setup_as_server_or_guest(self) -> None:
875        """
876        If the image is classified to be used as Xen image, it can
877        be either a Xen Server(dom0) or a Xen guest. The image
878        configuration is checked if the information uniquely identifies
879        the image as such
880        """
881        xen_message = dedent('''\n
882            Inconsistent Xen setup found:
883
884            The use of the 'xen_server' or 'xen_loader' attributes indicates
885            the target system for this image is Xen. However the image
886            specifies both attributes at the same time which classifies
887            the image to be both, a Xen Server(dom0) and a Xen guest at
888            the same time, which is not supported.
889
890            Please cleanup your image description. Setup only one
891            of 'xen_server' or 'xen_loader'.
892        ''')
893        ec2_message = dedent('''\n
894            Inconsistent Amazon EC2 setup found:
895
896            The firmware setup indicates the target system for this image
897            is Amazon EC2, which uses a Xen based virtualisation technology.
898            Therefore the image must be classified as a Xen guest and can
899            not be a Xen server as indicated by the 'xen_server' attribute
900
901            Please cleanup your image description. Delete the 'xen_server'
902            attribute for images used with Amazon EC2.
903        ''')
904        if self.xml_state.is_xen_server() and self.xml_state.is_xen_guest():
905            firmware = self.xml_state.build_type.get_firmware()
906            ec2_firmware_names = Defaults.get_ec2_capable_firmware_names()
907            if firmware and firmware in ec2_firmware_names:
908                raise KiwiRuntimeError(ec2_message)
909            else:
910                raise KiwiRuntimeError(xen_message)

If the image is classified to be used as Xen image, it can be either a Xen Server(dom0) or a Xen guest. The image configuration is checked if the information uniquely identifies the image as such

def check_mediacheck_installed(self) -> None:
912    def check_mediacheck_installed(self) -> None:
913        """
914        If the image description enables the mediacheck attribute
915        the required tools to run this check must be installed
916        on the image build host
917        """
918        message_tool_not_found = dedent('''\n
919            Required tool {name} not found in caller environment
920
921            The attribute 'mediacheck' is set to 'true' which requires
922            the above tool to be installed on the build system
923        ''')
924        if self.xml_state.build_type.get_mediacheck() is True:
925            tool = 'tagmedia'
926            media_tagger = RuntimeConfig().get_iso_media_tag_tool()
927            if media_tagger == 'checkmedia':
928                tool = 'tagmedia'
929            elif media_tagger == 'isomd5sum':
930                tool = 'implantisomd5'
931            if not Path.which(filename=tool, access_mode=os.X_OK):
932                raise KiwiRuntimeError(
933                    message_tool_not_found.format(name=tool)
934                )

If the image description enables the mediacheck attribute the required tools to run this check must be installed on the image build host

def check_image_version_provided(self) -> None:
936    def check_image_version_provided(self) -> None:
937        """
938        Kiwi requires a <version> element to be specified as part
939        of at least one <preferences> section.
940        """
941        message_missing_version = dedent('''\n
942            No version is defined in any of the <preferences>
943            sections. Please add
944
945                <version>image_version<version/>
946
947            inside the <preferences> section.
948        ''')
949
950        if not self.xml_state.get_image_version():
951            raise KiwiRuntimeError(message_missing_version)

Kiwi requires a element to be specified as part of at least one section.

def check_image_type_unique(self) -> None:
953    def check_image_type_unique(self) -> None:
954        """
955        Verify that the selected image type is unique within
956        the range of the configured types and profiles.
957        """
958        message = dedent('''\n
959            Conflicting image type setup detected
960
961            The selected image type '{0}' in the {1} profile
962            selection is not unique. There are the following type
963            settings which overrides each other:
964            {2}
965            To solve this conflict please move the image type
966            setup into its own profile and select them using
967            the --profile option at call time.
968        ''')
969        image_type_sections = []
970        type_dict: Dict[str, List[Any]] = {}
971        for preferences in self.xml_state.get_preferences_sections():
972            image_type_sections += preferences.get_type()
973
974        for image_type in image_type_sections:
975            type_name = image_type.get_image()
976            if type_dict.get(type_name):
977                type_dict[type_name].append(image_type)
978            else:
979                type_dict[type_name] = [image_type]
980
981        for type_name, type_list in list(type_dict.items()):
982            if len(type_list) > 1:
983                type_export = StringIO()
984                for image_type in type_list:
985                    type_export.write(os.linesep)
986                    image_type.export(type_export, 0)
987                raise KiwiRuntimeError(
988                    message.format(
989                        type_name, self.xml_state.profiles or ['Default'],
990                        type_export.getvalue()
991                    )
992                )

Verify that the selected image type is unique within the range of the configured types and profiles.

def check_efi_fat_image_has_correct_size(self) -> None:
 994    def check_efi_fat_image_has_correct_size(self) -> None:
 995        """
 996        Verify that the efifatimagesize does not exceed the max
 997        El Torito load size of 65535 * 512 bytes
 998        """
 999        message = dedent('''\n
1000            El Torito max load size exceeded
1001
1002            The configured efifatimagesize of '{0}MB' exceeds
1003            the El Torito max load size of 65535 * 512 bytes (~31MB).
1004        ''')
1005        fat_image_mbsize = int(
1006            self.xml_state.build_type
1007                .get_efifatimagesize() or defaults.EFI_FAT_IMAGE_SIZE
1008        )
1009        if fat_image_mbsize > 31:
1010            raise KiwiRuntimeError(
1011                message.format(fat_image_mbsize)
1012            )

Verify that the efifatimagesize does not exceed the max El Torito load size of 65535 * 512 bytes

def check_bootloader_env_compatible_with_loader(self) -> None:
1014    def check_bootloader_env_compatible_with_loader(self) -> None:
1015        """
1016        If there is an environment section as part of the bootloader
1017        section, check if the selected loader supports custom
1018        environment blobs
1019        """
1020        message = dedent('''\n
1021            Selected loader does not support custom environments
1022
1023            The selected bootloader {} does not support custom
1024            environment settings. Please drop the <environment>
1025            setting from <bootloadersettings> for this loader
1026        ''')
1027        env_supported_loaders = [
1028            'grub2',
1029            'grub2_s390x_emu',
1030            'custom'
1031        ]
1032        bootloader = self.xml_state.get_build_type_bootloader_name()
1033        variable_list = self.xml_state.\
1034            get_build_type_bootloader_environment_variables()
1035        if variable_list and bootloader not in env_supported_loaders:
1036            raise KiwiRuntimeError(
1037                message.format(bootloader)
1038            )

If there is an environment section as part of the bootloader section, check if the selected loader supports custom environment blobs