kiwi.xml_state

   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
  19from typing import (
  20    List, Optional, Any, Dict, NamedTuple, Callable, Union
  21)
  22import re
  23import logging
  24import copy
  25from textwrap import dedent
  26
  27# project
  28import kiwi.defaults as defaults
  29
  30from kiwi import xml_parse
  31from kiwi.storage.disk import ptable_entry_type
  32from kiwi.system.uri import Uri
  33from kiwi.defaults import Defaults
  34from kiwi.utils.size import StringToSize
  35from kiwi.command import Command
  36
  37from kiwi.exceptions import (
  38    KiwiProfileNotFound,
  39    KiwiTypeNotFound,
  40    KiwiDistributionNameError,
  41    KiwiFileAccessError
  42)
  43
  44log = logging.getLogger('kiwi')
  45
  46description_type = NamedTuple(
  47    'description_type', [
  48        ('author', str),
  49        ('contact', str),
  50        ('specification', str)
  51    ]
  52)
  53
  54package_type = NamedTuple(
  55    'package_type', [
  56        ('packages_section', xml_parse.packages),
  57        ('package_section', xml_parse.package)
  58    ]
  59)
  60
  61size_type = NamedTuple(
  62    'size_type', [
  63        ('mbytes', int),
  64        ('additive', str)
  65    ]
  66)
  67
  68volume_type = NamedTuple(
  69    'volume_type', [
  70        ('name', str),
  71        ('parent', str),
  72        ('size', str),
  73        ('realpath', str),
  74        ('mountpoint', Optional[str]),
  75        ('fullsize', bool),
  76        ('label', Optional[str]),
  77        ('attributes', list),
  78        ('is_root_volume', bool)
  79    ]
  80)
  81
  82
  83class DracutT(NamedTuple):
  84    uefi: bool
  85    modules: List[str]
  86    drivers: List[str]
  87
  88
  89class FileT(NamedTuple):
  90    target: str
  91    owner: str
  92    permissions: str
  93
  94
  95class ContainerT(NamedTuple):
  96    name: str
  97    backend: str
  98    container_file: str
  99    fetch_only: bool
 100    fetch_command: Callable
 101    load_command: List[str]
 102
 103
 104class XMLState:
 105    """
 106    **Implements methods to get stateful information from the XML data**
 107
 108    :param object xml_data: parse result from XMLDescription.load()
 109    :param list profiles: list of used profiles
 110    :param object build_type: build <type> section reference
 111    """
 112    def __init__(
 113        self, xml_data: Any, profiles: List = None,
 114        build_type: Any = None
 115    ):
 116        self.root_partition_uuid: Optional[str] = None
 117        self.root_filesystem_uuid: Optional[str] = None
 118        self.host_architecture = defaults.PLATFORM_MACHINE
 119        self.xml_data = xml_data
 120        self.profiles = self._used_profiles(profiles)
 121        self.build_type = self._build_type_section(
 122            build_type
 123        )
 124        self.resolve_this_path()
 125
 126    def get_preferences_sections(self) -> List:
 127        """
 128        All preferences sections for the selected profiles that match the
 129        host architecture
 130
 131        :return: list of <preferences> section reference(s)
 132
 133        :rtype: list
 134        """
 135        preferences_list = []
 136        for preferences in self._profiled(self.xml_data.get_preferences()):
 137            if self.preferences_matches_host_architecture(preferences):
 138                preferences_list.append(preferences)
 139        return preferences_list
 140
 141    def get_description_section(self) -> description_type:
 142        """
 143        The description section
 144
 145        :return:
 146            description_type tuple providing the elements
 147            author contact and specification
 148
 149        :rtype: tuple
 150        """
 151        description = self.xml_data.get_description()[0]
 152        return description_type(
 153            author=description.get_author()[0],
 154            contact=description.get_contact()[0],
 155            specification=description.get_specification()[0].strip()
 156        )
 157
 158    def get_users_sections(self) -> List:
 159        """
 160        All users sections for the selected profiles
 161
 162        :return: list of <users> section reference(s)
 163
 164        :rtype: list
 165        """
 166        users = []
 167        for users_section in self._profiled(self.xml_data.get_users()):
 168            if self.users_matches_host_architecture(users_section):
 169                users.append(users_section)
 170        return users
 171
 172    def get_build_type_bundle_format(self) -> str:
 173        """
 174        Return bundle_format for build type
 175
 176        The bundle_format string is validated against the available
 177        name tags from kiwi.system.result::result_name_tags.
 178
 179        :return: bundle format string
 180
 181        :rtype: str
 182        """
 183        return self.build_type.get_bundle_format()
 184
 185    def get_build_type_name(self) -> str:
 186        """
 187        Default build type name
 188
 189        :return: Content of image attribute from build type
 190
 191        :rtype: str
 192        """
 193        return self.build_type.get_image()
 194
 195    def btrfs_default_volume_requested(self) -> bool:
 196        """
 197        Check if setting a default volume for btrfs is requested
 198        """
 199        if self.build_type.get_btrfs_set_default_volume() is False:
 200            # Setting a default volume is explicitly switched off
 201            return False
 202        else:
 203            # In any other case (True | None) a default volume
 204            # is wanted and will be set
 205            return True
 206
 207    def get_image_version(self) -> str:
 208        """
 209        Image version from preferences section.
 210
 211        Multiple occurences of version in preferences sections are not
 212        forbidden, however only the first version found defines the
 213        final image version
 214
 215        :return: Content of <version> section
 216
 217        :rtype: str
 218        """
 219        for preferences in self.get_preferences_sections():
 220            version = preferences.get_version()
 221            if version:
 222                return version[0]
 223        return ''
 224
 225    def get_initrd_system(self) -> str:
 226        """
 227        Name of initrd system to use
 228
 229        Depending on the image type a specific initrd system is
 230        either pre selected or free of choice according to the
 231        XML type setup.
 232
 233        :return: 'dracut', 'kiwi' or 'none'
 234
 235        :rtype: str
 236        """
 237        pre_selection_map = {
 238            'vmx': 'dracut',
 239            'oem': 'dracut',
 240            'iso': 'dracut',
 241            'kis': 'dracut',
 242            'pxe': 'kiwi',
 243        }
 244        build_type = self.get_build_type_name()
 245        default_initrd_system = pre_selection_map.get(build_type) or 'none'
 246
 247        if build_type == 'iso':
 248            # iso type always use dracut as initrd system
 249            return default_initrd_system
 250
 251        # Allow to choose for any other build type
 252        return self.build_type.get_initrd_system() or default_initrd_system
 253
 254    def get_locale(self) -> Optional[List]:
 255        """
 256        Gets list of locale names if configured. Takes
 257        the first locale setup from the existing preferences
 258        sections into account.
 259
 260        :return: List of names or None
 261
 262        :rtype: list|None
 263        """
 264        for preferences in self.get_preferences_sections():
 265            locale_section = preferences.get_locale()
 266            if locale_section:
 267                return locale_section[0].split(',')
 268        return None
 269
 270    def get_rpm_locale(self) -> Optional[List]:
 271        """
 272        Gets list of locale names to filter out by rpm
 273        if rpm-locale-filtering is switched on the
 274        the list always contains: [POSIX, C, C.UTF-8]
 275        and is extended by the optionaly configured
 276        locale
 277
 278        :return: List of names or None
 279
 280        :rtype: list|None
 281        """
 282        if self.get_rpm_locale_filtering():
 283            rpm_locale = ['POSIX', 'C', 'C.UTF-8']
 284            configured_locale = self.get_locale()
 285            if configured_locale:
 286                for locale in configured_locale:
 287                    rpm_locale.append(locale)
 288            return rpm_locale
 289        return None
 290
 291    def get_rpm_locale_filtering(self) -> bool:
 292        """
 293        Gets the rpm-locale-filtering configuration flag. Returns
 294        False if not present.
 295
 296        :return: True or False
 297
 298        :rtype: bool
 299        """
 300        for preferences in self.get_preferences_sections():
 301            locale_filtering = preferences.get_rpm_locale_filtering()
 302            if locale_filtering:
 303                return locale_filtering[0]
 304        return False
 305
 306    def get_rpm_excludedocs(self) -> bool:
 307        """
 308        Gets the rpm-excludedocs configuration flag. Returns
 309        False if not present.
 310
 311        :return: True or False
 312
 313        :rtype: bool
 314        """
 315        for preferences in self.get_preferences_sections():
 316            exclude_docs = preferences.get_rpm_excludedocs()
 317            if exclude_docs:
 318                return exclude_docs[0]
 319        return False
 320
 321    def get_rpm_check_signatures(self) -> bool:
 322        """
 323        Gets the rpm-check-signatures configuration flag. Returns
 324        False if not present.
 325
 326        :return: True or False
 327
 328        :rtype: bool
 329        """
 330        for preferences in self.get_preferences_sections():
 331            check_signatures = preferences.get_rpm_check_signatures()
 332            if check_signatures:
 333                return check_signatures[0]
 334        return False
 335
 336    def get_package_manager(self) -> str:
 337        """
 338        Get configured package manager from selected preferences section
 339
 340        :return: Content of the <packagemanager> section
 341
 342        :rtype: str
 343        """
 344        for preferences in self.get_preferences_sections():
 345            package_manager = preferences.get_packagemanager()
 346            if package_manager:
 347                return package_manager[0]
 348        return Defaults.get_default_package_manager()
 349
 350    def get_release_version(self) -> str:
 351        """
 352        Get configured release version from selected preferences section
 353
 354        :return: Content of the <release-version> section or ''
 355
 356        :rtype: str
 357        """
 358        release_version = ''
 359        for preferences in self.get_preferences_sections():
 360            release_version = preferences.get_release_version()
 361            if release_version:
 362                release_version = release_version[0]
 363                break
 364        return release_version
 365
 366    def get_packages_sections(self, section_types: List) -> List:
 367        """
 368        List of packages sections matching given section type(s)
 369
 370        :param list section_types: type name(s) from packages sections
 371
 372        :return: list of <packages> section reference(s)
 373
 374        :rtype: list
 375        """
 376        result = []
 377        packages_sections = self._profiled(
 378            self.xml_data.get_packages()
 379        )
 380        for packages in packages_sections:
 381            packages_type = packages.get_type()
 382            if packages_type in section_types:
 383                result.append(packages)
 384        return result
 385
 386    def requires_matches_host_architecture(self, requires: Any) -> bool:
 387        """
 388        Tests if the given profile requires section is applicable for
 389        the current host architecture. If no architecture is specified
 390        within the section it is considered as a match returning True.
 391
 392        Note: The XML section pointer must provide an arch attribute
 393
 394        :param section: XML section object
 395
 396        :return: True or False
 397
 398        :rtype: bool
 399        """
 400        return self._section_matches_host_architecture(requires)
 401
 402    def volume_matches_host_architecture(self, volume: Any) -> bool:
 403        """
 404        Tests if the given volume section is applicable for the current host
 405        architecture. If no architecture is specified within the section
 406        it is considered as a match returning True.
 407
 408        Note: The XML section pointer must provide an arch attribute
 409
 410        :param section: XML section object
 411
 412        :return: True or False
 413
 414        :rtype: bool
 415        """
 416        return self._section_matches_host_architecture(volume)
 417
 418    def package_matches_host_architecture(self, package: Any) -> bool:
 419        """
 420        Tests if the given package section is applicable for the current host
 421        architecture. If no architecture is specified within the section
 422        it is considered as a match returning True.
 423
 424        Note: The XML section pointer must provide an arch attribute
 425
 426        :param section: XML section object
 427
 428        :return: True or False
 429
 430        :rtype: bool
 431        """
 432        return self._section_matches_host_architecture(package)
 433
 434    def users_matches_host_architecture(self, users: Any) -> bool:
 435        """
 436        Tests if the given users section is applicable for the current host
 437        architecture. If no architecture is specified within the section
 438        it is considered as a match returning True.
 439
 440        Note: The XML section pointer must provide an arch attribute
 441
 442        :param section: XML section object
 443
 444        :return: True or False
 445
 446        :rtype: bool
 447        """
 448        return self._section_matches_host_architecture(users)
 449
 450    def collection_matches_host_architecture(self, collection: Any) -> bool:
 451        """
 452        Tests if the given namedcollection section is applicable for
 453        the current host architecture. If no architecture is specified
 454        within the section it is considered as a match returning True.
 455
 456        Note: The XML section pointer must provide an arch attribute
 457
 458        :param section: XML section object
 459
 460        :return: True or False
 461
 462        :rtype: bool
 463        """
 464        return self._section_matches_host_architecture(collection)
 465
 466    def profile_matches_host_architecture(self, profile: Any) -> bool:
 467        """
 468        Tests if the given profile section is applicable for the current host
 469        architecture. If no architecture is specified within the section
 470        it is considered as a match returning True.
 471
 472        Note: The XML section pointer must provide an arch attribute
 473
 474        :param section: XML section object
 475
 476        :return: True or False
 477
 478        :rtype: bool
 479        """
 480        return self._section_matches_host_architecture(profile)
 481
 482    def preferences_matches_host_architecture(self, preferences: Any) -> bool:
 483        """
 484        Tests if the given preferences section is applicable for the
 485        current host architecture. If no architecture is specified within
 486        the section it is considered as a match returning True.
 487
 488        Note: The XML section pointer must provide an arch attribute
 489
 490        :param section: XML section object
 491
 492        :return: True or False
 493
 494        :rtype: bool
 495        """
 496        return self._section_matches_host_architecture(preferences)
 497
 498    def repository_matches_host_architecture(self, repository: Any) -> bool:
 499        """
 500        Tests if the given repository section is applicable for the
 501        current host architecture. If no architecture is specified within
 502        the section it is considered as a match returning True.
 503
 504        Note: The XML section pointer must provide an arch attribute
 505
 506        :param section: XML section object
 507
 508        :return: True or False
 509
 510        :rtype: bool
 511        """
 512        return self._section_matches_host_architecture(repository)
 513
 514    def containers_matches_host_architecture(self, containers: Any) -> bool:
 515        """
 516        Tests if the given containers section is applicable for the
 517        current host architecture. If no arch attribute is provided in
 518        the section it is considered as a match and returns: True.
 519
 520        :param section: XML section object
 521
 522        :return: True or False
 523
 524        :rtype: bool
 525        """
 526        return self._section_matches_host_architecture(containers)
 527
 528    def container_matches_host_architecture(self, container: Any) -> bool:
 529        """
 530        Tests if the given container section is applicable for the
 531        current host architecture. If no arch attribute is provided in
 532        the section it is considered as a match and returns: True.
 533
 534        :param section: XML section object
 535
 536        :return: True or False
 537
 538        :rtype: bool
 539        """
 540        return self._section_matches_host_architecture(container)
 541
 542    def get_package_sections(
 543        self, packages_sections: List
 544    ) -> List[package_type]:
 545        """
 546        List of package sections from the given packages sections.
 547        Each list element contains a tuple with the <package> section
 548        reference and the <packages> section this package belongs to
 549
 550        If a package entry specfies an architecture, it is only taken if
 551        the host architecture matches the configured architecture
 552
 553        :param list packages_sections: <packages>
 554
 555        :return:
 556            Contains list of package_type tuples
 557
 558            .. code:: python
 559
 560                [package_type(packages_section=object, package_section=object)]
 561
 562        :rtype: list
 563        """
 564        result = []
 565        if packages_sections:
 566            for packages_section in packages_sections:
 567                package_list = packages_section.get_package()
 568                if package_list:
 569                    for package in package_list:
 570                        if self.package_matches_host_architecture(package):
 571                            result.append(
 572                                package_type(
 573                                    packages_section=packages_section,
 574                                    package_section=package
 575                                )
 576                            )
 577        return result
 578
 579    def get_to_become_deleted_packages(self, force: bool = True) -> List:
 580        """
 581        List of package names from the type="delete" or type="uninstall"
 582        packages section(s)
 583
 584        :param bool force: return "delete" type if True, "uninstall" type
 585            otherwise
 586
 587        :return: package names
 588
 589        :rtype: list
 590        """
 591        result = []
 592        to_become_deleted_packages_sections = self.get_packages_sections(
 593            ['delete' if force else 'uninstall']
 594        )
 595        package_list = self.get_package_sections(
 596            to_become_deleted_packages_sections
 597        )
 598        if package_list:
 599            for package in package_list:
 600                result.append(package.package_section.get_name())
 601        return sorted(list(set(result)))
 602
 603    def get_bootstrap_packages_sections(self) -> List:
 604        """
 605        List of packages sections matching type="bootstrap"
 606
 607        :return: list of <packages> section reference(s)
 608
 609        :rtype: list
 610        """
 611        return self.get_packages_sections(['bootstrap'])
 612
 613    def get_image_packages_sections(self) -> List:
 614        """
 615        List of packages sections matching type="image"
 616
 617        :return: list of <packages> section reference(s)
 618
 619        :rtype: list
 620        """
 621        return self.get_packages_sections(['image'])
 622
 623    def get_bootstrap_packages(self, plus_packages: List = None) -> List:
 624        """
 625        List of package names from the type="bootstrap" packages section(s)
 626
 627        The list gets the selected package manager appended
 628        if there is a request to install packages inside of
 629        the image via a chroot operation
 630
 631        :param list plus_packages: list of additional packages
 632
 633        :return: package names
 634
 635        :rtype: list
 636        """
 637        result = []
 638        bootstrap_packages_sections = self.get_bootstrap_packages_sections()
 639        package_list = self.get_package_sections(
 640            bootstrap_packages_sections
 641        )
 642        if package_list:
 643            for package in package_list:
 644                result.append(package.package_section.get_name().strip())
 645            if self.get_system_packages():
 646                package_manager_name = self.get_package_manager()
 647                if package_manager_name == 'dnf4':
 648                    # The package name for dnf4 is just dnf. Thus
 649                    # the name must be adapted in this case
 650                    package_manager_name = 'dnf'
 651                elif package_manager_name == 'apk':
 652                    package_manager_name = 'apk-tools'
 653                result.append(package_manager_name)
 654        if plus_packages:
 655            result += plus_packages
 656        return sorted(list(set(result)))
 657
 658    def get_system_packages(self) -> List:
 659        """
 660        List of package names from the packages sections matching
 661        type="image" and type=build_type
 662
 663        :return: package names
 664
 665        :rtype: list
 666        """
 667        result = []
 668        image_packages_sections = self.get_packages_sections(
 669            ['image', self.get_build_type_name()]
 670        )
 671        package_list = self.get_package_sections(
 672            image_packages_sections
 673        )
 674        if package_list:
 675            for package in package_list:
 676                result.append(package.package_section.get_name().strip())
 677        return sorted(list(set(result)))
 678
 679    def get_bootstrap_files(self) -> Dict[str, FileT]:
 680        """
 681        List of file names from the type="bootstrap" packages section(s)
 682
 683        :return: file names
 684
 685        :rtype: dict
 686        """
 687        result = {}
 688        bootstrap_packages_sections = self.get_bootstrap_packages_sections()
 689        if bootstrap_packages_sections:
 690            for bootstrap_packages_section in bootstrap_packages_sections:
 691                file_list = bootstrap_packages_section.get_file() or []
 692                for file in file_list:
 693                    result[file.get_name()] = FileT(
 694                        target=file.get_target() or '',
 695                        owner=file.get_owner() or '',
 696                        permissions=file.get_permissions() or ''
 697                    )
 698        return result
 699
 700    def get_system_files(self) -> Dict[str, FileT]:
 701        """
 702        List of file names from the packages sections matching
 703        type="image" and type=build_type
 704
 705        :return: file names
 706
 707        :rtype: dict
 708        """
 709        result = {}
 710        image_packages_sections = self.get_packages_sections(
 711            ['image', self.get_build_type_name()]
 712        )
 713        for packages in image_packages_sections:
 714            for file in packages.get_file():
 715                result[file.get_name()] = FileT(
 716                    target=file.get_target() or '',
 717                    owner=file.get_owner() or '',
 718                    permissions=file.get_permissions() or ''
 719                )
 720        return result
 721
 722    def get_bootstrap_archives(self) -> List:
 723        """
 724        List of archive names from the type="bootstrap" packages section(s)
 725
 726        :return: archive names
 727
 728        :rtype: list
 729        """
 730        result = []
 731        bootstrap_packages_sections = self.get_bootstrap_packages_sections()
 732        if bootstrap_packages_sections:
 733            for bootstrap_packages_section in bootstrap_packages_sections:
 734                archive_list = bootstrap_packages_section.get_archive()
 735                if archive_list:
 736                    for archive in archive_list:
 737                        result.append(archive.get_name().strip())
 738        return sorted(result)
 739
 740    def get_system_archives(self) -> List:
 741        """
 742        List of archive names from the packages sections matching
 743        type="image" and type=build_type
 744
 745        :return: archive names
 746
 747        :rtype: list
 748        """
 749        result = []
 750        image_packages_sections = self.get_packages_sections(
 751            ['image', self.get_build_type_name()]
 752        )
 753        for packages in image_packages_sections:
 754            for archive in packages.get_archive():
 755                result.append(archive.get_name().strip())
 756        return sorted(result)
 757
 758    def get_ignore_packages(self, section_type: str) -> List:
 759        """
 760        List of ignore package names from the packages sections matching
 761        section_type and type=build_type
 762
 763        :return: package names
 764
 765        :rtype: list
 766        """
 767        result = []
 768        image_packages_sections = self.get_packages_sections(
 769            [section_type, self.get_build_type_name()]
 770        )
 771        for packages in image_packages_sections:
 772            for package in packages.get_ignore():
 773                if self.package_matches_host_architecture(package):
 774                    result.append(package.get_name().strip())
 775        return sorted(result)
 776
 777    def get_system_files_ignore_packages(self) -> List[str]:
 778        """
 779        List of ignore package names from the type="systemfiles"
 780        packages section(s)
 781
 782        :return: package names
 783
 784        :rtype: list
 785        """
 786        return self.get_ignore_packages('systemfiles')
 787
 788    def get_system_ignore_packages(self) -> List:
 789        """
 790        List of ignore package names from the packages sections matching
 791        type="image" and type=build_type
 792
 793        :return: package names
 794
 795        :rtype: list
 796        """
 797        return self.get_ignore_packages('image')
 798
 799    def get_bootstrap_ignore_packages(self) -> List:
 800        """
 801        List of ignore package names from the packages sections matching
 802        type="image" and type=build_type
 803
 804        :return: package names
 805
 806        :rtype: list
 807        """
 808        return self.get_ignore_packages('bootstrap')
 809
 810    def get_bootstrap_package_name(self) -> str:
 811        """
 812        bootstrap_package name from type="bootstrap" packages section
 813
 814        :return: bootstrap_package name
 815
 816        :rtype: str
 817        """
 818        typed_packages_sections = self.get_packages_sections(
 819            ['bootstrap', self.get_build_type_name()]
 820        )
 821        bootstrap_package = ''
 822        for packages in typed_packages_sections:
 823            bootstrap_package = packages.get_bootstrap_package()
 824            if bootstrap_package:
 825                break
 826        return bootstrap_package
 827
 828    def get_collection_type(self, section_type: str = 'image') -> str:
 829        """
 830        Collection type from packages sections matching given section
 831        type.
 832
 833        If no collection type is specified the default collection
 834        type is set to: onlyRequired
 835
 836        :param str section_type: type name from packages section
 837
 838        :return: collection type name
 839
 840        :rtype: str
 841        """
 842        typed_packages_sections = self.get_packages_sections(
 843            [section_type, self.get_build_type_name()]
 844        )
 845        collection_type = 'onlyRequired'
 846        for packages in typed_packages_sections:
 847            packages_collection_type = packages.get_patternType()
 848            if packages_collection_type:
 849                collection_type = packages_collection_type
 850                break
 851        return collection_type
 852
 853    def get_bootstrap_collection_type(self) -> str:
 854        """
 855        Collection type for packages sections matching type="bootstrap"
 856
 857        :return: collection type name
 858
 859        :rtype: str
 860        """
 861        return self.get_collection_type('bootstrap')
 862
 863    def get_system_collection_type(self) -> str:
 864        """
 865        Collection type for packages sections matching type="image"
 866
 867        :return: collection type name
 868
 869        :rtype: str
 870        """
 871        return self.get_collection_type('image')
 872
 873    def get_collection_modules(self) -> Dict[str, List[str]]:
 874        """
 875        Dict of collection modules to enable and/or disable
 876
 877        :return:
 878            Dict of the form:
 879
 880            .. code:: python
 881
 882                {
 883                    'enable': [
 884                        "module:stream", "module"
 885                    ],
 886                    'disable': [
 887                        "module"
 888                    ]
 889                }
 890
 891        :rtype: dict
 892        """
 893        modules: Dict[str, List[str]] = {
 894            'disable': [],
 895            'enable': []
 896        }
 897        for packages in self.get_bootstrap_packages_sections():
 898            for collection_module in packages.get_collectionModule():
 899                module_name = collection_module.get_name()
 900                if collection_module.get_enable() is False:
 901                    modules['disable'].append(module_name)
 902                else:
 903                    stream = collection_module.get_stream()
 904                    if stream:
 905                        modules['enable'].append(f'{module_name}:{stream}')
 906                    else:
 907                        modules['enable'].append(module_name)
 908        return modules
 909
 910    def get_collections(self, section_type: str = 'image') -> List:
 911        """
 912        List of collection names from the packages sections matching
 913        type=section_type and type=build_type
 914
 915        :return: collection names
 916
 917        :rtype: list
 918        """
 919        result = []
 920        typed_packages_sections = self.get_packages_sections(
 921            [section_type, self.get_build_type_name()]
 922        )
 923        for packages in typed_packages_sections:
 924            for collection in packages.get_namedCollection():
 925                if self.collection_matches_host_architecture(collection):
 926                    result.append(collection.get_name())
 927        return sorted(list(set(result)))
 928
 929    def get_bootstrap_collections(self) -> List:
 930        """
 931        List of collection names from the packages sections
 932        matching type="bootstrap"
 933
 934        :return: collection names
 935
 936        :rtype: list
 937        """
 938        return self.get_collections('bootstrap')
 939
 940    def get_system_collections(self) -> List:
 941        """
 942        List of collection names from the packages sections
 943        matching type="image"
 944
 945        :return: collection names
 946
 947        :rtype: list
 948        """
 949        return self.get_collections('image')
 950
 951    def get_products(self, section_type: str = 'image') -> List:
 952        """
 953        List of product names from the packages sections matching
 954        type=section_type and type=build_type
 955
 956        :param str section_type: type name from packages section
 957
 958        :return: product names
 959
 960        :rtype: list
 961        """
 962        result = []
 963        typed_packages_sections = self.get_packages_sections(
 964            [section_type, self.get_build_type_name()]
 965        )
 966        for packages in typed_packages_sections:
 967            for product in packages.get_product():
 968                result.append(product.get_name())
 969        return list(set(result))
 970
 971    def get_bootstrap_products(self) -> List:
 972        """
 973        List of product names from the packages sections
 974        matching type="bootstrap"
 975
 976        :return: product names
 977
 978        :rtype: list
 979        """
 980        return self.get_products('bootstrap')
 981
 982    def get_system_products(self) -> List:
 983        """
 984        List of product names from the packages sections
 985        matching type="image"
 986
 987        :return: product names
 988
 989        :rtype: list
 990        """
 991        return self.get_products('image')
 992
 993    def is_xen_server(self) -> bool:
 994        """
 995        Check if build type domain setup specifies a Xen Server (dom0)
 996
 997        :return: True or False
 998
 999        :rtype: bool
1000        """
1001        return self.build_type.get_xen_server()
1002
1003    def is_xen_guest(self) -> bool:
1004        """
1005        Check if build type setup specifies a Xen Guest (domX)
1006        The check is based on the architecture, the firmware and
1007        xen_loader configuration values:
1008
1009        * We only support Xen setup on the x86_64 architecture
1010
1011        * Firmware pointing to ec2 means the image is targeted to run
1012          in Amazon EC2 which is a Xen guest
1013
1014        * Machine setup with a xen_loader attribute also indicates a
1015          Xen guest target
1016
1017        :return: True or False
1018
1019        :rtype: bool
1020        """
1021        if self.host_architecture != 'x86_64':
1022            # We only support Xen stuff on x86_64
1023            return False
1024        firmware = self.build_type.get_firmware()
1025        machine_section = self.get_build_type_machine_section()
1026        if firmware and firmware in Defaults.get_ec2_capable_firmware_names():
1027            # the image is targeted to run in Amazon EC2 which is a Xen system
1028            return True
1029        elif machine_section and machine_section.get_xen_loader():
1030            # the image provides a machine section with a guest loader setup
1031            return True
1032        return False
1033
1034    def get_build_type_partitions_section(self) -> Any:
1035        """
1036        First partitions section from the build type section
1037
1038        :return: <partitions> section reference
1039
1040        :rtype: xml_parse::partitions
1041        """
1042        partitions_sections = self.build_type.get_partitions()
1043        if partitions_sections:
1044            return partitions_sections[0]
1045        return None
1046
1047    def get_build_type_system_disk_section(self) -> Any:
1048        """
1049        First system disk section from the build type section
1050
1051        :return: <systemdisk> section reference
1052
1053        :rtype: xml_parse::systemdisk
1054        """
1055        systemdisk_sections = self.build_type.get_systemdisk()
1056        if systemdisk_sections:
1057            return systemdisk_sections[0]
1058        return None
1059
1060    def get_build_type_machine_section(self) -> Any:
1061        """
1062        First machine section from the build type section
1063
1064        :return: <machine> section reference
1065
1066        :rtype: xml_parse::machine
1067        """
1068        machine_sections = self.build_type.get_machine()
1069        if machine_sections:
1070            return machine_sections[0]
1071        return None
1072
1073    def get_build_type_vagrant_config_section(self) -> Any:
1074        """
1075        First vagrantconfig section from the build type section
1076
1077        :return: <vagrantconfig> section reference
1078
1079        :rtype: xml_parse::vagrantconfig
1080        """
1081        vagrant_config_sections = self.build_type.get_vagrantconfig()
1082        if vagrant_config_sections:
1083            return vagrant_config_sections[0]
1084        return None
1085
1086    def get_vagrant_config_virtualbox_guest_additions(self) -> bool:
1087        """
1088        Attribute virtualbox_guest_additions_present from the first
1089        vagrantconfig section.
1090
1091        :return: True|False
1092
1093        :rtype: bool
1094        """
1095        vagrant_config_sections = self.get_build_type_vagrant_config_section()
1096        if not vagrant_config_sections.virtualbox_guest_additions_present:
1097            return Defaults.get_vagrant_config_virtualbox_guest_additions()
1098        else:
1099            return vagrant_config_sections.virtualbox_guest_additions_present
1100
1101    def get_build_type_vmdisk_section(self) -> Any:
1102        """
1103        First vmdisk section from the first machine section in the
1104        build type section
1105
1106        :return: <vmdisk> section reference
1107
1108        :rtype: xml_parse::vmdisk
1109        """
1110        machine_section = self.get_build_type_machine_section()
1111        if machine_section:
1112            vmdisk_sections = machine_section.get_vmdisk()
1113            if vmdisk_sections:
1114                return vmdisk_sections[0]
1115        return None
1116
1117    def get_build_type_vmnic_entries(self) -> List:
1118        """
1119        vmnic section(s) from the first machine section in the
1120        build type section
1121
1122        :return: list of <vmnic> section reference(s)
1123
1124        :rtype: list
1125        """
1126        machine_section = self.get_build_type_machine_section()
1127        if machine_section:
1128            return machine_section.get_vmnic()
1129        else:
1130            return []
1131
1132    def get_build_type_vmdvd_section(self) -> Any:
1133        """
1134        First vmdvd section from the first machine section in the
1135        build type section
1136
1137        :return: <vmdvd> section reference
1138
1139        :rtype: xml_parse::vmdvd
1140        """
1141        machine_section = self.get_build_type_machine_section()
1142        if machine_section:
1143            vmdvd_sections = machine_section.get_vmdvd()
1144            if vmdvd_sections:
1145                return vmdvd_sections[0]
1146        return None
1147
1148    def get_build_type_vmconfig_entries(self) -> List:
1149        """
1150        List of vmconfig-entry section values from the first
1151        machine section in the build type section
1152
1153        :return: <vmconfig_entry> section reference(s)
1154
1155        :rtype: list
1156        """
1157        machine_section = self.get_build_type_machine_section()
1158        if machine_section:
1159            vmconfig_entries = machine_section.get_vmconfig_entry()
1160            if vmconfig_entries:
1161                return vmconfig_entries
1162
1163        return []
1164
1165    def get_build_type_bootloader_section(self) -> Any:
1166        """
1167        First bootloader section from the build type section
1168
1169        :return: <bootloader> section reference
1170
1171        :rtype: xml_parse::bootloader
1172        """
1173        bootloader_sections = self.build_type.get_bootloader()
1174        if bootloader_sections:
1175            return bootloader_sections[0]
1176        return None
1177
1178    def get_build_type_bootloader_name(self) -> str:
1179        """
1180        Return bootloader name for selected build type
1181
1182        :return: bootloader name
1183
1184        :rtype: str
1185        """
1186        bootloader = self.get_build_type_bootloader_section()
1187        return bootloader.get_name() if bootloader else \
1188            Defaults.get_default_bootloader()
1189
1190    def get_build_type_bootloader_bls(self) -> bool:
1191        """
1192        Return bootloader bls setting for selected build type
1193
1194        :return: True or False
1195
1196        :rtype: bool
1197        """
1198        bootloader = self.get_build_type_bootloader_section()
1199        if bootloader and bootloader.get_bls() is not None:
1200            return bootloader.get_bls()
1201        return True
1202
1203    def get_build_type_bootloader_console(self) -> List[str]:
1204        """
1205        Return bootloader console setting for selected build type
1206
1207        :return:
1208            list of console settings for output (first element)
1209            and input (second element)
1210
1211        :rtype: list
1212        """
1213        result = ['', '']
1214        bootloader = self.get_build_type_bootloader_section()
1215        if bootloader:
1216            console_out = bootloader.get_output_console()
1217            console_in = bootloader.get_input_console()
1218            console_in = console_in if console_in else console_out
1219            result = [
1220                console_out if console_out and console_out != 'none' else '',
1221                console_in if console_in and console_in != 'none' else ''
1222            ]
1223        return result
1224
1225    def get_build_type_bootloader_serial_line_setup(self) -> Optional[str]:
1226        """
1227        Return bootloader serial line setup parameters for the
1228        selected build type
1229
1230        :return: serial line setup
1231
1232        :rtype: str
1233        """
1234        bootloader = self.get_build_type_bootloader_section()
1235        if bootloader:
1236            return bootloader.get_serial_line()
1237        return None
1238
1239    def get_build_type_bootloader_timeout(self) -> Optional[str]:
1240        """
1241        Return bootloader timeout setting for selected build type
1242
1243        :return: timeout string
1244
1245        :rtype: str
1246        """
1247        bootloader = self.get_build_type_bootloader_section()
1248        if bootloader:
1249            return bootloader.get_timeout()
1250        return None
1251
1252    def get_build_type_bootloader_timeout_style(self) -> Optional[str]:
1253        """
1254        Return bootloader timeout style setting for selected build type
1255
1256        :return: timeout_style string
1257
1258        :rtype: str
1259        """
1260        bootloader = self.get_build_type_bootloader_section()
1261        if bootloader:
1262            return bootloader.get_timeout_style()
1263        return None
1264
1265    def get_build_type_bootloader_targettype(self) -> Optional[str]:
1266        """
1267        Return bootloader target type setting. Only relevant for
1268        the zipl bootloader because zipl is installed differently
1269        depending on the storage target it runs later
1270
1271        :return: target type string
1272
1273        :rtype: str
1274        """
1275        bootloader = self.get_build_type_bootloader_section()
1276        if bootloader:
1277            return bootloader.get_targettype()
1278        return None
1279
1280    def get_build_type_bootloader_settings_section(self) -> Any:
1281        """
1282        First bootloadersettings section from the build
1283        type bootloader section
1284
1285        :return: <bootloadersettings> section reference
1286
1287        :rtype: xml_parse::bootloadersettings
1288        """
1289        bootloader_section = self.get_build_type_bootloader_section()
1290        bootloader_settings_section = None
1291        if bootloader_section and bootloader_section.get_bootloadersettings():
1292            bootloader_settings_section = \
1293                bootloader_section.get_bootloadersettings()[0]
1294        return bootloader_settings_section
1295
1296    def get_build_type_bootloader_securelinux_section(self) -> List[Any]:
1297        """
1298        First securelinux section from the build
1299        type bootloader section
1300
1301        :return: <securelinux> section reference
1302
1303        :rtype: xml_parse::securelinux
1304        """
1305        bootloader_section = self.get_build_type_bootloader_section()
1306        bootloader_securelinux_section = []
1307        if bootloader_section and bootloader_section.get_securelinux():
1308            bootloader_securelinux_section = \
1309                bootloader_section.get_securelinux()
1310        return bootloader_securelinux_section
1311
1312    def get_build_type_bootloader_environment_variables(self) -> List[str]:
1313        """
1314        List of bootloader variables from the build
1315        type > bootloader > bootloadersettings section
1316        """
1317        variable_list = []
1318        bootloader_settings_section = \
1319            self.get_build_type_bootloader_settings_section()
1320        if bootloader_settings_section:
1321            environment = bootloader_settings_section.get_environment()
1322            if environment and environment[0].get_env():
1323                for env in environment[0].get_env():
1324                    variable_list.append(
1325                        '{}{}'.format(
1326                            env.get_name(),
1327                            f'={env.get_value()}' if env.get_value() else ''
1328                        )
1329                    )
1330        return variable_list
1331
1332    def get_bootloader_options(self, option_type: str) -> List[str]:
1333        """
1334        List of custom options used in the process to
1335        run bootloader setup workloads
1336        """
1337        result: List[str] = []
1338        bootloader_settings = self.get_build_type_bootloader_settings_section()
1339        if bootloader_settings:
1340            options = []
1341            if option_type == 'shim':
1342                options = bootloader_settings.get_shimoption()
1343            elif option_type == 'install':
1344                options = bootloader_settings.get_installoption()
1345            elif option_type == 'config':
1346                options = bootloader_settings.get_configoption()
1347            for option in options:
1348                result.append(option.get_name())
1349                if option.get_value():
1350                    result.append(option.get_value())
1351        return result
1352
1353    def get_bootloader_shim_options(self) -> List[str]:
1354        """
1355        List of custom options used in the process to setup secure boot
1356        """
1357        return self.get_bootloader_options('shim')
1358
1359    def get_bootloader_install_options(self) -> List[str]:
1360        """
1361        List of custom options used in the bootloader installation
1362        """
1363        return self.get_bootloader_options('install')
1364
1365    def get_bootloader_config_options(self) -> List[str]:
1366        """
1367        List of custom options used in the bootloader configuration
1368        """
1369        return self.get_bootloader_options('config')
1370
1371    def get_build_type_bootloader_use_disk_password(self) -> bool:
1372        """
1373        Indicate whether the bootloader configuration should use the
1374        password protecting the encrypted root volume.
1375
1376        :return: True|False
1377
1378        :rtype: bool
1379        """
1380        bootloader = self.get_build_type_bootloader_section()
1381        if bootloader:
1382            return bootloader.get_use_disk_password()
1383        return False
1384
1385    def get_build_type_oemconfig_section(self) -> Any:
1386        """
1387        First oemconfig section from the build type section
1388
1389        :return: <oemconfig> section reference
1390
1391        :rtype: xml_parse::oemconfig
1392        """
1393        oemconfig_sections = self.build_type.get_oemconfig()
1394        if oemconfig_sections:
1395            return oemconfig_sections[0]
1396        return None
1397
1398    def get_oemconfig_oem_resize(self) -> bool:
1399        """
1400        State value to activate/deactivate disk resize. Returns a
1401        boolean value if specified or True to set resize on by default
1402
1403        :return: Content of <oem-resize> section value
1404
1405        :rtype: bool
1406        """
1407        oemconfig = self.get_build_type_oemconfig_section()
1408        if oemconfig and oemconfig.get_oem_resize():
1409            return oemconfig.get_oem_resize()[0]
1410        else:
1411            return True
1412
1413    def get_oemconfig_oem_systemsize(self) -> int:
1414        """
1415        State value to retrieve root partition size
1416
1417        :return: Content of <oem-systemsize> section value
1418
1419        :rtype: int
1420        """
1421        oemconfig = self.get_build_type_oemconfig_section()
1422        if oemconfig and oemconfig.get_oem_systemsize():
1423            return int(oemconfig.get_oem_systemsize()[0])
1424        else:
1425            return 0
1426
1427    def get_oemconfig_oem_multipath_scan(self) -> bool:
1428        """
1429        State value to activate multipath maps. Returns a boolean
1430        value if specified or False
1431
1432        :return: Content of <oem-multipath-scan> section value
1433
1434        :rtype: bool
1435        """
1436        oemconfig = self.get_build_type_oemconfig_section()
1437        if oemconfig and oemconfig.get_oem_multipath_scan():
1438            return oemconfig.get_oem_multipath_scan()[0]
1439        return False
1440
1441    def get_oemconfig_swap_mbytes(self) -> Optional[int]:
1442        """
1443        Return swapsize in MB if requested or None
1444
1445        Operates on the value of oem-swap and if set to true
1446        returns the given size or the default value.
1447
1448        :return: Content of <oem-swapsize> section value or default
1449
1450        :rtype: int
1451        """
1452        oemconfig = self.get_build_type_oemconfig_section()
1453        if oemconfig and oemconfig.get_oem_swap():
1454            swap_requested = oemconfig.get_oem_swap()[0]
1455            if swap_requested:
1456                swapsize = oemconfig.get_oem_swapsize()
1457                if swapsize:
1458                    return swapsize[0]
1459                else:
1460                    return Defaults.get_swapsize_mbytes()
1461        return None
1462
1463    def get_oemconfig_swap_name(self) -> str:
1464        """
1465        Return the swap space name
1466
1467        Operates on the value of oem-swapname and if set
1468        returns the configured name or the default name: LVSwap
1469
1470        The name of the swap space is used only if the
1471        image is configured to use the LVM volume manager.
1472        In this case swap is a volume and the volume takes
1473        a name. In any other case the given name will have
1474        no effect.
1475
1476        :return: Content of <oem-swapname> section value or default
1477
1478        :rtype: str
1479        """
1480        oemconfig = self.get_build_type_oemconfig_section()
1481        if oemconfig and oemconfig.get_oem_swapname():
1482            return oemconfig.get_oem_swapname()[0]
1483        return 'LVSwap'
1484
1485    def get_build_type_containerconfig_section(self) -> Any:
1486        """
1487        First containerconfig section from the build type section
1488
1489        :return: <containerconfig> section reference
1490
1491        :rtype: xml_parse::containerconfig
1492        """
1493        container_config_sections = self.build_type.get_containerconfig()
1494        if container_config_sections:
1495            return container_config_sections[0]
1496        return None
1497
1498    def get_dracut_config(self, action: str) -> DracutT:
1499        """
1500        Get dracut initrd config for the specified action
1501        """
1502        uefi = False
1503        modules = []
1504        drivers = []
1505        initrd_sections = self.build_type.get_initrd()
1506        for initrd_section in initrd_sections:
1507            if initrd_section.get_action() == action:
1508                for dracut in initrd_section.get_dracut():
1509                    uefi = bool(dracut.get_uefi())
1510                    if dracut.get_module():
1511                        modules.append(dracut.get_module())
1512                    if dracut.get_driver():
1513                        drivers.append(dracut.get_driver())
1514        return DracutT(
1515            uefi=uefi, modules=modules, drivers=drivers
1516        )
1517
1518    def get_installmedia_initrd_modules(self, action: str) -> List[str]:
1519        """
1520        Gets the list of modules to append in installation initrds
1521
1522        :return: a list of dracut module names
1523
1524        :rtype: list
1525        """
1526        modules: List[str] = []
1527        installmedia = self.build_type.get_installmedia()
1528        if not installmedia:
1529            return modules
1530        initrd_sections = installmedia[0].get_initrd()
1531        for initrd_section in initrd_sections:
1532            if initrd_section.get_action() == action:
1533                for module in initrd_section.get_dracut():
1534                    if module.get_module():
1535                        modules.append(module.get_module())
1536        return modules
1537
1538    def get_installmedia_initrd_drivers(self, action: str) -> List[str]:
1539        """
1540        Gets the list of drivers to append in installation initrds
1541
1542        :return: a list of dracut driver names
1543
1544        :rtype: list
1545        """
1546        drivers: List[str] = []
1547        installmedia = self.build_type.get_installmedia()
1548        if not installmedia:
1549            return drivers
1550        initrd_sections = installmedia[0].get_initrd()
1551        for initrd_section in initrd_sections:
1552            if initrd_section.get_action() == action:
1553                for driver in initrd_section.get_dracut():
1554                    if driver.get_driver():
1555                        drivers.append(driver.get_driver())
1556        return drivers
1557
1558    def get_build_type_size(
1559        self, include_unpartitioned: bool = False
1560    ) -> Optional[size_type]:
1561        """
1562        Size information from the build type section.
1563        If no unit is set the value is treated as mbytes
1564
1565        :param bool include_unpartitioned: sets if the unpartitioned area
1566            should be included in the computed size or not
1567
1568        :return: mbytes
1569
1570        :rtype: int
1571        """
1572        size_section = self.build_type.get_size()
1573        if size_section:
1574            unit = size_section[0].get_unit()
1575            additive = size_section[0].get_additive()
1576            unpartitioned = size_section[0].get_unpartitioned()
1577            value = int(size_section[0].get_valueOf_())
1578            if not include_unpartitioned and unpartitioned is not None:
1579                value -= unpartitioned
1580            if unit == 'G':
1581                value *= 1024
1582            return size_type(
1583                mbytes=value, additive=additive
1584            )
1585        return None
1586
1587    def get_build_type_unpartitioned_bytes(self) -> int:
1588        """
1589        Size of the unpartitioned area for image in megabytes
1590
1591        :return: mbytes
1592
1593        :rtype: int
1594        """
1595        size_section = self.build_type.get_size()
1596        if size_section:
1597            unit = size_section[0].get_unit() or 'M'
1598            unpartitioned = size_section[0].get_unpartitioned() or 0
1599            return StringToSize.to_bytes('{0}{1}'.format(unpartitioned, unit))
1600        return 0
1601
1602    def get_disk_start_sector(self) -> int:
1603        """
1604        First disk sector number to be used by the first disk partition.
1605
1606        :return: number
1607
1608        :rtype: int
1609        """
1610        disk_start_sector = self.build_type.get_disk_start_sector()
1611        if disk_start_sector is None:
1612            disk_start_sector = Defaults.get_default_disk_start_sector()
1613        return disk_start_sector
1614
1615    def get_build_type_spare_part_size(self) -> Optional[int]:
1616        """
1617        Size information for the spare_part size from the build
1618        type. If no unit is set the value is treated as mbytes
1619
1620        :return: mbytes
1621
1622        :rtype: int
1623        """
1624        spare_part_size = self.build_type.get_spare_part()
1625        if spare_part_size:
1626            return self._to_mega_byte(spare_part_size)
1627        return None
1628
1629    def get_build_type_spare_part_fs_attributes(self) -> Optional[List]:
1630        """
1631        Build type specific list of filesystem attributes applied to
1632        the spare partition.
1633
1634        :return: list of strings or empty list
1635
1636        :rtype: list
1637        """
1638        spare_part_attributes = self.build_type.get_spare_part_fs_attributes()
1639        if spare_part_attributes:
1640            return spare_part_attributes.strip().split(',')
1641        return None
1642
1643    def get_build_type_format_options(self) -> Dict:
1644        """
1645        Disk format options returned as a dictionary
1646
1647        :return: format options
1648
1649        :rtype: dict
1650        """
1651        result = {}
1652        format_options = self.build_type.get_formatoptions()
1653        if format_options:
1654            for option in format_options.split(','):
1655                key_value_list = option.split('=')
1656                if len(key_value_list) == 2:
1657                    result[key_value_list[0]] = key_value_list[1]
1658                else:
1659                    result[key_value_list[0]] = None
1660        return result
1661
1662    def get_volume_group_name(self) -> str:
1663        """
1664        Volume group name from selected <systemdisk> section
1665
1666        :return: volume group name
1667
1668        :rtype: str
1669        """
1670        systemdisk_section = self.get_build_type_system_disk_section()
1671        volume_group_name = None
1672        if systemdisk_section:
1673            volume_group_name = systemdisk_section.get_name()
1674        if not volume_group_name:
1675            volume_group_name = Defaults.get_default_volume_group_name()
1676        return volume_group_name
1677
1678    def get_users(self) -> List:
1679        """
1680        List of configured users.
1681
1682        Each entry in the list is a single xml_parse::user instance.
1683
1684        :return: list of <user> section reference(s)
1685
1686        :rtype: list
1687        """
1688        users_list = []
1689        users_names_added = []
1690        for users_section in self.get_users_sections():
1691            for user in users_section.get_user():
1692                if user.get_name() not in users_names_added:
1693                    users_list.append(user)
1694                    users_names_added.append(user.get_name())
1695
1696        return users_list
1697
1698    def get_user_groups(self, user_name) -> List[str]:
1699        """
1700        List of group names matching specified user
1701
1702        Each entry in the list is the name of a group and optionally its
1703        group ID separated by a colon, that the specified user belongs to.
1704        The first item in the list is the login or primary group. The
1705        list will be empty if no groups are specified in the
1706        description file.
1707
1708        :return: groups data for the given user
1709
1710        :rtype: list
1711        """
1712        groups_list = []
1713        for users_section in self.get_users_sections():
1714            for user in users_section.get_user():
1715                if user.get_name() == user_name:
1716                    user_groups = user.get_groups()
1717                    if user_groups:
1718                        groups_list += user.get_groups().split(',')
1719
1720        # order of list items matter, thus we don't use set() here
1721        # better faster, nicer solutions welcome :)
1722        result_group_list = []
1723        for item in groups_list:
1724            if item not in result_group_list:
1725                result_group_list.append(item)
1726
1727        return result_group_list
1728
1729    def get_container_config(self) -> Dict:
1730        """
1731        Dictionary of containerconfig information
1732
1733        Takes attributes and subsection data from the selected
1734        <containerconfig> section and stores it in a dictionary
1735        """
1736        container_config = self._match_docker_base_data()
1737        container_config.update(
1738            self._match_docker_entrypoint()
1739        )
1740        container_config.update(
1741            self._match_docker_subcommand()
1742        )
1743        container_config.update(
1744            self._match_docker_expose_ports()
1745        )
1746        container_config.update(
1747            self._match_docker_volumes()
1748        )
1749        container_config.update(
1750            self._match_docker_stopsignal()
1751        )
1752        container_config.update(
1753            self._match_docker_environment()
1754        )
1755        container_config.update(
1756            self._match_docker_labels()
1757        )
1758        container_config.update(
1759            self._match_docker_history()
1760        )
1761
1762        desc = self.get_description_section()
1763        author_contact = "{0} <{1}>".format(desc.author, desc.contact)
1764        if 'history' not in container_config:
1765            container_config['history'] = {}
1766        if 'author' not in container_config['history']:
1767            container_config['history']['author'] = author_contact
1768        if 'maintainer' not in container_config:
1769            container_config['maintainer'] = author_contact
1770
1771        return container_config
1772
1773    def set_container_config_tag(self, tag: str) -> None:
1774        """
1775        Set new tag name in containerconfig section
1776
1777        In order to set a new tag value an existing containerconfig and
1778        tag setup is required
1779
1780        :param str tag: tag name
1781        """
1782        container_config_section = self.get_build_type_containerconfig_section()
1783        if container_config_section and container_config_section.get_tag():
1784            container_config_section.set_tag(tag)
1785        else:
1786            message = dedent('''\n
1787                No <containerconfig> section and/or tag attribute configured
1788
1789                In order to set the tag {0} as new container tag,
1790                an initial containerconfig section including a tag
1791                setup is required
1792            ''')
1793            log.warning(message.format(tag))
1794
1795    def add_container_config_label(self, label_name: str, value: str) -> None:
1796        """
1797        Adds a new label in the containerconfig section, if a label with the
1798        same name is already defined in containerconfig it gets overwritten by
1799        this method.
1800
1801        :param str label_name: the string representing the label name
1802        :param str value: the value of the label
1803        """
1804        if self.get_build_type_name() not in ['docker', 'oci']:
1805            message = dedent('''\n
1806                Labels can only be configured for container image types
1807                docker and oci.
1808            ''')
1809            log.warning(message)
1810            return
1811
1812        container_config_section = self.get_build_type_containerconfig_section()
1813        if not container_config_section:
1814            container_config_section = xml_parse.containerconfig(
1815                name=Defaults.get_default_container_name(),
1816                tag=Defaults.get_default_container_tag()
1817            )
1818            self.build_type.set_containerconfig([container_config_section])
1819
1820        labels = container_config_section.get_labels()
1821        if not labels:
1822            labels = [xml_parse.labels()]
1823
1824        label_names = []
1825        for label in labels[0].get_label():
1826            label_names.append(label.get_name())
1827
1828        if label_name in label_names:
1829            labels[0].replace_label_at(
1830                label_names.index(label_name),
1831                xml_parse.label(label_name, value)
1832            )
1833        else:
1834            labels[0].add_label(xml_parse.label(label_name, value))
1835
1836        container_config_section.set_labels(labels)
1837
1838    def get_partitions(self) -> Dict[str, ptable_entry_type]:
1839        """
1840        Dictionary of configured partitions.
1841
1842        Each entry in the dict references a ptable_entry_type
1843        Each key in the dict references the name of the
1844        partition entry as handled by KIWI
1845
1846        :return:
1847            Contains dict of ptable_entry_type tuples
1848
1849            .. code:: python
1850
1851                {
1852                    'NAME': ptable_entry_type(
1853                        mbsize=int,
1854                        clone=int,
1855                        partition_name=str,
1856                        partition_type=str,
1857                        partition_id=Optional[int],
1858                        mountpoint=str,
1859                        filesystem=str,
1860                        label=str
1861                    )
1862                }
1863
1864        :rtype: dict
1865        """
1866        partitions: Dict[str, ptable_entry_type] = {}
1867        partitions_section = self.get_build_type_partitions_section()
1868        if not partitions_section:
1869            return partitions
1870        for partition in partitions_section.get_partition():
1871            name = partition.get_name()
1872            partition_name = partition.get_partition_name() or f'p.lx{name}'
1873            partitions[name] = ptable_entry_type(
1874                mbsize=self._to_mega_byte(partition.get_size()),
1875                clone=int(partition.get_clone()) if partition.get_clone() else 0,
1876                partition_name=partition_name,
1877                partition_type=partition.get_partition_type() or 't.linux',
1878                partition_id=partition.get_part_id(),
1879                mountpoint=partition.get_mountpoint(),
1880                filesystem=partition.get_filesystem(),
1881                label=partition.get_label() or ''
1882            )
1883        return partitions
1884
1885    def get_host_key_certificates(
1886        self
1887    ) -> Union[List[Dict[str, List[str]]], List[Dict[str, str]]]:
1888        cc_result = []
1889        cc_certificates: Dict[str, List[str]] = {}
1890        securelinux_list = \
1891            self.get_build_type_bootloader_securelinux_section()
1892        for securelinux in securelinux_list:
1893            cc_certificates = {
1894                'hkd_cert': [],
1895                'hkd_revocation_list': [],
1896                'hkd_ca_cert': securelinux.get_hkd_ca_cert(),
1897                'hkd_sign_cert': securelinux.get_hkd_sign_cert()
1898            }
1899            for hkd_cert in securelinux.get_hkd_cert():
1900                cc_certificates['hkd_cert'].append(hkd_cert.get_name())
1901            for hkd_revocation_list in securelinux.get_hkd_revocation_list():
1902                cc_certificates['hkd_revocation_list'].append(
1903                    hkd_revocation_list.get_name()
1904                )
1905            cc_result.append(cc_certificates)
1906        return cc_result
1907
1908    def get_containers(self) -> List[ContainerT]:
1909        containers = []
1910
1911        def build_fetch_command(
1912            root_dir: str,
1913            container_uri: str = '',
1914            container_file_name: str = '',
1915            container_endpoint: str = ''
1916        ):
1917            pass  # pragma: nocover
1918        for containers_section in self.get_containers_sections():
1919            for container in containers_section.get_container():
1920                if self.container_matches_host_architecture(container):
1921                    fetch_command = build_fetch_command
1922                    load_command = []
1923                    container_tag = container.get_tag() or 'latest'
1924                    container_path = container.get_path() or ''
1925                    container_endpoint = os.path.normpath(
1926                        '{0}/{1}/{2}:{3}'.format(
1927                            containers_section.get_source(), container_path,
1928                            container.name, container_tag
1929                        )
1930                    )
1931                    container_file_name = '{0}/{1}_{2}'.format(
1932                        defaults.LOCAL_CONTAINERS, container.name, container_tag
1933                    )
1934                    container_backend = containers_section.get_backend() or ''
1935                    if container_backend in ['podman', 'docker', 'container-snap']:
1936                        if Defaults.is_buildservice_worker():
1937                            container_uri = Uri(
1938                                'obsrepositories:/{0}'.format(
1939                                    container_endpoint
1940                                ), 'container'
1941                            ).translate()
1942
1943                            def build_fetch_command(
1944                                root_dir: str,
1945                                container_uri: str = container_uri,
1946                                container_file_name: str = container_file_name,
1947                                container_endpoint: str = container_endpoint
1948                            ):
1949                                def perform():
1950                                    Command.run(
1951                                        [
1952                                            'cp', '{0}.ociarchive'.format(
1953                                                container_uri
1954                                            ), os.path.normpath(
1955                                                '{0}/{1}'.format(
1956                                                    root_dir,
1957                                                    container_file_name
1958                                                )
1959                                            )
1960                                        ]
1961                                    )
1962                                perform()
1963                            fetch_command = build_fetch_command
1964                        else:
1965
1966                            def build_fetch_command(
1967                                root_dir: str,
1968                                container_uri: str = '',
1969                                container_file_name: str = container_file_name,
1970                                container_endpoint: str = container_endpoint
1971                            ):
1972                                def perform():
1973                                    Command.run(
1974                                        [
1975                                            'chroot', root_dir,
1976                                            '/usr/bin/skopeo', 'copy',
1977                                            'docker://{0}'.format(
1978                                                container_endpoint
1979                                            ),
1980                                            'oci-archive:{0}:{1}'.format(
1981                                                container_file_name,
1982                                                container_endpoint
1983                                            )
1984                                        ]
1985                                    )
1986                                perform()
1987                            fetch_command = build_fetch_command
1988                        if not container.get_fetch_only():
1989                            load_command = [
1990                                f'/usr/bin/{container_backend}',
1991                                'load', '-i', container_file_name
1992                            ]
1993                    containers.append(
1994                        ContainerT(
1995                            name=f'{container.name}_{container_tag}',
1996                            backend=container_backend,
1997                            container_file=container_file_name,
1998                            fetch_only=bool(container.get_fetch_only()),
1999                            fetch_command=fetch_command,
2000                            load_command=load_command
2001                        )
2002                    )
2003        return containers
2004
2005    def get_volumes(self) -> List[volume_type]:
2006        """
2007        List of configured systemdisk volumes.
2008
2009        Each entry in the list is a tuple with the following information
2010
2011        * name: name of the volume
2012        * size: size of the volume
2013        * realpath: system path to lookup volume data. If no mountpoint
2014          is set the volume name is used as data path.
2015        * mountpoint: volume mount point and volume data path
2016        * fullsize: takes all space True|False
2017        * attributes: list of volume attributes handled via chattr
2018
2019        :return:
2020            Contains list of volume_type tuples
2021
2022            .. code:: python
2023
2024                [
2025                    volume_type(
2026                        name=volume_name,
2027                        parent=volume_parent,
2028                        size=volume_size,
2029                        realpath=path,
2030                        mountpoint=path,
2031                        fullsize=True,
2032                        label=volume_label,
2033                        attributes=['no-copy-on-write'],
2034                        is_root_volume=True|False
2035                    )
2036                ]
2037
2038        :rtype: list
2039        """
2040        volume_type_list: List[volume_type] = []
2041        systemdisk_section = self.get_build_type_system_disk_section()
2042        selected_filesystem = self.build_type.get_filesystem()
2043        swap_mbytes = self.get_oemconfig_swap_mbytes()
2044        swap_name = self.get_oemconfig_swap_name()
2045        if not systemdisk_section:
2046            return volume_type_list
2047        volumes = systemdisk_section.get_volume()
2048        have_root_volume_setup = False
2049        have_full_size_volume = False
2050        if volumes:
2051            for volume in volumes:
2052                if not self.volume_matches_host_architecture(volume):
2053                    continue
2054                # volume setup for a full qualified volume with name and
2055                # mountpoint information. See below for exceptions
2056                name = volume.get_name()
2057                parent = volume.get_parent() or ''
2058                mountpoint = volume.get_mountpoint()
2059                realpath = mountpoint
2060                size = volume.get_size()
2061                freespace = volume.get_freespace()
2062                fullsize = False
2063                label = volume.get_label()
2064                attributes = []
2065                is_root_volume = False
2066
2067                if volume.get_quota():
2068                    attributes.append(f'quota={volume.get_quota()}')
2069
2070                if volume.get_copy_on_write() is False:
2071                    # by default copy-on-write is switched on for any
2072                    # filesystem. Thus only if no copy on write is requested
2073                    # the attribute is handled
2074                    attributes.append('no-copy-on-write')
2075
2076                if volume.get_filesystem_check() is True:
2077                    # by default filesystem check is switched off for any
2078                    # filesystem except the rootfs. Thus only if filesystem
2079                    # check is requested the attribute is handled
2080                    attributes.append('enable-for-filesystem-check')
2081
2082                if '@root' in name:
2083                    # setup root volume, it takes an optional volume
2084                    # name if specified as @root=name and has no specific
2085                    # mountpoint. The default name is set to
2086                    # defaults.ROOT_VOLUME_NAME if no other root volume
2087                    # name is provided
2088                    mountpoint = None
2089                    realpath = '/'
2090                    is_root_volume = True
2091                    root_volume_expression = re.match(
2092                        r'@root=(.+)', name
2093                    )
2094                    if root_volume_expression:
2095                        name = root_volume_expression.group(1)
2096                    else:
2097                        name = defaults.ROOT_VOLUME_NAME
2098                    have_root_volume_setup = True
2099                elif not mountpoint:
2100                    # setup volume without mountpoint. In this case the name
2101                    # attribute is used as mountpoint path and a name for the
2102                    # volume is created from that path information
2103                    mountpoint = name
2104                    realpath = mountpoint
2105                    name = self._to_volume_name(name)
2106
2107                if size:
2108                    size = 'size:' + format(
2109                        self._to_mega_byte(size)
2110                    )
2111                elif freespace:
2112                    size = 'freespace:' + format(
2113                        self._to_mega_byte(freespace)
2114                    )
2115                else:
2116                    size = 'freespace:' + format(
2117                        Defaults.get_min_volume_mbytes(selected_filesystem)
2118                    )
2119
2120                if ':all' in size:
2121                    size = None
2122                    fullsize = True
2123                    have_full_size_volume = True
2124
2125                volume_type_list.append(
2126                    volume_type(
2127                        name=name,
2128                        parent=parent,
2129                        size=size,
2130                        fullsize=fullsize,
2131                        mountpoint=mountpoint,
2132                        realpath=realpath,
2133                        label=label,
2134                        attributes=attributes,
2135                        is_root_volume=is_root_volume
2136                    )
2137                )
2138
2139        if not have_root_volume_setup:
2140            # There must always be a root volume setup. It will be the
2141            # full size volume if no other volume has this setup
2142            volume_management = self.get_volume_management()
2143            root_volume_name = \
2144                defaults.ROOT_VOLUME_NAME if volume_management == 'lvm' else ''
2145            if have_full_size_volume:
2146                size = 'freespace:' + format(
2147                    Defaults.get_min_volume_mbytes(selected_filesystem)
2148                )
2149                fullsize = False
2150            else:
2151                size = None
2152                fullsize = True
2153            volume_type_list.append(
2154                volume_type(
2155                    name=root_volume_name,
2156                    parent='',
2157                    size=size,
2158                    fullsize=fullsize,
2159                    mountpoint=None,
2160                    realpath='/',
2161                    label=None,
2162                    attributes=[],
2163                    is_root_volume=True
2164                )
2165            )
2166
2167        if swap_mbytes and self.get_volume_management() == 'lvm':
2168            volume_type_list.append(
2169                volume_type(
2170                    name=swap_name,
2171                    parent='',
2172                    size='size:{0}'.format(swap_mbytes),
2173                    fullsize=False,
2174                    mountpoint=None,
2175                    realpath='swap',
2176                    label='SWAP',
2177                    attributes=[],
2178                    is_root_volume=False
2179                )
2180            )
2181
2182        return volume_type_list
2183
2184    def get_volume_management(self) -> Optional[str]:
2185        """
2186        Provides information which volume management system is used
2187
2188        :return: name of volume manager
2189
2190        :rtype: str
2191        """
2192        volume_filesystems = ['btrfs']
2193        selected_filesystem = self.build_type.get_filesystem()
2194        selected_system_disk = self.get_build_type_system_disk_section()
2195        volume_management = None
2196        if selected_system_disk and selected_system_disk.get_preferlvm():
2197            # LVM volume management is preferred, use it
2198            volume_management = 'lvm'
2199        elif selected_filesystem in volume_filesystems and selected_system_disk:
2200            # specified filesystem has its own volume management system
2201            volume_management = selected_filesystem
2202        elif selected_system_disk:
2203            # systemdisk section is specified with non volume capable
2204            # filesystem and no volume management preference. So let's
2205            # use LVM by default
2206            volume_management = 'lvm'
2207        return volume_management
2208
2209    def get_drivers_list(self) -> List:
2210        """
2211        List of driver names from all drivers sections matching
2212        configured profiles
2213
2214        :return: driver names
2215
2216        :rtype: list
2217        """
2218        drivers_sections = self._profiled(
2219            self.xml_data.get_drivers()
2220        )
2221        result = []
2222        if drivers_sections:
2223            for driver in drivers_sections:
2224                for file_section in driver.get_file():
2225                    result.append(file_section.get_name())
2226        return result
2227
2228    def get_strip_list(self, section_type: str) -> List:
2229        """
2230        List of strip names matching the given section type
2231        and profiles
2232
2233        :param str section_type: type name from packages section
2234
2235        :return: strip names
2236
2237        :rtype: list
2238        """
2239        strip_sections = self._profiled(
2240            self.xml_data.get_strip()
2241        )
2242        result = []
2243        if strip_sections:
2244            for strip in strip_sections:
2245                if strip.get_type() == section_type:
2246                    for file_section in strip.get_file():
2247                        result.append(file_section.get_name())
2248        return result
2249
2250    def get_strip_files_to_delete(self) -> List:
2251        """
2252        Items to delete from strip section
2253
2254        :return: item names
2255
2256        :rtype: list
2257        """
2258        return self.get_strip_list('delete')
2259
2260    def get_strip_tools_to_keep(self) -> List:
2261        """
2262        Tools to keep from strip section
2263
2264        :return: tool names
2265
2266        :rtype: list
2267        """
2268        return self.get_strip_list('tools')
2269
2270    def get_strip_libraries_to_keep(self) -> List:
2271        """
2272        Libraries to keep from strip section
2273
2274        :return: librarie names
2275
2276        :rtype: list
2277        """
2278        return self.get_strip_list('libs')
2279
2280    def get_include_section_reference_file_names(self) -> List[str]:
2281        """
2282        List of all <include> section file name references
2283
2284        :return: List[str]
2285
2286        :rtype: list
2287        """
2288        include_files = []
2289        for include in self.xml_data.get_include():
2290            include_files.append(include.get_from())
2291        return include_files
2292
2293    def get_repository_sections(self) -> List:
2294        """
2295        List of all repository sections for the selected profiles that
2296        matches the host architecture
2297
2298        :return: <repository> section reference(s)
2299
2300        :rtype: list
2301        """
2302        repository_list = []
2303        for repository in self._profiled(self.xml_data.get_repository()):
2304            if self.repository_matches_host_architecture(repository):
2305                repository_list.append(repository)
2306        return repository_list
2307
2308    def get_containers_sections(self) -> List:
2309        """
2310        List of all containers sections for the selected profiles that
2311        matches the host architecture
2312
2313        :return: <containers> section reference(s)
2314
2315        :rtype: list
2316        """
2317        containers_list = []
2318        for containers in self._profiled(self.xml_data.get_containers()):
2319            if self.containers_matches_host_architecture(containers):
2320                containers_list.append(containers)
2321        return containers_list
2322
2323    def get_repository_sections_used_for_build(self) -> List:
2324        """
2325        List of all repositorys sections used to build the image and
2326        matching configured profiles.
2327
2328        :return: <repository> section reference(s)
2329
2330        :rtype: list
2331        """
2332        repos = self.get_repository_sections()
2333        return list(
2334            repo for repo in repos if not repo.get_imageonly()
2335        )
2336
2337    def get_repository_sections_used_in_image(self) -> List:
2338        """
2339        List of all repositorys sections to be configured in the resulting
2340        image matching configured profiles.
2341
2342        :return: <repository> section reference(s)
2343
2344        :rtype: list
2345        """
2346        repos = self.get_repository_sections()
2347        return list(
2348            repo for repo in repos
2349            if repo.get_imageinclude() or repo.get_imageonly()
2350        )
2351
2352    def delete_repository_sections(self) -> None:
2353        """
2354        Delete all repository sections matching configured profiles
2355        """
2356        self.xml_data.set_repository([])
2357
2358    def delete_repository_sections_used_for_build(self) -> None:
2359        """
2360        Delete all repository sections used to build the image matching
2361        configured profiles
2362        """
2363        used_for_build = self.get_repository_sections_used_for_build()
2364        all_repos = self.get_repository_sections()
2365        self.xml_data.set_repository(
2366            [
2367                repo for repo in all_repos if repo not in used_for_build
2368            ]
2369        )
2370
2371    def get_repositories_signing_keys(self) -> List[str]:
2372        """
2373        Get list of signing keys specified on the repositories
2374        """
2375        key_file_list: List[str] = []
2376        release_version = self.get_release_version()
2377        release_vars = [
2378            '$releasever',
2379            '${releasever}'
2380        ]
2381        for repository in self.get_repository_sections() or []:
2382            for signing in repository.get_source().get_signing() or []:
2383                normalized_key_url = Uri(signing.get_key()).translate()
2384                if release_version:
2385                    for release_var in release_vars:
2386                        if release_var in normalized_key_url:
2387                            normalized_key_url = normalized_key_url.replace(
2388                                release_var, release_version
2389                            )
2390                if normalized_key_url not in key_file_list:
2391                    key_file_list.append(normalized_key_url)
2392        return key_file_list
2393
2394    def set_repository(
2395        self, repo_source: str, repo_type: str, repo_alias: str,
2396        repo_prio: str, repo_imageinclude: bool = False,
2397        repo_package_gpgcheck: Optional[bool] = None,
2398        repo_signing_keys: List[str] = [], components: str = None,
2399        distribution: str = None, repo_gpgcheck: Optional[bool] = None,
2400        repo_sourcetype: str = None
2401    ) -> None:
2402        """
2403        Overwrite repository data of the first repository
2404
2405        :param str repo_source: repository URI
2406        :param str repo_type: type name defined by schema
2407        :param str repo_alias: alias name
2408        :param str repo_prio: priority number, package manager specific
2409        :param bool repo_imageinclude: setup repository inside of the image
2410        :param bool repo_package_gpgcheck: enable/disable package gpg checks
2411        :param list repo_signing_keys: list of signing key file names
2412        :param str components: component names for debian repos
2413        :param str distribution: base distribution name for debian repos
2414        :param bool repo_gpgcheck: enable/disable repo gpg checks
2415        """
2416        repository_sections = self.get_repository_sections()
2417        if repository_sections:
2418            repository = repository_sections[0]
2419            if repo_alias:
2420                repository.set_alias(repo_alias)
2421            if repo_type:
2422                repository.set_type(repo_type)
2423            if repo_source:
2424                repository.get_source().set_path(repo_source)
2425            if repo_prio:
2426                repository.set_priority(int(repo_prio))
2427            if repo_imageinclude:
2428                repository.set_imageinclude(repo_imageinclude)
2429            if repo_package_gpgcheck is not None:
2430                repository.set_package_gpgcheck(repo_package_gpgcheck)
2431            if repo_signing_keys:
2432                repository.get_source().set_signing(
2433                    [xml_parse.signing(key=k) for k in repo_signing_keys]
2434                )
2435            if components:
2436                repository.set_components(components)
2437            if distribution:
2438                repository.set_distribution(distribution)
2439            if repo_gpgcheck is not None:
2440                repository.set_repository_gpgcheck(repo_gpgcheck)
2441            if repo_sourcetype:
2442                repository.set_sourcetype(repo_sourcetype)
2443
2444    def add_repository(
2445        self, repo_source: str, repo_type: str, repo_alias: str = None,
2446        repo_prio: str = '', repo_imageinclude: bool = False,
2447        repo_package_gpgcheck: Optional[bool] = None,
2448        repo_signing_keys: List[str] = [], components: str = None,
2449        distribution: str = None, repo_gpgcheck: Optional[bool] = None,
2450        repo_sourcetype: str = None
2451    ) -> None:
2452        """
2453        Add a new repository section at the end of the list
2454
2455        :param str repo_source: repository URI
2456        :param str repo_type: type name defined by schema
2457        :param str repo_alias: alias name
2458        :param str repo_prio: priority number, package manager specific
2459        :param bool repo_imageinclude: setup repository inside of the image
2460        :param bool repo_package_gpgcheck: enable/disable package gpg checks
2461        :param list repo_signing_keys: list of signing key file names
2462        :param str components: component names for debian repos
2463        :param str distribution: base distribution name for debian repos
2464        :param bool repo_gpgcheck: enable/disable repo gpg checks
2465        """
2466        priority_number: Optional[int] = None
2467        try:
2468            priority_number = int(repo_prio)
2469        except Exception:
2470            pass
2471
2472        self.xml_data.add_repository(
2473            xml_parse.repository(
2474                type_=repo_type,
2475                alias=repo_alias,
2476                priority=priority_number,
2477                source=xml_parse.source(
2478                    path=repo_source,
2479                    signing=[
2480                        xml_parse.signing(key=k) for k in repo_signing_keys
2481                    ]
2482                ),
2483                imageinclude=repo_imageinclude,
2484                package_gpgcheck=repo_package_gpgcheck,
2485                repository_gpgcheck=repo_gpgcheck,
2486                components=components,
2487                distribution=distribution,
2488                sourcetype=repo_sourcetype
2489            )
2490        )
2491
2492    def add_certificate(self, cert_file: str, target_distribution: str) -> None:
2493        """
2494        Add <certificate name="cert_file"> to main <certificates> section
2495        The main section will be created if it does not exist. Also
2496        setup the target_distribution in the resulting main section.
2497        """
2498        certificates_section = self._profiled(
2499            self.xml_data.get_certificates()
2500        )
2501        if not certificates_section:
2502            self.xml_data.set_certificates(
2503                [
2504                    xml_parse.certificates(
2505                        target_distribution=target_distribution,
2506                        certificate=[xml_parse.certificate(name=cert_file)]
2507                    )
2508                ]
2509            )
2510        else:
2511            certificates_section[0].set_target_distribution(
2512                target_distribution
2513            )
2514            certificates_section[0].add_certificate(
2515                xml_parse.certificate(
2516                    name=cert_file
2517                )
2518            )
2519
2520    def get_certificates(self) -> List[str]:
2521        """
2522        Read list of certificates
2523        """
2524        cert_list = []
2525        certificates_section = self._profiled(
2526            self.xml_data.get_certificates()
2527        )
2528        if certificates_section:
2529            for certificate in certificates_section[0].get_certificate():
2530                cert_list.append(certificate.get_name())
2531        return sorted(list(set(cert_list)))
2532
2533    def get_certificates_target_distribution(self) -> str:
2534        """
2535        Read CA target distribution
2536        """
2537        target_distribution = ''
2538        certificates_section = self._profiled(
2539            self.xml_data.get_certificates()
2540        )
2541        if certificates_section:
2542            target_distribution = \
2543                certificates_section[0].get_target_distribution()
2544        return target_distribution
2545
2546    def resolve_this_path(self) -> None:
2547        """
2548        Resolve any this:// repo source path into the path
2549        representing the target inside of the image description
2550        directory
2551        """
2552        for repository in self.get_repository_sections() or []:
2553            repo_source = repository.get_source()
2554            repo_path = repo_source.get_path()
2555            if repo_path.startswith('this://'):
2556                repo_path = repo_path.replace('this://', '')
2557                repo_source.set_path(
2558                    'dir://{0}'.format(
2559                        os.path.realpath(
2560                            os.path.join(
2561                                self.xml_data.description_dir, repo_path
2562                            )
2563                        )
2564                    )
2565                )
2566
2567    def copy_displayname(self, target_state: Any) -> None:
2568        """
2569        Copy image displayname from this xml state to the target xml state
2570
2571        :param object target_state: XMLState instance
2572        """
2573        displayname = self.xml_data.get_displayname()
2574        if displayname:
2575            target_state.xml_data.set_displayname(displayname)
2576
2577    def copy_name(self, target_state: Any) -> None:
2578        """
2579        Copy image name from this xml state to the target xml state
2580
2581        :param object target_state: XMLState instance
2582        """
2583        target_state.xml_data.set_name(
2584            self.xml_data.get_name()
2585        )
2586
2587    def copy_drivers_sections(self, target_state: Any) -> None:
2588        """
2589        Copy drivers sections from this xml state to the target xml state
2590
2591        :param object target_state: XMLState instance
2592        """
2593        drivers_sections = self._profiled(
2594            self.xml_data.get_drivers()
2595        )
2596        if drivers_sections:
2597            for drivers_section in drivers_sections:
2598                target_state.xml_data.add_drivers(drivers_section)
2599
2600    def copy_systemdisk_section(self, target_state: Any) -> None:
2601        """
2602        Copy systemdisk sections from this xml state to the target xml state
2603
2604        :param object target_state: XMLState instance
2605        """
2606        systemdisk_section = self.get_build_type_system_disk_section()
2607        if systemdisk_section:
2608            target_state.build_type.set_systemdisk(
2609                [systemdisk_section]
2610            )
2611
2612    def copy_strip_sections(self, target_state: Any) -> None:
2613        """
2614        Copy strip sections from this xml state to the target xml state
2615
2616        :param object target_state: XMLState instance
2617        """
2618        strip_sections = self._profiled(
2619            self.xml_data.get_strip()
2620        )
2621        if strip_sections:
2622            for strip_section in strip_sections:
2623                target_state.xml_data.add_strip(strip_section)
2624
2625    def copy_machine_section(self, target_state: Any) -> None:
2626        """
2627        Copy machine sections from this xml state to the target xml state
2628
2629        :param object target_state: XMLState instance
2630        """
2631        machine_section = self.get_build_type_machine_section()
2632        if machine_section:
2633            target_state.build_type.set_machine(
2634                [machine_section]
2635            )
2636
2637    def copy_bootloader_section(self, target_state: Any) -> None:
2638        """
2639        Copy bootloader section from this xml state to the target xml state
2640
2641        :param object target_state: XMLState instance
2642        """
2643        bootloader_section = self.get_build_type_bootloader_section()
2644        if bootloader_section:
2645            target_state.build_type.set_bootloader(
2646                [bootloader_section]
2647            )
2648
2649    def copy_oemconfig_section(self, target_state: Any) -> None:
2650        """
2651        Copy oemconfig sections from this xml state to the target xml state
2652
2653        :param object target_state: XMLState instance
2654        """
2655        oemconfig_section = self.get_build_type_oemconfig_section()
2656        if oemconfig_section:
2657            target_state.build_type.set_oemconfig(
2658                [oemconfig_section]
2659            )
2660
2661    def copy_repository_sections(
2662        self, target_state: Any, wipe: bool = False
2663    ) -> None:
2664        """
2665        Copy repository sections from this xml state to the target xml state
2666
2667        :param object target_state: XMLState instance
2668        :param bool wipe: delete all repos in target prior to copy
2669        """
2670        repository_sections = self._profiled(
2671            self.xml_data.get_repository()
2672        )
2673        if repository_sections:
2674            if wipe:
2675                target_state.xml_data.set_repository([])
2676            for repository_section in repository_sections:
2677                repository_copy = copy.deepcopy(repository_section)
2678                # profiles are not copied because they might not exist
2679                # in the target description
2680                repository_copy.set_profiles(None)
2681                target_state.xml_data.add_repository(repository_copy)
2682
2683    def copy_preferences_subsections(
2684        self, section_names: List, target_state: Any
2685    ) -> None:
2686        """
2687        Copy subsections of the preferences sections, matching given
2688        section names, from this xml state to the target xml state
2689
2690        :param list section_names: preferences subsection names
2691        :param object target_state: XMLState instance
2692        """
2693        target_preferences_sections = target_state.get_preferences_sections()
2694        if target_preferences_sections:
2695            target_preferences_section = target_preferences_sections[0]
2696            for preferences_section in self.get_preferences_sections():
2697                for section_name in section_names:
2698                    get_section_method = getattr(
2699                        preferences_section, 'get_' + section_name
2700                    )
2701                    section = get_section_method()
2702                    if section:
2703                        set_section_method = getattr(
2704                            target_preferences_section, 'set_' + section_name
2705                        )
2706                        set_section_method(section)
2707
2708    def copy_build_type_attributes(
2709        self, attribute_names: List, target_state: Any
2710    ) -> None:
2711        """
2712        Copy specified attributes from this build type section to the
2713        target xml state build type section
2714
2715        :param list attribute_names: type section attributes
2716        :param object target_state: XMLState instance
2717        """
2718        for attribute in attribute_names:
2719            get_type_method = getattr(
2720                self.build_type, 'get_' + attribute
2721            )
2722            attribute_value = get_type_method()
2723            if attribute_value:
2724                set_type_method = getattr(
2725                    target_state.build_type, 'set_' + attribute
2726                )
2727                set_type_method(attribute_value)
2728
2729    def copy_bootincluded_packages(self, target_state: Any) -> None:
2730        """
2731        Copy packages marked as bootinclude to the packages
2732        type=bootstrap section in the target xml state. The package
2733        will also be removed from the packages type=delete section
2734        in the target xml state if present there
2735
2736        :param object target_state: XMLState instance
2737        """
2738        target_packages_sections = \
2739            target_state.get_bootstrap_packages_sections()
2740        if target_packages_sections:
2741            target_packages_section = \
2742                target_packages_sections[0]
2743            package_names_added = []
2744            packages_sections = self.get_packages_sections(
2745                ['image', 'bootstrap', self.get_build_type_name()]
2746            )
2747            package_list = self.get_package_sections(
2748                packages_sections
2749            )
2750            if package_list:
2751                for package in package_list:
2752                    if package.package_section.get_bootinclude():
2753                        target_packages_section.add_package(
2754                            xml_parse.package(
2755                                name=package.package_section.get_name()
2756                            )
2757                        )
2758                        package_names_added.append(
2759                            package.package_section.get_name()
2760                        )
2761            delete_packages_sections = target_state.get_packages_sections(
2762                ['delete']
2763            )
2764            package_list = self.get_package_sections(
2765                delete_packages_sections
2766            )
2767            if package_list:
2768                for package in package_list:
2769                    package_name = package.package_section.get_name()
2770                    if package_name in package_names_added:
2771                        package.packages_section.package.remove(
2772                            package.package_section
2773                        )
2774
2775    def copy_bootincluded_archives(self, target_state: Any) -> None:
2776        """
2777        Copy archives marked as bootinclude to the packages type=bootstrap
2778        section in the target xml state
2779
2780        :param object target_state: XMLState instance
2781        """
2782        target_bootstrap_packages_sections = \
2783            target_state.get_bootstrap_packages_sections()
2784        if target_bootstrap_packages_sections:
2785            target_bootstrap_packages_section = \
2786                target_bootstrap_packages_sections[0]
2787            packages_sections = self.get_packages_sections(
2788                ['image', 'bootstrap', self.get_build_type_name()]
2789            )
2790            for packages_section in packages_sections:
2791                archive_list = packages_section.get_archive()
2792                if archive_list:
2793                    for archive in archive_list:
2794                        if archive.get_bootinclude():
2795                            target_bootstrap_packages_section.add_archive(
2796                                xml_parse.archive(
2797                                    name=archive.get_name()
2798                                )
2799                            )
2800
2801    def copy_bootdelete_packages(self, target_state: Any) -> None:
2802        """
2803        Copy packages marked as bootdelete to the packages type=delete
2804        section in the target xml state
2805
2806        :param object target_state: XMLState instance
2807        """
2808        target_delete_packages_sections = target_state.get_packages_sections(
2809            ['delete']
2810        )
2811        if not target_delete_packages_sections:
2812            target_delete_packages_sections = [
2813                xml_parse.packages(type_='delete')
2814            ]
2815            target_state.xml_data.add_packages(
2816                target_delete_packages_sections[0]
2817            )
2818
2819        target_delete_packages_section = \
2820            target_delete_packages_sections[0]
2821        packages_sections = self.get_packages_sections(
2822            ['image', 'bootstrap', self.get_build_type_name()]
2823        )
2824        package_list = self.get_package_sections(
2825            packages_sections
2826        )
2827        if package_list:
2828            for package in package_list:
2829                if package.package_section.get_bootdelete():
2830                    target_delete_packages_section.add_package(
2831                        xml_parse.package(
2832                            name=package.package_section.get_name()
2833                        )
2834                    )
2835
2836    def get_distribution_name_from_boot_attribute(self) -> str:
2837        """
2838        Extract the distribution name from the boot attribute of the
2839        build type section.
2840
2841        If no boot attribute is configured or the contents does not
2842        match the kiwi defined naming schema for boot image descriptions,
2843        an exception is thrown
2844
2845        :return: lowercase distribution name
2846
2847        :rtype: str
2848        """
2849        boot_attribute = self.build_type.get_boot()
2850        if not boot_attribute:
2851            raise KiwiDistributionNameError(
2852                'No boot attribute to extract distribution name from found'
2853            )
2854        boot_attribute_format = '^.*-(.*)$'
2855        boot_attribute_expression = re.match(
2856            boot_attribute_format, boot_attribute
2857        )
2858        if not boot_attribute_expression:
2859            raise KiwiDistributionNameError(
2860                'Boot attribute "%s" does not match expected format %s' %
2861                (boot_attribute, boot_attribute_format)
2862            )
2863        return boot_attribute_expression.group(1).lower()
2864
2865    def get_fs_mount_option_list(self) -> List:
2866        """
2867        List of root filesystem mount options
2868
2869        The list contains one element with the information from the
2870        fsmountoptions attribute. The value there is passed along to
2871        the -o mount option
2872
2873        :return: max one element list with mount option string
2874
2875        :rtype: list
2876        """
2877        option_list = []
2878        mount_options = self.build_type.get_fsmountoptions()
2879        if mount_options:
2880            option_list = [mount_options]
2881
2882        return option_list
2883
2884    def get_fs_create_option_list(self) -> List:
2885        """
2886        List of root filesystem creation options
2887
2888        The list contains elements with the information from the
2889        fscreateoptions attribute string that got split into its
2890        substring components
2891
2892        :return: list with create options
2893
2894        :rtype: list
2895        """
2896        option_list = []
2897        create_options = self.build_type.get_fscreateoptions()
2898        if create_options:
2899            option_list = create_options.split()
2900
2901        return option_list
2902
2903    def get_luks_credentials(self) -> Optional[str]:
2904        """
2905        Return key or passphrase credentials to open the luks pool
2906
2907        :return: data
2908
2909        :rtype: str
2910        """
2911        data = self.build_type.get_luks()
2912        if data:
2913            keyfile_name = None
2914            try:
2915                # try to interpret data as an URI
2916                uri = Uri(data)
2917                if not uri.is_remote():
2918                    keyfile_name = uri.translate()
2919            except Exception:
2920                # this doesn't look like a valid URI, continue as just data
2921                pass
2922            if keyfile_name:
2923                try:
2924                    with open(keyfile_name) as keyfile:
2925                        return keyfile.read()
2926                except Exception as issue:
2927                    raise KiwiFileAccessError(
2928                        f'Failed to read from {keyfile_name!r}: {issue}'
2929                    )
2930        return data
2931
2932    def get_luks_format_options(self) -> List[str]:
2933        """
2934        Return list of luks format options
2935
2936        :return: list of options
2937
2938        :rtype: list
2939        """
2940        result = []
2941        luksversion = self.build_type.get_luks_version()
2942        luksformat = self.build_type.get_luksformat()
2943        luks_pbkdf = self.build_type.get_luks_pbkdf()
2944        if luksversion:
2945            result.append('--type')
2946            result.append(luksversion)
2947        if luksformat:
2948            for option in luksformat[0].get_option():
2949                result.append(option.get_name())
2950                if option.get_value():
2951                    result.append(option.get_value())
2952        if luks_pbkdf:
2953            # Allow to override the pbkdf algorithm that cryptsetup
2954            # uses by default. Cryptsetup may use argon2i by default,
2955            # which is not supported by all bootloaders.
2956            result.append('--pbkdf')
2957            result.append(luks_pbkdf)
2958        return result
2959
2960    def get_derived_from_image_uri(self) -> List[Uri]:
2961        """
2962        Uri object(s) of derived image if configured
2963
2964        Specific image types can be based on one ore more derived
2965        images. This method returns the location of this image(s)
2966        when configured in the XML description
2967
2968        :return: List of Uri instances
2969
2970        :rtype: list
2971        """
2972        image_uris = []
2973        derived_images = self.build_type.get_derived_from()
2974        if derived_images:
2975            for derived_image in derived_images.split(','):
2976                image_uris.append(
2977                    Uri(derived_image, repo_type='container')
2978                )
2979        return image_uris
2980
2981    def set_derived_from_image_uri(self, uri: str) -> None:
2982        """
2983        Set derived_from attribute to a new value
2984
2985        In order to set a new value the derived_from attribute
2986        must be already present in the image configuration
2987
2988        :param str uri: URI
2989        """
2990        if self.build_type.get_derived_from():
2991            self.build_type.set_derived_from(uri)
2992        else:
2993            message = dedent('''\n
2994                No derived_from attribute configured in image <type>
2995
2996                In order to set the uri {0} as base container reference
2997                an initial derived_from attribute must be set in the
2998                type section
2999            ''')
3000            log.warning(message.format(uri))
3001
3002    def set_root_partition_uuid(self, uuid: str) -> None:
3003        """
3004        Store PARTUUID provided in uuid as state information
3005
3006        :param str uuid: PARTUUID
3007        """
3008        self.root_partition_uuid = uuid
3009
3010    def get_root_partition_uuid(self) -> Optional[str]:
3011        """
3012        Return preserved PARTUUID
3013        """
3014        return self.root_partition_uuid
3015
3016    def set_root_filesystem_uuid(self, uuid: str) -> None:
3017        """
3018        Store UUID provided in uuid as state information
3019
3020        :param str uuid: UUID
3021        """
3022        self.root_filesystem_uuid = uuid
3023
3024    def get_root_filesystem_uuid(self) -> Optional[str]:
3025        """
3026        Return preserved UUID
3027        """
3028        return self.root_filesystem_uuid
3029
3030    @staticmethod
3031    def get_archives_target_dirs(
3032        packages_sections_names: Optional[List[xml_parse.packages]]
3033    ) -> Dict:
3034        """
3035        Dict of archive names and target dirs for packages section(s), if any
3036        :return: archive names and its target dir
3037        :rtype: dict
3038        """
3039        result = {}
3040        if packages_sections_names:
3041            for package_section_name in packages_sections_names:
3042                for archive in package_section_name.get_archive():
3043                    result[archive.get_name().strip()] = archive.get_target_dir()
3044
3045        return result
3046
3047    def get_bootstrap_archives_target_dirs(self) -> Dict:
3048        """
3049        Dict of archive names and target dirs from the type="bootstrap"
3050        packages section(s)
3051        :return: archive names and its target dir
3052        :rtype: dict
3053        """
3054        return self.get_archives_target_dirs(
3055            self.get_packages_sections(['bootstrap'])
3056        )
3057
3058    def get_system_archives_target_dirs(self) -> Dict:
3059        """
3060        Dict of archive names and its target dir from the packages sections matching
3061        type="image" and type=build_type
3062        :return: archive names and its target dir
3063        :rtype: dict
3064        """
3065        return self.get_archives_target_dirs(
3066            self.get_packages_sections(['image', self.get_build_type_name()])
3067        )
3068
3069    def _used_profiles(self, profiles=None):
3070        """
3071        return list of profiles to use. The method looks up the
3072        profiles section in the XML description and searches for
3073        profiles matching the architecture. If no arch specifier
3074        is set the profile is considered to be valid for any arch
3075
3076        If the profiles argument is not set only profiles
3077        marked with the attribute import=true will be selected.
3078        Profiles specified in the argument will take the highest
3079        priority and causes to skip the lookup of import profiles
3080        in the XML description
3081
3082        :param list profiles: selected profile names
3083        """
3084        available_profiles = dict()
3085        import_profiles = []
3086        for profiles_section in self.xml_data.get_profiles():
3087            for profile in profiles_section.get_profile():
3088                if self.profile_matches_host_architecture(profile):
3089                    name = profile.get_name()
3090                    available_profiles[name] = profile
3091                    if profile.get_import():
3092                        import_profiles.append(name)
3093
3094        if not profiles:
3095            return import_profiles
3096        else:
3097            resolved_profiles = []
3098            for profile in profiles:
3099                resolved_profiles += self._solve_profile_dependencies(
3100                    profile, available_profiles, resolved_profiles
3101                )
3102            return resolved_profiles
3103
3104    def _section_matches_host_architecture(self, section):
3105        architectures = section.get_arch()
3106        if architectures:
3107            if self.host_architecture not in architectures.split(','):
3108                return False
3109        return True
3110
3111    def _match_docker_base_data(self):
3112        container_config_section = self.get_build_type_containerconfig_section()
3113        container_base = {}
3114        if container_config_section:
3115            name = container_config_section.get_name()
3116            tag = container_config_section.get_tag()
3117            maintainer = container_config_section.get_maintainer()
3118            user = container_config_section.get_user()
3119            workingdir = container_config_section.get_workingdir()
3120            additional_names = container_config_section.get_additionalnames()
3121            if name:
3122                container_base['container_name'] = name
3123
3124            if tag:
3125                container_base['container_tag'] = tag
3126
3127            if additional_names:
3128                container_base['additional_names'] = additional_names.split(',')
3129
3130            if maintainer:
3131                container_base['maintainer'] = maintainer
3132
3133            if user:
3134                container_base['user'] = user
3135
3136            if workingdir:
3137                container_base['workingdir'] = workingdir
3138
3139        return container_base
3140
3141    def _match_docker_entrypoint(self):
3142        container_config_section = self.get_build_type_containerconfig_section()
3143        container_entry = {}
3144        if container_config_section:
3145            entrypoint = container_config_section.get_entrypoint()
3146            if entrypoint and entrypoint[0].get_execute():
3147                container_entry['entry_command'] = [
3148                    entrypoint[0].get_execute()
3149                ]
3150                argument_list = entrypoint[0].get_argument()
3151                if argument_list:
3152                    for argument in argument_list:
3153                        container_entry['entry_command'].append(
3154                            argument.get_name()
3155                        )
3156            elif entrypoint and entrypoint[0].get_clear():
3157                container_entry['entry_command'] = []
3158        return container_entry
3159
3160    def _match_docker_subcommand(self):
3161        container_config_section = self.get_build_type_containerconfig_section()
3162        container_subcommand = {}
3163        if container_config_section:
3164            subcommand = container_config_section.get_subcommand()
3165            if subcommand and subcommand[0].get_execute():
3166                container_subcommand['entry_subcommand'] = [
3167                    subcommand[0].get_execute()
3168                ]
3169                argument_list = subcommand[0].get_argument()
3170                if argument_list:
3171                    for argument in argument_list:
3172                        container_subcommand['entry_subcommand'].append(
3173                            argument.get_name()
3174                        )
3175            elif subcommand and subcommand[0].get_clear():
3176                container_subcommand['entry_subcommand'] = []
3177        return container_subcommand
3178
3179    def _match_docker_expose_ports(self):
3180        container_config_section = self.get_build_type_containerconfig_section()
3181        container_expose = {}
3182        if container_config_section:
3183            expose = container_config_section.get_expose()
3184            if expose and expose[0].get_port():
3185                container_expose['expose_ports'] = []
3186                for port in expose[0].get_port():
3187                    container_expose['expose_ports'].append(
3188                        format(port.get_number())
3189                    )
3190        return container_expose
3191
3192    def _match_docker_volumes(self):
3193        container_config_section = self.get_build_type_containerconfig_section()
3194        container_volumes = {}
3195        if container_config_section:
3196            volumes = container_config_section.get_volumes()
3197            if volumes and volumes[0].get_volume():
3198                container_volumes['volumes'] = []
3199                for volume in volumes[0].get_volume():
3200                    container_volumes['volumes'].append(volume.get_name())
3201        return container_volumes
3202
3203    def _match_docker_stopsignal(self) -> dict:
3204        container_config_section = self.get_build_type_containerconfig_section()
3205        container_stopsignal = {}
3206        if container_config_section:
3207            stopsignal_section = container_config_section.get_stopsignal()
3208            if stopsignal_section:
3209                container_stopsignal['stopsignal'] = stopsignal_section[0]
3210        return container_stopsignal
3211
3212    def _match_docker_environment(self):
3213        container_config_section = self.get_build_type_containerconfig_section()
3214        container_env = {}
3215        if container_config_section:
3216            environment = container_config_section.get_environment()
3217            if environment and environment[0].get_env():
3218                container_env['environment'] = {}
3219                for env in environment[0].get_env():
3220                    container_env['environment'][env.get_name()] = \
3221                        env.get_value()
3222        return container_env
3223
3224    def _match_docker_labels(self):
3225        container_config_section = self.get_build_type_containerconfig_section()
3226        container_labels = {}
3227        if container_config_section:
3228            labels = container_config_section.get_labels()
3229            if labels and labels[0].get_label():
3230                container_labels['labels'] = {}
3231                for label in labels[0].get_label():
3232                    container_labels['labels'][label.get_name()] = \
3233                        label.get_value()
3234        return container_labels
3235
3236    def _match_docker_history(self):
3237        container_config_section = self.get_build_type_containerconfig_section()
3238        container_history = {}
3239        if container_config_section:
3240            history = container_config_section.get_history()
3241            if history:
3242                container_history['history'] = {}
3243                if history[0].get_created_by():
3244                    container_history['history']['created_by'] = \
3245                        history[0].get_created_by()
3246                if history[0].get_author():
3247                    container_history['history']['author'] = \
3248                        history[0].get_author()
3249                if history[0].get_launcher():
3250                    container_history['history']['launcher'] = \
3251                        history[0].get_launcher()
3252                if history[0].get_application_id():
3253                    container_history['history']['application_id'] = \
3254                        history[0].get_application_id()
3255                if history[0].get_package_version():
3256                    container_history['history']['package_version'] = \
3257                        history[0].get_package_version()
3258                container_history['history']['comment'] = \
3259                    history[0].get_valueOf_()
3260        return container_history
3261
3262    def _solve_profile_dependencies(
3263        self, profile, available_profiles, current_profiles
3264    ):
3265        if profile not in available_profiles:
3266            raise KiwiProfileNotFound(
3267                'profile {0} not found for host arch {1}'.format(
3268                    profile, self.host_architecture
3269                )
3270            )
3271        profiles_to_add = []
3272        if profile not in current_profiles:
3273            profiles_to_add.append(profile)
3274            for required in available_profiles[profile].get_requires():
3275                if self.requires_matches_host_architecture(required):
3276                    if required.get_profile() not in current_profiles:
3277                        profiles_to_add += self._solve_profile_dependencies(
3278                            required.get_profile(), available_profiles,
3279                            current_profiles + profiles_to_add
3280                        )
3281        return profiles_to_add
3282
3283    def _build_type_section(self, build_type=None):
3284        """
3285        find type section matching build type and profiles or default
3286        """
3287        # lookup all preferences sections for selected profiles
3288        image_type_sections = []
3289        for preferences in self.get_preferences_sections():
3290            image_type_sections += preferences.get_type()
3291
3292        # lookup if build type matches provided type
3293        if build_type:
3294            for image_type in image_type_sections:
3295                if build_type == image_type.get_image():
3296                    return image_type
3297            raise KiwiTypeNotFound(
3298                'Build type {0!r} not found for applied profiles: {1!r}'.format(
3299                    build_type, self.profiles
3300                )
3301            )
3302
3303        # lookup if build type matches primary type
3304        for image_type in image_type_sections:
3305            if image_type.get_primary():
3306                return image_type
3307
3308        # build type is first type section in XML sequence
3309        if image_type_sections:
3310            return image_type_sections[0]
3311        raise KiwiTypeNotFound(
3312            'No build type defined with applied profiles: {0!r}'.format(
3313                self.profiles
3314            )
3315        )
3316
3317    def _profiled(self, xml_abstract):
3318        """
3319        return only those sections matching the instance stored
3320        profile list from the given XML abstract. Sections without
3321        a profile are wildcard sections and will be used in any
3322        case
3323        """
3324        result = []
3325        for section in xml_abstract:
3326            profiles = section.get_profiles()
3327            if profiles:
3328                for profile in profiles.split(','):
3329                    if self.profiles and profile in self.profiles:
3330                        result.append(section)
3331                        break
3332            else:
3333                result.append(section)
3334        return result
3335
3336    def _to_volume_name(self, name):
3337        name = name.strip()
3338        name = re.sub(r'^\/+', r'', name)
3339        name = name.replace('/', '_')
3340        return name
3341
3342    def _to_mega_byte(self, size):
3343        value = re.search(r'(\d+)([MG]*)', format(size))
3344        if value:
3345            number = value.group(1)
3346            unit = value.group(2)
3347            if unit == 'G':
3348                return int(number) * 1024
3349            else:
3350                return int(number)
3351        else:
3352            return size
log = <Logger kiwi (DEBUG)>
class description_type(builtins.tuple):

description_type(author, contact, specification)

description_type(author: str, contact: str, specification: str)

Create new instance of description_type(author, contact, specification)

author: str

Alias for field number 0

contact: str

Alias for field number 1

specification: str

Alias for field number 2

class package_type(builtins.tuple):

package_type(packages_section, package_section)

package_type( packages_section: kiwi.xml_parse.packages, package_section: kiwi.xml_parse.package)

Create new instance of package_type(packages_section, package_section)

packages_section: kiwi.xml_parse.packages

Alias for field number 0

package_section: kiwi.xml_parse.package

Alias for field number 1

class size_type(builtins.tuple):

size_type(mbytes, additive)

size_type(mbytes: int, additive: str)

Create new instance of size_type(mbytes, additive)

mbytes: int

Alias for field number 0

additive: str

Alias for field number 1

class volume_type(builtins.tuple):

volume_type(name, parent, size, realpath, mountpoint, fullsize, label, attributes, is_root_volume)

volume_type( name: str, parent: str, size: str, realpath: str, mountpoint: Optional[str], fullsize: bool, label: Optional[str], attributes: list, is_root_volume: bool)

Create new instance of volume_type(name, parent, size, realpath, mountpoint, fullsize, label, attributes, is_root_volume)

name: str

Alias for field number 0

parent: str

Alias for field number 1

size: str

Alias for field number 2

realpath: str

Alias for field number 3

mountpoint: Optional[str]

Alias for field number 4

fullsize: bool

Alias for field number 5

label: Optional[str]

Alias for field number 6

attributes: list

Alias for field number 7

is_root_volume: bool

Alias for field number 8

class DracutT(typing.NamedTuple):
84class DracutT(NamedTuple):
85    uefi: bool
86    modules: List[str]
87    drivers: List[str]

DracutT(uefi, modules, drivers)

DracutT(uefi: bool, modules: List[str], drivers: List[str])

Create new instance of DracutT(uefi, modules, drivers)

uefi: bool

Alias for field number 0

modules: List[str]

Alias for field number 1

drivers: List[str]

Alias for field number 2

class FileT(typing.NamedTuple):
90class FileT(NamedTuple):
91    target: str
92    owner: str
93    permissions: str

FileT(target, owner, permissions)

FileT(target: str, owner: str, permissions: str)

Create new instance of FileT(target, owner, permissions)

target: str

Alias for field number 0

owner: str

Alias for field number 1

permissions: str

Alias for field number 2

class ContainerT(typing.NamedTuple):
 96class ContainerT(NamedTuple):
 97    name: str
 98    backend: str
 99    container_file: str
100    fetch_only: bool
101    fetch_command: Callable
102    load_command: List[str]

ContainerT(name, backend, container_file, fetch_only, fetch_command, load_command)

ContainerT( name: str, backend: str, container_file: str, fetch_only: bool, fetch_command: Callable, load_command: List[str])

Create new instance of ContainerT(name, backend, container_file, fetch_only, fetch_command, load_command)

name: str

Alias for field number 0

backend: str

Alias for field number 1

container_file: str

Alias for field number 2

fetch_only: bool

Alias for field number 3

fetch_command: Callable

Alias for field number 4

load_command: List[str]

Alias for field number 5

class XMLState:
 105class XMLState:
 106    """
 107    **Implements methods to get stateful information from the XML data**
 108
 109    :param object xml_data: parse result from XMLDescription.load()
 110    :param list profiles: list of used profiles
 111    :param object build_type: build <type> section reference
 112    """
 113    def __init__(
 114        self, xml_data: Any, profiles: List = None,
 115        build_type: Any = None
 116    ):
 117        self.root_partition_uuid: Optional[str] = None
 118        self.root_filesystem_uuid: Optional[str] = None
 119        self.host_architecture = defaults.PLATFORM_MACHINE
 120        self.xml_data = xml_data
 121        self.profiles = self._used_profiles(profiles)
 122        self.build_type = self._build_type_section(
 123            build_type
 124        )
 125        self.resolve_this_path()
 126
 127    def get_preferences_sections(self) -> List:
 128        """
 129        All preferences sections for the selected profiles that match the
 130        host architecture
 131
 132        :return: list of <preferences> section reference(s)
 133
 134        :rtype: list
 135        """
 136        preferences_list = []
 137        for preferences in self._profiled(self.xml_data.get_preferences()):
 138            if self.preferences_matches_host_architecture(preferences):
 139                preferences_list.append(preferences)
 140        return preferences_list
 141
 142    def get_description_section(self) -> description_type:
 143        """
 144        The description section
 145
 146        :return:
 147            description_type tuple providing the elements
 148            author contact and specification
 149
 150        :rtype: tuple
 151        """
 152        description = self.xml_data.get_description()[0]
 153        return description_type(
 154            author=description.get_author()[0],
 155            contact=description.get_contact()[0],
 156            specification=description.get_specification()[0].strip()
 157        )
 158
 159    def get_users_sections(self) -> List:
 160        """
 161        All users sections for the selected profiles
 162
 163        :return: list of <users> section reference(s)
 164
 165        :rtype: list
 166        """
 167        users = []
 168        for users_section in self._profiled(self.xml_data.get_users()):
 169            if self.users_matches_host_architecture(users_section):
 170                users.append(users_section)
 171        return users
 172
 173    def get_build_type_bundle_format(self) -> str:
 174        """
 175        Return bundle_format for build type
 176
 177        The bundle_format string is validated against the available
 178        name tags from kiwi.system.result::result_name_tags.
 179
 180        :return: bundle format string
 181
 182        :rtype: str
 183        """
 184        return self.build_type.get_bundle_format()
 185
 186    def get_build_type_name(self) -> str:
 187        """
 188        Default build type name
 189
 190        :return: Content of image attribute from build type
 191
 192        :rtype: str
 193        """
 194        return self.build_type.get_image()
 195
 196    def btrfs_default_volume_requested(self) -> bool:
 197        """
 198        Check if setting a default volume for btrfs is requested
 199        """
 200        if self.build_type.get_btrfs_set_default_volume() is False:
 201            # Setting a default volume is explicitly switched off
 202            return False
 203        else:
 204            # In any other case (True | None) a default volume
 205            # is wanted and will be set
 206            return True
 207
 208    def get_image_version(self) -> str:
 209        """
 210        Image version from preferences section.
 211
 212        Multiple occurences of version in preferences sections are not
 213        forbidden, however only the first version found defines the
 214        final image version
 215
 216        :return: Content of <version> section
 217
 218        :rtype: str
 219        """
 220        for preferences in self.get_preferences_sections():
 221            version = preferences.get_version()
 222            if version:
 223                return version[0]
 224        return ''
 225
 226    def get_initrd_system(self) -> str:
 227        """
 228        Name of initrd system to use
 229
 230        Depending on the image type a specific initrd system is
 231        either pre selected or free of choice according to the
 232        XML type setup.
 233
 234        :return: 'dracut', 'kiwi' or 'none'
 235
 236        :rtype: str
 237        """
 238        pre_selection_map = {
 239            'vmx': 'dracut',
 240            'oem': 'dracut',
 241            'iso': 'dracut',
 242            'kis': 'dracut',
 243            'pxe': 'kiwi',
 244        }
 245        build_type = self.get_build_type_name()
 246        default_initrd_system = pre_selection_map.get(build_type) or 'none'
 247
 248        if build_type == 'iso':
 249            # iso type always use dracut as initrd system
 250            return default_initrd_system
 251
 252        # Allow to choose for any other build type
 253        return self.build_type.get_initrd_system() or default_initrd_system
 254
 255    def get_locale(self) -> Optional[List]:
 256        """
 257        Gets list of locale names if configured. Takes
 258        the first locale setup from the existing preferences
 259        sections into account.
 260
 261        :return: List of names or None
 262
 263        :rtype: list|None
 264        """
 265        for preferences in self.get_preferences_sections():
 266            locale_section = preferences.get_locale()
 267            if locale_section:
 268                return locale_section[0].split(',')
 269        return None
 270
 271    def get_rpm_locale(self) -> Optional[List]:
 272        """
 273        Gets list of locale names to filter out by rpm
 274        if rpm-locale-filtering is switched on the
 275        the list always contains: [POSIX, C, C.UTF-8]
 276        and is extended by the optionaly configured
 277        locale
 278
 279        :return: List of names or None
 280
 281        :rtype: list|None
 282        """
 283        if self.get_rpm_locale_filtering():
 284            rpm_locale = ['POSIX', 'C', 'C.UTF-8']
 285            configured_locale = self.get_locale()
 286            if configured_locale:
 287                for locale in configured_locale:
 288                    rpm_locale.append(locale)
 289            return rpm_locale
 290        return None
 291
 292    def get_rpm_locale_filtering(self) -> bool:
 293        """
 294        Gets the rpm-locale-filtering configuration flag. Returns
 295        False if not present.
 296
 297        :return: True or False
 298
 299        :rtype: bool
 300        """
 301        for preferences in self.get_preferences_sections():
 302            locale_filtering = preferences.get_rpm_locale_filtering()
 303            if locale_filtering:
 304                return locale_filtering[0]
 305        return False
 306
 307    def get_rpm_excludedocs(self) -> bool:
 308        """
 309        Gets the rpm-excludedocs configuration flag. Returns
 310        False if not present.
 311
 312        :return: True or False
 313
 314        :rtype: bool
 315        """
 316        for preferences in self.get_preferences_sections():
 317            exclude_docs = preferences.get_rpm_excludedocs()
 318            if exclude_docs:
 319                return exclude_docs[0]
 320        return False
 321
 322    def get_rpm_check_signatures(self) -> bool:
 323        """
 324        Gets the rpm-check-signatures configuration flag. Returns
 325        False if not present.
 326
 327        :return: True or False
 328
 329        :rtype: bool
 330        """
 331        for preferences in self.get_preferences_sections():
 332            check_signatures = preferences.get_rpm_check_signatures()
 333            if check_signatures:
 334                return check_signatures[0]
 335        return False
 336
 337    def get_package_manager(self) -> str:
 338        """
 339        Get configured package manager from selected preferences section
 340
 341        :return: Content of the <packagemanager> section
 342
 343        :rtype: str
 344        """
 345        for preferences in self.get_preferences_sections():
 346            package_manager = preferences.get_packagemanager()
 347            if package_manager:
 348                return package_manager[0]
 349        return Defaults.get_default_package_manager()
 350
 351    def get_release_version(self) -> str:
 352        """
 353        Get configured release version from selected preferences section
 354
 355        :return: Content of the <release-version> section or ''
 356
 357        :rtype: str
 358        """
 359        release_version = ''
 360        for preferences in self.get_preferences_sections():
 361            release_version = preferences.get_release_version()
 362            if release_version:
 363                release_version = release_version[0]
 364                break
 365        return release_version
 366
 367    def get_packages_sections(self, section_types: List) -> List:
 368        """
 369        List of packages sections matching given section type(s)
 370
 371        :param list section_types: type name(s) from packages sections
 372
 373        :return: list of <packages> section reference(s)
 374
 375        :rtype: list
 376        """
 377        result = []
 378        packages_sections = self._profiled(
 379            self.xml_data.get_packages()
 380        )
 381        for packages in packages_sections:
 382            packages_type = packages.get_type()
 383            if packages_type in section_types:
 384                result.append(packages)
 385        return result
 386
 387    def requires_matches_host_architecture(self, requires: Any) -> bool:
 388        """
 389        Tests if the given profile requires section is applicable for
 390        the current host architecture. If no architecture is specified
 391        within the section it is considered as a match returning True.
 392
 393        Note: The XML section pointer must provide an arch attribute
 394
 395        :param section: XML section object
 396
 397        :return: True or False
 398
 399        :rtype: bool
 400        """
 401        return self._section_matches_host_architecture(requires)
 402
 403    def volume_matches_host_architecture(self, volume: Any) -> bool:
 404        """
 405        Tests if the given volume section is applicable for the current host
 406        architecture. If no architecture is specified within the section
 407        it is considered as a match returning True.
 408
 409        Note: The XML section pointer must provide an arch attribute
 410
 411        :param section: XML section object
 412
 413        :return: True or False
 414
 415        :rtype: bool
 416        """
 417        return self._section_matches_host_architecture(volume)
 418
 419    def package_matches_host_architecture(self, package: Any) -> bool:
 420        """
 421        Tests if the given package section is applicable for the current host
 422        architecture. If no architecture is specified within the section
 423        it is considered as a match returning True.
 424
 425        Note: The XML section pointer must provide an arch attribute
 426
 427        :param section: XML section object
 428
 429        :return: True or False
 430
 431        :rtype: bool
 432        """
 433        return self._section_matches_host_architecture(package)
 434
 435    def users_matches_host_architecture(self, users: Any) -> bool:
 436        """
 437        Tests if the given users section is applicable for the current host
 438        architecture. If no architecture is specified within the section
 439        it is considered as a match returning True.
 440
 441        Note: The XML section pointer must provide an arch attribute
 442
 443        :param section: XML section object
 444
 445        :return: True or False
 446
 447        :rtype: bool
 448        """
 449        return self._section_matches_host_architecture(users)
 450
 451    def collection_matches_host_architecture(self, collection: Any) -> bool:
 452        """
 453        Tests if the given namedcollection section is applicable for
 454        the current host architecture. If no architecture is specified
 455        within the section it is considered as a match returning True.
 456
 457        Note: The XML section pointer must provide an arch attribute
 458
 459        :param section: XML section object
 460
 461        :return: True or False
 462
 463        :rtype: bool
 464        """
 465        return self._section_matches_host_architecture(collection)
 466
 467    def profile_matches_host_architecture(self, profile: Any) -> bool:
 468        """
 469        Tests if the given profile section is applicable for the current host
 470        architecture. If no architecture is specified within the section
 471        it is considered as a match returning True.
 472
 473        Note: The XML section pointer must provide an arch attribute
 474
 475        :param section: XML section object
 476
 477        :return: True or False
 478
 479        :rtype: bool
 480        """
 481        return self._section_matches_host_architecture(profile)
 482
 483    def preferences_matches_host_architecture(self, preferences: Any) -> bool:
 484        """
 485        Tests if the given preferences section is applicable for the
 486        current host architecture. If no architecture is specified within
 487        the section it is considered as a match returning True.
 488
 489        Note: The XML section pointer must provide an arch attribute
 490
 491        :param section: XML section object
 492
 493        :return: True or False
 494
 495        :rtype: bool
 496        """
 497        return self._section_matches_host_architecture(preferences)
 498
 499    def repository_matches_host_architecture(self, repository: Any) -> bool:
 500        """
 501        Tests if the given repository section is applicable for the
 502        current host architecture. If no architecture is specified within
 503        the section it is considered as a match returning True.
 504
 505        Note: The XML section pointer must provide an arch attribute
 506
 507        :param section: XML section object
 508
 509        :return: True or False
 510
 511        :rtype: bool
 512        """
 513        return self._section_matches_host_architecture(repository)
 514
 515    def containers_matches_host_architecture(self, containers: Any) -> bool:
 516        """
 517        Tests if the given containers section is applicable for the
 518        current host architecture. If no arch attribute is provided in
 519        the section it is considered as a match and returns: True.
 520
 521        :param section: XML section object
 522
 523        :return: True or False
 524
 525        :rtype: bool
 526        """
 527        return self._section_matches_host_architecture(containers)
 528
 529    def container_matches_host_architecture(self, container: Any) -> bool:
 530        """
 531        Tests if the given container section is applicable for the
 532        current host architecture. If no arch attribute is provided in
 533        the section it is considered as a match and returns: True.
 534
 535        :param section: XML section object
 536
 537        :return: True or False
 538
 539        :rtype: bool
 540        """
 541        return self._section_matches_host_architecture(container)
 542
 543    def get_package_sections(
 544        self, packages_sections: List
 545    ) -> List[package_type]:
 546        """
 547        List of package sections from the given packages sections.
 548        Each list element contains a tuple with the <package> section
 549        reference and the <packages> section this package belongs to
 550
 551        If a package entry specfies an architecture, it is only taken if
 552        the host architecture matches the configured architecture
 553
 554        :param list packages_sections: <packages>
 555
 556        :return:
 557            Contains list of package_type tuples
 558
 559            .. code:: python
 560
 561                [package_type(packages_section=object, package_section=object)]
 562
 563        :rtype: list
 564        """
 565        result = []
 566        if packages_sections:
 567            for packages_section in packages_sections:
 568                package_list = packages_section.get_package()
 569                if package_list:
 570                    for package in package_list:
 571                        if self.package_matches_host_architecture(package):
 572                            result.append(
 573                                package_type(
 574                                    packages_section=packages_section,
 575                                    package_section=package
 576                                )
 577                            )
 578        return result
 579
 580    def get_to_become_deleted_packages(self, force: bool = True) -> List:
 581        """
 582        List of package names from the type="delete" or type="uninstall"
 583        packages section(s)
 584
 585        :param bool force: return "delete" type if True, "uninstall" type
 586            otherwise
 587
 588        :return: package names
 589
 590        :rtype: list
 591        """
 592        result = []
 593        to_become_deleted_packages_sections = self.get_packages_sections(
 594            ['delete' if force else 'uninstall']
 595        )
 596        package_list = self.get_package_sections(
 597            to_become_deleted_packages_sections
 598        )
 599        if package_list:
 600            for package in package_list:
 601                result.append(package.package_section.get_name())
 602        return sorted(list(set(result)))
 603
 604    def get_bootstrap_packages_sections(self) -> List:
 605        """
 606        List of packages sections matching type="bootstrap"
 607
 608        :return: list of <packages> section reference(s)
 609
 610        :rtype: list
 611        """
 612        return self.get_packages_sections(['bootstrap'])
 613
 614    def get_image_packages_sections(self) -> List:
 615        """
 616        List of packages sections matching type="image"
 617
 618        :return: list of <packages> section reference(s)
 619
 620        :rtype: list
 621        """
 622        return self.get_packages_sections(['image'])
 623
 624    def get_bootstrap_packages(self, plus_packages: List = None) -> List:
 625        """
 626        List of package names from the type="bootstrap" packages section(s)
 627
 628        The list gets the selected package manager appended
 629        if there is a request to install packages inside of
 630        the image via a chroot operation
 631
 632        :param list plus_packages: list of additional packages
 633
 634        :return: package names
 635
 636        :rtype: list
 637        """
 638        result = []
 639        bootstrap_packages_sections = self.get_bootstrap_packages_sections()
 640        package_list = self.get_package_sections(
 641            bootstrap_packages_sections
 642        )
 643        if package_list:
 644            for package in package_list:
 645                result.append(package.package_section.get_name().strip())
 646            if self.get_system_packages():
 647                package_manager_name = self.get_package_manager()
 648                if package_manager_name == 'dnf4':
 649                    # The package name for dnf4 is just dnf. Thus
 650                    # the name must be adapted in this case
 651                    package_manager_name = 'dnf'
 652                elif package_manager_name == 'apk':
 653                    package_manager_name = 'apk-tools'
 654                result.append(package_manager_name)
 655        if plus_packages:
 656            result += plus_packages
 657        return sorted(list(set(result)))
 658
 659    def get_system_packages(self) -> List:
 660        """
 661        List of package names from the packages sections matching
 662        type="image" and type=build_type
 663
 664        :return: package names
 665
 666        :rtype: list
 667        """
 668        result = []
 669        image_packages_sections = self.get_packages_sections(
 670            ['image', self.get_build_type_name()]
 671        )
 672        package_list = self.get_package_sections(
 673            image_packages_sections
 674        )
 675        if package_list:
 676            for package in package_list:
 677                result.append(package.package_section.get_name().strip())
 678        return sorted(list(set(result)))
 679
 680    def get_bootstrap_files(self) -> Dict[str, FileT]:
 681        """
 682        List of file names from the type="bootstrap" packages section(s)
 683
 684        :return: file names
 685
 686        :rtype: dict
 687        """
 688        result = {}
 689        bootstrap_packages_sections = self.get_bootstrap_packages_sections()
 690        if bootstrap_packages_sections:
 691            for bootstrap_packages_section in bootstrap_packages_sections:
 692                file_list = bootstrap_packages_section.get_file() or []
 693                for file in file_list:
 694                    result[file.get_name()] = FileT(
 695                        target=file.get_target() or '',
 696                        owner=file.get_owner() or '',
 697                        permissions=file.get_permissions() or ''
 698                    )
 699        return result
 700
 701    def get_system_files(self) -> Dict[str, FileT]:
 702        """
 703        List of file names from the packages sections matching
 704        type="image" and type=build_type
 705
 706        :return: file names
 707
 708        :rtype: dict
 709        """
 710        result = {}
 711        image_packages_sections = self.get_packages_sections(
 712            ['image', self.get_build_type_name()]
 713        )
 714        for packages in image_packages_sections:
 715            for file in packages.get_file():
 716                result[file.get_name()] = FileT(
 717                    target=file.get_target() or '',
 718                    owner=file.get_owner() or '',
 719                    permissions=file.get_permissions() or ''
 720                )
 721        return result
 722
 723    def get_bootstrap_archives(self) -> List:
 724        """
 725        List of archive names from the type="bootstrap" packages section(s)
 726
 727        :return: archive names
 728
 729        :rtype: list
 730        """
 731        result = []
 732        bootstrap_packages_sections = self.get_bootstrap_packages_sections()
 733        if bootstrap_packages_sections:
 734            for bootstrap_packages_section in bootstrap_packages_sections:
 735                archive_list = bootstrap_packages_section.get_archive()
 736                if archive_list:
 737                    for archive in archive_list:
 738                        result.append(archive.get_name().strip())
 739        return sorted(result)
 740
 741    def get_system_archives(self) -> List:
 742        """
 743        List of archive names from the packages sections matching
 744        type="image" and type=build_type
 745
 746        :return: archive names
 747
 748        :rtype: list
 749        """
 750        result = []
 751        image_packages_sections = self.get_packages_sections(
 752            ['image', self.get_build_type_name()]
 753        )
 754        for packages in image_packages_sections:
 755            for archive in packages.get_archive():
 756                result.append(archive.get_name().strip())
 757        return sorted(result)
 758
 759    def get_ignore_packages(self, section_type: str) -> List:
 760        """
 761        List of ignore package names from the packages sections matching
 762        section_type and type=build_type
 763
 764        :return: package names
 765
 766        :rtype: list
 767        """
 768        result = []
 769        image_packages_sections = self.get_packages_sections(
 770            [section_type, self.get_build_type_name()]
 771        )
 772        for packages in image_packages_sections:
 773            for package in packages.get_ignore():
 774                if self.package_matches_host_architecture(package):
 775                    result.append(package.get_name().strip())
 776        return sorted(result)
 777
 778    def get_system_files_ignore_packages(self) -> List[str]:
 779        """
 780        List of ignore package names from the type="systemfiles"
 781        packages section(s)
 782
 783        :return: package names
 784
 785        :rtype: list
 786        """
 787        return self.get_ignore_packages('systemfiles')
 788
 789    def get_system_ignore_packages(self) -> List:
 790        """
 791        List of ignore package names from the packages sections matching
 792        type="image" and type=build_type
 793
 794        :return: package names
 795
 796        :rtype: list
 797        """
 798        return self.get_ignore_packages('image')
 799
 800    def get_bootstrap_ignore_packages(self) -> List:
 801        """
 802        List of ignore package names from the packages sections matching
 803        type="image" and type=build_type
 804
 805        :return: package names
 806
 807        :rtype: list
 808        """
 809        return self.get_ignore_packages('bootstrap')
 810
 811    def get_bootstrap_package_name(self) -> str:
 812        """
 813        bootstrap_package name from type="bootstrap" packages section
 814
 815        :return: bootstrap_package name
 816
 817        :rtype: str
 818        """
 819        typed_packages_sections = self.get_packages_sections(
 820            ['bootstrap', self.get_build_type_name()]
 821        )
 822        bootstrap_package = ''
 823        for packages in typed_packages_sections:
 824            bootstrap_package = packages.get_bootstrap_package()
 825            if bootstrap_package:
 826                break
 827        return bootstrap_package
 828
 829    def get_collection_type(self, section_type: str = 'image') -> str:
 830        """
 831        Collection type from packages sections matching given section
 832        type.
 833
 834        If no collection type is specified the default collection
 835        type is set to: onlyRequired
 836
 837        :param str section_type: type name from packages section
 838
 839        :return: collection type name
 840
 841        :rtype: str
 842        """
 843        typed_packages_sections = self.get_packages_sections(
 844            [section_type, self.get_build_type_name()]
 845        )
 846        collection_type = 'onlyRequired'
 847        for packages in typed_packages_sections:
 848            packages_collection_type = packages.get_patternType()
 849            if packages_collection_type:
 850                collection_type = packages_collection_type
 851                break
 852        return collection_type
 853
 854    def get_bootstrap_collection_type(self) -> str:
 855        """
 856        Collection type for packages sections matching type="bootstrap"
 857
 858        :return: collection type name
 859
 860        :rtype: str
 861        """
 862        return self.get_collection_type('bootstrap')
 863
 864    def get_system_collection_type(self) -> str:
 865        """
 866        Collection type for packages sections matching type="image"
 867
 868        :return: collection type name
 869
 870        :rtype: str
 871        """
 872        return self.get_collection_type('image')
 873
 874    def get_collection_modules(self) -> Dict[str, List[str]]:
 875        """
 876        Dict of collection modules to enable and/or disable
 877
 878        :return:
 879            Dict of the form:
 880
 881            .. code:: python
 882
 883                {
 884                    'enable': [
 885                        "module:stream", "module"
 886                    ],
 887                    'disable': [
 888                        "module"
 889                    ]
 890                }
 891
 892        :rtype: dict
 893        """
 894        modules: Dict[str, List[str]] = {
 895            'disable': [],
 896            'enable': []
 897        }
 898        for packages in self.get_bootstrap_packages_sections():
 899            for collection_module in packages.get_collectionModule():
 900                module_name = collection_module.get_name()
 901                if collection_module.get_enable() is False:
 902                    modules['disable'].append(module_name)
 903                else:
 904                    stream = collection_module.get_stream()
 905                    if stream:
 906                        modules['enable'].append(f'{module_name}:{stream}')
 907                    else:
 908                        modules['enable'].append(module_name)
 909        return modules
 910
 911    def get_collections(self, section_type: str = 'image') -> List:
 912        """
 913        List of collection names from the packages sections matching
 914        type=section_type and type=build_type
 915
 916        :return: collection names
 917
 918        :rtype: list
 919        """
 920        result = []
 921        typed_packages_sections = self.get_packages_sections(
 922            [section_type, self.get_build_type_name()]
 923        )
 924        for packages in typed_packages_sections:
 925            for collection in packages.get_namedCollection():
 926                if self.collection_matches_host_architecture(collection):
 927                    result.append(collection.get_name())
 928        return sorted(list(set(result)))
 929
 930    def get_bootstrap_collections(self) -> List:
 931        """
 932        List of collection names from the packages sections
 933        matching type="bootstrap"
 934
 935        :return: collection names
 936
 937        :rtype: list
 938        """
 939        return self.get_collections('bootstrap')
 940
 941    def get_system_collections(self) -> List:
 942        """
 943        List of collection names from the packages sections
 944        matching type="image"
 945
 946        :return: collection names
 947
 948        :rtype: list
 949        """
 950        return self.get_collections('image')
 951
 952    def get_products(self, section_type: str = 'image') -> List:
 953        """
 954        List of product names from the packages sections matching
 955        type=section_type and type=build_type
 956
 957        :param str section_type: type name from packages section
 958
 959        :return: product names
 960
 961        :rtype: list
 962        """
 963        result = []
 964        typed_packages_sections = self.get_packages_sections(
 965            [section_type, self.get_build_type_name()]
 966        )
 967        for packages in typed_packages_sections:
 968            for product in packages.get_product():
 969                result.append(product.get_name())
 970        return list(set(result))
 971
 972    def get_bootstrap_products(self) -> List:
 973        """
 974        List of product names from the packages sections
 975        matching type="bootstrap"
 976
 977        :return: product names
 978
 979        :rtype: list
 980        """
 981        return self.get_products('bootstrap')
 982
 983    def get_system_products(self) -> List:
 984        """
 985        List of product names from the packages sections
 986        matching type="image"
 987
 988        :return: product names
 989
 990        :rtype: list
 991        """
 992        return self.get_products('image')
 993
 994    def is_xen_server(self) -> bool:
 995        """
 996        Check if build type domain setup specifies a Xen Server (dom0)
 997
 998        :return: True or False
 999
1000        :rtype: bool
1001        """
1002        return self.build_type.get_xen_server()
1003
1004    def is_xen_guest(self) -> bool:
1005        """
1006        Check if build type setup specifies a Xen Guest (domX)
1007        The check is based on the architecture, the firmware and
1008        xen_loader configuration values:
1009
1010        * We only support Xen setup on the x86_64 architecture
1011
1012        * Firmware pointing to ec2 means the image is targeted to run
1013          in Amazon EC2 which is a Xen guest
1014
1015        * Machine setup with a xen_loader attribute also indicates a
1016          Xen guest target
1017
1018        :return: True or False
1019
1020        :rtype: bool
1021        """
1022        if self.host_architecture != 'x86_64':
1023            # We only support Xen stuff on x86_64
1024            return False
1025        firmware = self.build_type.get_firmware()
1026        machine_section = self.get_build_type_machine_section()
1027        if firmware and firmware in Defaults.get_ec2_capable_firmware_names():
1028            # the image is targeted to run in Amazon EC2 which is a Xen system
1029            return True
1030        elif machine_section and machine_section.get_xen_loader():
1031            # the image provides a machine section with a guest loader setup
1032            return True
1033        return False
1034
1035    def get_build_type_partitions_section(self) -> Any:
1036        """
1037        First partitions section from the build type section
1038
1039        :return: <partitions> section reference
1040
1041        :rtype: xml_parse::partitions
1042        """
1043        partitions_sections = self.build_type.get_partitions()
1044        if partitions_sections:
1045            return partitions_sections[0]
1046        return None
1047
1048    def get_build_type_system_disk_section(self) -> Any:
1049        """
1050        First system disk section from the build type section
1051
1052        :return: <systemdisk> section reference
1053
1054        :rtype: xml_parse::systemdisk
1055        """
1056        systemdisk_sections = self.build_type.get_systemdisk()
1057        if systemdisk_sections:
1058            return systemdisk_sections[0]
1059        return None
1060
1061    def get_build_type_machine_section(self) -> Any:
1062        """
1063        First machine section from the build type section
1064
1065        :return: <machine> section reference
1066
1067        :rtype: xml_parse::machine
1068        """
1069        machine_sections = self.build_type.get_machine()
1070        if machine_sections:
1071            return machine_sections[0]
1072        return None
1073
1074    def get_build_type_vagrant_config_section(self) -> Any:
1075        """
1076        First vagrantconfig section from the build type section
1077
1078        :return: <vagrantconfig> section reference
1079
1080        :rtype: xml_parse::vagrantconfig
1081        """
1082        vagrant_config_sections = self.build_type.get_vagrantconfig()
1083        if vagrant_config_sections:
1084            return vagrant_config_sections[0]
1085        return None
1086
1087    def get_vagrant_config_virtualbox_guest_additions(self) -> bool:
1088        """
1089        Attribute virtualbox_guest_additions_present from the first
1090        vagrantconfig section.
1091
1092        :return: True|False
1093
1094        :rtype: bool
1095        """
1096        vagrant_config_sections = self.get_build_type_vagrant_config_section()
1097        if not vagrant_config_sections.virtualbox_guest_additions_present:
1098            return Defaults.get_vagrant_config_virtualbox_guest_additions()
1099        else:
1100            return vagrant_config_sections.virtualbox_guest_additions_present
1101
1102    def get_build_type_vmdisk_section(self) -> Any:
1103        """
1104        First vmdisk section from the first machine section in the
1105        build type section
1106
1107        :return: <vmdisk> section reference
1108
1109        :rtype: xml_parse::vmdisk
1110        """
1111        machine_section = self.get_build_type_machine_section()
1112        if machine_section:
1113            vmdisk_sections = machine_section.get_vmdisk()
1114            if vmdisk_sections:
1115                return vmdisk_sections[0]
1116        return None
1117
1118    def get_build_type_vmnic_entries(self) -> List:
1119        """
1120        vmnic section(s) from the first machine section in the
1121        build type section
1122
1123        :return: list of <vmnic> section reference(s)
1124
1125        :rtype: list
1126        """
1127        machine_section = self.get_build_type_machine_section()
1128        if machine_section:
1129            return machine_section.get_vmnic()
1130        else:
1131            return []
1132
1133    def get_build_type_vmdvd_section(self) -> Any:
1134        """
1135        First vmdvd section from the first machine section in the
1136        build type section
1137
1138        :return: <vmdvd> section reference
1139
1140        :rtype: xml_parse::vmdvd
1141        """
1142        machine_section = self.get_build_type_machine_section()
1143        if machine_section:
1144            vmdvd_sections = machine_section.get_vmdvd()
1145            if vmdvd_sections:
1146                return vmdvd_sections[0]
1147        return None
1148
1149    def get_build_type_vmconfig_entries(self) -> List:
1150        """
1151        List of vmconfig-entry section values from the first
1152        machine section in the build type section
1153
1154        :return: <vmconfig_entry> section reference(s)
1155
1156        :rtype: list
1157        """
1158        machine_section = self.get_build_type_machine_section()
1159        if machine_section:
1160            vmconfig_entries = machine_section.get_vmconfig_entry()
1161            if vmconfig_entries:
1162                return vmconfig_entries
1163
1164        return []
1165
1166    def get_build_type_bootloader_section(self) -> Any:
1167        """
1168        First bootloader section from the build type section
1169
1170        :return: <bootloader> section reference
1171
1172        :rtype: xml_parse::bootloader
1173        """
1174        bootloader_sections = self.build_type.get_bootloader()
1175        if bootloader_sections:
1176            return bootloader_sections[0]
1177        return None
1178
1179    def get_build_type_bootloader_name(self) -> str:
1180        """
1181        Return bootloader name for selected build type
1182
1183        :return: bootloader name
1184
1185        :rtype: str
1186        """
1187        bootloader = self.get_build_type_bootloader_section()
1188        return bootloader.get_name() if bootloader else \
1189            Defaults.get_default_bootloader()
1190
1191    def get_build_type_bootloader_bls(self) -> bool:
1192        """
1193        Return bootloader bls setting for selected build type
1194
1195        :return: True or False
1196
1197        :rtype: bool
1198        """
1199        bootloader = self.get_build_type_bootloader_section()
1200        if bootloader and bootloader.get_bls() is not None:
1201            return bootloader.get_bls()
1202        return True
1203
1204    def get_build_type_bootloader_console(self) -> List[str]:
1205        """
1206        Return bootloader console setting for selected build type
1207
1208        :return:
1209            list of console settings for output (first element)
1210            and input (second element)
1211
1212        :rtype: list
1213        """
1214        result = ['', '']
1215        bootloader = self.get_build_type_bootloader_section()
1216        if bootloader:
1217            console_out = bootloader.get_output_console()
1218            console_in = bootloader.get_input_console()
1219            console_in = console_in if console_in else console_out
1220            result = [
1221                console_out if console_out and console_out != 'none' else '',
1222                console_in if console_in and console_in != 'none' else ''
1223            ]
1224        return result
1225
1226    def get_build_type_bootloader_serial_line_setup(self) -> Optional[str]:
1227        """
1228        Return bootloader serial line setup parameters for the
1229        selected build type
1230
1231        :return: serial line setup
1232
1233        :rtype: str
1234        """
1235        bootloader = self.get_build_type_bootloader_section()
1236        if bootloader:
1237            return bootloader.get_serial_line()
1238        return None
1239
1240    def get_build_type_bootloader_timeout(self) -> Optional[str]:
1241        """
1242        Return bootloader timeout setting for selected build type
1243
1244        :return: timeout string
1245
1246        :rtype: str
1247        """
1248        bootloader = self.get_build_type_bootloader_section()
1249        if bootloader:
1250            return bootloader.get_timeout()
1251        return None
1252
1253    def get_build_type_bootloader_timeout_style(self) -> Optional[str]:
1254        """
1255        Return bootloader timeout style setting for selected build type
1256
1257        :return: timeout_style string
1258
1259        :rtype: str
1260        """
1261        bootloader = self.get_build_type_bootloader_section()
1262        if bootloader:
1263            return bootloader.get_timeout_style()
1264        return None
1265
1266    def get_build_type_bootloader_targettype(self) -> Optional[str]:
1267        """
1268        Return bootloader target type setting. Only relevant for
1269        the zipl bootloader because zipl is installed differently
1270        depending on the storage target it runs later
1271
1272        :return: target type string
1273
1274        :rtype: str
1275        """
1276        bootloader = self.get_build_type_bootloader_section()
1277        if bootloader:
1278            return bootloader.get_targettype()
1279        return None
1280
1281    def get_build_type_bootloader_settings_section(self) -> Any:
1282        """
1283        First bootloadersettings section from the build
1284        type bootloader section
1285
1286        :return: <bootloadersettings> section reference
1287
1288        :rtype: xml_parse::bootloadersettings
1289        """
1290        bootloader_section = self.get_build_type_bootloader_section()
1291        bootloader_settings_section = None
1292        if bootloader_section and bootloader_section.get_bootloadersettings():
1293            bootloader_settings_section = \
1294                bootloader_section.get_bootloadersettings()[0]
1295        return bootloader_settings_section
1296
1297    def get_build_type_bootloader_securelinux_section(self) -> List[Any]:
1298        """
1299        First securelinux section from the build
1300        type bootloader section
1301
1302        :return: <securelinux> section reference
1303
1304        :rtype: xml_parse::securelinux
1305        """
1306        bootloader_section = self.get_build_type_bootloader_section()
1307        bootloader_securelinux_section = []
1308        if bootloader_section and bootloader_section.get_securelinux():
1309            bootloader_securelinux_section = \
1310                bootloader_section.get_securelinux()
1311        return bootloader_securelinux_section
1312
1313    def get_build_type_bootloader_environment_variables(self) -> List[str]:
1314        """
1315        List of bootloader variables from the build
1316        type > bootloader > bootloadersettings section
1317        """
1318        variable_list = []
1319        bootloader_settings_section = \
1320            self.get_build_type_bootloader_settings_section()
1321        if bootloader_settings_section:
1322            environment = bootloader_settings_section.get_environment()
1323            if environment and environment[0].get_env():
1324                for env in environment[0].get_env():
1325                    variable_list.append(
1326                        '{}{}'.format(
1327                            env.get_name(),
1328                            f'={env.get_value()}' if env.get_value() else ''
1329                        )
1330                    )
1331        return variable_list
1332
1333    def get_bootloader_options(self, option_type: str) -> List[str]:
1334        """
1335        List of custom options used in the process to
1336        run bootloader setup workloads
1337        """
1338        result: List[str] = []
1339        bootloader_settings = self.get_build_type_bootloader_settings_section()
1340        if bootloader_settings:
1341            options = []
1342            if option_type == 'shim':
1343                options = bootloader_settings.get_shimoption()
1344            elif option_type == 'install':
1345                options = bootloader_settings.get_installoption()
1346            elif option_type == 'config':
1347                options = bootloader_settings.get_configoption()
1348            for option in options:
1349                result.append(option.get_name())
1350                if option.get_value():
1351                    result.append(option.get_value())
1352        return result
1353
1354    def get_bootloader_shim_options(self) -> List[str]:
1355        """
1356        List of custom options used in the process to setup secure boot
1357        """
1358        return self.get_bootloader_options('shim')
1359
1360    def get_bootloader_install_options(self) -> List[str]:
1361        """
1362        List of custom options used in the bootloader installation
1363        """
1364        return self.get_bootloader_options('install')
1365
1366    def get_bootloader_config_options(self) -> List[str]:
1367        """
1368        List of custom options used in the bootloader configuration
1369        """
1370        return self.get_bootloader_options('config')
1371
1372    def get_build_type_bootloader_use_disk_password(self) -> bool:
1373        """
1374        Indicate whether the bootloader configuration should use the
1375        password protecting the encrypted root volume.
1376
1377        :return: True|False
1378
1379        :rtype: bool
1380        """
1381        bootloader = self.get_build_type_bootloader_section()
1382        if bootloader:
1383            return bootloader.get_use_disk_password()
1384        return False
1385
1386    def get_build_type_oemconfig_section(self) -> Any:
1387        """
1388        First oemconfig section from the build type section
1389
1390        :return: <oemconfig> section reference
1391
1392        :rtype: xml_parse::oemconfig
1393        """
1394        oemconfig_sections = self.build_type.get_oemconfig()
1395        if oemconfig_sections:
1396            return oemconfig_sections[0]
1397        return None
1398
1399    def get_oemconfig_oem_resize(self) -> bool:
1400        """
1401        State value to activate/deactivate disk resize. Returns a
1402        boolean value if specified or True to set resize on by default
1403
1404        :return: Content of <oem-resize> section value
1405
1406        :rtype: bool
1407        """
1408        oemconfig = self.get_build_type_oemconfig_section()
1409        if oemconfig and oemconfig.get_oem_resize():
1410            return oemconfig.get_oem_resize()[0]
1411        else:
1412            return True
1413
1414    def get_oemconfig_oem_systemsize(self) -> int:
1415        """
1416        State value to retrieve root partition size
1417
1418        :return: Content of <oem-systemsize> section value
1419
1420        :rtype: int
1421        """
1422        oemconfig = self.get_build_type_oemconfig_section()
1423        if oemconfig and oemconfig.get_oem_systemsize():
1424            return int(oemconfig.get_oem_systemsize()[0])
1425        else:
1426            return 0
1427
1428    def get_oemconfig_oem_multipath_scan(self) -> bool:
1429        """
1430        State value to activate multipath maps. Returns a boolean
1431        value if specified or False
1432
1433        :return: Content of <oem-multipath-scan> section value
1434
1435        :rtype: bool
1436        """
1437        oemconfig = self.get_build_type_oemconfig_section()
1438        if oemconfig and oemconfig.get_oem_multipath_scan():
1439            return oemconfig.get_oem_multipath_scan()[0]
1440        return False
1441
1442    def get_oemconfig_swap_mbytes(self) -> Optional[int]:
1443        """
1444        Return swapsize in MB if requested or None
1445
1446        Operates on the value of oem-swap and if set to true
1447        returns the given size or the default value.
1448
1449        :return: Content of <oem-swapsize> section value or default
1450
1451        :rtype: int
1452        """
1453        oemconfig = self.get_build_type_oemconfig_section()
1454        if oemconfig and oemconfig.get_oem_swap():
1455            swap_requested = oemconfig.get_oem_swap()[0]
1456            if swap_requested:
1457                swapsize = oemconfig.get_oem_swapsize()
1458                if swapsize:
1459                    return swapsize[0]
1460                else:
1461                    return Defaults.get_swapsize_mbytes()
1462        return None
1463
1464    def get_oemconfig_swap_name(self) -> str:
1465        """
1466        Return the swap space name
1467
1468        Operates on the value of oem-swapname and if set
1469        returns the configured name or the default name: LVSwap
1470
1471        The name of the swap space is used only if the
1472        image is configured to use the LVM volume manager.
1473        In this case swap is a volume and the volume takes
1474        a name. In any other case the given name will have
1475        no effect.
1476
1477        :return: Content of <oem-swapname> section value or default
1478
1479        :rtype: str
1480        """
1481        oemconfig = self.get_build_type_oemconfig_section()
1482        if oemconfig and oemconfig.get_oem_swapname():
1483            return oemconfig.get_oem_swapname()[0]
1484        return 'LVSwap'
1485
1486    def get_build_type_containerconfig_section(self) -> Any:
1487        """
1488        First containerconfig section from the build type section
1489
1490        :return: <containerconfig> section reference
1491
1492        :rtype: xml_parse::containerconfig
1493        """
1494        container_config_sections = self.build_type.get_containerconfig()
1495        if container_config_sections:
1496            return container_config_sections[0]
1497        return None
1498
1499    def get_dracut_config(self, action: str) -> DracutT:
1500        """
1501        Get dracut initrd config for the specified action
1502        """
1503        uefi = False
1504        modules = []
1505        drivers = []
1506        initrd_sections = self.build_type.get_initrd()
1507        for initrd_section in initrd_sections:
1508            if initrd_section.get_action() == action:
1509                for dracut in initrd_section.get_dracut():
1510                    uefi = bool(dracut.get_uefi())
1511                    if dracut.get_module():
1512                        modules.append(dracut.get_module())
1513                    if dracut.get_driver():
1514                        drivers.append(dracut.get_driver())
1515        return DracutT(
1516            uefi=uefi, modules=modules, drivers=drivers
1517        )
1518
1519    def get_installmedia_initrd_modules(self, action: str) -> List[str]:
1520        """
1521        Gets the list of modules to append in installation initrds
1522
1523        :return: a list of dracut module names
1524
1525        :rtype: list
1526        """
1527        modules: List[str] = []
1528        installmedia = self.build_type.get_installmedia()
1529        if not installmedia:
1530            return modules
1531        initrd_sections = installmedia[0].get_initrd()
1532        for initrd_section in initrd_sections:
1533            if initrd_section.get_action() == action:
1534                for module in initrd_section.get_dracut():
1535                    if module.get_module():
1536                        modules.append(module.get_module())
1537        return modules
1538
1539    def get_installmedia_initrd_drivers(self, action: str) -> List[str]:
1540        """
1541        Gets the list of drivers to append in installation initrds
1542
1543        :return: a list of dracut driver names
1544
1545        :rtype: list
1546        """
1547        drivers: List[str] = []
1548        installmedia = self.build_type.get_installmedia()
1549        if not installmedia:
1550            return drivers
1551        initrd_sections = installmedia[0].get_initrd()
1552        for initrd_section in initrd_sections:
1553            if initrd_section.get_action() == action:
1554                for driver in initrd_section.get_dracut():
1555                    if driver.get_driver():
1556                        drivers.append(driver.get_driver())
1557        return drivers
1558
1559    def get_build_type_size(
1560        self, include_unpartitioned: bool = False
1561    ) -> Optional[size_type]:
1562        """
1563        Size information from the build type section.
1564        If no unit is set the value is treated as mbytes
1565
1566        :param bool include_unpartitioned: sets if the unpartitioned area
1567            should be included in the computed size or not
1568
1569        :return: mbytes
1570
1571        :rtype: int
1572        """
1573        size_section = self.build_type.get_size()
1574        if size_section:
1575            unit = size_section[0].get_unit()
1576            additive = size_section[0].get_additive()
1577            unpartitioned = size_section[0].get_unpartitioned()
1578            value = int(size_section[0].get_valueOf_())
1579            if not include_unpartitioned and unpartitioned is not None:
1580                value -= unpartitioned
1581            if unit == 'G':
1582                value *= 1024
1583            return size_type(
1584                mbytes=value, additive=additive
1585            )
1586        return None
1587
1588    def get_build_type_unpartitioned_bytes(self) -> int:
1589        """
1590        Size of the unpartitioned area for image in megabytes
1591
1592        :return: mbytes
1593
1594        :rtype: int
1595        """
1596        size_section = self.build_type.get_size()
1597        if size_section:
1598            unit = size_section[0].get_unit() or 'M'
1599            unpartitioned = size_section[0].get_unpartitioned() or 0
1600            return StringToSize.to_bytes('{0}{1}'.format(unpartitioned, unit))
1601        return 0
1602
1603    def get_disk_start_sector(self) -> int:
1604        """
1605        First disk sector number to be used by the first disk partition.
1606
1607        :return: number
1608
1609        :rtype: int
1610        """
1611        disk_start_sector = self.build_type.get_disk_start_sector()
1612        if disk_start_sector is None:
1613            disk_start_sector = Defaults.get_default_disk_start_sector()
1614        return disk_start_sector
1615
1616    def get_build_type_spare_part_size(self) -> Optional[int]:
1617        """
1618        Size information for the spare_part size from the build
1619        type. If no unit is set the value is treated as mbytes
1620
1621        :return: mbytes
1622
1623        :rtype: int
1624        """
1625        spare_part_size = self.build_type.get_spare_part()
1626        if spare_part_size:
1627            return self._to_mega_byte(spare_part_size)
1628        return None
1629
1630    def get_build_type_spare_part_fs_attributes(self) -> Optional[List]:
1631        """
1632        Build type specific list of filesystem attributes applied to
1633        the spare partition.
1634
1635        :return: list of strings or empty list
1636
1637        :rtype: list
1638        """
1639        spare_part_attributes = self.build_type.get_spare_part_fs_attributes()
1640        if spare_part_attributes:
1641            return spare_part_attributes.strip().split(',')
1642        return None
1643
1644    def get_build_type_format_options(self) -> Dict:
1645        """
1646        Disk format options returned as a dictionary
1647
1648        :return: format options
1649
1650        :rtype: dict
1651        """
1652        result = {}
1653        format_options = self.build_type.get_formatoptions()
1654        if format_options:
1655            for option in format_options.split(','):
1656                key_value_list = option.split('=')
1657                if len(key_value_list) == 2:
1658                    result[key_value_list[0]] = key_value_list[1]
1659                else:
1660                    result[key_value_list[0]] = None
1661        return result
1662
1663    def get_volume_group_name(self) -> str:
1664        """
1665        Volume group name from selected <systemdisk> section
1666
1667        :return: volume group name
1668
1669        :rtype: str
1670        """
1671        systemdisk_section = self.get_build_type_system_disk_section()
1672        volume_group_name = None
1673        if systemdisk_section:
1674            volume_group_name = systemdisk_section.get_name()
1675        if not volume_group_name:
1676            volume_group_name = Defaults.get_default_volume_group_name()
1677        return volume_group_name
1678
1679    def get_users(self) -> List:
1680        """
1681        List of configured users.
1682
1683        Each entry in the list is a single xml_parse::user instance.
1684
1685        :return: list of <user> section reference(s)
1686
1687        :rtype: list
1688        """
1689        users_list = []
1690        users_names_added = []
1691        for users_section in self.get_users_sections():
1692            for user in users_section.get_user():
1693                if user.get_name() not in users_names_added:
1694                    users_list.append(user)
1695                    users_names_added.append(user.get_name())
1696
1697        return users_list
1698
1699    def get_user_groups(self, user_name) -> List[str]:
1700        """
1701        List of group names matching specified user
1702
1703        Each entry in the list is the name of a group and optionally its
1704        group ID separated by a colon, that the specified user belongs to.
1705        The first item in the list is the login or primary group. The
1706        list will be empty if no groups are specified in the
1707        description file.
1708
1709        :return: groups data for the given user
1710
1711        :rtype: list
1712        """
1713        groups_list = []
1714        for users_section in self.get_users_sections():
1715            for user in users_section.get_user():
1716                if user.get_name() == user_name:
1717                    user_groups = user.get_groups()
1718                    if user_groups:
1719                        groups_list += user.get_groups().split(',')
1720
1721        # order of list items matter, thus we don't use set() here
1722        # better faster, nicer solutions welcome :)
1723        result_group_list = []
1724        for item in groups_list:
1725            if item not in result_group_list:
1726                result_group_list.append(item)
1727
1728        return result_group_list
1729
1730    def get_container_config(self) -> Dict:
1731        """
1732        Dictionary of containerconfig information
1733
1734        Takes attributes and subsection data from the selected
1735        <containerconfig> section and stores it in a dictionary
1736        """
1737        container_config = self._match_docker_base_data()
1738        container_config.update(
1739            self._match_docker_entrypoint()
1740        )
1741        container_config.update(
1742            self._match_docker_subcommand()
1743        )
1744        container_config.update(
1745            self._match_docker_expose_ports()
1746        )
1747        container_config.update(
1748            self._match_docker_volumes()
1749        )
1750        container_config.update(
1751            self._match_docker_stopsignal()
1752        )
1753        container_config.update(
1754            self._match_docker_environment()
1755        )
1756        container_config.update(
1757            self._match_docker_labels()
1758        )
1759        container_config.update(
1760            self._match_docker_history()
1761        )
1762
1763        desc = self.get_description_section()
1764        author_contact = "{0} <{1}>".format(desc.author, desc.contact)
1765        if 'history' not in container_config:
1766            container_config['history'] = {}
1767        if 'author' not in container_config['history']:
1768            container_config['history']['author'] = author_contact
1769        if 'maintainer' not in container_config:
1770            container_config['maintainer'] = author_contact
1771
1772        return container_config
1773
1774    def set_container_config_tag(self, tag: str) -> None:
1775        """
1776        Set new tag name in containerconfig section
1777
1778        In order to set a new tag value an existing containerconfig and
1779        tag setup is required
1780
1781        :param str tag: tag name
1782        """
1783        container_config_section = self.get_build_type_containerconfig_section()
1784        if container_config_section and container_config_section.get_tag():
1785            container_config_section.set_tag(tag)
1786        else:
1787            message = dedent('''\n
1788                No <containerconfig> section and/or tag attribute configured
1789
1790                In order to set the tag {0} as new container tag,
1791                an initial containerconfig section including a tag
1792                setup is required
1793            ''')
1794            log.warning(message.format(tag))
1795
1796    def add_container_config_label(self, label_name: str, value: str) -> None:
1797        """
1798        Adds a new label in the containerconfig section, if a label with the
1799        same name is already defined in containerconfig it gets overwritten by
1800        this method.
1801
1802        :param str label_name: the string representing the label name
1803        :param str value: the value of the label
1804        """
1805        if self.get_build_type_name() not in ['docker', 'oci']:
1806            message = dedent('''\n
1807                Labels can only be configured for container image types
1808                docker and oci.
1809            ''')
1810            log.warning(message)
1811            return
1812
1813        container_config_section = self.get_build_type_containerconfig_section()
1814        if not container_config_section:
1815            container_config_section = xml_parse.containerconfig(
1816                name=Defaults.get_default_container_name(),
1817                tag=Defaults.get_default_container_tag()
1818            )
1819            self.build_type.set_containerconfig([container_config_section])
1820
1821        labels = container_config_section.get_labels()
1822        if not labels:
1823            labels = [xml_parse.labels()]
1824
1825        label_names = []
1826        for label in labels[0].get_label():
1827            label_names.append(label.get_name())
1828
1829        if label_name in label_names:
1830            labels[0].replace_label_at(
1831                label_names.index(label_name),
1832                xml_parse.label(label_name, value)
1833            )
1834        else:
1835            labels[0].add_label(xml_parse.label(label_name, value))
1836
1837        container_config_section.set_labels(labels)
1838
1839    def get_partitions(self) -> Dict[str, ptable_entry_type]:
1840        """
1841        Dictionary of configured partitions.
1842
1843        Each entry in the dict references a ptable_entry_type
1844        Each key in the dict references the name of the
1845        partition entry as handled by KIWI
1846
1847        :return:
1848            Contains dict of ptable_entry_type tuples
1849
1850            .. code:: python
1851
1852                {
1853                    'NAME': ptable_entry_type(
1854                        mbsize=int,
1855                        clone=int,
1856                        partition_name=str,
1857                        partition_type=str,
1858                        partition_id=Optional[int],
1859                        mountpoint=str,
1860                        filesystem=str,
1861                        label=str
1862                    )
1863                }
1864
1865        :rtype: dict
1866        """
1867        partitions: Dict[str, ptable_entry_type] = {}
1868        partitions_section = self.get_build_type_partitions_section()
1869        if not partitions_section:
1870            return partitions
1871        for partition in partitions_section.get_partition():
1872            name = partition.get_name()
1873            partition_name = partition.get_partition_name() or f'p.lx{name}'
1874            partitions[name] = ptable_entry_type(
1875                mbsize=self._to_mega_byte(partition.get_size()),
1876                clone=int(partition.get_clone()) if partition.get_clone() else 0,
1877                partition_name=partition_name,
1878                partition_type=partition.get_partition_type() or 't.linux',
1879                partition_id=partition.get_part_id(),
1880                mountpoint=partition.get_mountpoint(),
1881                filesystem=partition.get_filesystem(),
1882                label=partition.get_label() or ''
1883            )
1884        return partitions
1885
1886    def get_host_key_certificates(
1887        self
1888    ) -> Union[List[Dict[str, List[str]]], List[Dict[str, str]]]:
1889        cc_result = []
1890        cc_certificates: Dict[str, List[str]] = {}
1891        securelinux_list = \
1892            self.get_build_type_bootloader_securelinux_section()
1893        for securelinux in securelinux_list:
1894            cc_certificates = {
1895                'hkd_cert': [],
1896                'hkd_revocation_list': [],
1897                'hkd_ca_cert': securelinux.get_hkd_ca_cert(),
1898                'hkd_sign_cert': securelinux.get_hkd_sign_cert()
1899            }
1900            for hkd_cert in securelinux.get_hkd_cert():
1901                cc_certificates['hkd_cert'].append(hkd_cert.get_name())
1902            for hkd_revocation_list in securelinux.get_hkd_revocation_list():
1903                cc_certificates['hkd_revocation_list'].append(
1904                    hkd_revocation_list.get_name()
1905                )
1906            cc_result.append(cc_certificates)
1907        return cc_result
1908
1909    def get_containers(self) -> List[ContainerT]:
1910        containers = []
1911
1912        def build_fetch_command(
1913            root_dir: str,
1914            container_uri: str = '',
1915            container_file_name: str = '',
1916            container_endpoint: str = ''
1917        ):
1918            pass  # pragma: nocover
1919        for containers_section in self.get_containers_sections():
1920            for container in containers_section.get_container():
1921                if self.container_matches_host_architecture(container):
1922                    fetch_command = build_fetch_command
1923                    load_command = []
1924                    container_tag = container.get_tag() or 'latest'
1925                    container_path = container.get_path() or ''
1926                    container_endpoint = os.path.normpath(
1927                        '{0}/{1}/{2}:{3}'.format(
1928                            containers_section.get_source(), container_path,
1929                            container.name, container_tag
1930                        )
1931                    )
1932                    container_file_name = '{0}/{1}_{2}'.format(
1933                        defaults.LOCAL_CONTAINERS, container.name, container_tag
1934                    )
1935                    container_backend = containers_section.get_backend() or ''
1936                    if container_backend in ['podman', 'docker', 'container-snap']:
1937                        if Defaults.is_buildservice_worker():
1938                            container_uri = Uri(
1939                                'obsrepositories:/{0}'.format(
1940                                    container_endpoint
1941                                ), 'container'
1942                            ).translate()
1943
1944                            def build_fetch_command(
1945                                root_dir: str,
1946                                container_uri: str = container_uri,
1947                                container_file_name: str = container_file_name,
1948                                container_endpoint: str = container_endpoint
1949                            ):
1950                                def perform():
1951                                    Command.run(
1952                                        [
1953                                            'cp', '{0}.ociarchive'.format(
1954                                                container_uri
1955                                            ), os.path.normpath(
1956                                                '{0}/{1}'.format(
1957                                                    root_dir,
1958                                                    container_file_name
1959                                                )
1960                                            )
1961                                        ]
1962                                    )
1963                                perform()
1964                            fetch_command = build_fetch_command
1965                        else:
1966
1967                            def build_fetch_command(
1968                                root_dir: str,
1969                                container_uri: str = '',
1970                                container_file_name: str = container_file_name,
1971                                container_endpoint: str = container_endpoint
1972                            ):
1973                                def perform():
1974                                    Command.run(
1975                                        [
1976                                            'chroot', root_dir,
1977                                            '/usr/bin/skopeo', 'copy',
1978                                            'docker://{0}'.format(
1979                                                container_endpoint
1980                                            ),
1981                                            'oci-archive:{0}:{1}'.format(
1982                                                container_file_name,
1983                                                container_endpoint
1984                                            )
1985                                        ]
1986                                    )
1987                                perform()
1988                            fetch_command = build_fetch_command
1989                        if not container.get_fetch_only():
1990                            load_command = [
1991                                f'/usr/bin/{container_backend}',
1992                                'load', '-i', container_file_name
1993                            ]
1994                    containers.append(
1995                        ContainerT(
1996                            name=f'{container.name}_{container_tag}',
1997                            backend=container_backend,
1998                            container_file=container_file_name,
1999                            fetch_only=bool(container.get_fetch_only()),
2000                            fetch_command=fetch_command,
2001                            load_command=load_command
2002                        )
2003                    )
2004        return containers
2005
2006    def get_volumes(self) -> List[volume_type]:
2007        """
2008        List of configured systemdisk volumes.
2009
2010        Each entry in the list is a tuple with the following information
2011
2012        * name: name of the volume
2013        * size: size of the volume
2014        * realpath: system path to lookup volume data. If no mountpoint
2015          is set the volume name is used as data path.
2016        * mountpoint: volume mount point and volume data path
2017        * fullsize: takes all space True|False
2018        * attributes: list of volume attributes handled via chattr
2019
2020        :return:
2021            Contains list of volume_type tuples
2022
2023            .. code:: python
2024
2025                [
2026                    volume_type(
2027                        name=volume_name,
2028                        parent=volume_parent,
2029                        size=volume_size,
2030                        realpath=path,
2031                        mountpoint=path,
2032                        fullsize=True,
2033                        label=volume_label,
2034                        attributes=['no-copy-on-write'],
2035                        is_root_volume=True|False
2036                    )
2037                ]
2038
2039        :rtype: list
2040        """
2041        volume_type_list: List[volume_type] = []
2042        systemdisk_section = self.get_build_type_system_disk_section()
2043        selected_filesystem = self.build_type.get_filesystem()
2044        swap_mbytes = self.get_oemconfig_swap_mbytes()
2045        swap_name = self.get_oemconfig_swap_name()
2046        if not systemdisk_section:
2047            return volume_type_list
2048        volumes = systemdisk_section.get_volume()
2049        have_root_volume_setup = False
2050        have_full_size_volume = False
2051        if volumes:
2052            for volume in volumes:
2053                if not self.volume_matches_host_architecture(volume):
2054                    continue
2055                # volume setup for a full qualified volume with name and
2056                # mountpoint information. See below for exceptions
2057                name = volume.get_name()
2058                parent = volume.get_parent() or ''
2059                mountpoint = volume.get_mountpoint()
2060                realpath = mountpoint
2061                size = volume.get_size()
2062                freespace = volume.get_freespace()
2063                fullsize = False
2064                label = volume.get_label()
2065                attributes = []
2066                is_root_volume = False
2067
2068                if volume.get_quota():
2069                    attributes.append(f'quota={volume.get_quota()}')
2070
2071                if volume.get_copy_on_write() is False:
2072                    # by default copy-on-write is switched on for any
2073                    # filesystem. Thus only if no copy on write is requested
2074                    # the attribute is handled
2075                    attributes.append('no-copy-on-write')
2076
2077                if volume.get_filesystem_check() is True:
2078                    # by default filesystem check is switched off for any
2079                    # filesystem except the rootfs. Thus only if filesystem
2080                    # check is requested the attribute is handled
2081                    attributes.append('enable-for-filesystem-check')
2082
2083                if '@root' in name:
2084                    # setup root volume, it takes an optional volume
2085                    # name if specified as @root=name and has no specific
2086                    # mountpoint. The default name is set to
2087                    # defaults.ROOT_VOLUME_NAME if no other root volume
2088                    # name is provided
2089                    mountpoint = None
2090                    realpath = '/'
2091                    is_root_volume = True
2092                    root_volume_expression = re.match(
2093                        r'@root=(.+)', name
2094                    )
2095                    if root_volume_expression:
2096                        name = root_volume_expression.group(1)
2097                    else:
2098                        name = defaults.ROOT_VOLUME_NAME
2099                    have_root_volume_setup = True
2100                elif not mountpoint:
2101                    # setup volume without mountpoint. In this case the name
2102                    # attribute is used as mountpoint path and a name for the
2103                    # volume is created from that path information
2104                    mountpoint = name
2105                    realpath = mountpoint
2106                    name = self._to_volume_name(name)
2107
2108                if size:
2109                    size = 'size:' + format(
2110                        self._to_mega_byte(size)
2111                    )
2112                elif freespace:
2113                    size = 'freespace:' + format(
2114                        self._to_mega_byte(freespace)
2115                    )
2116                else:
2117                    size = 'freespace:' + format(
2118                        Defaults.get_min_volume_mbytes(selected_filesystem)
2119                    )
2120
2121                if ':all' in size:
2122                    size = None
2123                    fullsize = True
2124                    have_full_size_volume = True
2125
2126                volume_type_list.append(
2127                    volume_type(
2128                        name=name,
2129                        parent=parent,
2130                        size=size,
2131                        fullsize=fullsize,
2132                        mountpoint=mountpoint,
2133                        realpath=realpath,
2134                        label=label,
2135                        attributes=attributes,
2136                        is_root_volume=is_root_volume
2137                    )
2138                )
2139
2140        if not have_root_volume_setup:
2141            # There must always be a root volume setup. It will be the
2142            # full size volume if no other volume has this setup
2143            volume_management = self.get_volume_management()
2144            root_volume_name = \
2145                defaults.ROOT_VOLUME_NAME if volume_management == 'lvm' else ''
2146            if have_full_size_volume:
2147                size = 'freespace:' + format(
2148                    Defaults.get_min_volume_mbytes(selected_filesystem)
2149                )
2150                fullsize = False
2151            else:
2152                size = None
2153                fullsize = True
2154            volume_type_list.append(
2155                volume_type(
2156                    name=root_volume_name,
2157                    parent='',
2158                    size=size,
2159                    fullsize=fullsize,
2160                    mountpoint=None,
2161                    realpath='/',
2162                    label=None,
2163                    attributes=[],
2164                    is_root_volume=True
2165                )
2166            )
2167
2168        if swap_mbytes and self.get_volume_management() == 'lvm':
2169            volume_type_list.append(
2170                volume_type(
2171                    name=swap_name,
2172                    parent='',
2173                    size='size:{0}'.format(swap_mbytes),
2174                    fullsize=False,
2175                    mountpoint=None,
2176                    realpath='swap',
2177                    label='SWAP',
2178                    attributes=[],
2179                    is_root_volume=False
2180                )
2181            )
2182
2183        return volume_type_list
2184
2185    def get_volume_management(self) -> Optional[str]:
2186        """
2187        Provides information which volume management system is used
2188
2189        :return: name of volume manager
2190
2191        :rtype: str
2192        """
2193        volume_filesystems = ['btrfs']
2194        selected_filesystem = self.build_type.get_filesystem()
2195        selected_system_disk = self.get_build_type_system_disk_section()
2196        volume_management = None
2197        if selected_system_disk and selected_system_disk.get_preferlvm():
2198            # LVM volume management is preferred, use it
2199            volume_management = 'lvm'
2200        elif selected_filesystem in volume_filesystems and selected_system_disk:
2201            # specified filesystem has its own volume management system
2202            volume_management = selected_filesystem
2203        elif selected_system_disk:
2204            # systemdisk section is specified with non volume capable
2205            # filesystem and no volume management preference. So let's
2206            # use LVM by default
2207            volume_management = 'lvm'
2208        return volume_management
2209
2210    def get_drivers_list(self) -> List:
2211        """
2212        List of driver names from all drivers sections matching
2213        configured profiles
2214
2215        :return: driver names
2216
2217        :rtype: list
2218        """
2219        drivers_sections = self._profiled(
2220            self.xml_data.get_drivers()
2221        )
2222        result = []
2223        if drivers_sections:
2224            for driver in drivers_sections:
2225                for file_section in driver.get_file():
2226                    result.append(file_section.get_name())
2227        return result
2228
2229    def get_strip_list(self, section_type: str) -> List:
2230        """
2231        List of strip names matching the given section type
2232        and profiles
2233
2234        :param str section_type: type name from packages section
2235
2236        :return: strip names
2237
2238        :rtype: list
2239        """
2240        strip_sections = self._profiled(
2241            self.xml_data.get_strip()
2242        )
2243        result = []
2244        if strip_sections:
2245            for strip in strip_sections:
2246                if strip.get_type() == section_type:
2247                    for file_section in strip.get_file():
2248                        result.append(file_section.get_name())
2249        return result
2250
2251    def get_strip_files_to_delete(self) -> List:
2252        """
2253        Items to delete from strip section
2254
2255        :return: item names
2256
2257        :rtype: list
2258        """
2259        return self.get_strip_list('delete')
2260
2261    def get_strip_tools_to_keep(self) -> List:
2262        """
2263        Tools to keep from strip section
2264
2265        :return: tool names
2266
2267        :rtype: list
2268        """
2269        return self.get_strip_list('tools')
2270
2271    def get_strip_libraries_to_keep(self) -> List:
2272        """
2273        Libraries to keep from strip section
2274
2275        :return: librarie names
2276
2277        :rtype: list
2278        """
2279        return self.get_strip_list('libs')
2280
2281    def get_include_section_reference_file_names(self) -> List[str]:
2282        """
2283        List of all <include> section file name references
2284
2285        :return: List[str]
2286
2287        :rtype: list
2288        """
2289        include_files = []
2290        for include in self.xml_data.get_include():
2291            include_files.append(include.get_from())
2292        return include_files
2293
2294    def get_repository_sections(self) -> List:
2295        """
2296        List of all repository sections for the selected profiles that
2297        matches the host architecture
2298
2299        :return: <repository> section reference(s)
2300
2301        :rtype: list
2302        """
2303        repository_list = []
2304        for repository in self._profiled(self.xml_data.get_repository()):
2305            if self.repository_matches_host_architecture(repository):
2306                repository_list.append(repository)
2307        return repository_list
2308
2309    def get_containers_sections(self) -> List:
2310        """
2311        List of all containers sections for the selected profiles that
2312        matches the host architecture
2313
2314        :return: <containers> section reference(s)
2315
2316        :rtype: list
2317        """
2318        containers_list = []
2319        for containers in self._profiled(self.xml_data.get_containers()):
2320            if self.containers_matches_host_architecture(containers):
2321                containers_list.append(containers)
2322        return containers_list
2323
2324    def get_repository_sections_used_for_build(self) -> List:
2325        """
2326        List of all repositorys sections used to build the image and
2327        matching configured profiles.
2328
2329        :return: <repository> section reference(s)
2330
2331        :rtype: list
2332        """
2333        repos = self.get_repository_sections()
2334        return list(
2335            repo for repo in repos if not repo.get_imageonly()
2336        )
2337
2338    def get_repository_sections_used_in_image(self) -> List:
2339        """
2340        List of all repositorys sections to be configured in the resulting
2341        image matching configured profiles.
2342
2343        :return: <repository> section reference(s)
2344
2345        :rtype: list
2346        """
2347        repos = self.get_repository_sections()
2348        return list(
2349            repo for repo in repos
2350            if repo.get_imageinclude() or repo.get_imageonly()
2351        )
2352
2353    def delete_repository_sections(self) -> None:
2354        """
2355        Delete all repository sections matching configured profiles
2356        """
2357        self.xml_data.set_repository([])
2358
2359    def delete_repository_sections_used_for_build(self) -> None:
2360        """
2361        Delete all repository sections used to build the image matching
2362        configured profiles
2363        """
2364        used_for_build = self.get_repository_sections_used_for_build()
2365        all_repos = self.get_repository_sections()
2366        self.xml_data.set_repository(
2367            [
2368                repo for repo in all_repos if repo not in used_for_build
2369            ]
2370        )
2371
2372    def get_repositories_signing_keys(self) -> List[str]:
2373        """
2374        Get list of signing keys specified on the repositories
2375        """
2376        key_file_list: List[str] = []
2377        release_version = self.get_release_version()
2378        release_vars = [
2379            '$releasever',
2380            '${releasever}'
2381        ]
2382        for repository in self.get_repository_sections() or []:
2383            for signing in repository.get_source().get_signing() or []:
2384                normalized_key_url = Uri(signing.get_key()).translate()
2385                if release_version:
2386                    for release_var in release_vars:
2387                        if release_var in normalized_key_url:
2388                            normalized_key_url = normalized_key_url.replace(
2389                                release_var, release_version
2390                            )
2391                if normalized_key_url not in key_file_list:
2392                    key_file_list.append(normalized_key_url)
2393        return key_file_list
2394
2395    def set_repository(
2396        self, repo_source: str, repo_type: str, repo_alias: str,
2397        repo_prio: str, repo_imageinclude: bool = False,
2398        repo_package_gpgcheck: Optional[bool] = None,
2399        repo_signing_keys: List[str] = [], components: str = None,
2400        distribution: str = None, repo_gpgcheck: Optional[bool] = None,
2401        repo_sourcetype: str = None
2402    ) -> None:
2403        """
2404        Overwrite repository data of the first repository
2405
2406        :param str repo_source: repository URI
2407        :param str repo_type: type name defined by schema
2408        :param str repo_alias: alias name
2409        :param str repo_prio: priority number, package manager specific
2410        :param bool repo_imageinclude: setup repository inside of the image
2411        :param bool repo_package_gpgcheck: enable/disable package gpg checks
2412        :param list repo_signing_keys: list of signing key file names
2413        :param str components: component names for debian repos
2414        :param str distribution: base distribution name for debian repos
2415        :param bool repo_gpgcheck: enable/disable repo gpg checks
2416        """
2417        repository_sections = self.get_repository_sections()
2418        if repository_sections:
2419            repository = repository_sections[0]
2420            if repo_alias:
2421                repository.set_alias(repo_alias)
2422            if repo_type:
2423                repository.set_type(repo_type)
2424            if repo_source:
2425                repository.get_source().set_path(repo_source)
2426            if repo_prio:
2427                repository.set_priority(int(repo_prio))
2428            if repo_imageinclude:
2429                repository.set_imageinclude(repo_imageinclude)
2430            if repo_package_gpgcheck is not None:
2431                repository.set_package_gpgcheck(repo_package_gpgcheck)
2432            if repo_signing_keys:
2433                repository.get_source().set_signing(
2434                    [xml_parse.signing(key=k) for k in repo_signing_keys]
2435                )
2436            if components:
2437                repository.set_components(components)
2438            if distribution:
2439                repository.set_distribution(distribution)
2440            if repo_gpgcheck is not None:
2441                repository.set_repository_gpgcheck(repo_gpgcheck)
2442            if repo_sourcetype:
2443                repository.set_sourcetype(repo_sourcetype)
2444
2445    def add_repository(
2446        self, repo_source: str, repo_type: str, repo_alias: str = None,
2447        repo_prio: str = '', repo_imageinclude: bool = False,
2448        repo_package_gpgcheck: Optional[bool] = None,
2449        repo_signing_keys: List[str] = [], components: str = None,
2450        distribution: str = None, repo_gpgcheck: Optional[bool] = None,
2451        repo_sourcetype: str = None
2452    ) -> None:
2453        """
2454        Add a new repository section at the end of the list
2455
2456        :param str repo_source: repository URI
2457        :param str repo_type: type name defined by schema
2458        :param str repo_alias: alias name
2459        :param str repo_prio: priority number, package manager specific
2460        :param bool repo_imageinclude: setup repository inside of the image
2461        :param bool repo_package_gpgcheck: enable/disable package gpg checks
2462        :param list repo_signing_keys: list of signing key file names
2463        :param str components: component names for debian repos
2464        :param str distribution: base distribution name for debian repos
2465        :param bool repo_gpgcheck: enable/disable repo gpg checks
2466        """
2467        priority_number: Optional[int] = None
2468        try:
2469            priority_number = int(repo_prio)
2470        except Exception:
2471            pass
2472
2473        self.xml_data.add_repository(
2474            xml_parse.repository(
2475                type_=repo_type,
2476                alias=repo_alias,
2477                priority=priority_number,
2478                source=xml_parse.source(
2479                    path=repo_source,
2480                    signing=[
2481                        xml_parse.signing(key=k) for k in repo_signing_keys
2482                    ]
2483                ),
2484                imageinclude=repo_imageinclude,
2485                package_gpgcheck=repo_package_gpgcheck,
2486                repository_gpgcheck=repo_gpgcheck,
2487                components=components,
2488                distribution=distribution,
2489                sourcetype=repo_sourcetype
2490            )
2491        )
2492
2493    def add_certificate(self, cert_file: str, target_distribution: str) -> None:
2494        """
2495        Add <certificate name="cert_file"> to main <certificates> section
2496        The main section will be created if it does not exist. Also
2497        setup the target_distribution in the resulting main section.
2498        """
2499        certificates_section = self._profiled(
2500            self.xml_data.get_certificates()
2501        )
2502        if not certificates_section:
2503            self.xml_data.set_certificates(
2504                [
2505                    xml_parse.certificates(
2506                        target_distribution=target_distribution,
2507                        certificate=[xml_parse.certificate(name=cert_file)]
2508                    )
2509                ]
2510            )
2511        else:
2512            certificates_section[0].set_target_distribution(
2513                target_distribution
2514            )
2515            certificates_section[0].add_certificate(
2516                xml_parse.certificate(
2517                    name=cert_file
2518                )
2519            )
2520
2521    def get_certificates(self) -> List[str]:
2522        """
2523        Read list of certificates
2524        """
2525        cert_list = []
2526        certificates_section = self._profiled(
2527            self.xml_data.get_certificates()
2528        )
2529        if certificates_section:
2530            for certificate in certificates_section[0].get_certificate():
2531                cert_list.append(certificate.get_name())
2532        return sorted(list(set(cert_list)))
2533
2534    def get_certificates_target_distribution(self) -> str:
2535        """
2536        Read CA target distribution
2537        """
2538        target_distribution = ''
2539        certificates_section = self._profiled(
2540            self.xml_data.get_certificates()
2541        )
2542        if certificates_section:
2543            target_distribution = \
2544                certificates_section[0].get_target_distribution()
2545        return target_distribution
2546
2547    def resolve_this_path(self) -> None:
2548        """
2549        Resolve any this:// repo source path into the path
2550        representing the target inside of the image description
2551        directory
2552        """
2553        for repository in self.get_repository_sections() or []:
2554            repo_source = repository.get_source()
2555            repo_path = repo_source.get_path()
2556            if repo_path.startswith('this://'):
2557                repo_path = repo_path.replace('this://', '')
2558                repo_source.set_path(
2559                    'dir://{0}'.format(
2560                        os.path.realpath(
2561                            os.path.join(
2562                                self.xml_data.description_dir, repo_path
2563                            )
2564                        )
2565                    )
2566                )
2567
2568    def copy_displayname(self, target_state: Any) -> None:
2569        """
2570        Copy image displayname from this xml state to the target xml state
2571
2572        :param object target_state: XMLState instance
2573        """
2574        displayname = self.xml_data.get_displayname()
2575        if displayname:
2576            target_state.xml_data.set_displayname(displayname)
2577
2578    def copy_name(self, target_state: Any) -> None:
2579        """
2580        Copy image name from this xml state to the target xml state
2581
2582        :param object target_state: XMLState instance
2583        """
2584        target_state.xml_data.set_name(
2585            self.xml_data.get_name()
2586        )
2587
2588    def copy_drivers_sections(self, target_state: Any) -> None:
2589        """
2590        Copy drivers sections from this xml state to the target xml state
2591
2592        :param object target_state: XMLState instance
2593        """
2594        drivers_sections = self._profiled(
2595            self.xml_data.get_drivers()
2596        )
2597        if drivers_sections:
2598            for drivers_section in drivers_sections:
2599                target_state.xml_data.add_drivers(drivers_section)
2600
2601    def copy_systemdisk_section(self, target_state: Any) -> None:
2602        """
2603        Copy systemdisk sections from this xml state to the target xml state
2604
2605        :param object target_state: XMLState instance
2606        """
2607        systemdisk_section = self.get_build_type_system_disk_section()
2608        if systemdisk_section:
2609            target_state.build_type.set_systemdisk(
2610                [systemdisk_section]
2611            )
2612
2613    def copy_strip_sections(self, target_state: Any) -> None:
2614        """
2615        Copy strip sections from this xml state to the target xml state
2616
2617        :param object target_state: XMLState instance
2618        """
2619        strip_sections = self._profiled(
2620            self.xml_data.get_strip()
2621        )
2622        if strip_sections:
2623            for strip_section in strip_sections:
2624                target_state.xml_data.add_strip(strip_section)
2625
2626    def copy_machine_section(self, target_state: Any) -> None:
2627        """
2628        Copy machine sections from this xml state to the target xml state
2629
2630        :param object target_state: XMLState instance
2631        """
2632        machine_section = self.get_build_type_machine_section()
2633        if machine_section:
2634            target_state.build_type.set_machine(
2635                [machine_section]
2636            )
2637
2638    def copy_bootloader_section(self, target_state: Any) -> None:
2639        """
2640        Copy bootloader section from this xml state to the target xml state
2641
2642        :param object target_state: XMLState instance
2643        """
2644        bootloader_section = self.get_build_type_bootloader_section()
2645        if bootloader_section:
2646            target_state.build_type.set_bootloader(
2647                [bootloader_section]
2648            )
2649
2650    def copy_oemconfig_section(self, target_state: Any) -> None:
2651        """
2652        Copy oemconfig sections from this xml state to the target xml state
2653
2654        :param object target_state: XMLState instance
2655        """
2656        oemconfig_section = self.get_build_type_oemconfig_section()
2657        if oemconfig_section:
2658            target_state.build_type.set_oemconfig(
2659                [oemconfig_section]
2660            )
2661
2662    def copy_repository_sections(
2663        self, target_state: Any, wipe: bool = False
2664    ) -> None:
2665        """
2666        Copy repository sections from this xml state to the target xml state
2667
2668        :param object target_state: XMLState instance
2669        :param bool wipe: delete all repos in target prior to copy
2670        """
2671        repository_sections = self._profiled(
2672            self.xml_data.get_repository()
2673        )
2674        if repository_sections:
2675            if wipe:
2676                target_state.xml_data.set_repository([])
2677            for repository_section in repository_sections:
2678                repository_copy = copy.deepcopy(repository_section)
2679                # profiles are not copied because they might not exist
2680                # in the target description
2681                repository_copy.set_profiles(None)
2682                target_state.xml_data.add_repository(repository_copy)
2683
2684    def copy_preferences_subsections(
2685        self, section_names: List, target_state: Any
2686    ) -> None:
2687        """
2688        Copy subsections of the preferences sections, matching given
2689        section names, from this xml state to the target xml state
2690
2691        :param list section_names: preferences subsection names
2692        :param object target_state: XMLState instance
2693        """
2694        target_preferences_sections = target_state.get_preferences_sections()
2695        if target_preferences_sections:
2696            target_preferences_section = target_preferences_sections[0]
2697            for preferences_section in self.get_preferences_sections():
2698                for section_name in section_names:
2699                    get_section_method = getattr(
2700                        preferences_section, 'get_' + section_name
2701                    )
2702                    section = get_section_method()
2703                    if section:
2704                        set_section_method = getattr(
2705                            target_preferences_section, 'set_' + section_name
2706                        )
2707                        set_section_method(section)
2708
2709    def copy_build_type_attributes(
2710        self, attribute_names: List, target_state: Any
2711    ) -> None:
2712        """
2713        Copy specified attributes from this build type section to the
2714        target xml state build type section
2715
2716        :param list attribute_names: type section attributes
2717        :param object target_state: XMLState instance
2718        """
2719        for attribute in attribute_names:
2720            get_type_method = getattr(
2721                self.build_type, 'get_' + attribute
2722            )
2723            attribute_value = get_type_method()
2724            if attribute_value:
2725                set_type_method = getattr(
2726                    target_state.build_type, 'set_' + attribute
2727                )
2728                set_type_method(attribute_value)
2729
2730    def copy_bootincluded_packages(self, target_state: Any) -> None:
2731        """
2732        Copy packages marked as bootinclude to the packages
2733        type=bootstrap section in the target xml state. The package
2734        will also be removed from the packages type=delete section
2735        in the target xml state if present there
2736
2737        :param object target_state: XMLState instance
2738        """
2739        target_packages_sections = \
2740            target_state.get_bootstrap_packages_sections()
2741        if target_packages_sections:
2742            target_packages_section = \
2743                target_packages_sections[0]
2744            package_names_added = []
2745            packages_sections = self.get_packages_sections(
2746                ['image', 'bootstrap', self.get_build_type_name()]
2747            )
2748            package_list = self.get_package_sections(
2749                packages_sections
2750            )
2751            if package_list:
2752                for package in package_list:
2753                    if package.package_section.get_bootinclude():
2754                        target_packages_section.add_package(
2755                            xml_parse.package(
2756                                name=package.package_section.get_name()
2757                            )
2758                        )
2759                        package_names_added.append(
2760                            package.package_section.get_name()
2761                        )
2762            delete_packages_sections = target_state.get_packages_sections(
2763                ['delete']
2764            )
2765            package_list = self.get_package_sections(
2766                delete_packages_sections
2767            )
2768            if package_list:
2769                for package in package_list:
2770                    package_name = package.package_section.get_name()
2771                    if package_name in package_names_added:
2772                        package.packages_section.package.remove(
2773                            package.package_section
2774                        )
2775
2776    def copy_bootincluded_archives(self, target_state: Any) -> None:
2777        """
2778        Copy archives marked as bootinclude to the packages type=bootstrap
2779        section in the target xml state
2780
2781        :param object target_state: XMLState instance
2782        """
2783        target_bootstrap_packages_sections = \
2784            target_state.get_bootstrap_packages_sections()
2785        if target_bootstrap_packages_sections:
2786            target_bootstrap_packages_section = \
2787                target_bootstrap_packages_sections[0]
2788            packages_sections = self.get_packages_sections(
2789                ['image', 'bootstrap', self.get_build_type_name()]
2790            )
2791            for packages_section in packages_sections:
2792                archive_list = packages_section.get_archive()
2793                if archive_list:
2794                    for archive in archive_list:
2795                        if archive.get_bootinclude():
2796                            target_bootstrap_packages_section.add_archive(
2797                                xml_parse.archive(
2798                                    name=archive.get_name()
2799                                )
2800                            )
2801
2802    def copy_bootdelete_packages(self, target_state: Any) -> None:
2803        """
2804        Copy packages marked as bootdelete to the packages type=delete
2805        section in the target xml state
2806
2807        :param object target_state: XMLState instance
2808        """
2809        target_delete_packages_sections = target_state.get_packages_sections(
2810            ['delete']
2811        )
2812        if not target_delete_packages_sections:
2813            target_delete_packages_sections = [
2814                xml_parse.packages(type_='delete')
2815            ]
2816            target_state.xml_data.add_packages(
2817                target_delete_packages_sections[0]
2818            )
2819
2820        target_delete_packages_section = \
2821            target_delete_packages_sections[0]
2822        packages_sections = self.get_packages_sections(
2823            ['image', 'bootstrap', self.get_build_type_name()]
2824        )
2825        package_list = self.get_package_sections(
2826            packages_sections
2827        )
2828        if package_list:
2829            for package in package_list:
2830                if package.package_section.get_bootdelete():
2831                    target_delete_packages_section.add_package(
2832                        xml_parse.package(
2833                            name=package.package_section.get_name()
2834                        )
2835                    )
2836
2837    def get_distribution_name_from_boot_attribute(self) -> str:
2838        """
2839        Extract the distribution name from the boot attribute of the
2840        build type section.
2841
2842        If no boot attribute is configured or the contents does not
2843        match the kiwi defined naming schema for boot image descriptions,
2844        an exception is thrown
2845
2846        :return: lowercase distribution name
2847
2848        :rtype: str
2849        """
2850        boot_attribute = self.build_type.get_boot()
2851        if not boot_attribute:
2852            raise KiwiDistributionNameError(
2853                'No boot attribute to extract distribution name from found'
2854            )
2855        boot_attribute_format = '^.*-(.*)$'
2856        boot_attribute_expression = re.match(
2857            boot_attribute_format, boot_attribute
2858        )
2859        if not boot_attribute_expression:
2860            raise KiwiDistributionNameError(
2861                'Boot attribute "%s" does not match expected format %s' %
2862                (boot_attribute, boot_attribute_format)
2863            )
2864        return boot_attribute_expression.group(1).lower()
2865
2866    def get_fs_mount_option_list(self) -> List:
2867        """
2868        List of root filesystem mount options
2869
2870        The list contains one element with the information from the
2871        fsmountoptions attribute. The value there is passed along to
2872        the -o mount option
2873
2874        :return: max one element list with mount option string
2875
2876        :rtype: list
2877        """
2878        option_list = []
2879        mount_options = self.build_type.get_fsmountoptions()
2880        if mount_options:
2881            option_list = [mount_options]
2882
2883        return option_list
2884
2885    def get_fs_create_option_list(self) -> List:
2886        """
2887        List of root filesystem creation options
2888
2889        The list contains elements with the information from the
2890        fscreateoptions attribute string that got split into its
2891        substring components
2892
2893        :return: list with create options
2894
2895        :rtype: list
2896        """
2897        option_list = []
2898        create_options = self.build_type.get_fscreateoptions()
2899        if create_options:
2900            option_list = create_options.split()
2901
2902        return option_list
2903
2904    def get_luks_credentials(self) -> Optional[str]:
2905        """
2906        Return key or passphrase credentials to open the luks pool
2907
2908        :return: data
2909
2910        :rtype: str
2911        """
2912        data = self.build_type.get_luks()
2913        if data:
2914            keyfile_name = None
2915            try:
2916                # try to interpret data as an URI
2917                uri = Uri(data)
2918                if not uri.is_remote():
2919                    keyfile_name = uri.translate()
2920            except Exception:
2921                # this doesn't look like a valid URI, continue as just data
2922                pass
2923            if keyfile_name:
2924                try:
2925                    with open(keyfile_name) as keyfile:
2926                        return keyfile.read()
2927                except Exception as issue:
2928                    raise KiwiFileAccessError(
2929                        f'Failed to read from {keyfile_name!r}: {issue}'
2930                    )
2931        return data
2932
2933    def get_luks_format_options(self) -> List[str]:
2934        """
2935        Return list of luks format options
2936
2937        :return: list of options
2938
2939        :rtype: list
2940        """
2941        result = []
2942        luksversion = self.build_type.get_luks_version()
2943        luksformat = self.build_type.get_luksformat()
2944        luks_pbkdf = self.build_type.get_luks_pbkdf()
2945        if luksversion:
2946            result.append('--type')
2947            result.append(luksversion)
2948        if luksformat:
2949            for option in luksformat[0].get_option():
2950                result.append(option.get_name())
2951                if option.get_value():
2952                    result.append(option.get_value())
2953        if luks_pbkdf:
2954            # Allow to override the pbkdf algorithm that cryptsetup
2955            # uses by default. Cryptsetup may use argon2i by default,
2956            # which is not supported by all bootloaders.
2957            result.append('--pbkdf')
2958            result.append(luks_pbkdf)
2959        return result
2960
2961    def get_derived_from_image_uri(self) -> List[Uri]:
2962        """
2963        Uri object(s) of derived image if configured
2964
2965        Specific image types can be based on one ore more derived
2966        images. This method returns the location of this image(s)
2967        when configured in the XML description
2968
2969        :return: List of Uri instances
2970
2971        :rtype: list
2972        """
2973        image_uris = []
2974        derived_images = self.build_type.get_derived_from()
2975        if derived_images:
2976            for derived_image in derived_images.split(','):
2977                image_uris.append(
2978                    Uri(derived_image, repo_type='container')
2979                )
2980        return image_uris
2981
2982    def set_derived_from_image_uri(self, uri: str) -> None:
2983        """
2984        Set derived_from attribute to a new value
2985
2986        In order to set a new value the derived_from attribute
2987        must be already present in the image configuration
2988
2989        :param str uri: URI
2990        """
2991        if self.build_type.get_derived_from():
2992            self.build_type.set_derived_from(uri)
2993        else:
2994            message = dedent('''\n
2995                No derived_from attribute configured in image <type>
2996
2997                In order to set the uri {0} as base container reference
2998                an initial derived_from attribute must be set in the
2999                type section
3000            ''')
3001            log.warning(message.format(uri))
3002
3003    def set_root_partition_uuid(self, uuid: str) -> None:
3004        """
3005        Store PARTUUID provided in uuid as state information
3006
3007        :param str uuid: PARTUUID
3008        """
3009        self.root_partition_uuid = uuid
3010
3011    def get_root_partition_uuid(self) -> Optional[str]:
3012        """
3013        Return preserved PARTUUID
3014        """
3015        return self.root_partition_uuid
3016
3017    def set_root_filesystem_uuid(self, uuid: str) -> None:
3018        """
3019        Store UUID provided in uuid as state information
3020
3021        :param str uuid: UUID
3022        """
3023        self.root_filesystem_uuid = uuid
3024
3025    def get_root_filesystem_uuid(self) -> Optional[str]:
3026        """
3027        Return preserved UUID
3028        """
3029        return self.root_filesystem_uuid
3030
3031    @staticmethod
3032    def get_archives_target_dirs(
3033        packages_sections_names: Optional[List[xml_parse.packages]]
3034    ) -> Dict:
3035        """
3036        Dict of archive names and target dirs for packages section(s), if any
3037        :return: archive names and its target dir
3038        :rtype: dict
3039        """
3040        result = {}
3041        if packages_sections_names:
3042            for package_section_name in packages_sections_names:
3043                for archive in package_section_name.get_archive():
3044                    result[archive.get_name().strip()] = archive.get_target_dir()
3045
3046        return result
3047
3048    def get_bootstrap_archives_target_dirs(self) -> Dict:
3049        """
3050        Dict of archive names and target dirs from the type="bootstrap"
3051        packages section(s)
3052        :return: archive names and its target dir
3053        :rtype: dict
3054        """
3055        return self.get_archives_target_dirs(
3056            self.get_packages_sections(['bootstrap'])
3057        )
3058
3059    def get_system_archives_target_dirs(self) -> Dict:
3060        """
3061        Dict of archive names and its target dir from the packages sections matching
3062        type="image" and type=build_type
3063        :return: archive names and its target dir
3064        :rtype: dict
3065        """
3066        return self.get_archives_target_dirs(
3067            self.get_packages_sections(['image', self.get_build_type_name()])
3068        )
3069
3070    def _used_profiles(self, profiles=None):
3071        """
3072        return list of profiles to use. The method looks up the
3073        profiles section in the XML description and searches for
3074        profiles matching the architecture. If no arch specifier
3075        is set the profile is considered to be valid for any arch
3076
3077        If the profiles argument is not set only profiles
3078        marked with the attribute import=true will be selected.
3079        Profiles specified in the argument will take the highest
3080        priority and causes to skip the lookup of import profiles
3081        in the XML description
3082
3083        :param list profiles: selected profile names
3084        """
3085        available_profiles = dict()
3086        import_profiles = []
3087        for profiles_section in self.xml_data.get_profiles():
3088            for profile in profiles_section.get_profile():
3089                if self.profile_matches_host_architecture(profile):
3090                    name = profile.get_name()
3091                    available_profiles[name] = profile
3092                    if profile.get_import():
3093                        import_profiles.append(name)
3094
3095        if not profiles:
3096            return import_profiles
3097        else:
3098            resolved_profiles = []
3099            for profile in profiles:
3100                resolved_profiles += self._solve_profile_dependencies(
3101                    profile, available_profiles, resolved_profiles
3102                )
3103            return resolved_profiles
3104
3105    def _section_matches_host_architecture(self, section):
3106        architectures = section.get_arch()
3107        if architectures:
3108            if self.host_architecture not in architectures.split(','):
3109                return False
3110        return True
3111
3112    def _match_docker_base_data(self):
3113        container_config_section = self.get_build_type_containerconfig_section()
3114        container_base = {}
3115        if container_config_section:
3116            name = container_config_section.get_name()
3117            tag = container_config_section.get_tag()
3118            maintainer = container_config_section.get_maintainer()
3119            user = container_config_section.get_user()
3120            workingdir = container_config_section.get_workingdir()
3121            additional_names = container_config_section.get_additionalnames()
3122            if name:
3123                container_base['container_name'] = name
3124
3125            if tag:
3126                container_base['container_tag'] = tag
3127
3128            if additional_names:
3129                container_base['additional_names'] = additional_names.split(',')
3130
3131            if maintainer:
3132                container_base['maintainer'] = maintainer
3133
3134            if user:
3135                container_base['user'] = user
3136
3137            if workingdir:
3138                container_base['workingdir'] = workingdir
3139
3140        return container_base
3141
3142    def _match_docker_entrypoint(self):
3143        container_config_section = self.get_build_type_containerconfig_section()
3144        container_entry = {}
3145        if container_config_section:
3146            entrypoint = container_config_section.get_entrypoint()
3147            if entrypoint and entrypoint[0].get_execute():
3148                container_entry['entry_command'] = [
3149                    entrypoint[0].get_execute()
3150                ]
3151                argument_list = entrypoint[0].get_argument()
3152                if argument_list:
3153                    for argument in argument_list:
3154                        container_entry['entry_command'].append(
3155                            argument.get_name()
3156                        )
3157            elif entrypoint and entrypoint[0].get_clear():
3158                container_entry['entry_command'] = []
3159        return container_entry
3160
3161    def _match_docker_subcommand(self):
3162        container_config_section = self.get_build_type_containerconfig_section()
3163        container_subcommand = {}
3164        if container_config_section:
3165            subcommand = container_config_section.get_subcommand()
3166            if subcommand and subcommand[0].get_execute():
3167                container_subcommand['entry_subcommand'] = [
3168                    subcommand[0].get_execute()
3169                ]
3170                argument_list = subcommand[0].get_argument()
3171                if argument_list:
3172                    for argument in argument_list:
3173                        container_subcommand['entry_subcommand'].append(
3174                            argument.get_name()
3175                        )
3176            elif subcommand and subcommand[0].get_clear():
3177                container_subcommand['entry_subcommand'] = []
3178        return container_subcommand
3179
3180    def _match_docker_expose_ports(self):
3181        container_config_section = self.get_build_type_containerconfig_section()
3182        container_expose = {}
3183        if container_config_section:
3184            expose = container_config_section.get_expose()
3185            if expose and expose[0].get_port():
3186                container_expose['expose_ports'] = []
3187                for port in expose[0].get_port():
3188                    container_expose['expose_ports'].append(
3189                        format(port.get_number())
3190                    )
3191        return container_expose
3192
3193    def _match_docker_volumes(self):
3194        container_config_section = self.get_build_type_containerconfig_section()
3195        container_volumes = {}
3196        if container_config_section:
3197            volumes = container_config_section.get_volumes()
3198            if volumes and volumes[0].get_volume():
3199                container_volumes['volumes'] = []
3200                for volume in volumes[0].get_volume():
3201                    container_volumes['volumes'].append(volume.get_name())
3202        return container_volumes
3203
3204    def _match_docker_stopsignal(self) -> dict:
3205        container_config_section = self.get_build_type_containerconfig_section()
3206        container_stopsignal = {}
3207        if container_config_section:
3208            stopsignal_section = container_config_section.get_stopsignal()
3209            if stopsignal_section:
3210                container_stopsignal['stopsignal'] = stopsignal_section[0]
3211        return container_stopsignal
3212
3213    def _match_docker_environment(self):
3214        container_config_section = self.get_build_type_containerconfig_section()
3215        container_env = {}
3216        if container_config_section:
3217            environment = container_config_section.get_environment()
3218            if environment and environment[0].get_env():
3219                container_env['environment'] = {}
3220                for env in environment[0].get_env():
3221                    container_env['environment'][env.get_name()] = \
3222                        env.get_value()
3223        return container_env
3224
3225    def _match_docker_labels(self):
3226        container_config_section = self.get_build_type_containerconfig_section()
3227        container_labels = {}
3228        if container_config_section:
3229            labels = container_config_section.get_labels()
3230            if labels and labels[0].get_label():
3231                container_labels['labels'] = {}
3232                for label in labels[0].get_label():
3233                    container_labels['labels'][label.get_name()] = \
3234                        label.get_value()
3235        return container_labels
3236
3237    def _match_docker_history(self):
3238        container_config_section = self.get_build_type_containerconfig_section()
3239        container_history = {}
3240        if container_config_section:
3241            history = container_config_section.get_history()
3242            if history:
3243                container_history['history'] = {}
3244                if history[0].get_created_by():
3245                    container_history['history']['created_by'] = \
3246                        history[0].get_created_by()
3247                if history[0].get_author():
3248                    container_history['history']['author'] = \
3249                        history[0].get_author()
3250                if history[0].get_launcher():
3251                    container_history['history']['launcher'] = \
3252                        history[0].get_launcher()
3253                if history[0].get_application_id():
3254                    container_history['history']['application_id'] = \
3255                        history[0].get_application_id()
3256                if history[0].get_package_version():
3257                    container_history['history']['package_version'] = \
3258                        history[0].get_package_version()
3259                container_history['history']['comment'] = \
3260                    history[0].get_valueOf_()
3261        return container_history
3262
3263    def _solve_profile_dependencies(
3264        self, profile, available_profiles, current_profiles
3265    ):
3266        if profile not in available_profiles:
3267            raise KiwiProfileNotFound(
3268                'profile {0} not found for host arch {1}'.format(
3269                    profile, self.host_architecture
3270                )
3271            )
3272        profiles_to_add = []
3273        if profile not in current_profiles:
3274            profiles_to_add.append(profile)
3275            for required in available_profiles[profile].get_requires():
3276                if self.requires_matches_host_architecture(required):
3277                    if required.get_profile() not in current_profiles:
3278                        profiles_to_add += self._solve_profile_dependencies(
3279                            required.get_profile(), available_profiles,
3280                            current_profiles + profiles_to_add
3281                        )
3282        return profiles_to_add
3283
3284    def _build_type_section(self, build_type=None):
3285        """
3286        find type section matching build type and profiles or default
3287        """
3288        # lookup all preferences sections for selected profiles
3289        image_type_sections = []
3290        for preferences in self.get_preferences_sections():
3291            image_type_sections += preferences.get_type()
3292
3293        # lookup if build type matches provided type
3294        if build_type:
3295            for image_type in image_type_sections:
3296                if build_type == image_type.get_image():
3297                    return image_type
3298            raise KiwiTypeNotFound(
3299                'Build type {0!r} not found for applied profiles: {1!r}'.format(
3300                    build_type, self.profiles
3301                )
3302            )
3303
3304        # lookup if build type matches primary type
3305        for image_type in image_type_sections:
3306            if image_type.get_primary():
3307                return image_type
3308
3309        # build type is first type section in XML sequence
3310        if image_type_sections:
3311            return image_type_sections[0]
3312        raise KiwiTypeNotFound(
3313            'No build type defined with applied profiles: {0!r}'.format(
3314                self.profiles
3315            )
3316        )
3317
3318    def _profiled(self, xml_abstract):
3319        """
3320        return only those sections matching the instance stored
3321        profile list from the given XML abstract. Sections without
3322        a profile are wildcard sections and will be used in any
3323        case
3324        """
3325        result = []
3326        for section in xml_abstract:
3327            profiles = section.get_profiles()
3328            if profiles:
3329                for profile in profiles.split(','):
3330                    if self.profiles and profile in self.profiles:
3331                        result.append(section)
3332                        break
3333            else:
3334                result.append(section)
3335        return result
3336
3337    def _to_volume_name(self, name):
3338        name = name.strip()
3339        name = re.sub(r'^\/+', r'', name)
3340        name = name.replace('/', '_')
3341        return name
3342
3343    def _to_mega_byte(self, size):
3344        value = re.search(r'(\d+)([MG]*)', format(size))
3345        if value:
3346            number = value.group(1)
3347            unit = value.group(2)
3348            if unit == 'G':
3349                return int(number) * 1024
3350            else:
3351                return int(number)
3352        else:
3353            return size

Implements methods to get stateful information from the XML data

Parameters
  • object xml_data: parse result from XMLDescription.load()
  • list profiles: list of used profiles
  • object build_type: build section reference
XMLState(xml_data: Any, profiles: List = None, build_type: Any = None)
113    def __init__(
114        self, xml_data: Any, profiles: List = None,
115        build_type: Any = None
116    ):
117        self.root_partition_uuid: Optional[str] = None
118        self.root_filesystem_uuid: Optional[str] = None
119        self.host_architecture = defaults.PLATFORM_MACHINE
120        self.xml_data = xml_data
121        self.profiles = self._used_profiles(profiles)
122        self.build_type = self._build_type_section(
123            build_type
124        )
125        self.resolve_this_path()
root_partition_uuid: Optional[str]
root_filesystem_uuid: Optional[str]
host_architecture
xml_data
profiles
build_type
def get_preferences_sections(self) -> List:
127    def get_preferences_sections(self) -> List:
128        """
129        All preferences sections for the selected profiles that match the
130        host architecture
131
132        :return: list of <preferences> section reference(s)
133
134        :rtype: list
135        """
136        preferences_list = []
137        for preferences in self._profiled(self.xml_data.get_preferences()):
138            if self.preferences_matches_host_architecture(preferences):
139                preferences_list.append(preferences)
140        return preferences_list

All preferences sections for the selected profiles that match the host architecture

Returns

list of section reference(s)

def get_description_section(self) -> description_type:
142    def get_description_section(self) -> description_type:
143        """
144        The description section
145
146        :return:
147            description_type tuple providing the elements
148            author contact and specification
149
150        :rtype: tuple
151        """
152        description = self.xml_data.get_description()[0]
153        return description_type(
154            author=description.get_author()[0],
155            contact=description.get_contact()[0],
156            specification=description.get_specification()[0].strip()
157        )

The description section

Returns
description_type tuple providing the elements
author contact and specification
def get_users_sections(self) -> List:
159    def get_users_sections(self) -> List:
160        """
161        All users sections for the selected profiles
162
163        :return: list of <users> section reference(s)
164
165        :rtype: list
166        """
167        users = []
168        for users_section in self._profiled(self.xml_data.get_users()):
169            if self.users_matches_host_architecture(users_section):
170                users.append(users_section)
171        return users

All users sections for the selected profiles

Returns

list of section reference(s)

def get_build_type_bundle_format(self) -> str:
173    def get_build_type_bundle_format(self) -> str:
174        """
175        Return bundle_format for build type
176
177        The bundle_format string is validated against the available
178        name tags from kiwi.system.result::result_name_tags.
179
180        :return: bundle format string
181
182        :rtype: str
183        """
184        return self.build_type.get_bundle_format()

Return bundle_format for build type

The bundle_format string is validated against the available name tags from kiwi.system.result::result_name_tags.

Returns

bundle format string

def get_build_type_name(self) -> str:
186    def get_build_type_name(self) -> str:
187        """
188        Default build type name
189
190        :return: Content of image attribute from build type
191
192        :rtype: str
193        """
194        return self.build_type.get_image()

Default build type name

Returns

Content of image attribute from build type

def btrfs_default_volume_requested(self) -> bool:
196    def btrfs_default_volume_requested(self) -> bool:
197        """
198        Check if setting a default volume for btrfs is requested
199        """
200        if self.build_type.get_btrfs_set_default_volume() is False:
201            # Setting a default volume is explicitly switched off
202            return False
203        else:
204            # In any other case (True | None) a default volume
205            # is wanted and will be set
206            return True

Check if setting a default volume for btrfs is requested

def get_image_version(self) -> str:
208    def get_image_version(self) -> str:
209        """
210        Image version from preferences section.
211
212        Multiple occurences of version in preferences sections are not
213        forbidden, however only the first version found defines the
214        final image version
215
216        :return: Content of <version> section
217
218        :rtype: str
219        """
220        for preferences in self.get_preferences_sections():
221            version = preferences.get_version()
222            if version:
223                return version[0]
224        return ''

Image version from preferences section.

Multiple occurences of version in preferences sections are not forbidden, however only the first version found defines the final image version

Returns

Content of section

def get_initrd_system(self) -> str:
226    def get_initrd_system(self) -> str:
227        """
228        Name of initrd system to use
229
230        Depending on the image type a specific initrd system is
231        either pre selected or free of choice according to the
232        XML type setup.
233
234        :return: 'dracut', 'kiwi' or 'none'
235
236        :rtype: str
237        """
238        pre_selection_map = {
239            'vmx': 'dracut',
240            'oem': 'dracut',
241            'iso': 'dracut',
242            'kis': 'dracut',
243            'pxe': 'kiwi',
244        }
245        build_type = self.get_build_type_name()
246        default_initrd_system = pre_selection_map.get(build_type) or 'none'
247
248        if build_type == 'iso':
249            # iso type always use dracut as initrd system
250            return default_initrd_system
251
252        # Allow to choose for any other build type
253        return self.build_type.get_initrd_system() or default_initrd_system

Name of initrd system to use

Depending on the image type a specific initrd system is either pre selected or free of choice according to the XML type setup.

Returns

'dracut', 'kiwi' or 'none'

def get_locale(self) -> Optional[List]:
255    def get_locale(self) -> Optional[List]:
256        """
257        Gets list of locale names if configured. Takes
258        the first locale setup from the existing preferences
259        sections into account.
260
261        :return: List of names or None
262
263        :rtype: list|None
264        """
265        for preferences in self.get_preferences_sections():
266            locale_section = preferences.get_locale()
267            if locale_section:
268                return locale_section[0].split(',')
269        return None

Gets list of locale names if configured. Takes the first locale setup from the existing preferences sections into account.

Returns

List of names or None

def get_rpm_locale(self) -> Optional[List]:
271    def get_rpm_locale(self) -> Optional[List]:
272        """
273        Gets list of locale names to filter out by rpm
274        if rpm-locale-filtering is switched on the
275        the list always contains: [POSIX, C, C.UTF-8]
276        and is extended by the optionaly configured
277        locale
278
279        :return: List of names or None
280
281        :rtype: list|None
282        """
283        if self.get_rpm_locale_filtering():
284            rpm_locale = ['POSIX', 'C', 'C.UTF-8']
285            configured_locale = self.get_locale()
286            if configured_locale:
287                for locale in configured_locale:
288                    rpm_locale.append(locale)
289            return rpm_locale
290        return None

Gets list of locale names to filter out by rpm if rpm-locale-filtering is switched on the the list always contains: [POSIX, C, C.UTF-8] and is extended by the optionaly configured locale

Returns

List of names or None

def get_rpm_locale_filtering(self) -> bool:
292    def get_rpm_locale_filtering(self) -> bool:
293        """
294        Gets the rpm-locale-filtering configuration flag. Returns
295        False if not present.
296
297        :return: True or False
298
299        :rtype: bool
300        """
301        for preferences in self.get_preferences_sections():
302            locale_filtering = preferences.get_rpm_locale_filtering()
303            if locale_filtering:
304                return locale_filtering[0]
305        return False

Gets the rpm-locale-filtering configuration flag. Returns False if not present.

Returns

True or False

def get_rpm_excludedocs(self) -> bool:
307    def get_rpm_excludedocs(self) -> bool:
308        """
309        Gets the rpm-excludedocs configuration flag. Returns
310        False if not present.
311
312        :return: True or False
313
314        :rtype: bool
315        """
316        for preferences in self.get_preferences_sections():
317            exclude_docs = preferences.get_rpm_excludedocs()
318            if exclude_docs:
319                return exclude_docs[0]
320        return False

Gets the rpm-excludedocs configuration flag. Returns False if not present.

Returns

True or False

def get_rpm_check_signatures(self) -> bool:
322    def get_rpm_check_signatures(self) -> bool:
323        """
324        Gets the rpm-check-signatures configuration flag. Returns
325        False if not present.
326
327        :return: True or False
328
329        :rtype: bool
330        """
331        for preferences in self.get_preferences_sections():
332            check_signatures = preferences.get_rpm_check_signatures()
333            if check_signatures:
334                return check_signatures[0]
335        return False

Gets the rpm-check-signatures configuration flag. Returns False if not present.

Returns

True or False

def get_package_manager(self) -> str:
337    def get_package_manager(self) -> str:
338        """
339        Get configured package manager from selected preferences section
340
341        :return: Content of the <packagemanager> section
342
343        :rtype: str
344        """
345        for preferences in self.get_preferences_sections():
346            package_manager = preferences.get_packagemanager()
347            if package_manager:
348                return package_manager[0]
349        return Defaults.get_default_package_manager()

Get configured package manager from selected preferences section

Returns

Content of the section

def get_release_version(self) -> str:
351    def get_release_version(self) -> str:
352        """
353        Get configured release version from selected preferences section
354
355        :return: Content of the <release-version> section or ''
356
357        :rtype: str
358        """
359        release_version = ''
360        for preferences in self.get_preferences_sections():
361            release_version = preferences.get_release_version()
362            if release_version:
363                release_version = release_version[0]
364                break
365        return release_version

Get configured release version from selected preferences section

Returns

Content of the section or ''

def get_packages_sections(self, section_types: List) -> List:
367    def get_packages_sections(self, section_types: List) -> List:
368        """
369        List of packages sections matching given section type(s)
370
371        :param list section_types: type name(s) from packages sections
372
373        :return: list of <packages> section reference(s)
374
375        :rtype: list
376        """
377        result = []
378        packages_sections = self._profiled(
379            self.xml_data.get_packages()
380        )
381        for packages in packages_sections:
382            packages_type = packages.get_type()
383            if packages_type in section_types:
384                result.append(packages)
385        return result

List of packages sections matching given section type(s)

Parameters
  • list section_types: type name(s) from packages sections
Returns

list of section reference(s)

def requires_matches_host_architecture(self, requires: Any) -> bool:
387    def requires_matches_host_architecture(self, requires: Any) -> bool:
388        """
389        Tests if the given profile requires section is applicable for
390        the current host architecture. If no architecture is specified
391        within the section it is considered as a match returning True.
392
393        Note: The XML section pointer must provide an arch attribute
394
395        :param section: XML section object
396
397        :return: True or False
398
399        :rtype: bool
400        """
401        return self._section_matches_host_architecture(requires)

Tests if the given profile requires section is applicable for the current host architecture. If no architecture is specified within the section it is considered as a match returning True.

Note: The XML section pointer must provide an arch attribute

Parameters
  • section: XML section object
Returns

True or False

def volume_matches_host_architecture(self, volume: Any) -> bool:
403    def volume_matches_host_architecture(self, volume: Any) -> bool:
404        """
405        Tests if the given volume section is applicable for the current host
406        architecture. If no architecture is specified within the section
407        it is considered as a match returning True.
408
409        Note: The XML section pointer must provide an arch attribute
410
411        :param section: XML section object
412
413        :return: True or False
414
415        :rtype: bool
416        """
417        return self._section_matches_host_architecture(volume)

Tests if the given volume section is applicable for the current host architecture. If no architecture is specified within the section it is considered as a match returning True.

Note: The XML section pointer must provide an arch attribute

Parameters
  • section: XML section object
Returns

True or False

def package_matches_host_architecture(self, package: Any) -> bool:
419    def package_matches_host_architecture(self, package: Any) -> bool:
420        """
421        Tests if the given package section is applicable for the current host
422        architecture. If no architecture is specified within the section
423        it is considered as a match returning True.
424
425        Note: The XML section pointer must provide an arch attribute
426
427        :param section: XML section object
428
429        :return: True or False
430
431        :rtype: bool
432        """
433        return self._section_matches_host_architecture(package)

Tests if the given package section is applicable for the current host architecture. If no architecture is specified within the section it is considered as a match returning True.

Note: The XML section pointer must provide an arch attribute

Parameters
  • section: XML section object
Returns

True or False

def users_matches_host_architecture(self, users: Any) -> bool:
435    def users_matches_host_architecture(self, users: Any) -> bool:
436        """
437        Tests if the given users section is applicable for the current host
438        architecture. If no architecture is specified within the section
439        it is considered as a match returning True.
440
441        Note: The XML section pointer must provide an arch attribute
442
443        :param section: XML section object
444
445        :return: True or False
446
447        :rtype: bool
448        """
449        return self._section_matches_host_architecture(users)

Tests if the given users section is applicable for the current host architecture. If no architecture is specified within the section it is considered as a match returning True.

Note: The XML section pointer must provide an arch attribute

Parameters
  • section: XML section object
Returns

True or False

def collection_matches_host_architecture(self, collection: Any) -> bool:
451    def collection_matches_host_architecture(self, collection: Any) -> bool:
452        """
453        Tests if the given namedcollection section is applicable for
454        the current host architecture. If no architecture is specified
455        within the section it is considered as a match returning True.
456
457        Note: The XML section pointer must provide an arch attribute
458
459        :param section: XML section object
460
461        :return: True or False
462
463        :rtype: bool
464        """
465        return self._section_matches_host_architecture(collection)

Tests if the given namedcollection section is applicable for the current host architecture. If no architecture is specified within the section it is considered as a match returning True.

Note: The XML section pointer must provide an arch attribute

Parameters
  • section: XML section object
Returns

True or False

def profile_matches_host_architecture(self, profile: Any) -> bool:
467    def profile_matches_host_architecture(self, profile: Any) -> bool:
468        """
469        Tests if the given profile section is applicable for the current host
470        architecture. If no architecture is specified within the section
471        it is considered as a match returning True.
472
473        Note: The XML section pointer must provide an arch attribute
474
475        :param section: XML section object
476
477        :return: True or False
478
479        :rtype: bool
480        """
481        return self._section_matches_host_architecture(profile)

Tests if the given profile section is applicable for the current host architecture. If no architecture is specified within the section it is considered as a match returning True.

Note: The XML section pointer must provide an arch attribute

Parameters
  • section: XML section object
Returns

True or False

def preferences_matches_host_architecture(self, preferences: Any) -> bool:
483    def preferences_matches_host_architecture(self, preferences: Any) -> bool:
484        """
485        Tests if the given preferences section is applicable for the
486        current host architecture. If no architecture is specified within
487        the section it is considered as a match returning True.
488
489        Note: The XML section pointer must provide an arch attribute
490
491        :param section: XML section object
492
493        :return: True or False
494
495        :rtype: bool
496        """
497        return self._section_matches_host_architecture(preferences)

Tests if the given preferences section is applicable for the current host architecture. If no architecture is specified within the section it is considered as a match returning True.

Note: The XML section pointer must provide an arch attribute

Parameters
  • section: XML section object
Returns

True or False

def repository_matches_host_architecture(self, repository: Any) -> bool:
499    def repository_matches_host_architecture(self, repository: Any) -> bool:
500        """
501        Tests if the given repository section is applicable for the
502        current host architecture. If no architecture is specified within
503        the section it is considered as a match returning True.
504
505        Note: The XML section pointer must provide an arch attribute
506
507        :param section: XML section object
508
509        :return: True or False
510
511        :rtype: bool
512        """
513        return self._section_matches_host_architecture(repository)

Tests if the given repository section is applicable for the current host architecture. If no architecture is specified within the section it is considered as a match returning True.

Note: The XML section pointer must provide an arch attribute

Parameters
  • section: XML section object
Returns

True or False

def containers_matches_host_architecture(self, containers: Any) -> bool:
515    def containers_matches_host_architecture(self, containers: Any) -> bool:
516        """
517        Tests if the given containers section is applicable for the
518        current host architecture. If no arch attribute is provided in
519        the section it is considered as a match and returns: True.
520
521        :param section: XML section object
522
523        :return: True or False
524
525        :rtype: bool
526        """
527        return self._section_matches_host_architecture(containers)

Tests if the given containers section is applicable for the current host architecture. If no arch attribute is provided in the section it is considered as a match and returns: True.

Parameters
  • section: XML section object
Returns

True or False

def container_matches_host_architecture(self, container: Any) -> bool:
529    def container_matches_host_architecture(self, container: Any) -> bool:
530        """
531        Tests if the given container section is applicable for the
532        current host architecture. If no arch attribute is provided in
533        the section it is considered as a match and returns: True.
534
535        :param section: XML section object
536
537        :return: True or False
538
539        :rtype: bool
540        """
541        return self._section_matches_host_architecture(container)

Tests if the given container section is applicable for the current host architecture. If no arch attribute is provided in the section it is considered as a match and returns: True.

Parameters
  • section: XML section object
Returns

True or False

def get_package_sections(self, packages_sections: List) -> List[package_type]:
543    def get_package_sections(
544        self, packages_sections: List
545    ) -> List[package_type]:
546        """
547        List of package sections from the given packages sections.
548        Each list element contains a tuple with the <package> section
549        reference and the <packages> section this package belongs to
550
551        If a package entry specfies an architecture, it is only taken if
552        the host architecture matches the configured architecture
553
554        :param list packages_sections: <packages>
555
556        :return:
557            Contains list of package_type tuples
558
559            .. code:: python
560
561                [package_type(packages_section=object, package_section=object)]
562
563        :rtype: list
564        """
565        result = []
566        if packages_sections:
567            for packages_section in packages_sections:
568                package_list = packages_section.get_package()
569                if package_list:
570                    for package in package_list:
571                        if self.package_matches_host_architecture(package):
572                            result.append(
573                                package_type(
574                                    packages_section=packages_section,
575                                    package_section=package
576                                )
577                            )
578        return result

List of package sections from the given packages sections. Each list element contains a tuple with the section reference and the section this package belongs to

If a package entry specfies an architecture, it is only taken if the host architecture matches the configured architecture

Parameters
  • list packages_sections:
Returns
Contains list of package_type tuples

.. code:: python

    [package_type(packages_section=object, package_section=object)]
def get_to_become_deleted_packages(self, force: bool = True) -> List:
580    def get_to_become_deleted_packages(self, force: bool = True) -> List:
581        """
582        List of package names from the type="delete" or type="uninstall"
583        packages section(s)
584
585        :param bool force: return "delete" type if True, "uninstall" type
586            otherwise
587
588        :return: package names
589
590        :rtype: list
591        """
592        result = []
593        to_become_deleted_packages_sections = self.get_packages_sections(
594            ['delete' if force else 'uninstall']
595        )
596        package_list = self.get_package_sections(
597            to_become_deleted_packages_sections
598        )
599        if package_list:
600            for package in package_list:
601                result.append(package.package_section.get_name())
602        return sorted(list(set(result)))

List of package names from the type="delete" or type="uninstall" packages section(s)

Parameters
  • bool force: return "delete" type if True, "uninstall" type otherwise
Returns

package names

def get_bootstrap_packages_sections(self) -> List:
604    def get_bootstrap_packages_sections(self) -> List:
605        """
606        List of packages sections matching type="bootstrap"
607
608        :return: list of <packages> section reference(s)
609
610        :rtype: list
611        """
612        return self.get_packages_sections(['bootstrap'])

List of packages sections matching type="bootstrap"

Returns

list of section reference(s)

def get_image_packages_sections(self) -> List:
614    def get_image_packages_sections(self) -> List:
615        """
616        List of packages sections matching type="image"
617
618        :return: list of <packages> section reference(s)
619
620        :rtype: list
621        """
622        return self.get_packages_sections(['image'])

List of packages sections matching type="image"

Returns

list of section reference(s)

def get_bootstrap_packages(self, plus_packages: List = None) -> List:
624    def get_bootstrap_packages(self, plus_packages: List = None) -> List:
625        """
626        List of package names from the type="bootstrap" packages section(s)
627
628        The list gets the selected package manager appended
629        if there is a request to install packages inside of
630        the image via a chroot operation
631
632        :param list plus_packages: list of additional packages
633
634        :return: package names
635
636        :rtype: list
637        """
638        result = []
639        bootstrap_packages_sections = self.get_bootstrap_packages_sections()
640        package_list = self.get_package_sections(
641            bootstrap_packages_sections
642        )
643        if package_list:
644            for package in package_list:
645                result.append(package.package_section.get_name().strip())
646            if self.get_system_packages():
647                package_manager_name = self.get_package_manager()
648                if package_manager_name == 'dnf4':
649                    # The package name for dnf4 is just dnf. Thus
650                    # the name must be adapted in this case
651                    package_manager_name = 'dnf'
652                elif package_manager_name == 'apk':
653                    package_manager_name = 'apk-tools'
654                result.append(package_manager_name)
655        if plus_packages:
656            result += plus_packages
657        return sorted(list(set(result)))

List of package names from the type="bootstrap" packages section(s)

The list gets the selected package manager appended if there is a request to install packages inside of the image via a chroot operation

Parameters
  • list plus_packages: list of additional packages
Returns

package names

def get_system_packages(self) -> List:
659    def get_system_packages(self) -> List:
660        """
661        List of package names from the packages sections matching
662        type="image" and type=build_type
663
664        :return: package names
665
666        :rtype: list
667        """
668        result = []
669        image_packages_sections = self.get_packages_sections(
670            ['image', self.get_build_type_name()]
671        )
672        package_list = self.get_package_sections(
673            image_packages_sections
674        )
675        if package_list:
676            for package in package_list:
677                result.append(package.package_section.get_name().strip())
678        return sorted(list(set(result)))

List of package names from the packages sections matching type="image" and type=build_type

Returns

package names

def get_bootstrap_files(self) -> Dict[str, FileT]:
680    def get_bootstrap_files(self) -> Dict[str, FileT]:
681        """
682        List of file names from the type="bootstrap" packages section(s)
683
684        :return: file names
685
686        :rtype: dict
687        """
688        result = {}
689        bootstrap_packages_sections = self.get_bootstrap_packages_sections()
690        if bootstrap_packages_sections:
691            for bootstrap_packages_section in bootstrap_packages_sections:
692                file_list = bootstrap_packages_section.get_file() or []
693                for file in file_list:
694                    result[file.get_name()] = FileT(
695                        target=file.get_target() or '',
696                        owner=file.get_owner() or '',
697                        permissions=file.get_permissions() or ''
698                    )
699        return result

List of file names from the type="bootstrap" packages section(s)

Returns

file names

def get_system_files(self) -> Dict[str, FileT]:
701    def get_system_files(self) -> Dict[str, FileT]:
702        """
703        List of file names from the packages sections matching
704        type="image" and type=build_type
705
706        :return: file names
707
708        :rtype: dict
709        """
710        result = {}
711        image_packages_sections = self.get_packages_sections(
712            ['image', self.get_build_type_name()]
713        )
714        for packages in image_packages_sections:
715            for file in packages.get_file():
716                result[file.get_name()] = FileT(
717                    target=file.get_target() or '',
718                    owner=file.get_owner() or '',
719                    permissions=file.get_permissions() or ''
720                )
721        return result

List of file names from the packages sections matching type="image" and type=build_type

Returns

file names

def get_bootstrap_archives(self) -> List:
723    def get_bootstrap_archives(self) -> List:
724        """
725        List of archive names from the type="bootstrap" packages section(s)
726
727        :return: archive names
728
729        :rtype: list
730        """
731        result = []
732        bootstrap_packages_sections = self.get_bootstrap_packages_sections()
733        if bootstrap_packages_sections:
734            for bootstrap_packages_section in bootstrap_packages_sections:
735                archive_list = bootstrap_packages_section.get_archive()
736                if archive_list:
737                    for archive in archive_list:
738                        result.append(archive.get_name().strip())
739        return sorted(result)

List of archive names from the type="bootstrap" packages section(s)

Returns

archive names

def get_system_archives(self) -> List:
741    def get_system_archives(self) -> List:
742        """
743        List of archive names from the packages sections matching
744        type="image" and type=build_type
745
746        :return: archive names
747
748        :rtype: list
749        """
750        result = []
751        image_packages_sections = self.get_packages_sections(
752            ['image', self.get_build_type_name()]
753        )
754        for packages in image_packages_sections:
755            for archive in packages.get_archive():
756                result.append(archive.get_name().strip())
757        return sorted(result)

List of archive names from the packages sections matching type="image" and type=build_type

Returns

archive names

def get_ignore_packages(self, section_type: str) -> List:
759    def get_ignore_packages(self, section_type: str) -> List:
760        """
761        List of ignore package names from the packages sections matching
762        section_type and type=build_type
763
764        :return: package names
765
766        :rtype: list
767        """
768        result = []
769        image_packages_sections = self.get_packages_sections(
770            [section_type, self.get_build_type_name()]
771        )
772        for packages in image_packages_sections:
773            for package in packages.get_ignore():
774                if self.package_matches_host_architecture(package):
775                    result.append(package.get_name().strip())
776        return sorted(result)

List of ignore package names from the packages sections matching section_type and type=build_type

Returns

package names

def get_system_files_ignore_packages(self) -> List[str]:
778    def get_system_files_ignore_packages(self) -> List[str]:
779        """
780        List of ignore package names from the type="systemfiles"
781        packages section(s)
782
783        :return: package names
784
785        :rtype: list
786        """
787        return self.get_ignore_packages('systemfiles')

List of ignore package names from the type="systemfiles" packages section(s)

Returns

package names

def get_system_ignore_packages(self) -> List:
789    def get_system_ignore_packages(self) -> List:
790        """
791        List of ignore package names from the packages sections matching
792        type="image" and type=build_type
793
794        :return: package names
795
796        :rtype: list
797        """
798        return self.get_ignore_packages('image')

List of ignore package names from the packages sections matching type="image" and type=build_type

Returns

package names

def get_bootstrap_ignore_packages(self) -> List:
800    def get_bootstrap_ignore_packages(self) -> List:
801        """
802        List of ignore package names from the packages sections matching
803        type="image" and type=build_type
804
805        :return: package names
806
807        :rtype: list
808        """
809        return self.get_ignore_packages('bootstrap')

List of ignore package names from the packages sections matching type="image" and type=build_type

Returns

package names

def get_bootstrap_package_name(self) -> str:
811    def get_bootstrap_package_name(self) -> str:
812        """
813        bootstrap_package name from type="bootstrap" packages section
814
815        :return: bootstrap_package name
816
817        :rtype: str
818        """
819        typed_packages_sections = self.get_packages_sections(
820            ['bootstrap', self.get_build_type_name()]
821        )
822        bootstrap_package = ''
823        for packages in typed_packages_sections:
824            bootstrap_package = packages.get_bootstrap_package()
825            if bootstrap_package:
826                break
827        return bootstrap_package

bootstrap_package name from type="bootstrap" packages section

Returns

bootstrap_package name

def get_collection_type(self, section_type: str = 'image') -> str:
829    def get_collection_type(self, section_type: str = 'image') -> str:
830        """
831        Collection type from packages sections matching given section
832        type.
833
834        If no collection type is specified the default collection
835        type is set to: onlyRequired
836
837        :param str section_type: type name from packages section
838
839        :return: collection type name
840
841        :rtype: str
842        """
843        typed_packages_sections = self.get_packages_sections(
844            [section_type, self.get_build_type_name()]
845        )
846        collection_type = 'onlyRequired'
847        for packages in typed_packages_sections:
848            packages_collection_type = packages.get_patternType()
849            if packages_collection_type:
850                collection_type = packages_collection_type
851                break
852        return collection_type

Collection type from packages sections matching given section type.

If no collection type is specified the default collection type is set to: onlyRequired

Parameters
  • str section_type: type name from packages section
Returns

collection type name

def get_bootstrap_collection_type(self) -> str:
854    def get_bootstrap_collection_type(self) -> str:
855        """
856        Collection type for packages sections matching type="bootstrap"
857
858        :return: collection type name
859
860        :rtype: str
861        """
862        return self.get_collection_type('bootstrap')

Collection type for packages sections matching type="bootstrap"

Returns

collection type name

def get_system_collection_type(self) -> str:
864    def get_system_collection_type(self) -> str:
865        """
866        Collection type for packages sections matching type="image"
867
868        :return: collection type name
869
870        :rtype: str
871        """
872        return self.get_collection_type('image')

Collection type for packages sections matching type="image"

Returns

collection type name

def get_collection_modules(self) -> Dict[str, List[str]]:
874    def get_collection_modules(self) -> Dict[str, List[str]]:
875        """
876        Dict of collection modules to enable and/or disable
877
878        :return:
879            Dict of the form:
880
881            .. code:: python
882
883                {
884                    'enable': [
885                        "module:stream", "module"
886                    ],
887                    'disable': [
888                        "module"
889                    ]
890                }
891
892        :rtype: dict
893        """
894        modules: Dict[str, List[str]] = {
895            'disable': [],
896            'enable': []
897        }
898        for packages in self.get_bootstrap_packages_sections():
899            for collection_module in packages.get_collectionModule():
900                module_name = collection_module.get_name()
901                if collection_module.get_enable() is False:
902                    modules['disable'].append(module_name)
903                else:
904                    stream = collection_module.get_stream()
905                    if stream:
906                        modules['enable'].append(f'{module_name}:{stream}')
907                    else:
908                        modules['enable'].append(module_name)
909        return modules

Dict of collection modules to enable and/or disable

Returns
Dict of the form:

.. code:: python

    {
        'enable': [
            "module:stream", "module"
        ],
        'disable': [
            "module"
        ]
    }
def get_collections(self, section_type: str = 'image') -> List:
911    def get_collections(self, section_type: str = 'image') -> List:
912        """
913        List of collection names from the packages sections matching
914        type=section_type and type=build_type
915
916        :return: collection names
917
918        :rtype: list
919        """
920        result = []
921        typed_packages_sections = self.get_packages_sections(
922            [section_type, self.get_build_type_name()]
923        )
924        for packages in typed_packages_sections:
925            for collection in packages.get_namedCollection():
926                if self.collection_matches_host_architecture(collection):
927                    result.append(collection.get_name())
928        return sorted(list(set(result)))

List of collection names from the packages sections matching type=section_type and type=build_type

Returns

collection names

def get_bootstrap_collections(self) -> List:
930    def get_bootstrap_collections(self) -> List:
931        """
932        List of collection names from the packages sections
933        matching type="bootstrap"
934
935        :return: collection names
936
937        :rtype: list
938        """
939        return self.get_collections('bootstrap')

List of collection names from the packages sections matching type="bootstrap"

Returns

collection names

def get_system_collections(self) -> List:
941    def get_system_collections(self) -> List:
942        """
943        List of collection names from the packages sections
944        matching type="image"
945
946        :return: collection names
947
948        :rtype: list
949        """
950        return self.get_collections('image')

List of collection names from the packages sections matching type="image"

Returns

collection names

def get_products(self, section_type: str = 'image') -> List:
952    def get_products(self, section_type: str = 'image') -> List:
953        """
954        List of product names from the packages sections matching
955        type=section_type and type=build_type
956
957        :param str section_type: type name from packages section
958
959        :return: product names
960
961        :rtype: list
962        """
963        result = []
964        typed_packages_sections = self.get_packages_sections(
965            [section_type, self.get_build_type_name()]
966        )
967        for packages in typed_packages_sections:
968            for product in packages.get_product():
969                result.append(product.get_name())
970        return list(set(result))

List of product names from the packages sections matching type=section_type and type=build_type

Parameters
  • str section_type: type name from packages section
Returns

product names

def get_bootstrap_products(self) -> List:
972    def get_bootstrap_products(self) -> List:
973        """
974        List of product names from the packages sections
975        matching type="bootstrap"
976
977        :return: product names
978
979        :rtype: list
980        """
981        return self.get_products('bootstrap')

List of product names from the packages sections matching type="bootstrap"

Returns

product names

def get_system_products(self) -> List:
983    def get_system_products(self) -> List:
984        """
985        List of product names from the packages sections
986        matching type="image"
987
988        :return: product names
989
990        :rtype: list
991        """
992        return self.get_products('image')

List of product names from the packages sections matching type="image"

Returns

product names

def is_xen_server(self) -> bool:
 994    def is_xen_server(self) -> bool:
 995        """
 996        Check if build type domain setup specifies a Xen Server (dom0)
 997
 998        :return: True or False
 999
1000        :rtype: bool
1001        """
1002        return self.build_type.get_xen_server()

Check if build type domain setup specifies a Xen Server (dom0)

Returns

True or False

def is_xen_guest(self) -> bool:
1004    def is_xen_guest(self) -> bool:
1005        """
1006        Check if build type setup specifies a Xen Guest (domX)
1007        The check is based on the architecture, the firmware and
1008        xen_loader configuration values:
1009
1010        * We only support Xen setup on the x86_64 architecture
1011
1012        * Firmware pointing to ec2 means the image is targeted to run
1013          in Amazon EC2 which is a Xen guest
1014
1015        * Machine setup with a xen_loader attribute also indicates a
1016          Xen guest target
1017
1018        :return: True or False
1019
1020        :rtype: bool
1021        """
1022        if self.host_architecture != 'x86_64':
1023            # We only support Xen stuff on x86_64
1024            return False
1025        firmware = self.build_type.get_firmware()
1026        machine_section = self.get_build_type_machine_section()
1027        if firmware and firmware in Defaults.get_ec2_capable_firmware_names():
1028            # the image is targeted to run in Amazon EC2 which is a Xen system
1029            return True
1030        elif machine_section and machine_section.get_xen_loader():
1031            # the image provides a machine section with a guest loader setup
1032            return True
1033        return False

Check if build type setup specifies a Xen Guest (domX) The check is based on the architecture, the firmware and xen_loader configuration values:

  • We only support Xen setup on the x86_64 architecture

  • Firmware pointing to ec2 means the image is targeted to run in Amazon EC2 which is a Xen guest

  • Machine setup with a xen_loader attribute also indicates a Xen guest target

Returns

True or False

def get_build_type_partitions_section(self) -> Any:
1035    def get_build_type_partitions_section(self) -> Any:
1036        """
1037        First partitions section from the build type section
1038
1039        :return: <partitions> section reference
1040
1041        :rtype: xml_parse::partitions
1042        """
1043        partitions_sections = self.build_type.get_partitions()
1044        if partitions_sections:
1045            return partitions_sections[0]
1046        return None

First partitions section from the build type section

Returns

section reference

def get_build_type_system_disk_section(self) -> Any:
1048    def get_build_type_system_disk_section(self) -> Any:
1049        """
1050        First system disk section from the build type section
1051
1052        :return: <systemdisk> section reference
1053
1054        :rtype: xml_parse::systemdisk
1055        """
1056        systemdisk_sections = self.build_type.get_systemdisk()
1057        if systemdisk_sections:
1058            return systemdisk_sections[0]
1059        return None

First system disk section from the build type section

Returns

section reference

def get_build_type_machine_section(self) -> Any:
1061    def get_build_type_machine_section(self) -> Any:
1062        """
1063        First machine section from the build type section
1064
1065        :return: <machine> section reference
1066
1067        :rtype: xml_parse::machine
1068        """
1069        machine_sections = self.build_type.get_machine()
1070        if machine_sections:
1071            return machine_sections[0]
1072        return None

First machine section from the build type section

Returns

section reference

def get_build_type_vagrant_config_section(self) -> Any:
1074    def get_build_type_vagrant_config_section(self) -> Any:
1075        """
1076        First vagrantconfig section from the build type section
1077
1078        :return: <vagrantconfig> section reference
1079
1080        :rtype: xml_parse::vagrantconfig
1081        """
1082        vagrant_config_sections = self.build_type.get_vagrantconfig()
1083        if vagrant_config_sections:
1084            return vagrant_config_sections[0]
1085        return None

First vagrantconfig section from the build type section

Returns

section reference

def get_vagrant_config_virtualbox_guest_additions(self) -> bool:
1087    def get_vagrant_config_virtualbox_guest_additions(self) -> bool:
1088        """
1089        Attribute virtualbox_guest_additions_present from the first
1090        vagrantconfig section.
1091
1092        :return: True|False
1093
1094        :rtype: bool
1095        """
1096        vagrant_config_sections = self.get_build_type_vagrant_config_section()
1097        if not vagrant_config_sections.virtualbox_guest_additions_present:
1098            return Defaults.get_vagrant_config_virtualbox_guest_additions()
1099        else:
1100            return vagrant_config_sections.virtualbox_guest_additions_present

Attribute virtualbox_guest_additions_present from the first vagrantconfig section.

Returns

True|False

def get_build_type_vmdisk_section(self) -> Any:
1102    def get_build_type_vmdisk_section(self) -> Any:
1103        """
1104        First vmdisk section from the first machine section in the
1105        build type section
1106
1107        :return: <vmdisk> section reference
1108
1109        :rtype: xml_parse::vmdisk
1110        """
1111        machine_section = self.get_build_type_machine_section()
1112        if machine_section:
1113            vmdisk_sections = machine_section.get_vmdisk()
1114            if vmdisk_sections:
1115                return vmdisk_sections[0]
1116        return None

First vmdisk section from the first machine section in the build type section

Returns

section reference

def get_build_type_vmnic_entries(self) -> List:
1118    def get_build_type_vmnic_entries(self) -> List:
1119        """
1120        vmnic section(s) from the first machine section in the
1121        build type section
1122
1123        :return: list of <vmnic> section reference(s)
1124
1125        :rtype: list
1126        """
1127        machine_section = self.get_build_type_machine_section()
1128        if machine_section:
1129            return machine_section.get_vmnic()
1130        else:
1131            return []

vmnic section(s) from the first machine section in the build type section

Returns

list of section reference(s)

def get_build_type_vmdvd_section(self) -> Any:
1133    def get_build_type_vmdvd_section(self) -> Any:
1134        """
1135        First vmdvd section from the first machine section in the
1136        build type section
1137
1138        :return: <vmdvd> section reference
1139
1140        :rtype: xml_parse::vmdvd
1141        """
1142        machine_section = self.get_build_type_machine_section()
1143        if machine_section:
1144            vmdvd_sections = machine_section.get_vmdvd()
1145            if vmdvd_sections:
1146                return vmdvd_sections[0]
1147        return None

First vmdvd section from the first machine section in the build type section

Returns

section reference

def get_build_type_vmconfig_entries(self) -> List:
1149    def get_build_type_vmconfig_entries(self) -> List:
1150        """
1151        List of vmconfig-entry section values from the first
1152        machine section in the build type section
1153
1154        :return: <vmconfig_entry> section reference(s)
1155
1156        :rtype: list
1157        """
1158        machine_section = self.get_build_type_machine_section()
1159        if machine_section:
1160            vmconfig_entries = machine_section.get_vmconfig_entry()
1161            if vmconfig_entries:
1162                return vmconfig_entries
1163
1164        return []

List of vmconfig-entry section values from the first machine section in the build type section

Returns

section reference(s)

def get_build_type_bootloader_section(self) -> Any:
1166    def get_build_type_bootloader_section(self) -> Any:
1167        """
1168        First bootloader section from the build type section
1169
1170        :return: <bootloader> section reference
1171
1172        :rtype: xml_parse::bootloader
1173        """
1174        bootloader_sections = self.build_type.get_bootloader()
1175        if bootloader_sections:
1176            return bootloader_sections[0]
1177        return None

First bootloader section from the build type section

Returns

section reference

def get_build_type_bootloader_name(self) -> str:
1179    def get_build_type_bootloader_name(self) -> str:
1180        """
1181        Return bootloader name for selected build type
1182
1183        :return: bootloader name
1184
1185        :rtype: str
1186        """
1187        bootloader = self.get_build_type_bootloader_section()
1188        return bootloader.get_name() if bootloader else \
1189            Defaults.get_default_bootloader()

Return bootloader name for selected build type

Returns

bootloader name

def get_build_type_bootloader_bls(self) -> bool:
1191    def get_build_type_bootloader_bls(self) -> bool:
1192        """
1193        Return bootloader bls setting for selected build type
1194
1195        :return: True or False
1196
1197        :rtype: bool
1198        """
1199        bootloader = self.get_build_type_bootloader_section()
1200        if bootloader and bootloader.get_bls() is not None:
1201            return bootloader.get_bls()
1202        return True

Return bootloader bls setting for selected build type

Returns

True or False

def get_build_type_bootloader_console(self) -> List[str]:
1204    def get_build_type_bootloader_console(self) -> List[str]:
1205        """
1206        Return bootloader console setting for selected build type
1207
1208        :return:
1209            list of console settings for output (first element)
1210            and input (second element)
1211
1212        :rtype: list
1213        """
1214        result = ['', '']
1215        bootloader = self.get_build_type_bootloader_section()
1216        if bootloader:
1217            console_out = bootloader.get_output_console()
1218            console_in = bootloader.get_input_console()
1219            console_in = console_in if console_in else console_out
1220            result = [
1221                console_out if console_out and console_out != 'none' else '',
1222                console_in if console_in and console_in != 'none' else ''
1223            ]
1224        return result

Return bootloader console setting for selected build type

Returns
list of console settings for output (first element)
and input (second element)
def get_build_type_bootloader_serial_line_setup(self) -> Optional[str]:
1226    def get_build_type_bootloader_serial_line_setup(self) -> Optional[str]:
1227        """
1228        Return bootloader serial line setup parameters for the
1229        selected build type
1230
1231        :return: serial line setup
1232
1233        :rtype: str
1234        """
1235        bootloader = self.get_build_type_bootloader_section()
1236        if bootloader:
1237            return bootloader.get_serial_line()
1238        return None

Return bootloader serial line setup parameters for the selected build type

Returns

serial line setup

def get_build_type_bootloader_timeout(self) -> Optional[str]:
1240    def get_build_type_bootloader_timeout(self) -> Optional[str]:
1241        """
1242        Return bootloader timeout setting for selected build type
1243
1244        :return: timeout string
1245
1246        :rtype: str
1247        """
1248        bootloader = self.get_build_type_bootloader_section()
1249        if bootloader:
1250            return bootloader.get_timeout()
1251        return None

Return bootloader timeout setting for selected build type

Returns

timeout string

def get_build_type_bootloader_timeout_style(self) -> Optional[str]:
1253    def get_build_type_bootloader_timeout_style(self) -> Optional[str]:
1254        """
1255        Return bootloader timeout style setting for selected build type
1256
1257        :return: timeout_style string
1258
1259        :rtype: str
1260        """
1261        bootloader = self.get_build_type_bootloader_section()
1262        if bootloader:
1263            return bootloader.get_timeout_style()
1264        return None

Return bootloader timeout style setting for selected build type

Returns

timeout_style string

def get_build_type_bootloader_targettype(self) -> Optional[str]:
1266    def get_build_type_bootloader_targettype(self) -> Optional[str]:
1267        """
1268        Return bootloader target type setting. Only relevant for
1269        the zipl bootloader because zipl is installed differently
1270        depending on the storage target it runs later
1271
1272        :return: target type string
1273
1274        :rtype: str
1275        """
1276        bootloader = self.get_build_type_bootloader_section()
1277        if bootloader:
1278            return bootloader.get_targettype()
1279        return None

Return bootloader target type setting. Only relevant for the zipl bootloader because zipl is installed differently depending on the storage target it runs later

Returns

target type string

def get_build_type_bootloader_settings_section(self) -> Any:
1281    def get_build_type_bootloader_settings_section(self) -> Any:
1282        """
1283        First bootloadersettings section from the build
1284        type bootloader section
1285
1286        :return: <bootloadersettings> section reference
1287
1288        :rtype: xml_parse::bootloadersettings
1289        """
1290        bootloader_section = self.get_build_type_bootloader_section()
1291        bootloader_settings_section = None
1292        if bootloader_section and bootloader_section.get_bootloadersettings():
1293            bootloader_settings_section = \
1294                bootloader_section.get_bootloadersettings()[0]
1295        return bootloader_settings_section

First bootloadersettings section from the build type bootloader section

Returns

section reference

def get_build_type_bootloader_securelinux_section(self) -> List[Any]:
1297    def get_build_type_bootloader_securelinux_section(self) -> List[Any]:
1298        """
1299        First securelinux section from the build
1300        type bootloader section
1301
1302        :return: <securelinux> section reference
1303
1304        :rtype: xml_parse::securelinux
1305        """
1306        bootloader_section = self.get_build_type_bootloader_section()
1307        bootloader_securelinux_section = []
1308        if bootloader_section and bootloader_section.get_securelinux():
1309            bootloader_securelinux_section = \
1310                bootloader_section.get_securelinux()
1311        return bootloader_securelinux_section

First securelinux section from the build type bootloader section

Returns

section reference

def get_build_type_bootloader_environment_variables(self) -> List[str]:
1313    def get_build_type_bootloader_environment_variables(self) -> List[str]:
1314        """
1315        List of bootloader variables from the build
1316        type > bootloader > bootloadersettings section
1317        """
1318        variable_list = []
1319        bootloader_settings_section = \
1320            self.get_build_type_bootloader_settings_section()
1321        if bootloader_settings_section:
1322            environment = bootloader_settings_section.get_environment()
1323            if environment and environment[0].get_env():
1324                for env in environment[0].get_env():
1325                    variable_list.append(
1326                        '{}{}'.format(
1327                            env.get_name(),
1328                            f'={env.get_value()}' if env.get_value() else ''
1329                        )
1330                    )
1331        return variable_list

List of bootloader variables from the build type > bootloader > bootloadersettings section

def get_bootloader_options(self, option_type: str) -> List[str]:
1333    def get_bootloader_options(self, option_type: str) -> List[str]:
1334        """
1335        List of custom options used in the process to
1336        run bootloader setup workloads
1337        """
1338        result: List[str] = []
1339        bootloader_settings = self.get_build_type_bootloader_settings_section()
1340        if bootloader_settings:
1341            options = []
1342            if option_type == 'shim':
1343                options = bootloader_settings.get_shimoption()
1344            elif option_type == 'install':
1345                options = bootloader_settings.get_installoption()
1346            elif option_type == 'config':
1347                options = bootloader_settings.get_configoption()
1348            for option in options:
1349                result.append(option.get_name())
1350                if option.get_value():
1351                    result.append(option.get_value())
1352        return result

List of custom options used in the process to run bootloader setup workloads

def get_bootloader_shim_options(self) -> List[str]:
1354    def get_bootloader_shim_options(self) -> List[str]:
1355        """
1356        List of custom options used in the process to setup secure boot
1357        """
1358        return self.get_bootloader_options('shim')

List of custom options used in the process to setup secure boot

def get_bootloader_install_options(self) -> List[str]:
1360    def get_bootloader_install_options(self) -> List[str]:
1361        """
1362        List of custom options used in the bootloader installation
1363        """
1364        return self.get_bootloader_options('install')

List of custom options used in the bootloader installation

def get_bootloader_config_options(self) -> List[str]:
1366    def get_bootloader_config_options(self) -> List[str]:
1367        """
1368        List of custom options used in the bootloader configuration
1369        """
1370        return self.get_bootloader_options('config')

List of custom options used in the bootloader configuration

def get_build_type_bootloader_use_disk_password(self) -> bool:
1372    def get_build_type_bootloader_use_disk_password(self) -> bool:
1373        """
1374        Indicate whether the bootloader configuration should use the
1375        password protecting the encrypted root volume.
1376
1377        :return: True|False
1378
1379        :rtype: bool
1380        """
1381        bootloader = self.get_build_type_bootloader_section()
1382        if bootloader:
1383            return bootloader.get_use_disk_password()
1384        return False

Indicate whether the bootloader configuration should use the password protecting the encrypted root volume.

Returns

True|False

def get_build_type_oemconfig_section(self) -> Any:
1386    def get_build_type_oemconfig_section(self) -> Any:
1387        """
1388        First oemconfig section from the build type section
1389
1390        :return: <oemconfig> section reference
1391
1392        :rtype: xml_parse::oemconfig
1393        """
1394        oemconfig_sections = self.build_type.get_oemconfig()
1395        if oemconfig_sections:
1396            return oemconfig_sections[0]
1397        return None

First oemconfig section from the build type section

Returns

section reference

def get_oemconfig_oem_resize(self) -> bool:
1399    def get_oemconfig_oem_resize(self) -> bool:
1400        """
1401        State value to activate/deactivate disk resize. Returns a
1402        boolean value if specified or True to set resize on by default
1403
1404        :return: Content of <oem-resize> section value
1405
1406        :rtype: bool
1407        """
1408        oemconfig = self.get_build_type_oemconfig_section()
1409        if oemconfig and oemconfig.get_oem_resize():
1410            return oemconfig.get_oem_resize()[0]
1411        else:
1412            return True

State value to activate/deactivate disk resize. Returns a boolean value if specified or True to set resize on by default

Returns

Content of section value

def get_oemconfig_oem_systemsize(self) -> int:
1414    def get_oemconfig_oem_systemsize(self) -> int:
1415        """
1416        State value to retrieve root partition size
1417
1418        :return: Content of <oem-systemsize> section value
1419
1420        :rtype: int
1421        """
1422        oemconfig = self.get_build_type_oemconfig_section()
1423        if oemconfig and oemconfig.get_oem_systemsize():
1424            return int(oemconfig.get_oem_systemsize()[0])
1425        else:
1426            return 0

State value to retrieve root partition size

Returns

Content of section value

def get_oemconfig_oem_multipath_scan(self) -> bool:
1428    def get_oemconfig_oem_multipath_scan(self) -> bool:
1429        """
1430        State value to activate multipath maps. Returns a boolean
1431        value if specified or False
1432
1433        :return: Content of <oem-multipath-scan> section value
1434
1435        :rtype: bool
1436        """
1437        oemconfig = self.get_build_type_oemconfig_section()
1438        if oemconfig and oemconfig.get_oem_multipath_scan():
1439            return oemconfig.get_oem_multipath_scan()[0]
1440        return False

State value to activate multipath maps. Returns a boolean value if specified or False

Returns

Content of section value

def get_oemconfig_swap_mbytes(self) -> Optional[int]:
1442    def get_oemconfig_swap_mbytes(self) -> Optional[int]:
1443        """
1444        Return swapsize in MB if requested or None
1445
1446        Operates on the value of oem-swap and if set to true
1447        returns the given size or the default value.
1448
1449        :return: Content of <oem-swapsize> section value or default
1450
1451        :rtype: int
1452        """
1453        oemconfig = self.get_build_type_oemconfig_section()
1454        if oemconfig and oemconfig.get_oem_swap():
1455            swap_requested = oemconfig.get_oem_swap()[0]
1456            if swap_requested:
1457                swapsize = oemconfig.get_oem_swapsize()
1458                if swapsize:
1459                    return swapsize[0]
1460                else:
1461                    return Defaults.get_swapsize_mbytes()
1462        return None

Return swapsize in MB if requested or None

Operates on the value of oem-swap and if set to true returns the given size or the default value.

Returns

Content of section value or default

def get_oemconfig_swap_name(self) -> str:
1464    def get_oemconfig_swap_name(self) -> str:
1465        """
1466        Return the swap space name
1467
1468        Operates on the value of oem-swapname and if set
1469        returns the configured name or the default name: LVSwap
1470
1471        The name of the swap space is used only if the
1472        image is configured to use the LVM volume manager.
1473        In this case swap is a volume and the volume takes
1474        a name. In any other case the given name will have
1475        no effect.
1476
1477        :return: Content of <oem-swapname> section value or default
1478
1479        :rtype: str
1480        """
1481        oemconfig = self.get_build_type_oemconfig_section()
1482        if oemconfig and oemconfig.get_oem_swapname():
1483            return oemconfig.get_oem_swapname()[0]
1484        return 'LVSwap'

Return the swap space name

Operates on the value of oem-swapname and if set returns the configured name or the default name: LVSwap

The name of the swap space is used only if the image is configured to use the LVM volume manager. In this case swap is a volume and the volume takes a name. In any other case the given name will have no effect.

Returns

Content of section value or default

def get_build_type_containerconfig_section(self) -> Any:
1486    def get_build_type_containerconfig_section(self) -> Any:
1487        """
1488        First containerconfig section from the build type section
1489
1490        :return: <containerconfig> section reference
1491
1492        :rtype: xml_parse::containerconfig
1493        """
1494        container_config_sections = self.build_type.get_containerconfig()
1495        if container_config_sections:
1496            return container_config_sections[0]
1497        return None

First containerconfig section from the build type section

Returns

section reference

def get_dracut_config(self, action: str) -> DracutT:
1499    def get_dracut_config(self, action: str) -> DracutT:
1500        """
1501        Get dracut initrd config for the specified action
1502        """
1503        uefi = False
1504        modules = []
1505        drivers = []
1506        initrd_sections = self.build_type.get_initrd()
1507        for initrd_section in initrd_sections:
1508            if initrd_section.get_action() == action:
1509                for dracut in initrd_section.get_dracut():
1510                    uefi = bool(dracut.get_uefi())
1511                    if dracut.get_module():
1512                        modules.append(dracut.get_module())
1513                    if dracut.get_driver():
1514                        drivers.append(dracut.get_driver())
1515        return DracutT(
1516            uefi=uefi, modules=modules, drivers=drivers
1517        )

Get dracut initrd config for the specified action

def get_installmedia_initrd_modules(self, action: str) -> List[str]:
1519    def get_installmedia_initrd_modules(self, action: str) -> List[str]:
1520        """
1521        Gets the list of modules to append in installation initrds
1522
1523        :return: a list of dracut module names
1524
1525        :rtype: list
1526        """
1527        modules: List[str] = []
1528        installmedia = self.build_type.get_installmedia()
1529        if not installmedia:
1530            return modules
1531        initrd_sections = installmedia[0].get_initrd()
1532        for initrd_section in initrd_sections:
1533            if initrd_section.get_action() == action:
1534                for module in initrd_section.get_dracut():
1535                    if module.get_module():
1536                        modules.append(module.get_module())
1537        return modules

Gets the list of modules to append in installation initrds

Returns

a list of dracut module names

def get_installmedia_initrd_drivers(self, action: str) -> List[str]:
1539    def get_installmedia_initrd_drivers(self, action: str) -> List[str]:
1540        """
1541        Gets the list of drivers to append in installation initrds
1542
1543        :return: a list of dracut driver names
1544
1545        :rtype: list
1546        """
1547        drivers: List[str] = []
1548        installmedia = self.build_type.get_installmedia()
1549        if not installmedia:
1550            return drivers
1551        initrd_sections = installmedia[0].get_initrd()
1552        for initrd_section in initrd_sections:
1553            if initrd_section.get_action() == action:
1554                for driver in initrd_section.get_dracut():
1555                    if driver.get_driver():
1556                        drivers.append(driver.get_driver())
1557        return drivers

Gets the list of drivers to append in installation initrds

Returns

a list of dracut driver names

def get_build_type_size( self, include_unpartitioned: bool = False) -> Optional[size_type]:
1559    def get_build_type_size(
1560        self, include_unpartitioned: bool = False
1561    ) -> Optional[size_type]:
1562        """
1563        Size information from the build type section.
1564        If no unit is set the value is treated as mbytes
1565
1566        :param bool include_unpartitioned: sets if the unpartitioned area
1567            should be included in the computed size or not
1568
1569        :return: mbytes
1570
1571        :rtype: int
1572        """
1573        size_section = self.build_type.get_size()
1574        if size_section:
1575            unit = size_section[0].get_unit()
1576            additive = size_section[0].get_additive()
1577            unpartitioned = size_section[0].get_unpartitioned()
1578            value = int(size_section[0].get_valueOf_())
1579            if not include_unpartitioned and unpartitioned is not None:
1580                value -= unpartitioned
1581            if unit == 'G':
1582                value *= 1024
1583            return size_type(
1584                mbytes=value, additive=additive
1585            )
1586        return None

Size information from the build type section. If no unit is set the value is treated as mbytes

Parameters
  • bool include_unpartitioned: sets if the unpartitioned area should be included in the computed size or not
Returns

mbytes

def get_build_type_unpartitioned_bytes(self) -> int:
1588    def get_build_type_unpartitioned_bytes(self) -> int:
1589        """
1590        Size of the unpartitioned area for image in megabytes
1591
1592        :return: mbytes
1593
1594        :rtype: int
1595        """
1596        size_section = self.build_type.get_size()
1597        if size_section:
1598            unit = size_section[0].get_unit() or 'M'
1599            unpartitioned = size_section[0].get_unpartitioned() or 0
1600            return StringToSize.to_bytes('{0}{1}'.format(unpartitioned, unit))
1601        return 0

Size of the unpartitioned area for image in megabytes

Returns

mbytes

def get_disk_start_sector(self) -> int:
1603    def get_disk_start_sector(self) -> int:
1604        """
1605        First disk sector number to be used by the first disk partition.
1606
1607        :return: number
1608
1609        :rtype: int
1610        """
1611        disk_start_sector = self.build_type.get_disk_start_sector()
1612        if disk_start_sector is None:
1613            disk_start_sector = Defaults.get_default_disk_start_sector()
1614        return disk_start_sector

First disk sector number to be used by the first disk partition.

Returns

number

def get_build_type_spare_part_size(self) -> Optional[int]:
1616    def get_build_type_spare_part_size(self) -> Optional[int]:
1617        """
1618        Size information for the spare_part size from the build
1619        type. If no unit is set the value is treated as mbytes
1620
1621        :return: mbytes
1622
1623        :rtype: int
1624        """
1625        spare_part_size = self.build_type.get_spare_part()
1626        if spare_part_size:
1627            return self._to_mega_byte(spare_part_size)
1628        return None

Size information for the spare_part size from the build type. If no unit is set the value is treated as mbytes

Returns

mbytes

def get_build_type_spare_part_fs_attributes(self) -> Optional[List]:
1630    def get_build_type_spare_part_fs_attributes(self) -> Optional[List]:
1631        """
1632        Build type specific list of filesystem attributes applied to
1633        the spare partition.
1634
1635        :return: list of strings or empty list
1636
1637        :rtype: list
1638        """
1639        spare_part_attributes = self.build_type.get_spare_part_fs_attributes()
1640        if spare_part_attributes:
1641            return spare_part_attributes.strip().split(',')
1642        return None

Build type specific list of filesystem attributes applied to the spare partition.

Returns

list of strings or empty list

def get_build_type_format_options(self) -> Dict:
1644    def get_build_type_format_options(self) -> Dict:
1645        """
1646        Disk format options returned as a dictionary
1647
1648        :return: format options
1649
1650        :rtype: dict
1651        """
1652        result = {}
1653        format_options = self.build_type.get_formatoptions()
1654        if format_options:
1655            for option in format_options.split(','):
1656                key_value_list = option.split('=')
1657                if len(key_value_list) == 2:
1658                    result[key_value_list[0]] = key_value_list[1]
1659                else:
1660                    result[key_value_list[0]] = None
1661        return result

Disk format options returned as a dictionary

Returns

format options

def get_volume_group_name(self) -> str:
1663    def get_volume_group_name(self) -> str:
1664        """
1665        Volume group name from selected <systemdisk> section
1666
1667        :return: volume group name
1668
1669        :rtype: str
1670        """
1671        systemdisk_section = self.get_build_type_system_disk_section()
1672        volume_group_name = None
1673        if systemdisk_section:
1674            volume_group_name = systemdisk_section.get_name()
1675        if not volume_group_name:
1676            volume_group_name = Defaults.get_default_volume_group_name()
1677        return volume_group_name

Volume group name from selected section

Returns

volume group name

def get_users(self) -> List:
1679    def get_users(self) -> List:
1680        """
1681        List of configured users.
1682
1683        Each entry in the list is a single xml_parse::user instance.
1684
1685        :return: list of <user> section reference(s)
1686
1687        :rtype: list
1688        """
1689        users_list = []
1690        users_names_added = []
1691        for users_section in self.get_users_sections():
1692            for user in users_section.get_user():
1693                if user.get_name() not in users_names_added:
1694                    users_list.append(user)
1695                    users_names_added.append(user.get_name())
1696
1697        return users_list

List of configured users.

Each entry in the list is a single xml_parse::user instance.

Returns

list of section reference(s)

def get_user_groups(self, user_name) -> List[str]:
1699    def get_user_groups(self, user_name) -> List[str]:
1700        """
1701        List of group names matching specified user
1702
1703        Each entry in the list is the name of a group and optionally its
1704        group ID separated by a colon, that the specified user belongs to.
1705        The first item in the list is the login or primary group. The
1706        list will be empty if no groups are specified in the
1707        description file.
1708
1709        :return: groups data for the given user
1710
1711        :rtype: list
1712        """
1713        groups_list = []
1714        for users_section in self.get_users_sections():
1715            for user in users_section.get_user():
1716                if user.get_name() == user_name:
1717                    user_groups = user.get_groups()
1718                    if user_groups:
1719                        groups_list += user.get_groups().split(',')
1720
1721        # order of list items matter, thus we don't use set() here
1722        # better faster, nicer solutions welcome :)
1723        result_group_list = []
1724        for item in groups_list:
1725            if item not in result_group_list:
1726                result_group_list.append(item)
1727
1728        return result_group_list

List of group names matching specified user

Each entry in the list is the name of a group and optionally its group ID separated by a colon, that the specified user belongs to. The first item in the list is the login or primary group. The list will be empty if no groups are specified in the description file.

Returns

groups data for the given user

def get_container_config(self) -> Dict:
1730    def get_container_config(self) -> Dict:
1731        """
1732        Dictionary of containerconfig information
1733
1734        Takes attributes and subsection data from the selected
1735        <containerconfig> section and stores it in a dictionary
1736        """
1737        container_config = self._match_docker_base_data()
1738        container_config.update(
1739            self._match_docker_entrypoint()
1740        )
1741        container_config.update(
1742            self._match_docker_subcommand()
1743        )
1744        container_config.update(
1745            self._match_docker_expose_ports()
1746        )
1747        container_config.update(
1748            self._match_docker_volumes()
1749        )
1750        container_config.update(
1751            self._match_docker_stopsignal()
1752        )
1753        container_config.update(
1754            self._match_docker_environment()
1755        )
1756        container_config.update(
1757            self._match_docker_labels()
1758        )
1759        container_config.update(
1760            self._match_docker_history()
1761        )
1762
1763        desc = self.get_description_section()
1764        author_contact = "{0} <{1}>".format(desc.author, desc.contact)
1765        if 'history' not in container_config:
1766            container_config['history'] = {}
1767        if 'author' not in container_config['history']:
1768            container_config['history']['author'] = author_contact
1769        if 'maintainer' not in container_config:
1770            container_config['maintainer'] = author_contact
1771
1772        return container_config

Dictionary of containerconfig information

Takes attributes and subsection data from the selected section and stores it in a dictionary

def set_container_config_tag(self, tag: str) -> None:
1774    def set_container_config_tag(self, tag: str) -> None:
1775        """
1776        Set new tag name in containerconfig section
1777
1778        In order to set a new tag value an existing containerconfig and
1779        tag setup is required
1780
1781        :param str tag: tag name
1782        """
1783        container_config_section = self.get_build_type_containerconfig_section()
1784        if container_config_section and container_config_section.get_tag():
1785            container_config_section.set_tag(tag)
1786        else:
1787            message = dedent('''\n
1788                No <containerconfig> section and/or tag attribute configured
1789
1790                In order to set the tag {0} as new container tag,
1791                an initial containerconfig section including a tag
1792                setup is required
1793            ''')
1794            log.warning(message.format(tag))

Set new tag name in containerconfig section

In order to set a new tag value an existing containerconfig and tag setup is required

Parameters
  • str tag: tag name
def add_container_config_label(self, label_name: str, value: str) -> None:
1796    def add_container_config_label(self, label_name: str, value: str) -> None:
1797        """
1798        Adds a new label in the containerconfig section, if a label with the
1799        same name is already defined in containerconfig it gets overwritten by
1800        this method.
1801
1802        :param str label_name: the string representing the label name
1803        :param str value: the value of the label
1804        """
1805        if self.get_build_type_name() not in ['docker', 'oci']:
1806            message = dedent('''\n
1807                Labels can only be configured for container image types
1808                docker and oci.
1809            ''')
1810            log.warning(message)
1811            return
1812
1813        container_config_section = self.get_build_type_containerconfig_section()
1814        if not container_config_section:
1815            container_config_section = xml_parse.containerconfig(
1816                name=Defaults.get_default_container_name(),
1817                tag=Defaults.get_default_container_tag()
1818            )
1819            self.build_type.set_containerconfig([container_config_section])
1820
1821        labels = container_config_section.get_labels()
1822        if not labels:
1823            labels = [xml_parse.labels()]
1824
1825        label_names = []
1826        for label in labels[0].get_label():
1827            label_names.append(label.get_name())
1828
1829        if label_name in label_names:
1830            labels[0].replace_label_at(
1831                label_names.index(label_name),
1832                xml_parse.label(label_name, value)
1833            )
1834        else:
1835            labels[0].add_label(xml_parse.label(label_name, value))
1836
1837        container_config_section.set_labels(labels)

Adds a new label in the containerconfig section, if a label with the same name is already defined in containerconfig it gets overwritten by this method.

Parameters
  • str label_name: the string representing the label name
  • str value: the value of the label
def get_partitions(self) -> Dict[str, kiwi.storage.disk.ptable_entry_type]:
1839    def get_partitions(self) -> Dict[str, ptable_entry_type]:
1840        """
1841        Dictionary of configured partitions.
1842
1843        Each entry in the dict references a ptable_entry_type
1844        Each key in the dict references the name of the
1845        partition entry as handled by KIWI
1846
1847        :return:
1848            Contains dict of ptable_entry_type tuples
1849
1850            .. code:: python
1851
1852                {
1853                    'NAME': ptable_entry_type(
1854                        mbsize=int,
1855                        clone=int,
1856                        partition_name=str,
1857                        partition_type=str,
1858                        partition_id=Optional[int],
1859                        mountpoint=str,
1860                        filesystem=str,
1861                        label=str
1862                    )
1863                }
1864
1865        :rtype: dict
1866        """
1867        partitions: Dict[str, ptable_entry_type] = {}
1868        partitions_section = self.get_build_type_partitions_section()
1869        if not partitions_section:
1870            return partitions
1871        for partition in partitions_section.get_partition():
1872            name = partition.get_name()
1873            partition_name = partition.get_partition_name() or f'p.lx{name}'
1874            partitions[name] = ptable_entry_type(
1875                mbsize=self._to_mega_byte(partition.get_size()),
1876                clone=int(partition.get_clone()) if partition.get_clone() else 0,
1877                partition_name=partition_name,
1878                partition_type=partition.get_partition_type() or 't.linux',
1879                partition_id=partition.get_part_id(),
1880                mountpoint=partition.get_mountpoint(),
1881                filesystem=partition.get_filesystem(),
1882                label=partition.get_label() or ''
1883            )
1884        return partitions

Dictionary of configured partitions.

Each entry in the dict references a ptable_entry_type Each key in the dict references the name of the partition entry as handled by KIWI

Returns
Contains dict of ptable_entry_type tuples

.. code:: python

    {
        'NAME': ptable_entry_type(
            mbsize=int,
            clone=int,
            partition_name=str,
            partition_type=str,
            partition_id=Optional[int],
            mountpoint=str,
            filesystem=str,
            label=str
        )
    }
def get_host_key_certificates(self) -> Union[List[Dict[str, List[str]]], List[Dict[str, str]]]:
1886    def get_host_key_certificates(
1887        self
1888    ) -> Union[List[Dict[str, List[str]]], List[Dict[str, str]]]:
1889        cc_result = []
1890        cc_certificates: Dict[str, List[str]] = {}
1891        securelinux_list = \
1892            self.get_build_type_bootloader_securelinux_section()
1893        for securelinux in securelinux_list:
1894            cc_certificates = {
1895                'hkd_cert': [],
1896                'hkd_revocation_list': [],
1897                'hkd_ca_cert': securelinux.get_hkd_ca_cert(),
1898                'hkd_sign_cert': securelinux.get_hkd_sign_cert()
1899            }
1900            for hkd_cert in securelinux.get_hkd_cert():
1901                cc_certificates['hkd_cert'].append(hkd_cert.get_name())
1902            for hkd_revocation_list in securelinux.get_hkd_revocation_list():
1903                cc_certificates['hkd_revocation_list'].append(
1904                    hkd_revocation_list.get_name()
1905                )
1906            cc_result.append(cc_certificates)
1907        return cc_result
def get_containers(self) -> List[ContainerT]:
1909    def get_containers(self) -> List[ContainerT]:
1910        containers = []
1911
1912        def build_fetch_command(
1913            root_dir: str,
1914            container_uri: str = '',
1915            container_file_name: str = '',
1916            container_endpoint: str = ''
1917        ):
1918            pass  # pragma: nocover
1919        for containers_section in self.get_containers_sections():
1920            for container in containers_section.get_container():
1921                if self.container_matches_host_architecture(container):
1922                    fetch_command = build_fetch_command
1923                    load_command = []
1924                    container_tag = container.get_tag() or 'latest'
1925                    container_path = container.get_path() or ''
1926                    container_endpoint = os.path.normpath(
1927                        '{0}/{1}/{2}:{3}'.format(
1928                            containers_section.get_source(), container_path,
1929                            container.name, container_tag
1930                        )
1931                    )
1932                    container_file_name = '{0}/{1}_{2}'.format(
1933                        defaults.LOCAL_CONTAINERS, container.name, container_tag
1934                    )
1935                    container_backend = containers_section.get_backend() or ''
1936                    if container_backend in ['podman', 'docker', 'container-snap']:
1937                        if Defaults.is_buildservice_worker():
1938                            container_uri = Uri(
1939                                'obsrepositories:/{0}'.format(
1940                                    container_endpoint
1941                                ), 'container'
1942                            ).translate()
1943
1944                            def build_fetch_command(
1945                                root_dir: str,
1946                                container_uri: str = container_uri,
1947                                container_file_name: str = container_file_name,
1948                                container_endpoint: str = container_endpoint
1949                            ):
1950                                def perform():
1951                                    Command.run(
1952                                        [
1953                                            'cp', '{0}.ociarchive'.format(
1954                                                container_uri
1955                                            ), os.path.normpath(
1956                                                '{0}/{1}'.format(
1957                                                    root_dir,
1958                                                    container_file_name
1959                                                )
1960                                            )
1961                                        ]
1962                                    )
1963                                perform()
1964                            fetch_command = build_fetch_command
1965                        else:
1966
1967                            def build_fetch_command(
1968                                root_dir: str,
1969                                container_uri: str = '',
1970                                container_file_name: str = container_file_name,
1971                                container_endpoint: str = container_endpoint
1972                            ):
1973                                def perform():
1974                                    Command.run(
1975                                        [
1976                                            'chroot', root_dir,
1977                                            '/usr/bin/skopeo', 'copy',
1978                                            'docker://{0}'.format(
1979                                                container_endpoint
1980                                            ),
1981                                            'oci-archive:{0}:{1}'.format(
1982                                                container_file_name,
1983                                                container_endpoint
1984                                            )
1985                                        ]
1986                                    )
1987                                perform()
1988                            fetch_command = build_fetch_command
1989                        if not container.get_fetch_only():
1990                            load_command = [
1991                                f'/usr/bin/{container_backend}',
1992                                'load', '-i', container_file_name
1993                            ]
1994                    containers.append(
1995                        ContainerT(
1996                            name=f'{container.name}_{container_tag}',
1997                            backend=container_backend,
1998                            container_file=container_file_name,
1999                            fetch_only=bool(container.get_fetch_only()),
2000                            fetch_command=fetch_command,
2001                            load_command=load_command
2002                        )
2003                    )
2004        return containers
def get_volumes(self) -> List[volume_type]:
2006    def get_volumes(self) -> List[volume_type]:
2007        """
2008        List of configured systemdisk volumes.
2009
2010        Each entry in the list is a tuple with the following information
2011
2012        * name: name of the volume
2013        * size: size of the volume
2014        * realpath: system path to lookup volume data. If no mountpoint
2015          is set the volume name is used as data path.
2016        * mountpoint: volume mount point and volume data path
2017        * fullsize: takes all space True|False
2018        * attributes: list of volume attributes handled via chattr
2019
2020        :return:
2021            Contains list of volume_type tuples
2022
2023            .. code:: python
2024
2025                [
2026                    volume_type(
2027                        name=volume_name,
2028                        parent=volume_parent,
2029                        size=volume_size,
2030                        realpath=path,
2031                        mountpoint=path,
2032                        fullsize=True,
2033                        label=volume_label,
2034                        attributes=['no-copy-on-write'],
2035                        is_root_volume=True|False
2036                    )
2037                ]
2038
2039        :rtype: list
2040        """
2041        volume_type_list: List[volume_type] = []
2042        systemdisk_section = self.get_build_type_system_disk_section()
2043        selected_filesystem = self.build_type.get_filesystem()
2044        swap_mbytes = self.get_oemconfig_swap_mbytes()
2045        swap_name = self.get_oemconfig_swap_name()
2046        if not systemdisk_section:
2047            return volume_type_list
2048        volumes = systemdisk_section.get_volume()
2049        have_root_volume_setup = False
2050        have_full_size_volume = False
2051        if volumes:
2052            for volume in volumes:
2053                if not self.volume_matches_host_architecture(volume):
2054                    continue
2055                # volume setup for a full qualified volume with name and
2056                # mountpoint information. See below for exceptions
2057                name = volume.get_name()
2058                parent = volume.get_parent() or ''
2059                mountpoint = volume.get_mountpoint()
2060                realpath = mountpoint
2061                size = volume.get_size()
2062                freespace = volume.get_freespace()
2063                fullsize = False
2064                label = volume.get_label()
2065                attributes = []
2066                is_root_volume = False
2067
2068                if volume.get_quota():
2069                    attributes.append(f'quota={volume.get_quota()}')
2070
2071                if volume.get_copy_on_write() is False:
2072                    # by default copy-on-write is switched on for any
2073                    # filesystem. Thus only if no copy on write is requested
2074                    # the attribute is handled
2075                    attributes.append('no-copy-on-write')
2076
2077                if volume.get_filesystem_check() is True:
2078                    # by default filesystem check is switched off for any
2079                    # filesystem except the rootfs. Thus only if filesystem
2080                    # check is requested the attribute is handled
2081                    attributes.append('enable-for-filesystem-check')
2082
2083                if '@root' in name:
2084                    # setup root volume, it takes an optional volume
2085                    # name if specified as @root=name and has no specific
2086                    # mountpoint. The default name is set to
2087                    # defaults.ROOT_VOLUME_NAME if no other root volume
2088                    # name is provided
2089                    mountpoint = None
2090                    realpath = '/'
2091                    is_root_volume = True
2092                    root_volume_expression = re.match(
2093                        r'@root=(.+)', name
2094                    )
2095                    if root_volume_expression:
2096                        name = root_volume_expression.group(1)
2097                    else:
2098                        name = defaults.ROOT_VOLUME_NAME
2099                    have_root_volume_setup = True
2100                elif not mountpoint:
2101                    # setup volume without mountpoint. In this case the name
2102                    # attribute is used as mountpoint path and a name for the
2103                    # volume is created from that path information
2104                    mountpoint = name
2105                    realpath = mountpoint
2106                    name = self._to_volume_name(name)
2107
2108                if size:
2109                    size = 'size:' + format(
2110                        self._to_mega_byte(size)
2111                    )
2112                elif freespace:
2113                    size = 'freespace:' + format(
2114                        self._to_mega_byte(freespace)
2115                    )
2116                else:
2117                    size = 'freespace:' + format(
2118                        Defaults.get_min_volume_mbytes(selected_filesystem)
2119                    )
2120
2121                if ':all' in size:
2122                    size = None
2123                    fullsize = True
2124                    have_full_size_volume = True
2125
2126                volume_type_list.append(
2127                    volume_type(
2128                        name=name,
2129                        parent=parent,
2130                        size=size,
2131                        fullsize=fullsize,
2132                        mountpoint=mountpoint,
2133                        realpath=realpath,
2134                        label=label,
2135                        attributes=attributes,
2136                        is_root_volume=is_root_volume
2137                    )
2138                )
2139
2140        if not have_root_volume_setup:
2141            # There must always be a root volume setup. It will be the
2142            # full size volume if no other volume has this setup
2143            volume_management = self.get_volume_management()
2144            root_volume_name = \
2145                defaults.ROOT_VOLUME_NAME if volume_management == 'lvm' else ''
2146            if have_full_size_volume:
2147                size = 'freespace:' + format(
2148                    Defaults.get_min_volume_mbytes(selected_filesystem)
2149                )
2150                fullsize = False
2151            else:
2152                size = None
2153                fullsize = True
2154            volume_type_list.append(
2155                volume_type(
2156                    name=root_volume_name,
2157                    parent='',
2158                    size=size,
2159                    fullsize=fullsize,
2160                    mountpoint=None,
2161                    realpath='/',
2162                    label=None,
2163                    attributes=[],
2164                    is_root_volume=True
2165                )
2166            )
2167
2168        if swap_mbytes and self.get_volume_management() == 'lvm':
2169            volume_type_list.append(
2170                volume_type(
2171                    name=swap_name,
2172                    parent='',
2173                    size='size:{0}'.format(swap_mbytes),
2174                    fullsize=False,
2175                    mountpoint=None,
2176                    realpath='swap',
2177                    label='SWAP',
2178                    attributes=[],
2179                    is_root_volume=False
2180                )
2181            )
2182
2183        return volume_type_list

List of configured systemdisk volumes.

Each entry in the list is a tuple with the following information

  • name: name of the volume
  • size: size of the volume
  • realpath: system path to lookup volume data. If no mountpoint is set the volume name is used as data path.
  • mountpoint: volume mount point and volume data path
  • fullsize: takes all space True|False
  • attributes: list of volume attributes handled via chattr
Returns
Contains list of volume_type tuples

.. code:: python

    [
        volume_type(
            name=volume_name,
            parent=volume_parent,
            size=volume_size,
            realpath=path,
            mountpoint=path,
            fullsize=True,
            label=volume_label,
            attributes=['no-copy-on-write'],
            is_root_volume=True|False
        )
    ]
def get_volume_management(self) -> Optional[str]:
2185    def get_volume_management(self) -> Optional[str]:
2186        """
2187        Provides information which volume management system is used
2188
2189        :return: name of volume manager
2190
2191        :rtype: str
2192        """
2193        volume_filesystems = ['btrfs']
2194        selected_filesystem = self.build_type.get_filesystem()
2195        selected_system_disk = self.get_build_type_system_disk_section()
2196        volume_management = None
2197        if selected_system_disk and selected_system_disk.get_preferlvm():
2198            # LVM volume management is preferred, use it
2199            volume_management = 'lvm'
2200        elif selected_filesystem in volume_filesystems and selected_system_disk:
2201            # specified filesystem has its own volume management system
2202            volume_management = selected_filesystem
2203        elif selected_system_disk:
2204            # systemdisk section is specified with non volume capable
2205            # filesystem and no volume management preference. So let's
2206            # use LVM by default
2207            volume_management = 'lvm'
2208        return volume_management

Provides information which volume management system is used

Returns

name of volume manager

def get_drivers_list(self) -> List:
2210    def get_drivers_list(self) -> List:
2211        """
2212        List of driver names from all drivers sections matching
2213        configured profiles
2214
2215        :return: driver names
2216
2217        :rtype: list
2218        """
2219        drivers_sections = self._profiled(
2220            self.xml_data.get_drivers()
2221        )
2222        result = []
2223        if drivers_sections:
2224            for driver in drivers_sections:
2225                for file_section in driver.get_file():
2226                    result.append(file_section.get_name())
2227        return result

List of driver names from all drivers sections matching configured profiles

Returns

driver names

def get_strip_list(self, section_type: str) -> List:
2229    def get_strip_list(self, section_type: str) -> List:
2230        """
2231        List of strip names matching the given section type
2232        and profiles
2233
2234        :param str section_type: type name from packages section
2235
2236        :return: strip names
2237
2238        :rtype: list
2239        """
2240        strip_sections = self._profiled(
2241            self.xml_data.get_strip()
2242        )
2243        result = []
2244        if strip_sections:
2245            for strip in strip_sections:
2246                if strip.get_type() == section_type:
2247                    for file_section in strip.get_file():
2248                        result.append(file_section.get_name())
2249        return result

List of strip names matching the given section type and profiles

Parameters
  • str section_type: type name from packages section
Returns

strip names

def get_strip_files_to_delete(self) -> List:
2251    def get_strip_files_to_delete(self) -> List:
2252        """
2253        Items to delete from strip section
2254
2255        :return: item names
2256
2257        :rtype: list
2258        """
2259        return self.get_strip_list('delete')

Items to delete from strip section

Returns

item names

def get_strip_tools_to_keep(self) -> List:
2261    def get_strip_tools_to_keep(self) -> List:
2262        """
2263        Tools to keep from strip section
2264
2265        :return: tool names
2266
2267        :rtype: list
2268        """
2269        return self.get_strip_list('tools')

Tools to keep from strip section

Returns

tool names

def get_strip_libraries_to_keep(self) -> List:
2271    def get_strip_libraries_to_keep(self) -> List:
2272        """
2273        Libraries to keep from strip section
2274
2275        :return: librarie names
2276
2277        :rtype: list
2278        """
2279        return self.get_strip_list('libs')

Libraries to keep from strip section

Returns

librarie names

def get_include_section_reference_file_names(self) -> List[str]:
2281    def get_include_section_reference_file_names(self) -> List[str]:
2282        """
2283        List of all <include> section file name references
2284
2285        :return: List[str]
2286
2287        :rtype: list
2288        """
2289        include_files = []
2290        for include in self.xml_data.get_include():
2291            include_files.append(include.get_from())
2292        return include_files

List of all section file name references

Returns

List[str]

def get_repository_sections(self) -> List:
2294    def get_repository_sections(self) -> List:
2295        """
2296        List of all repository sections for the selected profiles that
2297        matches the host architecture
2298
2299        :return: <repository> section reference(s)
2300
2301        :rtype: list
2302        """
2303        repository_list = []
2304        for repository in self._profiled(self.xml_data.get_repository()):
2305            if self.repository_matches_host_architecture(repository):
2306                repository_list.append(repository)
2307        return repository_list

List of all repository sections for the selected profiles that matches the host architecture

Returns

section reference(s)

def get_containers_sections(self) -> List:
2309    def get_containers_sections(self) -> List:
2310        """
2311        List of all containers sections for the selected profiles that
2312        matches the host architecture
2313
2314        :return: <containers> section reference(s)
2315
2316        :rtype: list
2317        """
2318        containers_list = []
2319        for containers in self._profiled(self.xml_data.get_containers()):
2320            if self.containers_matches_host_architecture(containers):
2321                containers_list.append(containers)
2322        return containers_list

List of all containers sections for the selected profiles that matches the host architecture

Returns

section reference(s)

def get_repository_sections_used_for_build(self) -> List:
2324    def get_repository_sections_used_for_build(self) -> List:
2325        """
2326        List of all repositorys sections used to build the image and
2327        matching configured profiles.
2328
2329        :return: <repository> section reference(s)
2330
2331        :rtype: list
2332        """
2333        repos = self.get_repository_sections()
2334        return list(
2335            repo for repo in repos if not repo.get_imageonly()
2336        )

List of all repositorys sections used to build the image and matching configured profiles.

Returns

section reference(s)

def get_repository_sections_used_in_image(self) -> List:
2338    def get_repository_sections_used_in_image(self) -> List:
2339        """
2340        List of all repositorys sections to be configured in the resulting
2341        image matching configured profiles.
2342
2343        :return: <repository> section reference(s)
2344
2345        :rtype: list
2346        """
2347        repos = self.get_repository_sections()
2348        return list(
2349            repo for repo in repos
2350            if repo.get_imageinclude() or repo.get_imageonly()
2351        )

List of all repositorys sections to be configured in the resulting image matching configured profiles.

Returns

section reference(s)

def delete_repository_sections(self) -> None:
2353    def delete_repository_sections(self) -> None:
2354        """
2355        Delete all repository sections matching configured profiles
2356        """
2357        self.xml_data.set_repository([])

Delete all repository sections matching configured profiles

def delete_repository_sections_used_for_build(self) -> None:
2359    def delete_repository_sections_used_for_build(self) -> None:
2360        """
2361        Delete all repository sections used to build the image matching
2362        configured profiles
2363        """
2364        used_for_build = self.get_repository_sections_used_for_build()
2365        all_repos = self.get_repository_sections()
2366        self.xml_data.set_repository(
2367            [
2368                repo for repo in all_repos if repo not in used_for_build
2369            ]
2370        )

Delete all repository sections used to build the image matching configured profiles

def get_repositories_signing_keys(self) -> List[str]:
2372    def get_repositories_signing_keys(self) -> List[str]:
2373        """
2374        Get list of signing keys specified on the repositories
2375        """
2376        key_file_list: List[str] = []
2377        release_version = self.get_release_version()
2378        release_vars = [
2379            '$releasever',
2380            '${releasever}'
2381        ]
2382        for repository in self.get_repository_sections() or []:
2383            for signing in repository.get_source().get_signing() or []:
2384                normalized_key_url = Uri(signing.get_key()).translate()
2385                if release_version:
2386                    for release_var in release_vars:
2387                        if release_var in normalized_key_url:
2388                            normalized_key_url = normalized_key_url.replace(
2389                                release_var, release_version
2390                            )
2391                if normalized_key_url not in key_file_list:
2392                    key_file_list.append(normalized_key_url)
2393        return key_file_list

Get list of signing keys specified on the repositories

def set_repository( self, repo_source: str, repo_type: str, repo_alias: str, repo_prio: str, repo_imageinclude: bool = False, repo_package_gpgcheck: Optional[bool] = None, repo_signing_keys: List[str] = [], components: str = None, distribution: str = None, repo_gpgcheck: Optional[bool] = None, repo_sourcetype: str = None) -> None:
2395    def set_repository(
2396        self, repo_source: str, repo_type: str, repo_alias: str,
2397        repo_prio: str, repo_imageinclude: bool = False,
2398        repo_package_gpgcheck: Optional[bool] = None,
2399        repo_signing_keys: List[str] = [], components: str = None,
2400        distribution: str = None, repo_gpgcheck: Optional[bool] = None,
2401        repo_sourcetype: str = None
2402    ) -> None:
2403        """
2404        Overwrite repository data of the first repository
2405
2406        :param str repo_source: repository URI
2407        :param str repo_type: type name defined by schema
2408        :param str repo_alias: alias name
2409        :param str repo_prio: priority number, package manager specific
2410        :param bool repo_imageinclude: setup repository inside of the image
2411        :param bool repo_package_gpgcheck: enable/disable package gpg checks
2412        :param list repo_signing_keys: list of signing key file names
2413        :param str components: component names for debian repos
2414        :param str distribution: base distribution name for debian repos
2415        :param bool repo_gpgcheck: enable/disable repo gpg checks
2416        """
2417        repository_sections = self.get_repository_sections()
2418        if repository_sections:
2419            repository = repository_sections[0]
2420            if repo_alias:
2421                repository.set_alias(repo_alias)
2422            if repo_type:
2423                repository.set_type(repo_type)
2424            if repo_source:
2425                repository.get_source().set_path(repo_source)
2426            if repo_prio:
2427                repository.set_priority(int(repo_prio))
2428            if repo_imageinclude:
2429                repository.set_imageinclude(repo_imageinclude)
2430            if repo_package_gpgcheck is not None:
2431                repository.set_package_gpgcheck(repo_package_gpgcheck)
2432            if repo_signing_keys:
2433                repository.get_source().set_signing(
2434                    [xml_parse.signing(key=k) for k in repo_signing_keys]
2435                )
2436            if components:
2437                repository.set_components(components)
2438            if distribution:
2439                repository.set_distribution(distribution)
2440            if repo_gpgcheck is not None:
2441                repository.set_repository_gpgcheck(repo_gpgcheck)
2442            if repo_sourcetype:
2443                repository.set_sourcetype(repo_sourcetype)

Overwrite repository data of the first repository

Parameters
  • str repo_source: repository URI
  • str repo_type: type name defined by schema
  • str repo_alias: alias name
  • str repo_prio: priority number, package manager specific
  • bool repo_imageinclude: setup repository inside of the image
  • bool repo_package_gpgcheck: enable/disable package gpg checks
  • list repo_signing_keys: list of signing key file names
  • str components: component names for debian repos
  • str distribution: base distribution name for debian repos
  • bool repo_gpgcheck: enable/disable repo gpg checks
def add_repository( self, repo_source: str, repo_type: str, repo_alias: str = None, repo_prio: str = '', repo_imageinclude: bool = False, repo_package_gpgcheck: Optional[bool] = None, repo_signing_keys: List[str] = [], components: str = None, distribution: str = None, repo_gpgcheck: Optional[bool] = None, repo_sourcetype: str = None) -> None:
2445    def add_repository(
2446        self, repo_source: str, repo_type: str, repo_alias: str = None,
2447        repo_prio: str = '', repo_imageinclude: bool = False,
2448        repo_package_gpgcheck: Optional[bool] = None,
2449        repo_signing_keys: List[str] = [], components: str = None,
2450        distribution: str = None, repo_gpgcheck: Optional[bool] = None,
2451        repo_sourcetype: str = None
2452    ) -> None:
2453        """
2454        Add a new repository section at the end of the list
2455
2456        :param str repo_source: repository URI
2457        :param str repo_type: type name defined by schema
2458        :param str repo_alias: alias name
2459        :param str repo_prio: priority number, package manager specific
2460        :param bool repo_imageinclude: setup repository inside of the image
2461        :param bool repo_package_gpgcheck: enable/disable package gpg checks
2462        :param list repo_signing_keys: list of signing key file names
2463        :param str components: component names for debian repos
2464        :param str distribution: base distribution name for debian repos
2465        :param bool repo_gpgcheck: enable/disable repo gpg checks
2466        """
2467        priority_number: Optional[int] = None
2468        try:
2469            priority_number = int(repo_prio)
2470        except Exception:
2471            pass
2472
2473        self.xml_data.add_repository(
2474            xml_parse.repository(
2475                type_=repo_type,
2476                alias=repo_alias,
2477                priority=priority_number,
2478                source=xml_parse.source(
2479                    path=repo_source,
2480                    signing=[
2481                        xml_parse.signing(key=k) for k in repo_signing_keys
2482                    ]
2483                ),
2484                imageinclude=repo_imageinclude,
2485                package_gpgcheck=repo_package_gpgcheck,
2486                repository_gpgcheck=repo_gpgcheck,
2487                components=components,
2488                distribution=distribution,
2489                sourcetype=repo_sourcetype
2490            )
2491        )

Add a new repository section at the end of the list

Parameters
  • str repo_source: repository URI
  • str repo_type: type name defined by schema
  • str repo_alias: alias name
  • str repo_prio: priority number, package manager specific
  • bool repo_imageinclude: setup repository inside of the image
  • bool repo_package_gpgcheck: enable/disable package gpg checks
  • list repo_signing_keys: list of signing key file names
  • str components: component names for debian repos
  • str distribution: base distribution name for debian repos
  • bool repo_gpgcheck: enable/disable repo gpg checks
def add_certificate(self, cert_file: str, target_distribution: str) -> None:
2493    def add_certificate(self, cert_file: str, target_distribution: str) -> None:
2494        """
2495        Add <certificate name="cert_file"> to main <certificates> section
2496        The main section will be created if it does not exist. Also
2497        setup the target_distribution in the resulting main section.
2498        """
2499        certificates_section = self._profiled(
2500            self.xml_data.get_certificates()
2501        )
2502        if not certificates_section:
2503            self.xml_data.set_certificates(
2504                [
2505                    xml_parse.certificates(
2506                        target_distribution=target_distribution,
2507                        certificate=[xml_parse.certificate(name=cert_file)]
2508                    )
2509                ]
2510            )
2511        else:
2512            certificates_section[0].set_target_distribution(
2513                target_distribution
2514            )
2515            certificates_section[0].add_certificate(
2516                xml_parse.certificate(
2517                    name=cert_file
2518                )
2519            )

Add to main section The main section will be created if it does not exist. Also setup the target_distribution in the resulting main section.

def get_certificates(self) -> List[str]:
2521    def get_certificates(self) -> List[str]:
2522        """
2523        Read list of certificates
2524        """
2525        cert_list = []
2526        certificates_section = self._profiled(
2527            self.xml_data.get_certificates()
2528        )
2529        if certificates_section:
2530            for certificate in certificates_section[0].get_certificate():
2531                cert_list.append(certificate.get_name())
2532        return sorted(list(set(cert_list)))

Read list of certificates

def get_certificates_target_distribution(self) -> str:
2534    def get_certificates_target_distribution(self) -> str:
2535        """
2536        Read CA target distribution
2537        """
2538        target_distribution = ''
2539        certificates_section = self._profiled(
2540            self.xml_data.get_certificates()
2541        )
2542        if certificates_section:
2543            target_distribution = \
2544                certificates_section[0].get_target_distribution()
2545        return target_distribution

Read CA target distribution

def resolve_this_path(self) -> None:
2547    def resolve_this_path(self) -> None:
2548        """
2549        Resolve any this:// repo source path into the path
2550        representing the target inside of the image description
2551        directory
2552        """
2553        for repository in self.get_repository_sections() or []:
2554            repo_source = repository.get_source()
2555            repo_path = repo_source.get_path()
2556            if repo_path.startswith('this://'):
2557                repo_path = repo_path.replace('this://', '')
2558                repo_source.set_path(
2559                    'dir://{0}'.format(
2560                        os.path.realpath(
2561                            os.path.join(
2562                                self.xml_data.description_dir, repo_path
2563                            )
2564                        )
2565                    )
2566                )

Resolve any this:// repo source path into the path representing the target inside of the image description directory

def copy_displayname(self, target_state: Any) -> None:
2568    def copy_displayname(self, target_state: Any) -> None:
2569        """
2570        Copy image displayname from this xml state to the target xml state
2571
2572        :param object target_state: XMLState instance
2573        """
2574        displayname = self.xml_data.get_displayname()
2575        if displayname:
2576            target_state.xml_data.set_displayname(displayname)

Copy image displayname from this xml state to the target xml state

Parameters
  • object target_state: XMLState instance
def copy_name(self, target_state: Any) -> None:
2578    def copy_name(self, target_state: Any) -> None:
2579        """
2580        Copy image name from this xml state to the target xml state
2581
2582        :param object target_state: XMLState instance
2583        """
2584        target_state.xml_data.set_name(
2585            self.xml_data.get_name()
2586        )

Copy image name from this xml state to the target xml state

Parameters
  • object target_state: XMLState instance
def copy_drivers_sections(self, target_state: Any) -> None:
2588    def copy_drivers_sections(self, target_state: Any) -> None:
2589        """
2590        Copy drivers sections from this xml state to the target xml state
2591
2592        :param object target_state: XMLState instance
2593        """
2594        drivers_sections = self._profiled(
2595            self.xml_data.get_drivers()
2596        )
2597        if drivers_sections:
2598            for drivers_section in drivers_sections:
2599                target_state.xml_data.add_drivers(drivers_section)

Copy drivers sections from this xml state to the target xml state

Parameters
  • object target_state: XMLState instance
def copy_systemdisk_section(self, target_state: Any) -> None:
2601    def copy_systemdisk_section(self, target_state: Any) -> None:
2602        """
2603        Copy systemdisk sections from this xml state to the target xml state
2604
2605        :param object target_state: XMLState instance
2606        """
2607        systemdisk_section = self.get_build_type_system_disk_section()
2608        if systemdisk_section:
2609            target_state.build_type.set_systemdisk(
2610                [systemdisk_section]
2611            )

Copy systemdisk sections from this xml state to the target xml state

Parameters
  • object target_state: XMLState instance
def copy_strip_sections(self, target_state: Any) -> None:
2613    def copy_strip_sections(self, target_state: Any) -> None:
2614        """
2615        Copy strip sections from this xml state to the target xml state
2616
2617        :param object target_state: XMLState instance
2618        """
2619        strip_sections = self._profiled(
2620            self.xml_data.get_strip()
2621        )
2622        if strip_sections:
2623            for strip_section in strip_sections:
2624                target_state.xml_data.add_strip(strip_section)

Copy strip sections from this xml state to the target xml state

Parameters
  • object target_state: XMLState instance
def copy_machine_section(self, target_state: Any) -> None:
2626    def copy_machine_section(self, target_state: Any) -> None:
2627        """
2628        Copy machine sections from this xml state to the target xml state
2629
2630        :param object target_state: XMLState instance
2631        """
2632        machine_section = self.get_build_type_machine_section()
2633        if machine_section:
2634            target_state.build_type.set_machine(
2635                [machine_section]
2636            )

Copy machine sections from this xml state to the target xml state

Parameters
  • object target_state: XMLState instance
def copy_bootloader_section(self, target_state: Any) -> None:
2638    def copy_bootloader_section(self, target_state: Any) -> None:
2639        """
2640        Copy bootloader section from this xml state to the target xml state
2641
2642        :param object target_state: XMLState instance
2643        """
2644        bootloader_section = self.get_build_type_bootloader_section()
2645        if bootloader_section:
2646            target_state.build_type.set_bootloader(
2647                [bootloader_section]
2648            )

Copy bootloader section from this xml state to the target xml state

Parameters
  • object target_state: XMLState instance
def copy_oemconfig_section(self, target_state: Any) -> None:
2650    def copy_oemconfig_section(self, target_state: Any) -> None:
2651        """
2652        Copy oemconfig sections from this xml state to the target xml state
2653
2654        :param object target_state: XMLState instance
2655        """
2656        oemconfig_section = self.get_build_type_oemconfig_section()
2657        if oemconfig_section:
2658            target_state.build_type.set_oemconfig(
2659                [oemconfig_section]
2660            )

Copy oemconfig sections from this xml state to the target xml state

Parameters
  • object target_state: XMLState instance
def copy_repository_sections(self, target_state: Any, wipe: bool = False) -> None:
2662    def copy_repository_sections(
2663        self, target_state: Any, wipe: bool = False
2664    ) -> None:
2665        """
2666        Copy repository sections from this xml state to the target xml state
2667
2668        :param object target_state: XMLState instance
2669        :param bool wipe: delete all repos in target prior to copy
2670        """
2671        repository_sections = self._profiled(
2672            self.xml_data.get_repository()
2673        )
2674        if repository_sections:
2675            if wipe:
2676                target_state.xml_data.set_repository([])
2677            for repository_section in repository_sections:
2678                repository_copy = copy.deepcopy(repository_section)
2679                # profiles are not copied because they might not exist
2680                # in the target description
2681                repository_copy.set_profiles(None)
2682                target_state.xml_data.add_repository(repository_copy)

Copy repository sections from this xml state to the target xml state

Parameters
  • object target_state: XMLState instance
  • bool wipe: delete all repos in target prior to copy
def copy_preferences_subsections(self, section_names: List, target_state: Any) -> None:
2684    def copy_preferences_subsections(
2685        self, section_names: List, target_state: Any
2686    ) -> None:
2687        """
2688        Copy subsections of the preferences sections, matching given
2689        section names, from this xml state to the target xml state
2690
2691        :param list section_names: preferences subsection names
2692        :param object target_state: XMLState instance
2693        """
2694        target_preferences_sections = target_state.get_preferences_sections()
2695        if target_preferences_sections:
2696            target_preferences_section = target_preferences_sections[0]
2697            for preferences_section in self.get_preferences_sections():
2698                for section_name in section_names:
2699                    get_section_method = getattr(
2700                        preferences_section, 'get_' + section_name
2701                    )
2702                    section = get_section_method()
2703                    if section:
2704                        set_section_method = getattr(
2705                            target_preferences_section, 'set_' + section_name
2706                        )
2707                        set_section_method(section)

Copy subsections of the preferences sections, matching given section names, from this xml state to the target xml state

Parameters
  • list section_names: preferences subsection names
  • object target_state: XMLState instance
def copy_build_type_attributes(self, attribute_names: List, target_state: Any) -> None:
2709    def copy_build_type_attributes(
2710        self, attribute_names: List, target_state: Any
2711    ) -> None:
2712        """
2713        Copy specified attributes from this build type section to the
2714        target xml state build type section
2715
2716        :param list attribute_names: type section attributes
2717        :param object target_state: XMLState instance
2718        """
2719        for attribute in attribute_names:
2720            get_type_method = getattr(
2721                self.build_type, 'get_' + attribute
2722            )
2723            attribute_value = get_type_method()
2724            if attribute_value:
2725                set_type_method = getattr(
2726                    target_state.build_type, 'set_' + attribute
2727                )
2728                set_type_method(attribute_value)

Copy specified attributes from this build type section to the target xml state build type section

Parameters
  • list attribute_names: type section attributes
  • object target_state: XMLState instance
def copy_bootincluded_packages(self, target_state: Any) -> None:
2730    def copy_bootincluded_packages(self, target_state: Any) -> None:
2731        """
2732        Copy packages marked as bootinclude to the packages
2733        type=bootstrap section in the target xml state. The package
2734        will also be removed from the packages type=delete section
2735        in the target xml state if present there
2736
2737        :param object target_state: XMLState instance
2738        """
2739        target_packages_sections = \
2740            target_state.get_bootstrap_packages_sections()
2741        if target_packages_sections:
2742            target_packages_section = \
2743                target_packages_sections[0]
2744            package_names_added = []
2745            packages_sections = self.get_packages_sections(
2746                ['image', 'bootstrap', self.get_build_type_name()]
2747            )
2748            package_list = self.get_package_sections(
2749                packages_sections
2750            )
2751            if package_list:
2752                for package in package_list:
2753                    if package.package_section.get_bootinclude():
2754                        target_packages_section.add_package(
2755                            xml_parse.package(
2756                                name=package.package_section.get_name()
2757                            )
2758                        )
2759                        package_names_added.append(
2760                            package.package_section.get_name()
2761                        )
2762            delete_packages_sections = target_state.get_packages_sections(
2763                ['delete']
2764            )
2765            package_list = self.get_package_sections(
2766                delete_packages_sections
2767            )
2768            if package_list:
2769                for package in package_list:
2770                    package_name = package.package_section.get_name()
2771                    if package_name in package_names_added:
2772                        package.packages_section.package.remove(
2773                            package.package_section
2774                        )

Copy packages marked as bootinclude to the packages type=bootstrap section in the target xml state. The package will also be removed from the packages type=delete section in the target xml state if present there

Parameters
  • object target_state: XMLState instance
def copy_bootincluded_archives(self, target_state: Any) -> None:
2776    def copy_bootincluded_archives(self, target_state: Any) -> None:
2777        """
2778        Copy archives marked as bootinclude to the packages type=bootstrap
2779        section in the target xml state
2780
2781        :param object target_state: XMLState instance
2782        """
2783        target_bootstrap_packages_sections = \
2784            target_state.get_bootstrap_packages_sections()
2785        if target_bootstrap_packages_sections:
2786            target_bootstrap_packages_section = \
2787                target_bootstrap_packages_sections[0]
2788            packages_sections = self.get_packages_sections(
2789                ['image', 'bootstrap', self.get_build_type_name()]
2790            )
2791            for packages_section in packages_sections:
2792                archive_list = packages_section.get_archive()
2793                if archive_list:
2794                    for archive in archive_list:
2795                        if archive.get_bootinclude():
2796                            target_bootstrap_packages_section.add_archive(
2797                                xml_parse.archive(
2798                                    name=archive.get_name()
2799                                )
2800                            )

Copy archives marked as bootinclude to the packages type=bootstrap section in the target xml state

Parameters
  • object target_state: XMLState instance
def copy_bootdelete_packages(self, target_state: Any) -> None:
2802    def copy_bootdelete_packages(self, target_state: Any) -> None:
2803        """
2804        Copy packages marked as bootdelete to the packages type=delete
2805        section in the target xml state
2806
2807        :param object target_state: XMLState instance
2808        """
2809        target_delete_packages_sections = target_state.get_packages_sections(
2810            ['delete']
2811        )
2812        if not target_delete_packages_sections:
2813            target_delete_packages_sections = [
2814                xml_parse.packages(type_='delete')
2815            ]
2816            target_state.xml_data.add_packages(
2817                target_delete_packages_sections[0]
2818            )
2819
2820        target_delete_packages_section = \
2821            target_delete_packages_sections[0]
2822        packages_sections = self.get_packages_sections(
2823            ['image', 'bootstrap', self.get_build_type_name()]
2824        )
2825        package_list = self.get_package_sections(
2826            packages_sections
2827        )
2828        if package_list:
2829            for package in package_list:
2830                if package.package_section.get_bootdelete():
2831                    target_delete_packages_section.add_package(
2832                        xml_parse.package(
2833                            name=package.package_section.get_name()
2834                        )
2835                    )

Copy packages marked as bootdelete to the packages type=delete section in the target xml state

Parameters
  • object target_state: XMLState instance
def get_distribution_name_from_boot_attribute(self) -> str:
2837    def get_distribution_name_from_boot_attribute(self) -> str:
2838        """
2839        Extract the distribution name from the boot attribute of the
2840        build type section.
2841
2842        If no boot attribute is configured or the contents does not
2843        match the kiwi defined naming schema for boot image descriptions,
2844        an exception is thrown
2845
2846        :return: lowercase distribution name
2847
2848        :rtype: str
2849        """
2850        boot_attribute = self.build_type.get_boot()
2851        if not boot_attribute:
2852            raise KiwiDistributionNameError(
2853                'No boot attribute to extract distribution name from found'
2854            )
2855        boot_attribute_format = '^.*-(.*)$'
2856        boot_attribute_expression = re.match(
2857            boot_attribute_format, boot_attribute
2858        )
2859        if not boot_attribute_expression:
2860            raise KiwiDistributionNameError(
2861                'Boot attribute "%s" does not match expected format %s' %
2862                (boot_attribute, boot_attribute_format)
2863            )
2864        return boot_attribute_expression.group(1).lower()

Extract the distribution name from the boot attribute of the build type section.

If no boot attribute is configured or the contents does not match the kiwi defined naming schema for boot image descriptions, an exception is thrown

Returns

lowercase distribution name

def get_fs_mount_option_list(self) -> List:
2866    def get_fs_mount_option_list(self) -> List:
2867        """
2868        List of root filesystem mount options
2869
2870        The list contains one element with the information from the
2871        fsmountoptions attribute. The value there is passed along to
2872        the -o mount option
2873
2874        :return: max one element list with mount option string
2875
2876        :rtype: list
2877        """
2878        option_list = []
2879        mount_options = self.build_type.get_fsmountoptions()
2880        if mount_options:
2881            option_list = [mount_options]
2882
2883        return option_list

List of root filesystem mount options

The list contains one element with the information from the fsmountoptions attribute. The value there is passed along to the -o mount option

Returns

max one element list with mount option string

def get_fs_create_option_list(self) -> List:
2885    def get_fs_create_option_list(self) -> List:
2886        """
2887        List of root filesystem creation options
2888
2889        The list contains elements with the information from the
2890        fscreateoptions attribute string that got split into its
2891        substring components
2892
2893        :return: list with create options
2894
2895        :rtype: list
2896        """
2897        option_list = []
2898        create_options = self.build_type.get_fscreateoptions()
2899        if create_options:
2900            option_list = create_options.split()
2901
2902        return option_list

List of root filesystem creation options

The list contains elements with the information from the fscreateoptions attribute string that got split into its substring components

Returns

list with create options

def get_luks_credentials(self) -> Optional[str]:
2904    def get_luks_credentials(self) -> Optional[str]:
2905        """
2906        Return key or passphrase credentials to open the luks pool
2907
2908        :return: data
2909
2910        :rtype: str
2911        """
2912        data = self.build_type.get_luks()
2913        if data:
2914            keyfile_name = None
2915            try:
2916                # try to interpret data as an URI
2917                uri = Uri(data)
2918                if not uri.is_remote():
2919                    keyfile_name = uri.translate()
2920            except Exception:
2921                # this doesn't look like a valid URI, continue as just data
2922                pass
2923            if keyfile_name:
2924                try:
2925                    with open(keyfile_name) as keyfile:
2926                        return keyfile.read()
2927                except Exception as issue:
2928                    raise KiwiFileAccessError(
2929                        f'Failed to read from {keyfile_name!r}: {issue}'
2930                    )
2931        return data

Return key or passphrase credentials to open the luks pool

Returns

data

def get_luks_format_options(self) -> List[str]:
2933    def get_luks_format_options(self) -> List[str]:
2934        """
2935        Return list of luks format options
2936
2937        :return: list of options
2938
2939        :rtype: list
2940        """
2941        result = []
2942        luksversion = self.build_type.get_luks_version()
2943        luksformat = self.build_type.get_luksformat()
2944        luks_pbkdf = self.build_type.get_luks_pbkdf()
2945        if luksversion:
2946            result.append('--type')
2947            result.append(luksversion)
2948        if luksformat:
2949            for option in luksformat[0].get_option():
2950                result.append(option.get_name())
2951                if option.get_value():
2952                    result.append(option.get_value())
2953        if luks_pbkdf:
2954            # Allow to override the pbkdf algorithm that cryptsetup
2955            # uses by default. Cryptsetup may use argon2i by default,
2956            # which is not supported by all bootloaders.
2957            result.append('--pbkdf')
2958            result.append(luks_pbkdf)
2959        return result

Return list of luks format options

Returns

list of options

def get_derived_from_image_uri(self) -> List[kiwi.system.uri.Uri]:
2961    def get_derived_from_image_uri(self) -> List[Uri]:
2962        """
2963        Uri object(s) of derived image if configured
2964
2965        Specific image types can be based on one ore more derived
2966        images. This method returns the location of this image(s)
2967        when configured in the XML description
2968
2969        :return: List of Uri instances
2970
2971        :rtype: list
2972        """
2973        image_uris = []
2974        derived_images = self.build_type.get_derived_from()
2975        if derived_images:
2976            for derived_image in derived_images.split(','):
2977                image_uris.append(
2978                    Uri(derived_image, repo_type='container')
2979                )
2980        return image_uris

Uri object(s) of derived image if configured

Specific image types can be based on one ore more derived images. This method returns the location of this image(s) when configured in the XML description

Returns

List of Uri instances

def set_derived_from_image_uri(self, uri: str) -> None:
2982    def set_derived_from_image_uri(self, uri: str) -> None:
2983        """
2984        Set derived_from attribute to a new value
2985
2986        In order to set a new value the derived_from attribute
2987        must be already present in the image configuration
2988
2989        :param str uri: URI
2990        """
2991        if self.build_type.get_derived_from():
2992            self.build_type.set_derived_from(uri)
2993        else:
2994            message = dedent('''\n
2995                No derived_from attribute configured in image <type>
2996
2997                In order to set the uri {0} as base container reference
2998                an initial derived_from attribute must be set in the
2999                type section
3000            ''')
3001            log.warning(message.format(uri))

Set derived_from attribute to a new value

In order to set a new value the derived_from attribute must be already present in the image configuration

Parameters
  • str uri: URI
def set_root_partition_uuid(self, uuid: str) -> None:
3003    def set_root_partition_uuid(self, uuid: str) -> None:
3004        """
3005        Store PARTUUID provided in uuid as state information
3006
3007        :param str uuid: PARTUUID
3008        """
3009        self.root_partition_uuid = uuid

Store PARTUUID provided in uuid as state information

Parameters
  • str uuid: PARTUUID
def get_root_partition_uuid(self) -> Optional[str]:
3011    def get_root_partition_uuid(self) -> Optional[str]:
3012        """
3013        Return preserved PARTUUID
3014        """
3015        return self.root_partition_uuid

Return preserved PARTUUID

def set_root_filesystem_uuid(self, uuid: str) -> None:
3017    def set_root_filesystem_uuid(self, uuid: str) -> None:
3018        """
3019        Store UUID provided in uuid as state information
3020
3021        :param str uuid: UUID
3022        """
3023        self.root_filesystem_uuid = uuid

Store UUID provided in uuid as state information

Parameters
  • str uuid: UUID
def get_root_filesystem_uuid(self) -> Optional[str]:
3025    def get_root_filesystem_uuid(self) -> Optional[str]:
3026        """
3027        Return preserved UUID
3028        """
3029        return self.root_filesystem_uuid

Return preserved UUID

@staticmethod
def get_archives_target_dirs(packages_sections_names: Optional[List[kiwi.xml_parse.packages]]) -> Dict:
3031    @staticmethod
3032    def get_archives_target_dirs(
3033        packages_sections_names: Optional[List[xml_parse.packages]]
3034    ) -> Dict:
3035        """
3036        Dict of archive names and target dirs for packages section(s), if any
3037        :return: archive names and its target dir
3038        :rtype: dict
3039        """
3040        result = {}
3041        if packages_sections_names:
3042            for package_section_name in packages_sections_names:
3043                for archive in package_section_name.get_archive():
3044                    result[archive.get_name().strip()] = archive.get_target_dir()
3045
3046        return result

Dict of archive names and target dirs for packages section(s), if any

Returns

archive names and its target dir

def get_bootstrap_archives_target_dirs(self) -> Dict:
3048    def get_bootstrap_archives_target_dirs(self) -> Dict:
3049        """
3050        Dict of archive names and target dirs from the type="bootstrap"
3051        packages section(s)
3052        :return: archive names and its target dir
3053        :rtype: dict
3054        """
3055        return self.get_archives_target_dirs(
3056            self.get_packages_sections(['bootstrap'])
3057        )

Dict of archive names and target dirs from the type="bootstrap" packages section(s)

Returns

archive names and its target dir

def get_system_archives_target_dirs(self) -> Dict:
3059    def get_system_archives_target_dirs(self) -> Dict:
3060        """
3061        Dict of archive names and its target dir from the packages sections matching
3062        type="image" and type=build_type
3063        :return: archive names and its target dir
3064        :rtype: dict
3065        """
3066        return self.get_archives_target_dirs(
3067            self.get_packages_sections(['image', self.get_build_type_name()])
3068        )

Dict of archive names and its target dir from the packages sections matching type="image" and type=build_type

Returns

archive names and its target dir