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_video_mode(self) -> str:
1240        """
1241        Return bootloader video mode for the selected build type.
1242        If not specified the method returns "auto" as default
1243        mode name.
1244
1245        :return: video mode name or auto
1246
1247        :rtype: str
1248        """
1249        bootloader = self.get_build_type_bootloader_section()
1250        if bootloader:
1251            return bootloader.get_video_mode()
1252        return 'auto'
1253
1254    def get_build_type_bootloader_timeout(self) -> Optional[str]:
1255        """
1256        Return bootloader timeout setting for selected build type
1257
1258        :return: timeout string
1259
1260        :rtype: str
1261        """
1262        bootloader = self.get_build_type_bootloader_section()
1263        if bootloader:
1264            return bootloader.get_timeout()
1265        return None
1266
1267    def get_build_type_bootloader_timeout_style(self) -> Optional[str]:
1268        """
1269        Return bootloader timeout style setting for selected build type
1270
1271        :return: timeout_style string
1272
1273        :rtype: str
1274        """
1275        bootloader = self.get_build_type_bootloader_section()
1276        if bootloader:
1277            return bootloader.get_timeout_style()
1278        return None
1279
1280    def get_build_type_bootloader_targettype(self) -> Optional[str]:
1281        """
1282        Return bootloader target type setting. Only relevant for
1283        the zipl bootloader because zipl is installed differently
1284        depending on the storage target it runs later
1285
1286        :return: target type string
1287
1288        :rtype: str
1289        """
1290        bootloader = self.get_build_type_bootloader_section()
1291        if bootloader:
1292            return bootloader.get_targettype()
1293        return None
1294
1295    def get_build_type_bootloader_settings_section(self) -> Any:
1296        """
1297        First bootloadersettings section from the build
1298        type bootloader section
1299
1300        :return: <bootloadersettings> section reference
1301
1302        :rtype: xml_parse::bootloadersettings
1303        """
1304        bootloader_section = self.get_build_type_bootloader_section()
1305        bootloader_settings_section = None
1306        if bootloader_section and bootloader_section.get_bootloadersettings():
1307            bootloader_settings_section = \
1308                bootloader_section.get_bootloadersettings()[0]
1309        return bootloader_settings_section
1310
1311    def get_build_type_bootloader_securelinux_section(self) -> List[Any]:
1312        """
1313        First securelinux section from the build
1314        type bootloader section
1315
1316        :return: <securelinux> section reference
1317
1318        :rtype: xml_parse::securelinux
1319        """
1320        bootloader_section = self.get_build_type_bootloader_section()
1321        bootloader_securelinux_section = []
1322        if bootloader_section and bootloader_section.get_securelinux():
1323            bootloader_securelinux_section = \
1324                bootloader_section.get_securelinux()
1325        return bootloader_securelinux_section
1326
1327    def get_build_type_bootloader_environment_variables(self) -> List[str]:
1328        """
1329        List of bootloader variables from the build
1330        type > bootloader > bootloadersettings section
1331        """
1332        variable_list = []
1333        bootloader_settings_section = \
1334            self.get_build_type_bootloader_settings_section()
1335        if bootloader_settings_section:
1336            environment = bootloader_settings_section.get_environment()
1337            if environment and environment[0].get_env():
1338                for env in environment[0].get_env():
1339                    variable_list.append(
1340                        '{}{}'.format(
1341                            env.get_name(),
1342                            f'={env.get_value()}' if env.get_value() else ''
1343                        )
1344                    )
1345        return variable_list
1346
1347    def get_bootloader_options(self, option_type: str) -> List[str]:
1348        """
1349        List of custom options used in the process to
1350        run bootloader setup workloads
1351        """
1352        result: List[str] = []
1353        bootloader_settings = self.get_build_type_bootloader_settings_section()
1354        if bootloader_settings:
1355            options = []
1356            if option_type == 'shim':
1357                options = bootloader_settings.get_shimoption()
1358            elif option_type == 'install':
1359                options = bootloader_settings.get_installoption()
1360            elif option_type == 'config':
1361                options = bootloader_settings.get_configoption()
1362            for option in options:
1363                result.append(option.get_name())
1364                if option.get_value():
1365                    result.append(option.get_value())
1366        return result
1367
1368    def get_bootloader_shim_options(self) -> List[str]:
1369        """
1370        List of custom options used in the process to setup secure boot
1371        """
1372        return self.get_bootloader_options('shim')
1373
1374    def get_bootloader_install_options(self) -> List[str]:
1375        """
1376        List of custom options used in the bootloader installation
1377        """
1378        return self.get_bootloader_options('install')
1379
1380    def get_bootloader_config_options(self) -> List[str]:
1381        """
1382        List of custom options used in the bootloader configuration
1383        """
1384        return self.get_bootloader_options('config')
1385
1386    def get_build_type_bootloader_use_disk_password(self) -> bool:
1387        """
1388        Indicate whether the bootloader configuration should use the
1389        password protecting the encrypted root volume.
1390
1391        :return: True|False
1392
1393        :rtype: bool
1394        """
1395        bootloader = self.get_build_type_bootloader_section()
1396        if bootloader:
1397            return bootloader.get_use_disk_password()
1398        return False
1399
1400    def get_build_type_oemconfig_section(self) -> Any:
1401        """
1402        First oemconfig section from the build type section
1403
1404        :return: <oemconfig> section reference
1405
1406        :rtype: xml_parse::oemconfig
1407        """
1408        oemconfig_sections = self.build_type.get_oemconfig()
1409        if oemconfig_sections:
1410            return oemconfig_sections[0]
1411        return None
1412
1413    def get_oemconfig_oem_resize(self) -> bool:
1414        """
1415        State value to activate/deactivate disk resize. Returns a
1416        boolean value if specified or True to set resize on by default
1417
1418        :return: Content of <oem-resize> section value
1419
1420        :rtype: bool
1421        """
1422        oemconfig = self.get_build_type_oemconfig_section()
1423        if oemconfig and oemconfig.get_oem_resize():
1424            return oemconfig.get_oem_resize()[0]
1425        else:
1426            return True
1427
1428    def get_oemconfig_oem_systemsize(self) -> int:
1429        """
1430        State value to retrieve root partition size
1431
1432        :return: Content of <oem-systemsize> section value
1433
1434        :rtype: int
1435        """
1436        oemconfig = self.get_build_type_oemconfig_section()
1437        if oemconfig and oemconfig.get_oem_systemsize():
1438            return int(oemconfig.get_oem_systemsize()[0])
1439        else:
1440            return 0
1441
1442    def get_oemconfig_oem_multipath_scan(self) -> bool:
1443        """
1444        State value to activate multipath maps. Returns a boolean
1445        value if specified or False
1446
1447        :return: Content of <oem-multipath-scan> section value
1448
1449        :rtype: bool
1450        """
1451        oemconfig = self.get_build_type_oemconfig_section()
1452        if oemconfig and oemconfig.get_oem_multipath_scan():
1453            return oemconfig.get_oem_multipath_scan()[0]
1454        return False
1455
1456    def get_oemconfig_swap_mbytes(self) -> Optional[int]:
1457        """
1458        Return swapsize in MB if requested or None
1459
1460        Operates on the value of oem-swap and if set to true
1461        returns the given size or the default value.
1462
1463        :return: Content of <oem-swapsize> section value or default
1464
1465        :rtype: int
1466        """
1467        oemconfig = self.get_build_type_oemconfig_section()
1468        if oemconfig and oemconfig.get_oem_swap():
1469            swap_requested = oemconfig.get_oem_swap()[0]
1470            if swap_requested:
1471                swapsize = oemconfig.get_oem_swapsize()
1472                if swapsize:
1473                    return swapsize[0]
1474                else:
1475                    return Defaults.get_swapsize_mbytes()
1476        return None
1477
1478    def get_oemconfig_swap_name(self) -> str:
1479        """
1480        Return the swap space name
1481
1482        Operates on the value of oem-swapname and if set
1483        returns the configured name or the default name: LVSwap
1484
1485        The name of the swap space is used only if the
1486        image is configured to use the LVM volume manager.
1487        In this case swap is a volume and the volume takes
1488        a name. In any other case the given name will have
1489        no effect.
1490
1491        :return: Content of <oem-swapname> section value or default
1492
1493        :rtype: str
1494        """
1495        oemconfig = self.get_build_type_oemconfig_section()
1496        if oemconfig and oemconfig.get_oem_swapname():
1497            return oemconfig.get_oem_swapname()[0]
1498        return 'LVSwap'
1499
1500    def get_build_type_containerconfig_section(self) -> Any:
1501        """
1502        First containerconfig section from the build type section
1503
1504        :return: <containerconfig> section reference
1505
1506        :rtype: xml_parse::containerconfig
1507        """
1508        container_config_sections = self.build_type.get_containerconfig()
1509        if container_config_sections:
1510            return container_config_sections[0]
1511        return None
1512
1513    def get_dracut_config(self, action: str) -> DracutT:
1514        """
1515        Get dracut initrd config for the specified action
1516        """
1517        uefi = False
1518        modules = []
1519        drivers = []
1520        initrd_sections = self.build_type.get_initrd()
1521        for initrd_section in initrd_sections:
1522            if initrd_section.get_action() == action:
1523                for dracut in initrd_section.get_dracut():
1524                    uefi = bool(dracut.get_uefi())
1525                    if dracut.get_module():
1526                        modules.append(dracut.get_module())
1527                    if dracut.get_driver():
1528                        drivers.append(dracut.get_driver())
1529        return DracutT(
1530            uefi=uefi, modules=modules, drivers=drivers
1531        )
1532
1533    def get_installmedia_initrd_modules(self, action: str) -> List[str]:
1534        """
1535        Gets the list of modules to append in installation initrds
1536
1537        :return: a list of dracut module names
1538
1539        :rtype: list
1540        """
1541        modules: List[str] = []
1542        installmedia = self.build_type.get_installmedia()
1543        if not installmedia:
1544            return modules
1545        initrd_sections = installmedia[0].get_initrd()
1546        for initrd_section in initrd_sections:
1547            if initrd_section.get_action() == action:
1548                for module in initrd_section.get_dracut():
1549                    if module.get_module():
1550                        modules.append(module.get_module())
1551        return modules
1552
1553    def get_installmedia_initrd_drivers(self, action: str) -> List[str]:
1554        """
1555        Gets the list of drivers to append in installation initrds
1556
1557        :return: a list of dracut driver names
1558
1559        :rtype: list
1560        """
1561        drivers: List[str] = []
1562        installmedia = self.build_type.get_installmedia()
1563        if not installmedia:
1564            return drivers
1565        initrd_sections = installmedia[0].get_initrd()
1566        for initrd_section in initrd_sections:
1567            if initrd_section.get_action() == action:
1568                for driver in initrd_section.get_dracut():
1569                    if driver.get_driver():
1570                        drivers.append(driver.get_driver())
1571        return drivers
1572
1573    def get_build_type_size(
1574        self, include_unpartitioned: bool = False
1575    ) -> Optional[size_type]:
1576        """
1577        Size information from the build type section.
1578        If no unit is set the value is treated as mbytes
1579
1580        :param bool include_unpartitioned: sets if the unpartitioned area
1581            should be included in the computed size or not
1582
1583        :return: mbytes
1584
1585        :rtype: int
1586        """
1587        size_section = self.build_type.get_size()
1588        if size_section:
1589            unit = size_section[0].get_unit()
1590            additive = size_section[0].get_additive()
1591            unpartitioned = size_section[0].get_unpartitioned()
1592            value = int(size_section[0].get_valueOf_())
1593            if not include_unpartitioned and unpartitioned is not None:
1594                value -= unpartitioned
1595            if unit == 'G':
1596                value *= 1024
1597            return size_type(
1598                mbytes=value, additive=additive
1599            )
1600        return None
1601
1602    def get_build_type_unpartitioned_bytes(self) -> int:
1603        """
1604        Size of the unpartitioned area for image in megabytes
1605
1606        :return: mbytes
1607
1608        :rtype: int
1609        """
1610        size_section = self.build_type.get_size()
1611        if size_section:
1612            unit = size_section[0].get_unit() or 'M'
1613            unpartitioned = size_section[0].get_unpartitioned() or 0
1614            return StringToSize.to_bytes('{0}{1}'.format(unpartitioned, unit))
1615        return 0
1616
1617    def get_disk_start_sector(self) -> int:
1618        """
1619        First disk sector number to be used by the first disk partition.
1620
1621        :return: number
1622
1623        :rtype: int
1624        """
1625        disk_start_sector = self.build_type.get_disk_start_sector()
1626        if disk_start_sector is None:
1627            disk_start_sector = Defaults.get_default_disk_start_sector()
1628        return disk_start_sector
1629
1630    def get_build_type_spare_part_size(self) -> Optional[int]:
1631        """
1632        Size information for the spare_part size from the build
1633        type. If no unit is set the value is treated as mbytes
1634
1635        :return: mbytes
1636
1637        :rtype: int
1638        """
1639        spare_part_size = self.build_type.get_spare_part()
1640        if spare_part_size:
1641            return self._to_mega_byte(spare_part_size)
1642        return None
1643
1644    def get_build_type_spare_part_fs_attributes(self) -> Optional[List]:
1645        """
1646        Build type specific list of filesystem attributes applied to
1647        the spare partition.
1648
1649        :return: list of strings or empty list
1650
1651        :rtype: list
1652        """
1653        spare_part_attributes = self.build_type.get_spare_part_fs_attributes()
1654        if spare_part_attributes:
1655            return spare_part_attributes.strip().split(',')
1656        return None
1657
1658    def get_build_type_format_options(self) -> Dict:
1659        """
1660        Disk format options returned as a dictionary
1661
1662        :return: format options
1663
1664        :rtype: dict
1665        """
1666        result = {}
1667        format_options = self.build_type.get_formatoptions()
1668        if format_options:
1669            for option in format_options.split(','):
1670                key_value_list = option.split('=')
1671                if len(key_value_list) == 2:
1672                    result[key_value_list[0]] = key_value_list[1]
1673                else:
1674                    result[key_value_list[0]] = None
1675        return result
1676
1677    def get_volume_group_name(self) -> str:
1678        """
1679        Volume group name from selected <systemdisk> section
1680
1681        :return: volume group name
1682
1683        :rtype: str
1684        """
1685        systemdisk_section = self.get_build_type_system_disk_section()
1686        volume_group_name = None
1687        if systemdisk_section:
1688            volume_group_name = systemdisk_section.get_name()
1689        if not volume_group_name:
1690            volume_group_name = Defaults.get_default_volume_group_name()
1691        return volume_group_name
1692
1693    def get_users(self) -> List:
1694        """
1695        List of configured users.
1696
1697        Each entry in the list is a single xml_parse::user instance.
1698
1699        :return: list of <user> section reference(s)
1700
1701        :rtype: list
1702        """
1703        users_list = []
1704        users_names_added = []
1705        for users_section in self.get_users_sections():
1706            for user in users_section.get_user():
1707                if user.get_name() not in users_names_added:
1708                    users_list.append(user)
1709                    users_names_added.append(user.get_name())
1710
1711        return users_list
1712
1713    def get_user_groups(self, user_name) -> List[str]:
1714        """
1715        List of group names matching specified user
1716
1717        Each entry in the list is the name of a group and optionally its
1718        group ID separated by a colon, that the specified user belongs to.
1719        The first item in the list is the login or primary group. The
1720        list will be empty if no groups are specified in the
1721        description file.
1722
1723        :return: groups data for the given user
1724
1725        :rtype: list
1726        """
1727        groups_list = []
1728        for users_section in self.get_users_sections():
1729            for user in users_section.get_user():
1730                if user.get_name() == user_name:
1731                    user_groups = user.get_groups()
1732                    if user_groups:
1733                        groups_list += user.get_groups().split(',')
1734
1735        # order of list items matter, thus we don't use set() here
1736        # better faster, nicer solutions welcome :)
1737        result_group_list = []
1738        for item in groups_list:
1739            if item not in result_group_list:
1740                result_group_list.append(item)
1741
1742        return result_group_list
1743
1744    def get_container_config(self) -> Dict:
1745        """
1746        Dictionary of containerconfig information
1747
1748        Takes attributes and subsection data from the selected
1749        <containerconfig> section and stores it in a dictionary
1750        """
1751        container_config = self._match_docker_base_data()
1752        container_config.update(
1753            self._match_docker_entrypoint()
1754        )
1755        container_config.update(
1756            self._match_docker_subcommand()
1757        )
1758        container_config.update(
1759            self._match_docker_expose_ports()
1760        )
1761        container_config.update(
1762            self._match_docker_volumes()
1763        )
1764        container_config.update(
1765            self._match_docker_stopsignal()
1766        )
1767        container_config.update(
1768            self._match_docker_environment()
1769        )
1770        container_config.update(
1771            self._match_docker_labels()
1772        )
1773        container_config.update(
1774            self._match_docker_history()
1775        )
1776
1777        desc = self.get_description_section()
1778        author_contact = "{0} <{1}>".format(desc.author, desc.contact)
1779        if 'history' not in container_config:
1780            container_config['history'] = {}
1781        if 'author' not in container_config['history']:
1782            container_config['history']['author'] = author_contact
1783        if 'maintainer' not in container_config:
1784            container_config['maintainer'] = author_contact
1785
1786        return container_config
1787
1788    def set_container_config_tag(self, tag: str) -> None:
1789        """
1790        Set new tag name in containerconfig section
1791
1792        In order to set a new tag value an existing containerconfig and
1793        tag setup is required
1794
1795        :param str tag: tag name
1796        """
1797        container_config_section = self.get_build_type_containerconfig_section()
1798        if container_config_section and container_config_section.get_tag():
1799            container_config_section.set_tag(tag)
1800        else:
1801            message = dedent('''\n
1802                No <containerconfig> section and/or tag attribute configured
1803
1804                In order to set the tag {0} as new container tag,
1805                an initial containerconfig section including a tag
1806                setup is required
1807            ''')
1808            log.warning(message.format(tag))
1809
1810    def add_container_config_label(self, label_name: str, value: str) -> None:
1811        """
1812        Adds a new label in the containerconfig section, if a label with the
1813        same name is already defined in containerconfig it gets overwritten by
1814        this method.
1815
1816        :param str label_name: the string representing the label name
1817        :param str value: the value of the label
1818        """
1819        if self.get_build_type_name() not in ['docker', 'oci']:
1820            message = dedent('''\n
1821                Labels can only be configured for container image types
1822                docker and oci.
1823            ''')
1824            log.warning(message)
1825            return
1826
1827        container_config_section = self.get_build_type_containerconfig_section()
1828        if not container_config_section:
1829            container_config_section = xml_parse.containerconfig(
1830                name=Defaults.get_default_container_name(),
1831                tag=Defaults.get_default_container_tag()
1832            )
1833            self.build_type.set_containerconfig([container_config_section])
1834
1835        labels = container_config_section.get_labels()
1836        if not labels:
1837            labels = [xml_parse.labels()]
1838
1839        label_names = []
1840        for label in labels[0].get_label():
1841            label_names.append(label.get_name())
1842
1843        if label_name in label_names:
1844            labels[0].replace_label_at(
1845                label_names.index(label_name),
1846                xml_parse.label(label_name, value)
1847            )
1848        else:
1849            labels[0].add_label(xml_parse.label(label_name, value))
1850
1851        container_config_section.set_labels(labels)
1852
1853    def get_partitions(self) -> Dict[str, ptable_entry_type]:
1854        """
1855        Dictionary of configured partitions.
1856
1857        Each entry in the dict references a ptable_entry_type
1858        Each key in the dict references the name of the
1859        partition entry as handled by KIWI
1860
1861        :return:
1862            Contains dict of ptable_entry_type tuples
1863
1864            .. code:: python
1865
1866                {
1867                    'NAME': ptable_entry_type(
1868                        mbsize=int,
1869                        clone=int,
1870                        partition_name=str,
1871                        partition_type=str,
1872                        partition_id=Optional[int],
1873                        mountpoint=str,
1874                        filesystem=str,
1875                        label=str
1876                    )
1877                }
1878
1879        :rtype: dict
1880        """
1881        partitions: Dict[str, ptable_entry_type] = {}
1882        partitions_section = self.get_build_type_partitions_section()
1883        if not partitions_section:
1884            return partitions
1885        for partition in partitions_section.get_partition():
1886            name = partition.get_name()
1887            partition_name = partition.get_partition_name() or f'p.lx{name}'
1888            partitions[name] = ptable_entry_type(
1889                mbsize=self._to_mega_byte(partition.get_size()),
1890                clone=int(partition.get_clone()) if partition.get_clone() else 0,
1891                partition_name=partition_name,
1892                partition_type=partition.get_partition_type() or 't.linux',
1893                partition_id=partition.get_part_id(),
1894                mountpoint=partition.get_mountpoint(),
1895                filesystem=partition.get_filesystem(),
1896                label=partition.get_label() or ''
1897            )
1898        return partitions
1899
1900    def get_host_key_certificates(
1901        self
1902    ) -> Union[List[Dict[str, List[str]]], List[Dict[str, str]]]:
1903        cc_result = []
1904        cc_certificates: Dict[str, List[str]] = {}
1905        securelinux_list = \
1906            self.get_build_type_bootloader_securelinux_section()
1907        for securelinux in securelinux_list:
1908            cc_certificates = {
1909                'hkd_cert': [],
1910                'hkd_revocation_list': [],
1911                'hkd_ca_cert': securelinux.get_hkd_ca_cert(),
1912                'hkd_sign_cert': securelinux.get_hkd_sign_cert()
1913            }
1914            for hkd_cert in securelinux.get_hkd_cert():
1915                cc_certificates['hkd_cert'].append(hkd_cert.get_name())
1916            for hkd_revocation_list in securelinux.get_hkd_revocation_list():
1917                cc_certificates['hkd_revocation_list'].append(
1918                    hkd_revocation_list.get_name()
1919                )
1920            cc_result.append(cc_certificates)
1921        return cc_result
1922
1923    def get_containers(self) -> List[ContainerT]:
1924        containers = []
1925
1926        def build_fetch_command(
1927            root_dir: str,
1928            container_uri: str = '',
1929            container_file_name: str = '',
1930            container_endpoint: str = ''
1931        ):
1932            pass  # pragma: nocover
1933        for containers_section in self.get_containers_sections():
1934            for container in containers_section.get_container():
1935                if self.container_matches_host_architecture(container):
1936                    fetch_command = build_fetch_command
1937                    load_command = []
1938                    container_tag = container.get_tag() or 'latest'
1939                    container_path = container.get_path() or ''
1940                    container_endpoint = os.path.normpath(
1941                        '{0}/{1}/{2}:{3}'.format(
1942                            containers_section.get_source(), container_path,
1943                            container.name, container_tag
1944                        )
1945                    )
1946                    container_file_name = '{0}/{1}_{2}'.format(
1947                        defaults.LOCAL_CONTAINERS, container.name, container_tag
1948                    )
1949                    container_backend = containers_section.get_backend() or ''
1950                    if container_backend in ['podman', 'docker', 'container-snap']:
1951                        if Defaults.is_buildservice_worker():
1952                            container_uri = Uri(
1953                                'obsrepositories:/{0}'.format(
1954                                    container_endpoint
1955                                ), 'container'
1956                            ).translate()
1957
1958                            def build_fetch_command(
1959                                root_dir: str,
1960                                container_uri: str = container_uri,
1961                                container_file_name: str = container_file_name,
1962                                container_endpoint: str = container_endpoint
1963                            ):
1964                                def perform():
1965                                    Command.run(
1966                                        [
1967                                            'cp', '{0}.ociarchive'.format(
1968                                                container_uri
1969                                            ), os.path.normpath(
1970                                                '{0}/{1}'.format(
1971                                                    root_dir,
1972                                                    container_file_name
1973                                                )
1974                                            )
1975                                        ]
1976                                    )
1977                                perform()
1978                            fetch_command = build_fetch_command
1979                        else:
1980
1981                            def build_fetch_command(
1982                                root_dir: str,
1983                                container_uri: str = '',
1984                                container_file_name: str = container_file_name,
1985                                container_endpoint: str = container_endpoint
1986                            ):
1987                                def perform():
1988                                    Command.run(
1989                                        [
1990                                            'chroot', root_dir,
1991                                            '/usr/bin/skopeo', 'copy',
1992                                            'docker://{0}'.format(
1993                                                container_endpoint
1994                                            ),
1995                                            'oci-archive:{0}:{1}'.format(
1996                                                container_file_name,
1997                                                container_endpoint
1998                                            )
1999                                        ]
2000                                    )
2001                                perform()
2002                            fetch_command = build_fetch_command
2003                        if not container.get_fetch_only():
2004                            load_command = [
2005                                f'/usr/bin/{container_backend}',
2006                                'load', '-i', container_file_name
2007                            ]
2008                    containers.append(
2009                        ContainerT(
2010                            name=f'{container.name}_{container_tag}',
2011                            backend=container_backend,
2012                            container_file=container_file_name,
2013                            fetch_only=bool(container.get_fetch_only()),
2014                            fetch_command=fetch_command,
2015                            load_command=load_command
2016                        )
2017                    )
2018        return containers
2019
2020    def get_volumes(self) -> List[volume_type]:
2021        """
2022        List of configured systemdisk volumes.
2023
2024        Each entry in the list is a tuple with the following information
2025
2026        * name: name of the volume
2027        * size: size of the volume
2028        * realpath: system path to lookup volume data. If no mountpoint
2029          is set the volume name is used as data path.
2030        * mountpoint: volume mount point and volume data path
2031        * fullsize: takes all space True|False
2032        * attributes: list of volume attributes handled via chattr
2033
2034        :return:
2035            Contains list of volume_type tuples
2036
2037            .. code:: python
2038
2039                [
2040                    volume_type(
2041                        name=volume_name,
2042                        parent=volume_parent,
2043                        size=volume_size,
2044                        realpath=path,
2045                        mountpoint=path,
2046                        fullsize=True,
2047                        label=volume_label,
2048                        attributes=['no-copy-on-write'],
2049                        is_root_volume=True|False
2050                    )
2051                ]
2052
2053        :rtype: list
2054        """
2055        volume_type_list: List[volume_type] = []
2056        systemdisk_section = self.get_build_type_system_disk_section()
2057        selected_filesystem = self.build_type.get_filesystem()
2058        swap_mbytes = self.get_oemconfig_swap_mbytes()
2059        swap_name = self.get_oemconfig_swap_name()
2060        if not systemdisk_section:
2061            return volume_type_list
2062        volumes = systemdisk_section.get_volume()
2063        have_root_volume_setup = False
2064        have_full_size_volume = False
2065        if volumes:
2066            for volume in volumes:
2067                if not self.volume_matches_host_architecture(volume):
2068                    continue
2069                # volume setup for a full qualified volume with name and
2070                # mountpoint information. See below for exceptions
2071                name = volume.get_name()
2072                parent = volume.get_parent() or ''
2073                mountpoint = volume.get_mountpoint()
2074                realpath = mountpoint
2075                size = volume.get_size()
2076                freespace = volume.get_freespace()
2077                fullsize = False
2078                label = volume.get_label()
2079                attributes = []
2080                is_root_volume = False
2081
2082                if volume.get_quota():
2083                    attributes.append(f'quota={volume.get_quota()}')
2084
2085                if volume.get_copy_on_write() is False:
2086                    # by default copy-on-write is switched on for any
2087                    # filesystem. Thus only if no copy on write is requested
2088                    # the attribute is handled
2089                    attributes.append('no-copy-on-write')
2090
2091                if volume.get_filesystem_check() is True:
2092                    # by default filesystem check is switched off for any
2093                    # filesystem except the rootfs. Thus only if filesystem
2094                    # check is requested the attribute is handled
2095                    attributes.append('enable-for-filesystem-check')
2096
2097                if '@root' in name:
2098                    # setup root volume, it takes an optional volume
2099                    # name if specified as @root=name and has no specific
2100                    # mountpoint. The default name is set to
2101                    # defaults.ROOT_VOLUME_NAME if no other root volume
2102                    # name is provided
2103                    mountpoint = None
2104                    realpath = '/'
2105                    is_root_volume = True
2106                    root_volume_expression = re.match(
2107                        r'@root=(.+)', name
2108                    )
2109                    if root_volume_expression:
2110                        name = root_volume_expression.group(1)
2111                    else:
2112                        name = defaults.ROOT_VOLUME_NAME
2113                    have_root_volume_setup = True
2114                elif not mountpoint:
2115                    # setup volume without mountpoint. In this case the name
2116                    # attribute is used as mountpoint path and a name for the
2117                    # volume is created from that path information
2118                    mountpoint = name
2119                    realpath = mountpoint
2120                    name = self._to_volume_name(name)
2121
2122                if size:
2123                    size = 'size:' + format(
2124                        self._to_mega_byte(size)
2125                    )
2126                elif freespace:
2127                    size = 'freespace:' + format(
2128                        self._to_mega_byte(freespace)
2129                    )
2130                else:
2131                    size = 'freespace:' + format(
2132                        Defaults.get_min_volume_mbytes(selected_filesystem)
2133                    )
2134
2135                if ':all' in size:
2136                    size = None
2137                    fullsize = True
2138                    have_full_size_volume = True
2139
2140                volume_type_list.append(
2141                    volume_type(
2142                        name=name,
2143                        parent=parent,
2144                        size=size,
2145                        fullsize=fullsize,
2146                        mountpoint=mountpoint,
2147                        realpath=realpath,
2148                        label=label,
2149                        attributes=attributes,
2150                        is_root_volume=is_root_volume
2151                    )
2152                )
2153
2154        if not have_root_volume_setup:
2155            # There must always be a root volume setup. It will be the
2156            # full size volume if no other volume has this setup
2157            volume_management = self.get_volume_management()
2158            root_volume_name = \
2159                defaults.ROOT_VOLUME_NAME if volume_management == 'lvm' else ''
2160            if have_full_size_volume:
2161                size = 'freespace:' + format(
2162                    Defaults.get_min_volume_mbytes(selected_filesystem)
2163                )
2164                fullsize = False
2165            else:
2166                size = None
2167                fullsize = True
2168            volume_type_list.append(
2169                volume_type(
2170                    name=root_volume_name,
2171                    parent='',
2172                    size=size,
2173                    fullsize=fullsize,
2174                    mountpoint=None,
2175                    realpath='/',
2176                    label=None,
2177                    attributes=[],
2178                    is_root_volume=True
2179                )
2180            )
2181
2182        if swap_mbytes and self.get_volume_management() == 'lvm':
2183            volume_type_list.append(
2184                volume_type(
2185                    name=swap_name,
2186                    parent='',
2187                    size='size:{0}'.format(swap_mbytes),
2188                    fullsize=False,
2189                    mountpoint=None,
2190                    realpath='swap',
2191                    label='SWAP',
2192                    attributes=[],
2193                    is_root_volume=False
2194                )
2195            )
2196
2197        return volume_type_list
2198
2199    def get_volume_management(self) -> Optional[str]:
2200        """
2201        Provides information which volume management system is used
2202
2203        :return: name of volume manager
2204
2205        :rtype: str
2206        """
2207        volume_filesystems = ['btrfs']
2208        selected_filesystem = self.build_type.get_filesystem()
2209        selected_system_disk = self.get_build_type_system_disk_section()
2210        volume_management = None
2211        if selected_system_disk and selected_system_disk.get_preferlvm():
2212            # LVM volume management is preferred, use it
2213            volume_management = 'lvm'
2214        elif selected_filesystem in volume_filesystems and selected_system_disk:
2215            # specified filesystem has its own volume management system
2216            volume_management = selected_filesystem
2217        elif selected_system_disk:
2218            # systemdisk section is specified with non volume capable
2219            # filesystem and no volume management preference. So let's
2220            # use LVM by default
2221            volume_management = 'lvm'
2222        return volume_management
2223
2224    def get_drivers_list(self) -> List:
2225        """
2226        List of driver names from all drivers sections matching
2227        configured profiles
2228
2229        :return: driver names
2230
2231        :rtype: list
2232        """
2233        drivers_sections = self._profiled(
2234            self.xml_data.get_drivers()
2235        )
2236        result = []
2237        if drivers_sections:
2238            for driver in drivers_sections:
2239                for file_section in driver.get_file():
2240                    result.append(file_section.get_name())
2241        return result
2242
2243    def get_strip_list(self, section_type: str) -> List:
2244        """
2245        List of strip names matching the given section type
2246        and profiles
2247
2248        :param str section_type: type name from packages section
2249
2250        :return: strip names
2251
2252        :rtype: list
2253        """
2254        strip_sections = self._profiled(
2255            self.xml_data.get_strip()
2256        )
2257        result = []
2258        if strip_sections:
2259            for strip in strip_sections:
2260                if strip.get_type() == section_type:
2261                    for file_section in strip.get_file():
2262                        result.append(file_section.get_name())
2263        return result
2264
2265    def get_strip_files_to_delete(self) -> List:
2266        """
2267        Items to delete from strip section
2268
2269        :return: item names
2270
2271        :rtype: list
2272        """
2273        return self.get_strip_list('delete')
2274
2275    def get_strip_tools_to_keep(self) -> List:
2276        """
2277        Tools to keep from strip section
2278
2279        :return: tool names
2280
2281        :rtype: list
2282        """
2283        return self.get_strip_list('tools')
2284
2285    def get_strip_libraries_to_keep(self) -> List:
2286        """
2287        Libraries to keep from strip section
2288
2289        :return: librarie names
2290
2291        :rtype: list
2292        """
2293        return self.get_strip_list('libs')
2294
2295    def get_include_section_reference_file_names(self) -> List[str]:
2296        """
2297        List of all <include> section file name references
2298
2299        :return: List[str]
2300
2301        :rtype: list
2302        """
2303        include_files = []
2304        for include in self.xml_data.get_include():
2305            include_files.append(include.get_from())
2306        return include_files
2307
2308    def get_repository_sections(self) -> List:
2309        """
2310        List of all repository sections for the selected profiles that
2311        matches the host architecture
2312
2313        :return: <repository> section reference(s)
2314
2315        :rtype: list
2316        """
2317        repository_list = []
2318        for repository in self._profiled(self.xml_data.get_repository()):
2319            if self.repository_matches_host_architecture(repository):
2320                repository_list.append(repository)
2321        return repository_list
2322
2323    def get_containers_sections(self) -> List:
2324        """
2325        List of all containers sections for the selected profiles that
2326        matches the host architecture
2327
2328        :return: <containers> section reference(s)
2329
2330        :rtype: list
2331        """
2332        containers_list = []
2333        for containers in self._profiled(self.xml_data.get_containers()):
2334            if self.containers_matches_host_architecture(containers):
2335                containers_list.append(containers)
2336        return containers_list
2337
2338    def get_repository_sections_used_for_build(self) -> List:
2339        """
2340        List of all repositorys sections used to build the image and
2341        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 if not repo.get_imageonly()
2350        )
2351
2352    def get_repository_sections_used_in_image(self) -> List:
2353        """
2354        List of all repositorys sections to be configured in the resulting
2355        image matching configured profiles.
2356
2357        :return: <repository> section reference(s)
2358
2359        :rtype: list
2360        """
2361        repos = self.get_repository_sections()
2362        return list(
2363            repo for repo in repos
2364            if repo.get_imageinclude() or repo.get_imageonly()
2365        )
2366
2367    def delete_repository_sections(self) -> None:
2368        """
2369        Delete all repository sections matching configured profiles
2370        """
2371        self.xml_data.set_repository([])
2372
2373    def delete_repository_sections_used_for_build(self) -> None:
2374        """
2375        Delete all repository sections used to build the image matching
2376        configured profiles
2377        """
2378        used_for_build = self.get_repository_sections_used_for_build()
2379        all_repos = self.get_repository_sections()
2380        self.xml_data.set_repository(
2381            [
2382                repo for repo in all_repos if repo not in used_for_build
2383            ]
2384        )
2385
2386    def get_repositories_signing_keys(self) -> List[str]:
2387        """
2388        Get list of signing keys specified on the repositories
2389        """
2390        key_file_list: List[str] = []
2391        release_version = self.get_release_version()
2392        release_vars = [
2393            '$releasever',
2394            '${releasever}'
2395        ]
2396        for repository in self.get_repository_sections() or []:
2397            for signing in repository.get_source().get_signing() or []:
2398                normalized_key_url = Uri(signing.get_key()).translate()
2399                if release_version:
2400                    for release_var in release_vars:
2401                        if release_var in normalized_key_url:
2402                            normalized_key_url = normalized_key_url.replace(
2403                                release_var, release_version
2404                            )
2405                if normalized_key_url not in key_file_list:
2406                    key_file_list.append(normalized_key_url)
2407        return key_file_list
2408
2409    def set_repository(
2410        self, repo_source: str, repo_type: str, repo_alias: str,
2411        repo_prio: str, repo_imageinclude: bool = False,
2412        repo_package_gpgcheck: Optional[bool] = None,
2413        repo_signing_keys: List[str] = [], components: str = None,
2414        distribution: str = None, repo_gpgcheck: Optional[bool] = None,
2415        repo_sourcetype: str = None
2416    ) -> None:
2417        """
2418        Overwrite repository data of the first repository
2419
2420        :param str repo_source: repository URI
2421        :param str repo_type: type name defined by schema
2422        :param str repo_alias: alias name
2423        :param str repo_prio: priority number, package manager specific
2424        :param bool repo_imageinclude: setup repository inside of the image
2425        :param bool repo_package_gpgcheck: enable/disable package gpg checks
2426        :param list repo_signing_keys: list of signing key file names
2427        :param str components: component names for debian repos
2428        :param str distribution: base distribution name for debian repos
2429        :param bool repo_gpgcheck: enable/disable repo gpg checks
2430        """
2431        repository_sections = self.get_repository_sections()
2432        if repository_sections:
2433            repository = repository_sections[0]
2434            if repo_alias:
2435                repository.set_alias(repo_alias)
2436            if repo_type:
2437                repository.set_type(repo_type)
2438            if repo_source:
2439                repository.get_source().set_path(repo_source)
2440            if repo_prio:
2441                repository.set_priority(int(repo_prio))
2442            if repo_imageinclude:
2443                repository.set_imageinclude(repo_imageinclude)
2444            if repo_package_gpgcheck is not None:
2445                repository.set_package_gpgcheck(repo_package_gpgcheck)
2446            if repo_signing_keys:
2447                repository.get_source().set_signing(
2448                    [xml_parse.signing(key=k) for k in repo_signing_keys]
2449                )
2450            if components:
2451                repository.set_components(components)
2452            if distribution:
2453                repository.set_distribution(distribution)
2454            if repo_gpgcheck is not None:
2455                repository.set_repository_gpgcheck(repo_gpgcheck)
2456            if repo_sourcetype:
2457                repository.set_sourcetype(repo_sourcetype)
2458
2459    def add_repository(
2460        self, repo_source: str, repo_type: str, repo_alias: str = None,
2461        repo_prio: str = '', repo_imageinclude: bool = False,
2462        repo_package_gpgcheck: Optional[bool] = None,
2463        repo_signing_keys: List[str] = [], components: str = None,
2464        distribution: str = None, repo_gpgcheck: Optional[bool] = None,
2465        repo_sourcetype: str = None
2466    ) -> None:
2467        """
2468        Add a new repository section at the end of the list
2469
2470        :param str repo_source: repository URI
2471        :param str repo_type: type name defined by schema
2472        :param str repo_alias: alias name
2473        :param str repo_prio: priority number, package manager specific
2474        :param bool repo_imageinclude: setup repository inside of the image
2475        :param bool repo_package_gpgcheck: enable/disable package gpg checks
2476        :param list repo_signing_keys: list of signing key file names
2477        :param str components: component names for debian repos
2478        :param str distribution: base distribution name for debian repos
2479        :param bool repo_gpgcheck: enable/disable repo gpg checks
2480        """
2481        priority_number: Optional[int] = None
2482        try:
2483            priority_number = int(repo_prio)
2484        except Exception:
2485            pass
2486
2487        self.xml_data.add_repository(
2488            xml_parse.repository(
2489                type_=repo_type,
2490                alias=repo_alias,
2491                priority=priority_number,
2492                source=xml_parse.source(
2493                    path=repo_source,
2494                    signing=[
2495                        xml_parse.signing(key=k) for k in repo_signing_keys
2496                    ]
2497                ),
2498                imageinclude=repo_imageinclude,
2499                package_gpgcheck=repo_package_gpgcheck,
2500                repository_gpgcheck=repo_gpgcheck,
2501                components=components,
2502                distribution=distribution,
2503                sourcetype=repo_sourcetype
2504            )
2505        )
2506
2507    def add_certificate(self, cert_file: str, target_distribution: str) -> None:
2508        """
2509        Add <certificate name="cert_file"> to main <certificates> section
2510        The main section will be created if it does not exist. Also
2511        setup the target_distribution in the resulting main section.
2512        """
2513        certificates_section = self._profiled(
2514            self.xml_data.get_certificates()
2515        )
2516        if not certificates_section:
2517            self.xml_data.set_certificates(
2518                [
2519                    xml_parse.certificates(
2520                        target_distribution=target_distribution,
2521                        certificate=[xml_parse.certificate(name=cert_file)]
2522                    )
2523                ]
2524            )
2525        else:
2526            certificates_section[0].set_target_distribution(
2527                target_distribution
2528            )
2529            certificates_section[0].add_certificate(
2530                xml_parse.certificate(
2531                    name=cert_file
2532                )
2533            )
2534
2535    def get_certificates(self) -> List[str]:
2536        """
2537        Read list of certificates
2538        """
2539        cert_list = []
2540        certificates_section = self._profiled(
2541            self.xml_data.get_certificates()
2542        )
2543        if certificates_section:
2544            for certificate in certificates_section[0].get_certificate():
2545                cert_list.append(certificate.get_name())
2546        return sorted(list(set(cert_list)))
2547
2548    def get_certificates_target_distribution(self) -> str:
2549        """
2550        Read CA target distribution
2551        """
2552        target_distribution = ''
2553        certificates_section = self._profiled(
2554            self.xml_data.get_certificates()
2555        )
2556        if certificates_section:
2557            target_distribution = \
2558                certificates_section[0].get_target_distribution()
2559        return target_distribution
2560
2561    def resolve_this_path(self) -> None:
2562        """
2563        Resolve any this:// repo source path into the path
2564        representing the target inside of the image description
2565        directory
2566        """
2567        for repository in self.get_repository_sections() or []:
2568            repo_source = repository.get_source()
2569            repo_path = repo_source.get_path()
2570            if repo_path.startswith('this://'):
2571                repo_path = repo_path.replace('this://', '')
2572                repo_source.set_path(
2573                    'dir://{0}'.format(
2574                        os.path.realpath(
2575                            os.path.join(
2576                                self.xml_data.description_dir, repo_path
2577                            )
2578                        )
2579                    )
2580                )
2581
2582    def copy_displayname(self, target_state: Any) -> None:
2583        """
2584        Copy image displayname from this xml state to the target xml state
2585
2586        :param object target_state: XMLState instance
2587        """
2588        displayname = self.xml_data.get_displayname()
2589        if displayname:
2590            target_state.xml_data.set_displayname(displayname)
2591
2592    def copy_name(self, target_state: Any) -> None:
2593        """
2594        Copy image name from this xml state to the target xml state
2595
2596        :param object target_state: XMLState instance
2597        """
2598        target_state.xml_data.set_name(
2599            self.xml_data.get_name()
2600        )
2601
2602    def copy_drivers_sections(self, target_state: Any) -> None:
2603        """
2604        Copy drivers sections from this xml state to the target xml state
2605
2606        :param object target_state: XMLState instance
2607        """
2608        drivers_sections = self._profiled(
2609            self.xml_data.get_drivers()
2610        )
2611        if drivers_sections:
2612            for drivers_section in drivers_sections:
2613                target_state.xml_data.add_drivers(drivers_section)
2614
2615    def copy_systemdisk_section(self, target_state: Any) -> None:
2616        """
2617        Copy systemdisk sections from this xml state to the target xml state
2618
2619        :param object target_state: XMLState instance
2620        """
2621        systemdisk_section = self.get_build_type_system_disk_section()
2622        if systemdisk_section:
2623            target_state.build_type.set_systemdisk(
2624                [systemdisk_section]
2625            )
2626
2627    def copy_strip_sections(self, target_state: Any) -> None:
2628        """
2629        Copy strip sections from this xml state to the target xml state
2630
2631        :param object target_state: XMLState instance
2632        """
2633        strip_sections = self._profiled(
2634            self.xml_data.get_strip()
2635        )
2636        if strip_sections:
2637            for strip_section in strip_sections:
2638                target_state.xml_data.add_strip(strip_section)
2639
2640    def copy_machine_section(self, target_state: Any) -> None:
2641        """
2642        Copy machine sections from this xml state to the target xml state
2643
2644        :param object target_state: XMLState instance
2645        """
2646        machine_section = self.get_build_type_machine_section()
2647        if machine_section:
2648            target_state.build_type.set_machine(
2649                [machine_section]
2650            )
2651
2652    def copy_bootloader_section(self, target_state: Any) -> None:
2653        """
2654        Copy bootloader section from this xml state to the target xml state
2655
2656        :param object target_state: XMLState instance
2657        """
2658        bootloader_section = self.get_build_type_bootloader_section()
2659        if bootloader_section:
2660            target_state.build_type.set_bootloader(
2661                [bootloader_section]
2662            )
2663
2664    def copy_oemconfig_section(self, target_state: Any) -> None:
2665        """
2666        Copy oemconfig sections from this xml state to the target xml state
2667
2668        :param object target_state: XMLState instance
2669        """
2670        oemconfig_section = self.get_build_type_oemconfig_section()
2671        if oemconfig_section:
2672            target_state.build_type.set_oemconfig(
2673                [oemconfig_section]
2674            )
2675
2676    def copy_repository_sections(
2677        self, target_state: Any, wipe: bool = False
2678    ) -> None:
2679        """
2680        Copy repository sections from this xml state to the target xml state
2681
2682        :param object target_state: XMLState instance
2683        :param bool wipe: delete all repos in target prior to copy
2684        """
2685        repository_sections = self._profiled(
2686            self.xml_data.get_repository()
2687        )
2688        if repository_sections:
2689            if wipe:
2690                target_state.xml_data.set_repository([])
2691            for repository_section in repository_sections:
2692                repository_copy = copy.deepcopy(repository_section)
2693                # profiles are not copied because they might not exist
2694                # in the target description
2695                repository_copy.set_profiles(None)
2696                target_state.xml_data.add_repository(repository_copy)
2697
2698    def copy_preferences_subsections(
2699        self, section_names: List, target_state: Any
2700    ) -> None:
2701        """
2702        Copy subsections of the preferences sections, matching given
2703        section names, from this xml state to the target xml state
2704
2705        :param list section_names: preferences subsection names
2706        :param object target_state: XMLState instance
2707        """
2708        target_preferences_sections = target_state.get_preferences_sections()
2709        if target_preferences_sections:
2710            target_preferences_section = target_preferences_sections[0]
2711            for preferences_section in self.get_preferences_sections():
2712                for section_name in section_names:
2713                    get_section_method = getattr(
2714                        preferences_section, 'get_' + section_name
2715                    )
2716                    section = get_section_method()
2717                    if section:
2718                        set_section_method = getattr(
2719                            target_preferences_section, 'set_' + section_name
2720                        )
2721                        set_section_method(section)
2722
2723    def copy_build_type_attributes(
2724        self, attribute_names: List, target_state: Any
2725    ) -> None:
2726        """
2727        Copy specified attributes from this build type section to the
2728        target xml state build type section
2729
2730        :param list attribute_names: type section attributes
2731        :param object target_state: XMLState instance
2732        """
2733        for attribute in attribute_names:
2734            get_type_method = getattr(
2735                self.build_type, 'get_' + attribute
2736            )
2737            attribute_value = get_type_method()
2738            if attribute_value:
2739                set_type_method = getattr(
2740                    target_state.build_type, 'set_' + attribute
2741                )
2742                set_type_method(attribute_value)
2743
2744    def copy_bootincluded_packages(self, target_state: Any) -> None:
2745        """
2746        Copy packages marked as bootinclude to the packages
2747        type=bootstrap section in the target xml state. The package
2748        will also be removed from the packages type=delete section
2749        in the target xml state if present there
2750
2751        :param object target_state: XMLState instance
2752        """
2753        target_packages_sections = \
2754            target_state.get_bootstrap_packages_sections()
2755        if target_packages_sections:
2756            target_packages_section = \
2757                target_packages_sections[0]
2758            package_names_added = []
2759            packages_sections = self.get_packages_sections(
2760                ['image', 'bootstrap', self.get_build_type_name()]
2761            )
2762            package_list = self.get_package_sections(
2763                packages_sections
2764            )
2765            if package_list:
2766                for package in package_list:
2767                    if package.package_section.get_bootinclude():
2768                        target_packages_section.add_package(
2769                            xml_parse.package(
2770                                name=package.package_section.get_name()
2771                            )
2772                        )
2773                        package_names_added.append(
2774                            package.package_section.get_name()
2775                        )
2776            delete_packages_sections = target_state.get_packages_sections(
2777                ['delete']
2778            )
2779            package_list = self.get_package_sections(
2780                delete_packages_sections
2781            )
2782            if package_list:
2783                for package in package_list:
2784                    package_name = package.package_section.get_name()
2785                    if package_name in package_names_added:
2786                        package.packages_section.package.remove(
2787                            package.package_section
2788                        )
2789
2790    def copy_bootincluded_archives(self, target_state: Any) -> None:
2791        """
2792        Copy archives marked as bootinclude to the packages type=bootstrap
2793        section in the target xml state
2794
2795        :param object target_state: XMLState instance
2796        """
2797        target_bootstrap_packages_sections = \
2798            target_state.get_bootstrap_packages_sections()
2799        if target_bootstrap_packages_sections:
2800            target_bootstrap_packages_section = \
2801                target_bootstrap_packages_sections[0]
2802            packages_sections = self.get_packages_sections(
2803                ['image', 'bootstrap', self.get_build_type_name()]
2804            )
2805            for packages_section in packages_sections:
2806                archive_list = packages_section.get_archive()
2807                if archive_list:
2808                    for archive in archive_list:
2809                        if archive.get_bootinclude():
2810                            target_bootstrap_packages_section.add_archive(
2811                                xml_parse.archive(
2812                                    name=archive.get_name()
2813                                )
2814                            )
2815
2816    def copy_bootdelete_packages(self, target_state: Any) -> None:
2817        """
2818        Copy packages marked as bootdelete to the packages type=delete
2819        section in the target xml state
2820
2821        :param object target_state: XMLState instance
2822        """
2823        target_delete_packages_sections = target_state.get_packages_sections(
2824            ['delete']
2825        )
2826        if not target_delete_packages_sections:
2827            target_delete_packages_sections = [
2828                xml_parse.packages(type_='delete')
2829            ]
2830            target_state.xml_data.add_packages(
2831                target_delete_packages_sections[0]
2832            )
2833
2834        target_delete_packages_section = \
2835            target_delete_packages_sections[0]
2836        packages_sections = self.get_packages_sections(
2837            ['image', 'bootstrap', self.get_build_type_name()]
2838        )
2839        package_list = self.get_package_sections(
2840            packages_sections
2841        )
2842        if package_list:
2843            for package in package_list:
2844                if package.package_section.get_bootdelete():
2845                    target_delete_packages_section.add_package(
2846                        xml_parse.package(
2847                            name=package.package_section.get_name()
2848                        )
2849                    )
2850
2851    def get_distribution_name_from_boot_attribute(self) -> str:
2852        """
2853        Extract the distribution name from the boot attribute of the
2854        build type section.
2855
2856        If no boot attribute is configured or the contents does not
2857        match the kiwi defined naming schema for boot image descriptions,
2858        an exception is thrown
2859
2860        :return: lowercase distribution name
2861
2862        :rtype: str
2863        """
2864        boot_attribute = self.build_type.get_boot()
2865        if not boot_attribute:
2866            raise KiwiDistributionNameError(
2867                'No boot attribute to extract distribution name from found'
2868            )
2869        boot_attribute_format = '^.*-(.*)$'
2870        boot_attribute_expression = re.match(
2871            boot_attribute_format, boot_attribute
2872        )
2873        if not boot_attribute_expression:
2874            raise KiwiDistributionNameError(
2875                'Boot attribute "%s" does not match expected format %s' %
2876                (boot_attribute, boot_attribute_format)
2877            )
2878        return boot_attribute_expression.group(1).lower()
2879
2880    def get_fs_mount_option_list(self) -> List:
2881        """
2882        List of root filesystem mount options
2883
2884        The list contains one element with the information from the
2885        fsmountoptions attribute. The value there is passed along to
2886        the -o mount option
2887
2888        :return: max one element list with mount option string
2889
2890        :rtype: list
2891        """
2892        option_list = []
2893        mount_options = self.build_type.get_fsmountoptions()
2894        if mount_options:
2895            option_list = [mount_options]
2896
2897        return option_list
2898
2899    def get_fs_create_option_list(self) -> List:
2900        """
2901        List of root filesystem creation options
2902
2903        The list contains elements with the information from the
2904        fscreateoptions attribute string that got split into its
2905        substring components
2906
2907        :return: list with create options
2908
2909        :rtype: list
2910        """
2911        option_list = []
2912        create_options = self.build_type.get_fscreateoptions()
2913        if create_options:
2914            option_list = create_options.split()
2915
2916        return option_list
2917
2918    def get_luks_credentials(self) -> Optional[str]:
2919        """
2920        Return key or passphrase credentials to open the luks pool
2921
2922        :return: data
2923
2924        :rtype: str
2925        """
2926        data = self.build_type.get_luks()
2927        if data:
2928            keyfile_name = None
2929            try:
2930                # try to interpret data as an URI
2931                uri = Uri(data)
2932                if not uri.is_remote():
2933                    keyfile_name = uri.translate()
2934            except Exception:
2935                # this doesn't look like a valid URI, continue as just data
2936                pass
2937            if keyfile_name:
2938                try:
2939                    with open(keyfile_name) as keyfile:
2940                        return keyfile.read()
2941                except Exception as issue:
2942                    raise KiwiFileAccessError(
2943                        f'Failed to read from {keyfile_name!r}: {issue}'
2944                    )
2945        return data
2946
2947    def get_luks_format_options(self) -> List[str]:
2948        """
2949        Return list of luks format options
2950
2951        :return: list of options
2952
2953        :rtype: list
2954        """
2955        result = []
2956        luksversion = self.build_type.get_luks_version()
2957        luksformat = self.build_type.get_luksformat()
2958        luks_pbkdf = self.build_type.get_luks_pbkdf()
2959        if luksversion:
2960            result.append('--type')
2961            result.append(luksversion)
2962        if luksformat:
2963            for option in luksformat[0].get_option():
2964                result.append(option.get_name())
2965                if option.get_value():
2966                    result.append(option.get_value())
2967        if luks_pbkdf:
2968            # Allow to override the pbkdf algorithm that cryptsetup
2969            # uses by default. Cryptsetup may use argon2i by default,
2970            # which is not supported by all bootloaders.
2971            result.append('--pbkdf')
2972            result.append(luks_pbkdf)
2973        return result
2974
2975    def get_derived_from_image_uri(self) -> List[Uri]:
2976        """
2977        Uri object(s) of derived image if configured
2978
2979        Specific image types can be based on one ore more derived
2980        images. This method returns the location of this image(s)
2981        when configured in the XML description
2982
2983        :return: List of Uri instances
2984
2985        :rtype: list
2986        """
2987        image_uris = []
2988        derived_images = self.build_type.get_derived_from()
2989        if derived_images:
2990            for derived_image in derived_images.split(','):
2991                image_uris.append(
2992                    Uri(derived_image, repo_type='container')
2993                )
2994        return image_uris
2995
2996    def set_derived_from_image_uri(self, uri: str) -> None:
2997        """
2998        Set derived_from attribute to a new value
2999
3000        In order to set a new value the derived_from attribute
3001        must be already present in the image configuration
3002
3003        :param str uri: URI
3004        """
3005        if self.build_type.get_derived_from():
3006            self.build_type.set_derived_from(uri)
3007        else:
3008            message = dedent('''\n
3009                No derived_from attribute configured in image <type>
3010
3011                In order to set the uri {0} as base container reference
3012                an initial derived_from attribute must be set in the
3013                type section
3014            ''')
3015            log.warning(message.format(uri))
3016
3017    def set_root_partition_uuid(self, uuid: str) -> None:
3018        """
3019        Store PARTUUID provided in uuid as state information
3020
3021        :param str uuid: PARTUUID
3022        """
3023        self.root_partition_uuid = uuid
3024
3025    def get_root_partition_uuid(self) -> Optional[str]:
3026        """
3027        Return preserved PARTUUID
3028        """
3029        return self.root_partition_uuid
3030
3031    def set_root_filesystem_uuid(self, uuid: str) -> None:
3032        """
3033        Store UUID provided in uuid as state information
3034
3035        :param str uuid: UUID
3036        """
3037        self.root_filesystem_uuid = uuid
3038
3039    def get_root_filesystem_uuid(self) -> Optional[str]:
3040        """
3041        Return preserved UUID
3042        """
3043        return self.root_filesystem_uuid
3044
3045    @staticmethod
3046    def get_archives_target_dirs(
3047        packages_sections_names: Optional[List[xml_parse.packages]]
3048    ) -> Dict:
3049        """
3050        Dict of archive names and target dirs for packages section(s), if any
3051        :return: archive names and its target dir
3052        :rtype: dict
3053        """
3054        result = {}
3055        if packages_sections_names:
3056            for package_section_name in packages_sections_names:
3057                for archive in package_section_name.get_archive():
3058                    result[archive.get_name().strip()] = archive.get_target_dir()
3059
3060        return result
3061
3062    def get_bootstrap_archives_target_dirs(self) -> Dict:
3063        """
3064        Dict of archive names and target dirs from the type="bootstrap"
3065        packages section(s)
3066        :return: archive names and its target dir
3067        :rtype: dict
3068        """
3069        return self.get_archives_target_dirs(
3070            self.get_packages_sections(['bootstrap'])
3071        )
3072
3073    def get_system_archives_target_dirs(self) -> Dict:
3074        """
3075        Dict of archive names and its target dir from the packages sections matching
3076        type="image" and type=build_type
3077        :return: archive names and its target dir
3078        :rtype: dict
3079        """
3080        return self.get_archives_target_dirs(
3081            self.get_packages_sections(['image', self.get_build_type_name()])
3082        )
3083
3084    def _used_profiles(self, profiles=None):
3085        """
3086        return list of profiles to use. The method looks up the
3087        profiles section in the XML description and searches for
3088        profiles matching the architecture. If no arch specifier
3089        is set the profile is considered to be valid for any arch
3090
3091        If the profiles argument is not set only profiles
3092        marked with the attribute import=true will be selected.
3093        Profiles specified in the argument will take the highest
3094        priority and causes to skip the lookup of import profiles
3095        in the XML description
3096
3097        :param list profiles: selected profile names
3098        """
3099        available_profiles = dict()
3100        import_profiles = []
3101        for profiles_section in self.xml_data.get_profiles():
3102            for profile in profiles_section.get_profile():
3103                if self.profile_matches_host_architecture(profile):
3104                    name = profile.get_name()
3105                    available_profiles[name] = profile
3106                    if profile.get_import():
3107                        import_profiles.append(name)
3108
3109        if not profiles:
3110            return import_profiles
3111        else:
3112            resolved_profiles = []
3113            for profile in profiles:
3114                resolved_profiles += self._solve_profile_dependencies(
3115                    profile, available_profiles, resolved_profiles
3116                )
3117            return resolved_profiles
3118
3119    def _section_matches_host_architecture(self, section):
3120        architectures = section.get_arch()
3121        if architectures:
3122            if self.host_architecture not in architectures.split(','):
3123                return False
3124        return True
3125
3126    def _match_docker_base_data(self):
3127        container_config_section = self.get_build_type_containerconfig_section()
3128        container_base = {}
3129        if container_config_section:
3130            name = container_config_section.get_name()
3131            tag = container_config_section.get_tag()
3132            maintainer = container_config_section.get_maintainer()
3133            user = container_config_section.get_user()
3134            workingdir = container_config_section.get_workingdir()
3135            additional_names = container_config_section.get_additionalnames()
3136            if name:
3137                container_base['container_name'] = name
3138
3139            if tag:
3140                container_base['container_tag'] = tag
3141
3142            if additional_names:
3143                container_base['additional_names'] = additional_names.split(',')
3144
3145            if maintainer:
3146                container_base['maintainer'] = maintainer
3147
3148            if user:
3149                container_base['user'] = user
3150
3151            if workingdir:
3152                container_base['workingdir'] = workingdir
3153
3154        return container_base
3155
3156    def _match_docker_entrypoint(self):
3157        container_config_section = self.get_build_type_containerconfig_section()
3158        container_entry = {}
3159        if container_config_section:
3160            entrypoint = container_config_section.get_entrypoint()
3161            if entrypoint and entrypoint[0].get_execute():
3162                container_entry['entry_command'] = [
3163                    entrypoint[0].get_execute()
3164                ]
3165                argument_list = entrypoint[0].get_argument()
3166                if argument_list:
3167                    for argument in argument_list:
3168                        container_entry['entry_command'].append(
3169                            argument.get_name()
3170                        )
3171            elif entrypoint and entrypoint[0].get_clear():
3172                container_entry['entry_command'] = []
3173        return container_entry
3174
3175    def _match_docker_subcommand(self):
3176        container_config_section = self.get_build_type_containerconfig_section()
3177        container_subcommand = {}
3178        if container_config_section:
3179            subcommand = container_config_section.get_subcommand()
3180            if subcommand and subcommand[0].get_execute():
3181                container_subcommand['entry_subcommand'] = [
3182                    subcommand[0].get_execute()
3183                ]
3184                argument_list = subcommand[0].get_argument()
3185                if argument_list:
3186                    for argument in argument_list:
3187                        container_subcommand['entry_subcommand'].append(
3188                            argument.get_name()
3189                        )
3190            elif subcommand and subcommand[0].get_clear():
3191                container_subcommand['entry_subcommand'] = []
3192        return container_subcommand
3193
3194    def _match_docker_expose_ports(self):
3195        container_config_section = self.get_build_type_containerconfig_section()
3196        container_expose = {}
3197        if container_config_section:
3198            expose = container_config_section.get_expose()
3199            if expose and expose[0].get_port():
3200                container_expose['expose_ports'] = []
3201                for port in expose[0].get_port():
3202                    container_expose['expose_ports'].append(
3203                        format(port.get_number())
3204                    )
3205        return container_expose
3206
3207    def _match_docker_volumes(self):
3208        container_config_section = self.get_build_type_containerconfig_section()
3209        container_volumes = {}
3210        if container_config_section:
3211            volumes = container_config_section.get_volumes()
3212            if volumes and volumes[0].get_volume():
3213                container_volumes['volumes'] = []
3214                for volume in volumes[0].get_volume():
3215                    container_volumes['volumes'].append(volume.get_name())
3216        return container_volumes
3217
3218    def _match_docker_stopsignal(self) -> dict:
3219        container_config_section = self.get_build_type_containerconfig_section()
3220        container_stopsignal = {}
3221        if container_config_section:
3222            stopsignal_section = container_config_section.get_stopsignal()
3223            if stopsignal_section:
3224                container_stopsignal['stopsignal'] = stopsignal_section[0]
3225        return container_stopsignal
3226
3227    def _match_docker_environment(self):
3228        container_config_section = self.get_build_type_containerconfig_section()
3229        container_env = {}
3230        if container_config_section:
3231            environment = container_config_section.get_environment()
3232            if environment and environment[0].get_env():
3233                container_env['environment'] = {}
3234                for env in environment[0].get_env():
3235                    container_env['environment'][env.get_name()] = \
3236                        env.get_value()
3237        return container_env
3238
3239    def _match_docker_labels(self):
3240        container_config_section = self.get_build_type_containerconfig_section()
3241        container_labels = {}
3242        if container_config_section:
3243            labels = container_config_section.get_labels()
3244            if labels and labels[0].get_label():
3245                container_labels['labels'] = {}
3246                for label in labels[0].get_label():
3247                    container_labels['labels'][label.get_name()] = \
3248                        label.get_value()
3249        return container_labels
3250
3251    def _match_docker_history(self):
3252        container_config_section = self.get_build_type_containerconfig_section()
3253        container_history = {}
3254        if container_config_section:
3255            history = container_config_section.get_history()
3256            if history:
3257                container_history['history'] = {}
3258                if history[0].get_created_by():
3259                    container_history['history']['created_by'] = \
3260                        history[0].get_created_by()
3261                if history[0].get_author():
3262                    container_history['history']['author'] = \
3263                        history[0].get_author()
3264                if history[0].get_launcher():
3265                    container_history['history']['launcher'] = \
3266                        history[0].get_launcher()
3267                if history[0].get_application_id():
3268                    container_history['history']['application_id'] = \
3269                        history[0].get_application_id()
3270                if history[0].get_package_version():
3271                    container_history['history']['package_version'] = \
3272                        history[0].get_package_version()
3273                container_history['history']['comment'] = \
3274                    history[0].get_valueOf_()
3275        return container_history
3276
3277    def _solve_profile_dependencies(
3278        self, profile, available_profiles, current_profiles
3279    ):
3280        if profile not in available_profiles:
3281            raise KiwiProfileNotFound(
3282                'profile {0} not found for host arch {1}'.format(
3283                    profile, self.host_architecture
3284                )
3285            )
3286        profiles_to_add = []
3287        if profile not in current_profiles:
3288            profiles_to_add.append(profile)
3289            for required in available_profiles[profile].get_requires():
3290                if self.requires_matches_host_architecture(required):
3291                    if required.get_profile() not in current_profiles:
3292                        profiles_to_add += self._solve_profile_dependencies(
3293                            required.get_profile(), available_profiles,
3294                            current_profiles + profiles_to_add
3295                        )
3296        return profiles_to_add
3297
3298    def _build_type_section(self, build_type=None):
3299        """
3300        find type section matching build type and profiles or default
3301        """
3302        # lookup all preferences sections for selected profiles
3303        image_type_sections = []
3304        for preferences in self.get_preferences_sections():
3305            image_type_sections += preferences.get_type()
3306
3307        # lookup if build type matches provided type
3308        if build_type:
3309            for image_type in image_type_sections:
3310                if build_type == image_type.get_image():
3311                    return image_type
3312            raise KiwiTypeNotFound(
3313                'Build type {0!r} not found for applied profiles: {1!r}'.format(
3314                    build_type, self.profiles
3315                )
3316            )
3317
3318        # lookup if build type matches primary type
3319        for image_type in image_type_sections:
3320            if image_type.get_primary():
3321                return image_type
3322
3323        # build type is first type section in XML sequence
3324        if image_type_sections:
3325            return image_type_sections[0]
3326        raise KiwiTypeNotFound(
3327            'No build type defined with applied profiles: {0!r}'.format(
3328                self.profiles
3329            )
3330        )
3331
3332    def _profiled(self, xml_abstract):
3333        """
3334        return only those sections matching the instance stored
3335        profile list from the given XML abstract. Sections without
3336        a profile are wildcard sections and will be used in any
3337        case
3338        """
3339        result = []
3340        for section in xml_abstract:
3341            profiles = section.get_profiles()
3342            if profiles:
3343                for profile in profiles.split(','):
3344                    if self.profiles and profile in self.profiles:
3345                        result.append(section)
3346                        break
3347            else:
3348                result.append(section)
3349        return result
3350
3351    def _to_volume_name(self, name):
3352        name = name.strip()
3353        name = re.sub(r'^\/+', r'', name)
3354        name = name.replace('/', '_')
3355        return name
3356
3357    def _to_mega_byte(self, size):
3358        value = re.search(r'(\d+)([MG]*)', format(size))
3359        if value:
3360            number = value.group(1)
3361            unit = value.group(2)
3362            if unit == 'G':
3363                return int(number) * 1024
3364            else:
3365                return int(number)
3366        else:
3367            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_video_mode(self) -> str:
1241        """
1242        Return bootloader video mode for the selected build type.
1243        If not specified the method returns "auto" as default
1244        mode name.
1245
1246        :return: video mode name or auto
1247
1248        :rtype: str
1249        """
1250        bootloader = self.get_build_type_bootloader_section()
1251        if bootloader:
1252            return bootloader.get_video_mode()
1253        return 'auto'
1254
1255    def get_build_type_bootloader_timeout(self) -> Optional[str]:
1256        """
1257        Return bootloader timeout setting for selected build type
1258
1259        :return: timeout string
1260
1261        :rtype: str
1262        """
1263        bootloader = self.get_build_type_bootloader_section()
1264        if bootloader:
1265            return bootloader.get_timeout()
1266        return None
1267
1268    def get_build_type_bootloader_timeout_style(self) -> Optional[str]:
1269        """
1270        Return bootloader timeout style setting for selected build type
1271
1272        :return: timeout_style string
1273
1274        :rtype: str
1275        """
1276        bootloader = self.get_build_type_bootloader_section()
1277        if bootloader:
1278            return bootloader.get_timeout_style()
1279        return None
1280
1281    def get_build_type_bootloader_targettype(self) -> Optional[str]:
1282        """
1283        Return bootloader target type setting. Only relevant for
1284        the zipl bootloader because zipl is installed differently
1285        depending on the storage target it runs later
1286
1287        :return: target type string
1288
1289        :rtype: str
1290        """
1291        bootloader = self.get_build_type_bootloader_section()
1292        if bootloader:
1293            return bootloader.get_targettype()
1294        return None
1295
1296    def get_build_type_bootloader_settings_section(self) -> Any:
1297        """
1298        First bootloadersettings section from the build
1299        type bootloader section
1300
1301        :return: <bootloadersettings> section reference
1302
1303        :rtype: xml_parse::bootloadersettings
1304        """
1305        bootloader_section = self.get_build_type_bootloader_section()
1306        bootloader_settings_section = None
1307        if bootloader_section and bootloader_section.get_bootloadersettings():
1308            bootloader_settings_section = \
1309                bootloader_section.get_bootloadersettings()[0]
1310        return bootloader_settings_section
1311
1312    def get_build_type_bootloader_securelinux_section(self) -> List[Any]:
1313        """
1314        First securelinux section from the build
1315        type bootloader section
1316
1317        :return: <securelinux> section reference
1318
1319        :rtype: xml_parse::securelinux
1320        """
1321        bootloader_section = self.get_build_type_bootloader_section()
1322        bootloader_securelinux_section = []
1323        if bootloader_section and bootloader_section.get_securelinux():
1324            bootloader_securelinux_section = \
1325                bootloader_section.get_securelinux()
1326        return bootloader_securelinux_section
1327
1328    def get_build_type_bootloader_environment_variables(self) -> List[str]:
1329        """
1330        List of bootloader variables from the build
1331        type > bootloader > bootloadersettings section
1332        """
1333        variable_list = []
1334        bootloader_settings_section = \
1335            self.get_build_type_bootloader_settings_section()
1336        if bootloader_settings_section:
1337            environment = bootloader_settings_section.get_environment()
1338            if environment and environment[0].get_env():
1339                for env in environment[0].get_env():
1340                    variable_list.append(
1341                        '{}{}'.format(
1342                            env.get_name(),
1343                            f'={env.get_value()}' if env.get_value() else ''
1344                        )
1345                    )
1346        return variable_list
1347
1348    def get_bootloader_options(self, option_type: str) -> List[str]:
1349        """
1350        List of custom options used in the process to
1351        run bootloader setup workloads
1352        """
1353        result: List[str] = []
1354        bootloader_settings = self.get_build_type_bootloader_settings_section()
1355        if bootloader_settings:
1356            options = []
1357            if option_type == 'shim':
1358                options = bootloader_settings.get_shimoption()
1359            elif option_type == 'install':
1360                options = bootloader_settings.get_installoption()
1361            elif option_type == 'config':
1362                options = bootloader_settings.get_configoption()
1363            for option in options:
1364                result.append(option.get_name())
1365                if option.get_value():
1366                    result.append(option.get_value())
1367        return result
1368
1369    def get_bootloader_shim_options(self) -> List[str]:
1370        """
1371        List of custom options used in the process to setup secure boot
1372        """
1373        return self.get_bootloader_options('shim')
1374
1375    def get_bootloader_install_options(self) -> List[str]:
1376        """
1377        List of custom options used in the bootloader installation
1378        """
1379        return self.get_bootloader_options('install')
1380
1381    def get_bootloader_config_options(self) -> List[str]:
1382        """
1383        List of custom options used in the bootloader configuration
1384        """
1385        return self.get_bootloader_options('config')
1386
1387    def get_build_type_bootloader_use_disk_password(self) -> bool:
1388        """
1389        Indicate whether the bootloader configuration should use the
1390        password protecting the encrypted root volume.
1391
1392        :return: True|False
1393
1394        :rtype: bool
1395        """
1396        bootloader = self.get_build_type_bootloader_section()
1397        if bootloader:
1398            return bootloader.get_use_disk_password()
1399        return False
1400
1401    def get_build_type_oemconfig_section(self) -> Any:
1402        """
1403        First oemconfig section from the build type section
1404
1405        :return: <oemconfig> section reference
1406
1407        :rtype: xml_parse::oemconfig
1408        """
1409        oemconfig_sections = self.build_type.get_oemconfig()
1410        if oemconfig_sections:
1411            return oemconfig_sections[0]
1412        return None
1413
1414    def get_oemconfig_oem_resize(self) -> bool:
1415        """
1416        State value to activate/deactivate disk resize. Returns a
1417        boolean value if specified or True to set resize on by default
1418
1419        :return: Content of <oem-resize> section value
1420
1421        :rtype: bool
1422        """
1423        oemconfig = self.get_build_type_oemconfig_section()
1424        if oemconfig and oemconfig.get_oem_resize():
1425            return oemconfig.get_oem_resize()[0]
1426        else:
1427            return True
1428
1429    def get_oemconfig_oem_systemsize(self) -> int:
1430        """
1431        State value to retrieve root partition size
1432
1433        :return: Content of <oem-systemsize> section value
1434
1435        :rtype: int
1436        """
1437        oemconfig = self.get_build_type_oemconfig_section()
1438        if oemconfig and oemconfig.get_oem_systemsize():
1439            return int(oemconfig.get_oem_systemsize()[0])
1440        else:
1441            return 0
1442
1443    def get_oemconfig_oem_multipath_scan(self) -> bool:
1444        """
1445        State value to activate multipath maps. Returns a boolean
1446        value if specified or False
1447
1448        :return: Content of <oem-multipath-scan> section value
1449
1450        :rtype: bool
1451        """
1452        oemconfig = self.get_build_type_oemconfig_section()
1453        if oemconfig and oemconfig.get_oem_multipath_scan():
1454            return oemconfig.get_oem_multipath_scan()[0]
1455        return False
1456
1457    def get_oemconfig_swap_mbytes(self) -> Optional[int]:
1458        """
1459        Return swapsize in MB if requested or None
1460
1461        Operates on the value of oem-swap and if set to true
1462        returns the given size or the default value.
1463
1464        :return: Content of <oem-swapsize> section value or default
1465
1466        :rtype: int
1467        """
1468        oemconfig = self.get_build_type_oemconfig_section()
1469        if oemconfig and oemconfig.get_oem_swap():
1470            swap_requested = oemconfig.get_oem_swap()[0]
1471            if swap_requested:
1472                swapsize = oemconfig.get_oem_swapsize()
1473                if swapsize:
1474                    return swapsize[0]
1475                else:
1476                    return Defaults.get_swapsize_mbytes()
1477        return None
1478
1479    def get_oemconfig_swap_name(self) -> str:
1480        """
1481        Return the swap space name
1482
1483        Operates on the value of oem-swapname and if set
1484        returns the configured name or the default name: LVSwap
1485
1486        The name of the swap space is used only if the
1487        image is configured to use the LVM volume manager.
1488        In this case swap is a volume and the volume takes
1489        a name. In any other case the given name will have
1490        no effect.
1491
1492        :return: Content of <oem-swapname> section value or default
1493
1494        :rtype: str
1495        """
1496        oemconfig = self.get_build_type_oemconfig_section()
1497        if oemconfig and oemconfig.get_oem_swapname():
1498            return oemconfig.get_oem_swapname()[0]
1499        return 'LVSwap'
1500
1501    def get_build_type_containerconfig_section(self) -> Any:
1502        """
1503        First containerconfig section from the build type section
1504
1505        :return: <containerconfig> section reference
1506
1507        :rtype: xml_parse::containerconfig
1508        """
1509        container_config_sections = self.build_type.get_containerconfig()
1510        if container_config_sections:
1511            return container_config_sections[0]
1512        return None
1513
1514    def get_dracut_config(self, action: str) -> DracutT:
1515        """
1516        Get dracut initrd config for the specified action
1517        """
1518        uefi = False
1519        modules = []
1520        drivers = []
1521        initrd_sections = self.build_type.get_initrd()
1522        for initrd_section in initrd_sections:
1523            if initrd_section.get_action() == action:
1524                for dracut in initrd_section.get_dracut():
1525                    uefi = bool(dracut.get_uefi())
1526                    if dracut.get_module():
1527                        modules.append(dracut.get_module())
1528                    if dracut.get_driver():
1529                        drivers.append(dracut.get_driver())
1530        return DracutT(
1531            uefi=uefi, modules=modules, drivers=drivers
1532        )
1533
1534    def get_installmedia_initrd_modules(self, action: str) -> List[str]:
1535        """
1536        Gets the list of modules to append in installation initrds
1537
1538        :return: a list of dracut module names
1539
1540        :rtype: list
1541        """
1542        modules: List[str] = []
1543        installmedia = self.build_type.get_installmedia()
1544        if not installmedia:
1545            return modules
1546        initrd_sections = installmedia[0].get_initrd()
1547        for initrd_section in initrd_sections:
1548            if initrd_section.get_action() == action:
1549                for module in initrd_section.get_dracut():
1550                    if module.get_module():
1551                        modules.append(module.get_module())
1552        return modules
1553
1554    def get_installmedia_initrd_drivers(self, action: str) -> List[str]:
1555        """
1556        Gets the list of drivers to append in installation initrds
1557
1558        :return: a list of dracut driver names
1559
1560        :rtype: list
1561        """
1562        drivers: List[str] = []
1563        installmedia = self.build_type.get_installmedia()
1564        if not installmedia:
1565            return drivers
1566        initrd_sections = installmedia[0].get_initrd()
1567        for initrd_section in initrd_sections:
1568            if initrd_section.get_action() == action:
1569                for driver in initrd_section.get_dracut():
1570                    if driver.get_driver():
1571                        drivers.append(driver.get_driver())
1572        return drivers
1573
1574    def get_build_type_size(
1575        self, include_unpartitioned: bool = False
1576    ) -> Optional[size_type]:
1577        """
1578        Size information from the build type section.
1579        If no unit is set the value is treated as mbytes
1580
1581        :param bool include_unpartitioned: sets if the unpartitioned area
1582            should be included in the computed size or not
1583
1584        :return: mbytes
1585
1586        :rtype: int
1587        """
1588        size_section = self.build_type.get_size()
1589        if size_section:
1590            unit = size_section[0].get_unit()
1591            additive = size_section[0].get_additive()
1592            unpartitioned = size_section[0].get_unpartitioned()
1593            value = int(size_section[0].get_valueOf_())
1594            if not include_unpartitioned and unpartitioned is not None:
1595                value -= unpartitioned
1596            if unit == 'G':
1597                value *= 1024
1598            return size_type(
1599                mbytes=value, additive=additive
1600            )
1601        return None
1602
1603    def get_build_type_unpartitioned_bytes(self) -> int:
1604        """
1605        Size of the unpartitioned area for image in megabytes
1606
1607        :return: mbytes
1608
1609        :rtype: int
1610        """
1611        size_section = self.build_type.get_size()
1612        if size_section:
1613            unit = size_section[0].get_unit() or 'M'
1614            unpartitioned = size_section[0].get_unpartitioned() or 0
1615            return StringToSize.to_bytes('{0}{1}'.format(unpartitioned, unit))
1616        return 0
1617
1618    def get_disk_start_sector(self) -> int:
1619        """
1620        First disk sector number to be used by the first disk partition.
1621
1622        :return: number
1623
1624        :rtype: int
1625        """
1626        disk_start_sector = self.build_type.get_disk_start_sector()
1627        if disk_start_sector is None:
1628            disk_start_sector = Defaults.get_default_disk_start_sector()
1629        return disk_start_sector
1630
1631    def get_build_type_spare_part_size(self) -> Optional[int]:
1632        """
1633        Size information for the spare_part size from the build
1634        type. If no unit is set the value is treated as mbytes
1635
1636        :return: mbytes
1637
1638        :rtype: int
1639        """
1640        spare_part_size = self.build_type.get_spare_part()
1641        if spare_part_size:
1642            return self._to_mega_byte(spare_part_size)
1643        return None
1644
1645    def get_build_type_spare_part_fs_attributes(self) -> Optional[List]:
1646        """
1647        Build type specific list of filesystem attributes applied to
1648        the spare partition.
1649
1650        :return: list of strings or empty list
1651
1652        :rtype: list
1653        """
1654        spare_part_attributes = self.build_type.get_spare_part_fs_attributes()
1655        if spare_part_attributes:
1656            return spare_part_attributes.strip().split(',')
1657        return None
1658
1659    def get_build_type_format_options(self) -> Dict:
1660        """
1661        Disk format options returned as a dictionary
1662
1663        :return: format options
1664
1665        :rtype: dict
1666        """
1667        result = {}
1668        format_options = self.build_type.get_formatoptions()
1669        if format_options:
1670            for option in format_options.split(','):
1671                key_value_list = option.split('=')
1672                if len(key_value_list) == 2:
1673                    result[key_value_list[0]] = key_value_list[1]
1674                else:
1675                    result[key_value_list[0]] = None
1676        return result
1677
1678    def get_volume_group_name(self) -> str:
1679        """
1680        Volume group name from selected <systemdisk> section
1681
1682        :return: volume group name
1683
1684        :rtype: str
1685        """
1686        systemdisk_section = self.get_build_type_system_disk_section()
1687        volume_group_name = None
1688        if systemdisk_section:
1689            volume_group_name = systemdisk_section.get_name()
1690        if not volume_group_name:
1691            volume_group_name = Defaults.get_default_volume_group_name()
1692        return volume_group_name
1693
1694    def get_users(self) -> List:
1695        """
1696        List of configured users.
1697
1698        Each entry in the list is a single xml_parse::user instance.
1699
1700        :return: list of <user> section reference(s)
1701
1702        :rtype: list
1703        """
1704        users_list = []
1705        users_names_added = []
1706        for users_section in self.get_users_sections():
1707            for user in users_section.get_user():
1708                if user.get_name() not in users_names_added:
1709                    users_list.append(user)
1710                    users_names_added.append(user.get_name())
1711
1712        return users_list
1713
1714    def get_user_groups(self, user_name) -> List[str]:
1715        """
1716        List of group names matching specified user
1717
1718        Each entry in the list is the name of a group and optionally its
1719        group ID separated by a colon, that the specified user belongs to.
1720        The first item in the list is the login or primary group. The
1721        list will be empty if no groups are specified in the
1722        description file.
1723
1724        :return: groups data for the given user
1725
1726        :rtype: list
1727        """
1728        groups_list = []
1729        for users_section in self.get_users_sections():
1730            for user in users_section.get_user():
1731                if user.get_name() == user_name:
1732                    user_groups = user.get_groups()
1733                    if user_groups:
1734                        groups_list += user.get_groups().split(',')
1735
1736        # order of list items matter, thus we don't use set() here
1737        # better faster, nicer solutions welcome :)
1738        result_group_list = []
1739        for item in groups_list:
1740            if item not in result_group_list:
1741                result_group_list.append(item)
1742
1743        return result_group_list
1744
1745    def get_container_config(self) -> Dict:
1746        """
1747        Dictionary of containerconfig information
1748
1749        Takes attributes and subsection data from the selected
1750        <containerconfig> section and stores it in a dictionary
1751        """
1752        container_config = self._match_docker_base_data()
1753        container_config.update(
1754            self._match_docker_entrypoint()
1755        )
1756        container_config.update(
1757            self._match_docker_subcommand()
1758        )
1759        container_config.update(
1760            self._match_docker_expose_ports()
1761        )
1762        container_config.update(
1763            self._match_docker_volumes()
1764        )
1765        container_config.update(
1766            self._match_docker_stopsignal()
1767        )
1768        container_config.update(
1769            self._match_docker_environment()
1770        )
1771        container_config.update(
1772            self._match_docker_labels()
1773        )
1774        container_config.update(
1775            self._match_docker_history()
1776        )
1777
1778        desc = self.get_description_section()
1779        author_contact = "{0} <{1}>".format(desc.author, desc.contact)
1780        if 'history' not in container_config:
1781            container_config['history'] = {}
1782        if 'author' not in container_config['history']:
1783            container_config['history']['author'] = author_contact
1784        if 'maintainer' not in container_config:
1785            container_config['maintainer'] = author_contact
1786
1787        return container_config
1788
1789    def set_container_config_tag(self, tag: str) -> None:
1790        """
1791        Set new tag name in containerconfig section
1792
1793        In order to set a new tag value an existing containerconfig and
1794        tag setup is required
1795
1796        :param str tag: tag name
1797        """
1798        container_config_section = self.get_build_type_containerconfig_section()
1799        if container_config_section and container_config_section.get_tag():
1800            container_config_section.set_tag(tag)
1801        else:
1802            message = dedent('''\n
1803                No <containerconfig> section and/or tag attribute configured
1804
1805                In order to set the tag {0} as new container tag,
1806                an initial containerconfig section including a tag
1807                setup is required
1808            ''')
1809            log.warning(message.format(tag))
1810
1811    def add_container_config_label(self, label_name: str, value: str) -> None:
1812        """
1813        Adds a new label in the containerconfig section, if a label with the
1814        same name is already defined in containerconfig it gets overwritten by
1815        this method.
1816
1817        :param str label_name: the string representing the label name
1818        :param str value: the value of the label
1819        """
1820        if self.get_build_type_name() not in ['docker', 'oci']:
1821            message = dedent('''\n
1822                Labels can only be configured for container image types
1823                docker and oci.
1824            ''')
1825            log.warning(message)
1826            return
1827
1828        container_config_section = self.get_build_type_containerconfig_section()
1829        if not container_config_section:
1830            container_config_section = xml_parse.containerconfig(
1831                name=Defaults.get_default_container_name(),
1832                tag=Defaults.get_default_container_tag()
1833            )
1834            self.build_type.set_containerconfig([container_config_section])
1835
1836        labels = container_config_section.get_labels()
1837        if not labels:
1838            labels = [xml_parse.labels()]
1839
1840        label_names = []
1841        for label in labels[0].get_label():
1842            label_names.append(label.get_name())
1843
1844        if label_name in label_names:
1845            labels[0].replace_label_at(
1846                label_names.index(label_name),
1847                xml_parse.label(label_name, value)
1848            )
1849        else:
1850            labels[0].add_label(xml_parse.label(label_name, value))
1851
1852        container_config_section.set_labels(labels)
1853
1854    def get_partitions(self) -> Dict[str, ptable_entry_type]:
1855        """
1856        Dictionary of configured partitions.
1857
1858        Each entry in the dict references a ptable_entry_type
1859        Each key in the dict references the name of the
1860        partition entry as handled by KIWI
1861
1862        :return:
1863            Contains dict of ptable_entry_type tuples
1864
1865            .. code:: python
1866
1867                {
1868                    'NAME': ptable_entry_type(
1869                        mbsize=int,
1870                        clone=int,
1871                        partition_name=str,
1872                        partition_type=str,
1873                        partition_id=Optional[int],
1874                        mountpoint=str,
1875                        filesystem=str,
1876                        label=str
1877                    )
1878                }
1879
1880        :rtype: dict
1881        """
1882        partitions: Dict[str, ptable_entry_type] = {}
1883        partitions_section = self.get_build_type_partitions_section()
1884        if not partitions_section:
1885            return partitions
1886        for partition in partitions_section.get_partition():
1887            name = partition.get_name()
1888            partition_name = partition.get_partition_name() or f'p.lx{name}'
1889            partitions[name] = ptable_entry_type(
1890                mbsize=self._to_mega_byte(partition.get_size()),
1891                clone=int(partition.get_clone()) if partition.get_clone() else 0,
1892                partition_name=partition_name,
1893                partition_type=partition.get_partition_type() or 't.linux',
1894                partition_id=partition.get_part_id(),
1895                mountpoint=partition.get_mountpoint(),
1896                filesystem=partition.get_filesystem(),
1897                label=partition.get_label() or ''
1898            )
1899        return partitions
1900
1901    def get_host_key_certificates(
1902        self
1903    ) -> Union[List[Dict[str, List[str]]], List[Dict[str, str]]]:
1904        cc_result = []
1905        cc_certificates: Dict[str, List[str]] = {}
1906        securelinux_list = \
1907            self.get_build_type_bootloader_securelinux_section()
1908        for securelinux in securelinux_list:
1909            cc_certificates = {
1910                'hkd_cert': [],
1911                'hkd_revocation_list': [],
1912                'hkd_ca_cert': securelinux.get_hkd_ca_cert(),
1913                'hkd_sign_cert': securelinux.get_hkd_sign_cert()
1914            }
1915            for hkd_cert in securelinux.get_hkd_cert():
1916                cc_certificates['hkd_cert'].append(hkd_cert.get_name())
1917            for hkd_revocation_list in securelinux.get_hkd_revocation_list():
1918                cc_certificates['hkd_revocation_list'].append(
1919                    hkd_revocation_list.get_name()
1920                )
1921            cc_result.append(cc_certificates)
1922        return cc_result
1923
1924    def get_containers(self) -> List[ContainerT]:
1925        containers = []
1926
1927        def build_fetch_command(
1928            root_dir: str,
1929            container_uri: str = '',
1930            container_file_name: str = '',
1931            container_endpoint: str = ''
1932        ):
1933            pass  # pragma: nocover
1934        for containers_section in self.get_containers_sections():
1935            for container in containers_section.get_container():
1936                if self.container_matches_host_architecture(container):
1937                    fetch_command = build_fetch_command
1938                    load_command = []
1939                    container_tag = container.get_tag() or 'latest'
1940                    container_path = container.get_path() or ''
1941                    container_endpoint = os.path.normpath(
1942                        '{0}/{1}/{2}:{3}'.format(
1943                            containers_section.get_source(), container_path,
1944                            container.name, container_tag
1945                        )
1946                    )
1947                    container_file_name = '{0}/{1}_{2}'.format(
1948                        defaults.LOCAL_CONTAINERS, container.name, container_tag
1949                    )
1950                    container_backend = containers_section.get_backend() or ''
1951                    if container_backend in ['podman', 'docker', 'container-snap']:
1952                        if Defaults.is_buildservice_worker():
1953                            container_uri = Uri(
1954                                'obsrepositories:/{0}'.format(
1955                                    container_endpoint
1956                                ), 'container'
1957                            ).translate()
1958
1959                            def build_fetch_command(
1960                                root_dir: str,
1961                                container_uri: str = container_uri,
1962                                container_file_name: str = container_file_name,
1963                                container_endpoint: str = container_endpoint
1964                            ):
1965                                def perform():
1966                                    Command.run(
1967                                        [
1968                                            'cp', '{0}.ociarchive'.format(
1969                                                container_uri
1970                                            ), os.path.normpath(
1971                                                '{0}/{1}'.format(
1972                                                    root_dir,
1973                                                    container_file_name
1974                                                )
1975                                            )
1976                                        ]
1977                                    )
1978                                perform()
1979                            fetch_command = build_fetch_command
1980                        else:
1981
1982                            def build_fetch_command(
1983                                root_dir: str,
1984                                container_uri: str = '',
1985                                container_file_name: str = container_file_name,
1986                                container_endpoint: str = container_endpoint
1987                            ):
1988                                def perform():
1989                                    Command.run(
1990                                        [
1991                                            'chroot', root_dir,
1992                                            '/usr/bin/skopeo', 'copy',
1993                                            'docker://{0}'.format(
1994                                                container_endpoint
1995                                            ),
1996                                            'oci-archive:{0}:{1}'.format(
1997                                                container_file_name,
1998                                                container_endpoint
1999                                            )
2000                                        ]
2001                                    )
2002                                perform()
2003                            fetch_command = build_fetch_command
2004                        if not container.get_fetch_only():
2005                            load_command = [
2006                                f'/usr/bin/{container_backend}',
2007                                'load', '-i', container_file_name
2008                            ]
2009                    containers.append(
2010                        ContainerT(
2011                            name=f'{container.name}_{container_tag}',
2012                            backend=container_backend,
2013                            container_file=container_file_name,
2014                            fetch_only=bool(container.get_fetch_only()),
2015                            fetch_command=fetch_command,
2016                            load_command=load_command
2017                        )
2018                    )
2019        return containers
2020
2021    def get_volumes(self) -> List[volume_type]:
2022        """
2023        List of configured systemdisk volumes.
2024
2025        Each entry in the list is a tuple with the following information
2026
2027        * name: name of the volume
2028        * size: size of the volume
2029        * realpath: system path to lookup volume data. If no mountpoint
2030          is set the volume name is used as data path.
2031        * mountpoint: volume mount point and volume data path
2032        * fullsize: takes all space True|False
2033        * attributes: list of volume attributes handled via chattr
2034
2035        :return:
2036            Contains list of volume_type tuples
2037
2038            .. code:: python
2039
2040                [
2041                    volume_type(
2042                        name=volume_name,
2043                        parent=volume_parent,
2044                        size=volume_size,
2045                        realpath=path,
2046                        mountpoint=path,
2047                        fullsize=True,
2048                        label=volume_label,
2049                        attributes=['no-copy-on-write'],
2050                        is_root_volume=True|False
2051                    )
2052                ]
2053
2054        :rtype: list
2055        """
2056        volume_type_list: List[volume_type] = []
2057        systemdisk_section = self.get_build_type_system_disk_section()
2058        selected_filesystem = self.build_type.get_filesystem()
2059        swap_mbytes = self.get_oemconfig_swap_mbytes()
2060        swap_name = self.get_oemconfig_swap_name()
2061        if not systemdisk_section:
2062            return volume_type_list
2063        volumes = systemdisk_section.get_volume()
2064        have_root_volume_setup = False
2065        have_full_size_volume = False
2066        if volumes:
2067            for volume in volumes:
2068                if not self.volume_matches_host_architecture(volume):
2069                    continue
2070                # volume setup for a full qualified volume with name and
2071                # mountpoint information. See below for exceptions
2072                name = volume.get_name()
2073                parent = volume.get_parent() or ''
2074                mountpoint = volume.get_mountpoint()
2075                realpath = mountpoint
2076                size = volume.get_size()
2077                freespace = volume.get_freespace()
2078                fullsize = False
2079                label = volume.get_label()
2080                attributes = []
2081                is_root_volume = False
2082
2083                if volume.get_quota():
2084                    attributes.append(f'quota={volume.get_quota()}')
2085
2086                if volume.get_copy_on_write() is False:
2087                    # by default copy-on-write is switched on for any
2088                    # filesystem. Thus only if no copy on write is requested
2089                    # the attribute is handled
2090                    attributes.append('no-copy-on-write')
2091
2092                if volume.get_filesystem_check() is True:
2093                    # by default filesystem check is switched off for any
2094                    # filesystem except the rootfs. Thus only if filesystem
2095                    # check is requested the attribute is handled
2096                    attributes.append('enable-for-filesystem-check')
2097
2098                if '@root' in name:
2099                    # setup root volume, it takes an optional volume
2100                    # name if specified as @root=name and has no specific
2101                    # mountpoint. The default name is set to
2102                    # defaults.ROOT_VOLUME_NAME if no other root volume
2103                    # name is provided
2104                    mountpoint = None
2105                    realpath = '/'
2106                    is_root_volume = True
2107                    root_volume_expression = re.match(
2108                        r'@root=(.+)', name
2109                    )
2110                    if root_volume_expression:
2111                        name = root_volume_expression.group(1)
2112                    else:
2113                        name = defaults.ROOT_VOLUME_NAME
2114                    have_root_volume_setup = True
2115                elif not mountpoint:
2116                    # setup volume without mountpoint. In this case the name
2117                    # attribute is used as mountpoint path and a name for the
2118                    # volume is created from that path information
2119                    mountpoint = name
2120                    realpath = mountpoint
2121                    name = self._to_volume_name(name)
2122
2123                if size:
2124                    size = 'size:' + format(
2125                        self._to_mega_byte(size)
2126                    )
2127                elif freespace:
2128                    size = 'freespace:' + format(
2129                        self._to_mega_byte(freespace)
2130                    )
2131                else:
2132                    size = 'freespace:' + format(
2133                        Defaults.get_min_volume_mbytes(selected_filesystem)
2134                    )
2135
2136                if ':all' in size:
2137                    size = None
2138                    fullsize = True
2139                    have_full_size_volume = True
2140
2141                volume_type_list.append(
2142                    volume_type(
2143                        name=name,
2144                        parent=parent,
2145                        size=size,
2146                        fullsize=fullsize,
2147                        mountpoint=mountpoint,
2148                        realpath=realpath,
2149                        label=label,
2150                        attributes=attributes,
2151                        is_root_volume=is_root_volume
2152                    )
2153                )
2154
2155        if not have_root_volume_setup:
2156            # There must always be a root volume setup. It will be the
2157            # full size volume if no other volume has this setup
2158            volume_management = self.get_volume_management()
2159            root_volume_name = \
2160                defaults.ROOT_VOLUME_NAME if volume_management == 'lvm' else ''
2161            if have_full_size_volume:
2162                size = 'freespace:' + format(
2163                    Defaults.get_min_volume_mbytes(selected_filesystem)
2164                )
2165                fullsize = False
2166            else:
2167                size = None
2168                fullsize = True
2169            volume_type_list.append(
2170                volume_type(
2171                    name=root_volume_name,
2172                    parent='',
2173                    size=size,
2174                    fullsize=fullsize,
2175                    mountpoint=None,
2176                    realpath='/',
2177                    label=None,
2178                    attributes=[],
2179                    is_root_volume=True
2180                )
2181            )
2182
2183        if swap_mbytes and self.get_volume_management() == 'lvm':
2184            volume_type_list.append(
2185                volume_type(
2186                    name=swap_name,
2187                    parent='',
2188                    size='size:{0}'.format(swap_mbytes),
2189                    fullsize=False,
2190                    mountpoint=None,
2191                    realpath='swap',
2192                    label='SWAP',
2193                    attributes=[],
2194                    is_root_volume=False
2195                )
2196            )
2197
2198        return volume_type_list
2199
2200    def get_volume_management(self) -> Optional[str]:
2201        """
2202        Provides information which volume management system is used
2203
2204        :return: name of volume manager
2205
2206        :rtype: str
2207        """
2208        volume_filesystems = ['btrfs']
2209        selected_filesystem = self.build_type.get_filesystem()
2210        selected_system_disk = self.get_build_type_system_disk_section()
2211        volume_management = None
2212        if selected_system_disk and selected_system_disk.get_preferlvm():
2213            # LVM volume management is preferred, use it
2214            volume_management = 'lvm'
2215        elif selected_filesystem in volume_filesystems and selected_system_disk:
2216            # specified filesystem has its own volume management system
2217            volume_management = selected_filesystem
2218        elif selected_system_disk:
2219            # systemdisk section is specified with non volume capable
2220            # filesystem and no volume management preference. So let's
2221            # use LVM by default
2222            volume_management = 'lvm'
2223        return volume_management
2224
2225    def get_drivers_list(self) -> List:
2226        """
2227        List of driver names from all drivers sections matching
2228        configured profiles
2229
2230        :return: driver names
2231
2232        :rtype: list
2233        """
2234        drivers_sections = self._profiled(
2235            self.xml_data.get_drivers()
2236        )
2237        result = []
2238        if drivers_sections:
2239            for driver in drivers_sections:
2240                for file_section in driver.get_file():
2241                    result.append(file_section.get_name())
2242        return result
2243
2244    def get_strip_list(self, section_type: str) -> List:
2245        """
2246        List of strip names matching the given section type
2247        and profiles
2248
2249        :param str section_type: type name from packages section
2250
2251        :return: strip names
2252
2253        :rtype: list
2254        """
2255        strip_sections = self._profiled(
2256            self.xml_data.get_strip()
2257        )
2258        result = []
2259        if strip_sections:
2260            for strip in strip_sections:
2261                if strip.get_type() == section_type:
2262                    for file_section in strip.get_file():
2263                        result.append(file_section.get_name())
2264        return result
2265
2266    def get_strip_files_to_delete(self) -> List:
2267        """
2268        Items to delete from strip section
2269
2270        :return: item names
2271
2272        :rtype: list
2273        """
2274        return self.get_strip_list('delete')
2275
2276    def get_strip_tools_to_keep(self) -> List:
2277        """
2278        Tools to keep from strip section
2279
2280        :return: tool names
2281
2282        :rtype: list
2283        """
2284        return self.get_strip_list('tools')
2285
2286    def get_strip_libraries_to_keep(self) -> List:
2287        """
2288        Libraries to keep from strip section
2289
2290        :return: librarie names
2291
2292        :rtype: list
2293        """
2294        return self.get_strip_list('libs')
2295
2296    def get_include_section_reference_file_names(self) -> List[str]:
2297        """
2298        List of all <include> section file name references
2299
2300        :return: List[str]
2301
2302        :rtype: list
2303        """
2304        include_files = []
2305        for include in self.xml_data.get_include():
2306            include_files.append(include.get_from())
2307        return include_files
2308
2309    def get_repository_sections(self) -> List:
2310        """
2311        List of all repository sections for the selected profiles that
2312        matches the host architecture
2313
2314        :return: <repository> section reference(s)
2315
2316        :rtype: list
2317        """
2318        repository_list = []
2319        for repository in self._profiled(self.xml_data.get_repository()):
2320            if self.repository_matches_host_architecture(repository):
2321                repository_list.append(repository)
2322        return repository_list
2323
2324    def get_containers_sections(self) -> List:
2325        """
2326        List of all containers sections for the selected profiles that
2327        matches the host architecture
2328
2329        :return: <containers> section reference(s)
2330
2331        :rtype: list
2332        """
2333        containers_list = []
2334        for containers in self._profiled(self.xml_data.get_containers()):
2335            if self.containers_matches_host_architecture(containers):
2336                containers_list.append(containers)
2337        return containers_list
2338
2339    def get_repository_sections_used_for_build(self) -> List:
2340        """
2341        List of all repositorys sections used to build the image and
2342        matching configured profiles.
2343
2344        :return: <repository> section reference(s)
2345
2346        :rtype: list
2347        """
2348        repos = self.get_repository_sections()
2349        return list(
2350            repo for repo in repos if not repo.get_imageonly()
2351        )
2352
2353    def get_repository_sections_used_in_image(self) -> List:
2354        """
2355        List of all repositorys sections to be configured in the resulting
2356        image matching configured profiles.
2357
2358        :return: <repository> section reference(s)
2359
2360        :rtype: list
2361        """
2362        repos = self.get_repository_sections()
2363        return list(
2364            repo for repo in repos
2365            if repo.get_imageinclude() or repo.get_imageonly()
2366        )
2367
2368    def delete_repository_sections(self) -> None:
2369        """
2370        Delete all repository sections matching configured profiles
2371        """
2372        self.xml_data.set_repository([])
2373
2374    def delete_repository_sections_used_for_build(self) -> None:
2375        """
2376        Delete all repository sections used to build the image matching
2377        configured profiles
2378        """
2379        used_for_build = self.get_repository_sections_used_for_build()
2380        all_repos = self.get_repository_sections()
2381        self.xml_data.set_repository(
2382            [
2383                repo for repo in all_repos if repo not in used_for_build
2384            ]
2385        )
2386
2387    def get_repositories_signing_keys(self) -> List[str]:
2388        """
2389        Get list of signing keys specified on the repositories
2390        """
2391        key_file_list: List[str] = []
2392        release_version = self.get_release_version()
2393        release_vars = [
2394            '$releasever',
2395            '${releasever}'
2396        ]
2397        for repository in self.get_repository_sections() or []:
2398            for signing in repository.get_source().get_signing() or []:
2399                normalized_key_url = Uri(signing.get_key()).translate()
2400                if release_version:
2401                    for release_var in release_vars:
2402                        if release_var in normalized_key_url:
2403                            normalized_key_url = normalized_key_url.replace(
2404                                release_var, release_version
2405                            )
2406                if normalized_key_url not in key_file_list:
2407                    key_file_list.append(normalized_key_url)
2408        return key_file_list
2409
2410    def set_repository(
2411        self, repo_source: str, repo_type: str, repo_alias: str,
2412        repo_prio: str, repo_imageinclude: bool = False,
2413        repo_package_gpgcheck: Optional[bool] = None,
2414        repo_signing_keys: List[str] = [], components: str = None,
2415        distribution: str = None, repo_gpgcheck: Optional[bool] = None,
2416        repo_sourcetype: str = None
2417    ) -> None:
2418        """
2419        Overwrite repository data of the first repository
2420
2421        :param str repo_source: repository URI
2422        :param str repo_type: type name defined by schema
2423        :param str repo_alias: alias name
2424        :param str repo_prio: priority number, package manager specific
2425        :param bool repo_imageinclude: setup repository inside of the image
2426        :param bool repo_package_gpgcheck: enable/disable package gpg checks
2427        :param list repo_signing_keys: list of signing key file names
2428        :param str components: component names for debian repos
2429        :param str distribution: base distribution name for debian repos
2430        :param bool repo_gpgcheck: enable/disable repo gpg checks
2431        """
2432        repository_sections = self.get_repository_sections()
2433        if repository_sections:
2434            repository = repository_sections[0]
2435            if repo_alias:
2436                repository.set_alias(repo_alias)
2437            if repo_type:
2438                repository.set_type(repo_type)
2439            if repo_source:
2440                repository.get_source().set_path(repo_source)
2441            if repo_prio:
2442                repository.set_priority(int(repo_prio))
2443            if repo_imageinclude:
2444                repository.set_imageinclude(repo_imageinclude)
2445            if repo_package_gpgcheck is not None:
2446                repository.set_package_gpgcheck(repo_package_gpgcheck)
2447            if repo_signing_keys:
2448                repository.get_source().set_signing(
2449                    [xml_parse.signing(key=k) for k in repo_signing_keys]
2450                )
2451            if components:
2452                repository.set_components(components)
2453            if distribution:
2454                repository.set_distribution(distribution)
2455            if repo_gpgcheck is not None:
2456                repository.set_repository_gpgcheck(repo_gpgcheck)
2457            if repo_sourcetype:
2458                repository.set_sourcetype(repo_sourcetype)
2459
2460    def add_repository(
2461        self, repo_source: str, repo_type: str, repo_alias: str = None,
2462        repo_prio: str = '', repo_imageinclude: bool = False,
2463        repo_package_gpgcheck: Optional[bool] = None,
2464        repo_signing_keys: List[str] = [], components: str = None,
2465        distribution: str = None, repo_gpgcheck: Optional[bool] = None,
2466        repo_sourcetype: str = None
2467    ) -> None:
2468        """
2469        Add a new repository section at the end of the list
2470
2471        :param str repo_source: repository URI
2472        :param str repo_type: type name defined by schema
2473        :param str repo_alias: alias name
2474        :param str repo_prio: priority number, package manager specific
2475        :param bool repo_imageinclude: setup repository inside of the image
2476        :param bool repo_package_gpgcheck: enable/disable package gpg checks
2477        :param list repo_signing_keys: list of signing key file names
2478        :param str components: component names for debian repos
2479        :param str distribution: base distribution name for debian repos
2480        :param bool repo_gpgcheck: enable/disable repo gpg checks
2481        """
2482        priority_number: Optional[int] = None
2483        try:
2484            priority_number = int(repo_prio)
2485        except Exception:
2486            pass
2487
2488        self.xml_data.add_repository(
2489            xml_parse.repository(
2490                type_=repo_type,
2491                alias=repo_alias,
2492                priority=priority_number,
2493                source=xml_parse.source(
2494                    path=repo_source,
2495                    signing=[
2496                        xml_parse.signing(key=k) for k in repo_signing_keys
2497                    ]
2498                ),
2499                imageinclude=repo_imageinclude,
2500                package_gpgcheck=repo_package_gpgcheck,
2501                repository_gpgcheck=repo_gpgcheck,
2502                components=components,
2503                distribution=distribution,
2504                sourcetype=repo_sourcetype
2505            )
2506        )
2507
2508    def add_certificate(self, cert_file: str, target_distribution: str) -> None:
2509        """
2510        Add <certificate name="cert_file"> to main <certificates> section
2511        The main section will be created if it does not exist. Also
2512        setup the target_distribution in the resulting main section.
2513        """
2514        certificates_section = self._profiled(
2515            self.xml_data.get_certificates()
2516        )
2517        if not certificates_section:
2518            self.xml_data.set_certificates(
2519                [
2520                    xml_parse.certificates(
2521                        target_distribution=target_distribution,
2522                        certificate=[xml_parse.certificate(name=cert_file)]
2523                    )
2524                ]
2525            )
2526        else:
2527            certificates_section[0].set_target_distribution(
2528                target_distribution
2529            )
2530            certificates_section[0].add_certificate(
2531                xml_parse.certificate(
2532                    name=cert_file
2533                )
2534            )
2535
2536    def get_certificates(self) -> List[str]:
2537        """
2538        Read list of certificates
2539        """
2540        cert_list = []
2541        certificates_section = self._profiled(
2542            self.xml_data.get_certificates()
2543        )
2544        if certificates_section:
2545            for certificate in certificates_section[0].get_certificate():
2546                cert_list.append(certificate.get_name())
2547        return sorted(list(set(cert_list)))
2548
2549    def get_certificates_target_distribution(self) -> str:
2550        """
2551        Read CA target distribution
2552        """
2553        target_distribution = ''
2554        certificates_section = self._profiled(
2555            self.xml_data.get_certificates()
2556        )
2557        if certificates_section:
2558            target_distribution = \
2559                certificates_section[0].get_target_distribution()
2560        return target_distribution
2561
2562    def resolve_this_path(self) -> None:
2563        """
2564        Resolve any this:// repo source path into the path
2565        representing the target inside of the image description
2566        directory
2567        """
2568        for repository in self.get_repository_sections() or []:
2569            repo_source = repository.get_source()
2570            repo_path = repo_source.get_path()
2571            if repo_path.startswith('this://'):
2572                repo_path = repo_path.replace('this://', '')
2573                repo_source.set_path(
2574                    'dir://{0}'.format(
2575                        os.path.realpath(
2576                            os.path.join(
2577                                self.xml_data.description_dir, repo_path
2578                            )
2579                        )
2580                    )
2581                )
2582
2583    def copy_displayname(self, target_state: Any) -> None:
2584        """
2585        Copy image displayname from this xml state to the target xml state
2586
2587        :param object target_state: XMLState instance
2588        """
2589        displayname = self.xml_data.get_displayname()
2590        if displayname:
2591            target_state.xml_data.set_displayname(displayname)
2592
2593    def copy_name(self, target_state: Any) -> None:
2594        """
2595        Copy image name from this xml state to the target xml state
2596
2597        :param object target_state: XMLState instance
2598        """
2599        target_state.xml_data.set_name(
2600            self.xml_data.get_name()
2601        )
2602
2603    def copy_drivers_sections(self, target_state: Any) -> None:
2604        """
2605        Copy drivers sections from this xml state to the target xml state
2606
2607        :param object target_state: XMLState instance
2608        """
2609        drivers_sections = self._profiled(
2610            self.xml_data.get_drivers()
2611        )
2612        if drivers_sections:
2613            for drivers_section in drivers_sections:
2614                target_state.xml_data.add_drivers(drivers_section)
2615
2616    def copy_systemdisk_section(self, target_state: Any) -> None:
2617        """
2618        Copy systemdisk sections from this xml state to the target xml state
2619
2620        :param object target_state: XMLState instance
2621        """
2622        systemdisk_section = self.get_build_type_system_disk_section()
2623        if systemdisk_section:
2624            target_state.build_type.set_systemdisk(
2625                [systemdisk_section]
2626            )
2627
2628    def copy_strip_sections(self, target_state: Any) -> None:
2629        """
2630        Copy strip sections from this xml state to the target xml state
2631
2632        :param object target_state: XMLState instance
2633        """
2634        strip_sections = self._profiled(
2635            self.xml_data.get_strip()
2636        )
2637        if strip_sections:
2638            for strip_section in strip_sections:
2639                target_state.xml_data.add_strip(strip_section)
2640
2641    def copy_machine_section(self, target_state: Any) -> None:
2642        """
2643        Copy machine sections from this xml state to the target xml state
2644
2645        :param object target_state: XMLState instance
2646        """
2647        machine_section = self.get_build_type_machine_section()
2648        if machine_section:
2649            target_state.build_type.set_machine(
2650                [machine_section]
2651            )
2652
2653    def copy_bootloader_section(self, target_state: Any) -> None:
2654        """
2655        Copy bootloader section from this xml state to the target xml state
2656
2657        :param object target_state: XMLState instance
2658        """
2659        bootloader_section = self.get_build_type_bootloader_section()
2660        if bootloader_section:
2661            target_state.build_type.set_bootloader(
2662                [bootloader_section]
2663            )
2664
2665    def copy_oemconfig_section(self, target_state: Any) -> None:
2666        """
2667        Copy oemconfig sections from this xml state to the target xml state
2668
2669        :param object target_state: XMLState instance
2670        """
2671        oemconfig_section = self.get_build_type_oemconfig_section()
2672        if oemconfig_section:
2673            target_state.build_type.set_oemconfig(
2674                [oemconfig_section]
2675            )
2676
2677    def copy_repository_sections(
2678        self, target_state: Any, wipe: bool = False
2679    ) -> None:
2680        """
2681        Copy repository sections from this xml state to the target xml state
2682
2683        :param object target_state: XMLState instance
2684        :param bool wipe: delete all repos in target prior to copy
2685        """
2686        repository_sections = self._profiled(
2687            self.xml_data.get_repository()
2688        )
2689        if repository_sections:
2690            if wipe:
2691                target_state.xml_data.set_repository([])
2692            for repository_section in repository_sections:
2693                repository_copy = copy.deepcopy(repository_section)
2694                # profiles are not copied because they might not exist
2695                # in the target description
2696                repository_copy.set_profiles(None)
2697                target_state.xml_data.add_repository(repository_copy)
2698
2699    def copy_preferences_subsections(
2700        self, section_names: List, target_state: Any
2701    ) -> None:
2702        """
2703        Copy subsections of the preferences sections, matching given
2704        section names, from this xml state to the target xml state
2705
2706        :param list section_names: preferences subsection names
2707        :param object target_state: XMLState instance
2708        """
2709        target_preferences_sections = target_state.get_preferences_sections()
2710        if target_preferences_sections:
2711            target_preferences_section = target_preferences_sections[0]
2712            for preferences_section in self.get_preferences_sections():
2713                for section_name in section_names:
2714                    get_section_method = getattr(
2715                        preferences_section, 'get_' + section_name
2716                    )
2717                    section = get_section_method()
2718                    if section:
2719                        set_section_method = getattr(
2720                            target_preferences_section, 'set_' + section_name
2721                        )
2722                        set_section_method(section)
2723
2724    def copy_build_type_attributes(
2725        self, attribute_names: List, target_state: Any
2726    ) -> None:
2727        """
2728        Copy specified attributes from this build type section to the
2729        target xml state build type section
2730
2731        :param list attribute_names: type section attributes
2732        :param object target_state: XMLState instance
2733        """
2734        for attribute in attribute_names:
2735            get_type_method = getattr(
2736                self.build_type, 'get_' + attribute
2737            )
2738            attribute_value = get_type_method()
2739            if attribute_value:
2740                set_type_method = getattr(
2741                    target_state.build_type, 'set_' + attribute
2742                )
2743                set_type_method(attribute_value)
2744
2745    def copy_bootincluded_packages(self, target_state: Any) -> None:
2746        """
2747        Copy packages marked as bootinclude to the packages
2748        type=bootstrap section in the target xml state. The package
2749        will also be removed from the packages type=delete section
2750        in the target xml state if present there
2751
2752        :param object target_state: XMLState instance
2753        """
2754        target_packages_sections = \
2755            target_state.get_bootstrap_packages_sections()
2756        if target_packages_sections:
2757            target_packages_section = \
2758                target_packages_sections[0]
2759            package_names_added = []
2760            packages_sections = self.get_packages_sections(
2761                ['image', 'bootstrap', self.get_build_type_name()]
2762            )
2763            package_list = self.get_package_sections(
2764                packages_sections
2765            )
2766            if package_list:
2767                for package in package_list:
2768                    if package.package_section.get_bootinclude():
2769                        target_packages_section.add_package(
2770                            xml_parse.package(
2771                                name=package.package_section.get_name()
2772                            )
2773                        )
2774                        package_names_added.append(
2775                            package.package_section.get_name()
2776                        )
2777            delete_packages_sections = target_state.get_packages_sections(
2778                ['delete']
2779            )
2780            package_list = self.get_package_sections(
2781                delete_packages_sections
2782            )
2783            if package_list:
2784                for package in package_list:
2785                    package_name = package.package_section.get_name()
2786                    if package_name in package_names_added:
2787                        package.packages_section.package.remove(
2788                            package.package_section
2789                        )
2790
2791    def copy_bootincluded_archives(self, target_state: Any) -> None:
2792        """
2793        Copy archives marked as bootinclude to the packages type=bootstrap
2794        section in the target xml state
2795
2796        :param object target_state: XMLState instance
2797        """
2798        target_bootstrap_packages_sections = \
2799            target_state.get_bootstrap_packages_sections()
2800        if target_bootstrap_packages_sections:
2801            target_bootstrap_packages_section = \
2802                target_bootstrap_packages_sections[0]
2803            packages_sections = self.get_packages_sections(
2804                ['image', 'bootstrap', self.get_build_type_name()]
2805            )
2806            for packages_section in packages_sections:
2807                archive_list = packages_section.get_archive()
2808                if archive_list:
2809                    for archive in archive_list:
2810                        if archive.get_bootinclude():
2811                            target_bootstrap_packages_section.add_archive(
2812                                xml_parse.archive(
2813                                    name=archive.get_name()
2814                                )
2815                            )
2816
2817    def copy_bootdelete_packages(self, target_state: Any) -> None:
2818        """
2819        Copy packages marked as bootdelete to the packages type=delete
2820        section in the target xml state
2821
2822        :param object target_state: XMLState instance
2823        """
2824        target_delete_packages_sections = target_state.get_packages_sections(
2825            ['delete']
2826        )
2827        if not target_delete_packages_sections:
2828            target_delete_packages_sections = [
2829                xml_parse.packages(type_='delete')
2830            ]
2831            target_state.xml_data.add_packages(
2832                target_delete_packages_sections[0]
2833            )
2834
2835        target_delete_packages_section = \
2836            target_delete_packages_sections[0]
2837        packages_sections = self.get_packages_sections(
2838            ['image', 'bootstrap', self.get_build_type_name()]
2839        )
2840        package_list = self.get_package_sections(
2841            packages_sections
2842        )
2843        if package_list:
2844            for package in package_list:
2845                if package.package_section.get_bootdelete():
2846                    target_delete_packages_section.add_package(
2847                        xml_parse.package(
2848                            name=package.package_section.get_name()
2849                        )
2850                    )
2851
2852    def get_distribution_name_from_boot_attribute(self) -> str:
2853        """
2854        Extract the distribution name from the boot attribute of the
2855        build type section.
2856
2857        If no boot attribute is configured or the contents does not
2858        match the kiwi defined naming schema for boot image descriptions,
2859        an exception is thrown
2860
2861        :return: lowercase distribution name
2862
2863        :rtype: str
2864        """
2865        boot_attribute = self.build_type.get_boot()
2866        if not boot_attribute:
2867            raise KiwiDistributionNameError(
2868                'No boot attribute to extract distribution name from found'
2869            )
2870        boot_attribute_format = '^.*-(.*)$'
2871        boot_attribute_expression = re.match(
2872            boot_attribute_format, boot_attribute
2873        )
2874        if not boot_attribute_expression:
2875            raise KiwiDistributionNameError(
2876                'Boot attribute "%s" does not match expected format %s' %
2877                (boot_attribute, boot_attribute_format)
2878            )
2879        return boot_attribute_expression.group(1).lower()
2880
2881    def get_fs_mount_option_list(self) -> List:
2882        """
2883        List of root filesystem mount options
2884
2885        The list contains one element with the information from the
2886        fsmountoptions attribute. The value there is passed along to
2887        the -o mount option
2888
2889        :return: max one element list with mount option string
2890
2891        :rtype: list
2892        """
2893        option_list = []
2894        mount_options = self.build_type.get_fsmountoptions()
2895        if mount_options:
2896            option_list = [mount_options]
2897
2898        return option_list
2899
2900    def get_fs_create_option_list(self) -> List:
2901        """
2902        List of root filesystem creation options
2903
2904        The list contains elements with the information from the
2905        fscreateoptions attribute string that got split into its
2906        substring components
2907
2908        :return: list with create options
2909
2910        :rtype: list
2911        """
2912        option_list = []
2913        create_options = self.build_type.get_fscreateoptions()
2914        if create_options:
2915            option_list = create_options.split()
2916
2917        return option_list
2918
2919    def get_luks_credentials(self) -> Optional[str]:
2920        """
2921        Return key or passphrase credentials to open the luks pool
2922
2923        :return: data
2924
2925        :rtype: str
2926        """
2927        data = self.build_type.get_luks()
2928        if data:
2929            keyfile_name = None
2930            try:
2931                # try to interpret data as an URI
2932                uri = Uri(data)
2933                if not uri.is_remote():
2934                    keyfile_name = uri.translate()
2935            except Exception:
2936                # this doesn't look like a valid URI, continue as just data
2937                pass
2938            if keyfile_name:
2939                try:
2940                    with open(keyfile_name) as keyfile:
2941                        return keyfile.read()
2942                except Exception as issue:
2943                    raise KiwiFileAccessError(
2944                        f'Failed to read from {keyfile_name!r}: {issue}'
2945                    )
2946        return data
2947
2948    def get_luks_format_options(self) -> List[str]:
2949        """
2950        Return list of luks format options
2951
2952        :return: list of options
2953
2954        :rtype: list
2955        """
2956        result = []
2957        luksversion = self.build_type.get_luks_version()
2958        luksformat = self.build_type.get_luksformat()
2959        luks_pbkdf = self.build_type.get_luks_pbkdf()
2960        if luksversion:
2961            result.append('--type')
2962            result.append(luksversion)
2963        if luksformat:
2964            for option in luksformat[0].get_option():
2965                result.append(option.get_name())
2966                if option.get_value():
2967                    result.append(option.get_value())
2968        if luks_pbkdf:
2969            # Allow to override the pbkdf algorithm that cryptsetup
2970            # uses by default. Cryptsetup may use argon2i by default,
2971            # which is not supported by all bootloaders.
2972            result.append('--pbkdf')
2973            result.append(luks_pbkdf)
2974        return result
2975
2976    def get_derived_from_image_uri(self) -> List[Uri]:
2977        """
2978        Uri object(s) of derived image if configured
2979
2980        Specific image types can be based on one ore more derived
2981        images. This method returns the location of this image(s)
2982        when configured in the XML description
2983
2984        :return: List of Uri instances
2985
2986        :rtype: list
2987        """
2988        image_uris = []
2989        derived_images = self.build_type.get_derived_from()
2990        if derived_images:
2991            for derived_image in derived_images.split(','):
2992                image_uris.append(
2993                    Uri(derived_image, repo_type='container')
2994                )
2995        return image_uris
2996
2997    def set_derived_from_image_uri(self, uri: str) -> None:
2998        """
2999        Set derived_from attribute to a new value
3000
3001        In order to set a new value the derived_from attribute
3002        must be already present in the image configuration
3003
3004        :param str uri: URI
3005        """
3006        if self.build_type.get_derived_from():
3007            self.build_type.set_derived_from(uri)
3008        else:
3009            message = dedent('''\n
3010                No derived_from attribute configured in image <type>
3011
3012                In order to set the uri {0} as base container reference
3013                an initial derived_from attribute must be set in the
3014                type section
3015            ''')
3016            log.warning(message.format(uri))
3017
3018    def set_root_partition_uuid(self, uuid: str) -> None:
3019        """
3020        Store PARTUUID provided in uuid as state information
3021
3022        :param str uuid: PARTUUID
3023        """
3024        self.root_partition_uuid = uuid
3025
3026    def get_root_partition_uuid(self) -> Optional[str]:
3027        """
3028        Return preserved PARTUUID
3029        """
3030        return self.root_partition_uuid
3031
3032    def set_root_filesystem_uuid(self, uuid: str) -> None:
3033        """
3034        Store UUID provided in uuid as state information
3035
3036        :param str uuid: UUID
3037        """
3038        self.root_filesystem_uuid = uuid
3039
3040    def get_root_filesystem_uuid(self) -> Optional[str]:
3041        """
3042        Return preserved UUID
3043        """
3044        return self.root_filesystem_uuid
3045
3046    @staticmethod
3047    def get_archives_target_dirs(
3048        packages_sections_names: Optional[List[xml_parse.packages]]
3049    ) -> Dict:
3050        """
3051        Dict of archive names and target dirs for packages section(s), if any
3052        :return: archive names and its target dir
3053        :rtype: dict
3054        """
3055        result = {}
3056        if packages_sections_names:
3057            for package_section_name in packages_sections_names:
3058                for archive in package_section_name.get_archive():
3059                    result[archive.get_name().strip()] = archive.get_target_dir()
3060
3061        return result
3062
3063    def get_bootstrap_archives_target_dirs(self) -> Dict:
3064        """
3065        Dict of archive names and target dirs from the type="bootstrap"
3066        packages section(s)
3067        :return: archive names and its target dir
3068        :rtype: dict
3069        """
3070        return self.get_archives_target_dirs(
3071            self.get_packages_sections(['bootstrap'])
3072        )
3073
3074    def get_system_archives_target_dirs(self) -> Dict:
3075        """
3076        Dict of archive names and its target dir from the packages sections matching
3077        type="image" and type=build_type
3078        :return: archive names and its target dir
3079        :rtype: dict
3080        """
3081        return self.get_archives_target_dirs(
3082            self.get_packages_sections(['image', self.get_build_type_name()])
3083        )
3084
3085    def _used_profiles(self, profiles=None):
3086        """
3087        return list of profiles to use. The method looks up the
3088        profiles section in the XML description and searches for
3089        profiles matching the architecture. If no arch specifier
3090        is set the profile is considered to be valid for any arch
3091
3092        If the profiles argument is not set only profiles
3093        marked with the attribute import=true will be selected.
3094        Profiles specified in the argument will take the highest
3095        priority and causes to skip the lookup of import profiles
3096        in the XML description
3097
3098        :param list profiles: selected profile names
3099        """
3100        available_profiles = dict()
3101        import_profiles = []
3102        for profiles_section in self.xml_data.get_profiles():
3103            for profile in profiles_section.get_profile():
3104                if self.profile_matches_host_architecture(profile):
3105                    name = profile.get_name()
3106                    available_profiles[name] = profile
3107                    if profile.get_import():
3108                        import_profiles.append(name)
3109
3110        if not profiles:
3111            return import_profiles
3112        else:
3113            resolved_profiles = []
3114            for profile in profiles:
3115                resolved_profiles += self._solve_profile_dependencies(
3116                    profile, available_profiles, resolved_profiles
3117                )
3118            return resolved_profiles
3119
3120    def _section_matches_host_architecture(self, section):
3121        architectures = section.get_arch()
3122        if architectures:
3123            if self.host_architecture not in architectures.split(','):
3124                return False
3125        return True
3126
3127    def _match_docker_base_data(self):
3128        container_config_section = self.get_build_type_containerconfig_section()
3129        container_base = {}
3130        if container_config_section:
3131            name = container_config_section.get_name()
3132            tag = container_config_section.get_tag()
3133            maintainer = container_config_section.get_maintainer()
3134            user = container_config_section.get_user()
3135            workingdir = container_config_section.get_workingdir()
3136            additional_names = container_config_section.get_additionalnames()
3137            if name:
3138                container_base['container_name'] = name
3139
3140            if tag:
3141                container_base['container_tag'] = tag
3142
3143            if additional_names:
3144                container_base['additional_names'] = additional_names.split(',')
3145
3146            if maintainer:
3147                container_base['maintainer'] = maintainer
3148
3149            if user:
3150                container_base['user'] = user
3151
3152            if workingdir:
3153                container_base['workingdir'] = workingdir
3154
3155        return container_base
3156
3157    def _match_docker_entrypoint(self):
3158        container_config_section = self.get_build_type_containerconfig_section()
3159        container_entry = {}
3160        if container_config_section:
3161            entrypoint = container_config_section.get_entrypoint()
3162            if entrypoint and entrypoint[0].get_execute():
3163                container_entry['entry_command'] = [
3164                    entrypoint[0].get_execute()
3165                ]
3166                argument_list = entrypoint[0].get_argument()
3167                if argument_list:
3168                    for argument in argument_list:
3169                        container_entry['entry_command'].append(
3170                            argument.get_name()
3171                        )
3172            elif entrypoint and entrypoint[0].get_clear():
3173                container_entry['entry_command'] = []
3174        return container_entry
3175
3176    def _match_docker_subcommand(self):
3177        container_config_section = self.get_build_type_containerconfig_section()
3178        container_subcommand = {}
3179        if container_config_section:
3180            subcommand = container_config_section.get_subcommand()
3181            if subcommand and subcommand[0].get_execute():
3182                container_subcommand['entry_subcommand'] = [
3183                    subcommand[0].get_execute()
3184                ]
3185                argument_list = subcommand[0].get_argument()
3186                if argument_list:
3187                    for argument in argument_list:
3188                        container_subcommand['entry_subcommand'].append(
3189                            argument.get_name()
3190                        )
3191            elif subcommand and subcommand[0].get_clear():
3192                container_subcommand['entry_subcommand'] = []
3193        return container_subcommand
3194
3195    def _match_docker_expose_ports(self):
3196        container_config_section = self.get_build_type_containerconfig_section()
3197        container_expose = {}
3198        if container_config_section:
3199            expose = container_config_section.get_expose()
3200            if expose and expose[0].get_port():
3201                container_expose['expose_ports'] = []
3202                for port in expose[0].get_port():
3203                    container_expose['expose_ports'].append(
3204                        format(port.get_number())
3205                    )
3206        return container_expose
3207
3208    def _match_docker_volumes(self):
3209        container_config_section = self.get_build_type_containerconfig_section()
3210        container_volumes = {}
3211        if container_config_section:
3212            volumes = container_config_section.get_volumes()
3213            if volumes and volumes[0].get_volume():
3214                container_volumes['volumes'] = []
3215                for volume in volumes[0].get_volume():
3216                    container_volumes['volumes'].append(volume.get_name())
3217        return container_volumes
3218
3219    def _match_docker_stopsignal(self) -> dict:
3220        container_config_section = self.get_build_type_containerconfig_section()
3221        container_stopsignal = {}
3222        if container_config_section:
3223            stopsignal_section = container_config_section.get_stopsignal()
3224            if stopsignal_section:
3225                container_stopsignal['stopsignal'] = stopsignal_section[0]
3226        return container_stopsignal
3227
3228    def _match_docker_environment(self):
3229        container_config_section = self.get_build_type_containerconfig_section()
3230        container_env = {}
3231        if container_config_section:
3232            environment = container_config_section.get_environment()
3233            if environment and environment[0].get_env():
3234                container_env['environment'] = {}
3235                for env in environment[0].get_env():
3236                    container_env['environment'][env.get_name()] = \
3237                        env.get_value()
3238        return container_env
3239
3240    def _match_docker_labels(self):
3241        container_config_section = self.get_build_type_containerconfig_section()
3242        container_labels = {}
3243        if container_config_section:
3244            labels = container_config_section.get_labels()
3245            if labels and labels[0].get_label():
3246                container_labels['labels'] = {}
3247                for label in labels[0].get_label():
3248                    container_labels['labels'][label.get_name()] = \
3249                        label.get_value()
3250        return container_labels
3251
3252    def _match_docker_history(self):
3253        container_config_section = self.get_build_type_containerconfig_section()
3254        container_history = {}
3255        if container_config_section:
3256            history = container_config_section.get_history()
3257            if history:
3258                container_history['history'] = {}
3259                if history[0].get_created_by():
3260                    container_history['history']['created_by'] = \
3261                        history[0].get_created_by()
3262                if history[0].get_author():
3263                    container_history['history']['author'] = \
3264                        history[0].get_author()
3265                if history[0].get_launcher():
3266                    container_history['history']['launcher'] = \
3267                        history[0].get_launcher()
3268                if history[0].get_application_id():
3269                    container_history['history']['application_id'] = \
3270                        history[0].get_application_id()
3271                if history[0].get_package_version():
3272                    container_history['history']['package_version'] = \
3273                        history[0].get_package_version()
3274                container_history['history']['comment'] = \
3275                    history[0].get_valueOf_()
3276        return container_history
3277
3278    def _solve_profile_dependencies(
3279        self, profile, available_profiles, current_profiles
3280    ):
3281        if profile not in available_profiles:
3282            raise KiwiProfileNotFound(
3283                'profile {0} not found for host arch {1}'.format(
3284                    profile, self.host_architecture
3285                )
3286            )
3287        profiles_to_add = []
3288        if profile not in current_profiles:
3289            profiles_to_add.append(profile)
3290            for required in available_profiles[profile].get_requires():
3291                if self.requires_matches_host_architecture(required):
3292                    if required.get_profile() not in current_profiles:
3293                        profiles_to_add += self._solve_profile_dependencies(
3294                            required.get_profile(), available_profiles,
3295                            current_profiles + profiles_to_add
3296                        )
3297        return profiles_to_add
3298
3299    def _build_type_section(self, build_type=None):
3300        """
3301        find type section matching build type and profiles or default
3302        """
3303        # lookup all preferences sections for selected profiles
3304        image_type_sections = []
3305        for preferences in self.get_preferences_sections():
3306            image_type_sections += preferences.get_type()
3307
3308        # lookup if build type matches provided type
3309        if build_type:
3310            for image_type in image_type_sections:
3311                if build_type == image_type.get_image():
3312                    return image_type
3313            raise KiwiTypeNotFound(
3314                'Build type {0!r} not found for applied profiles: {1!r}'.format(
3315                    build_type, self.profiles
3316                )
3317            )
3318
3319        # lookup if build type matches primary type
3320        for image_type in image_type_sections:
3321            if image_type.get_primary():
3322                return image_type
3323
3324        # build type is first type section in XML sequence
3325        if image_type_sections:
3326            return image_type_sections[0]
3327        raise KiwiTypeNotFound(
3328            'No build type defined with applied profiles: {0!r}'.format(
3329                self.profiles
3330            )
3331        )
3332
3333    def _profiled(self, xml_abstract):
3334        """
3335        return only those sections matching the instance stored
3336        profile list from the given XML abstract. Sections without
3337        a profile are wildcard sections and will be used in any
3338        case
3339        """
3340        result = []
3341        for section in xml_abstract:
3342            profiles = section.get_profiles()
3343            if profiles:
3344                for profile in profiles.split(','):
3345                    if self.profiles and profile in self.profiles:
3346                        result.append(section)
3347                        break
3348            else:
3349                result.append(section)
3350        return result
3351
3352    def _to_volume_name(self, name):
3353        name = name.strip()
3354        name = re.sub(r'^\/+', r'', name)
3355        name = name.replace('/', '_')
3356        return name
3357
3358    def _to_mega_byte(self, size):
3359        value = re.search(r'(\d+)([MG]*)', format(size))
3360        if value:
3361            number = value.group(1)
3362            unit = value.group(2)
3363            if unit == 'G':
3364                return int(number) * 1024
3365            else:
3366                return int(number)
3367        else:
3368            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_video_mode(self) -> str:
1240    def get_build_type_bootloader_video_mode(self) -> str:
1241        """
1242        Return bootloader video mode for the selected build type.
1243        If not specified the method returns "auto" as default
1244        mode name.
1245
1246        :return: video mode name or auto
1247
1248        :rtype: str
1249        """
1250        bootloader = self.get_build_type_bootloader_section()
1251        if bootloader:
1252            return bootloader.get_video_mode()
1253        return 'auto'

Return bootloader video mode for the selected build type. If not specified the method returns "auto" as default mode name.

Returns

video mode name or auto

def get_build_type_bootloader_timeout(self) -> Optional[str]:
1255    def get_build_type_bootloader_timeout(self) -> Optional[str]:
1256        """
1257        Return bootloader timeout setting for selected build type
1258
1259        :return: timeout string
1260
1261        :rtype: str
1262        """
1263        bootloader = self.get_build_type_bootloader_section()
1264        if bootloader:
1265            return bootloader.get_timeout()
1266        return None

Return bootloader timeout setting for selected build type

Returns

timeout string

def get_build_type_bootloader_timeout_style(self) -> Optional[str]:
1268    def get_build_type_bootloader_timeout_style(self) -> Optional[str]:
1269        """
1270        Return bootloader timeout style setting for selected build type
1271
1272        :return: timeout_style string
1273
1274        :rtype: str
1275        """
1276        bootloader = self.get_build_type_bootloader_section()
1277        if bootloader:
1278            return bootloader.get_timeout_style()
1279        return None

Return bootloader timeout style setting for selected build type

Returns

timeout_style string

def get_build_type_bootloader_targettype(self) -> Optional[str]:
1281    def get_build_type_bootloader_targettype(self) -> Optional[str]:
1282        """
1283        Return bootloader target type setting. Only relevant for
1284        the zipl bootloader because zipl is installed differently
1285        depending on the storage target it runs later
1286
1287        :return: target type string
1288
1289        :rtype: str
1290        """
1291        bootloader = self.get_build_type_bootloader_section()
1292        if bootloader:
1293            return bootloader.get_targettype()
1294        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:
1296    def get_build_type_bootloader_settings_section(self) -> Any:
1297        """
1298        First bootloadersettings section from the build
1299        type bootloader section
1300
1301        :return: <bootloadersettings> section reference
1302
1303        :rtype: xml_parse::bootloadersettings
1304        """
1305        bootloader_section = self.get_build_type_bootloader_section()
1306        bootloader_settings_section = None
1307        if bootloader_section and bootloader_section.get_bootloadersettings():
1308            bootloader_settings_section = \
1309                bootloader_section.get_bootloadersettings()[0]
1310        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]:
1312    def get_build_type_bootloader_securelinux_section(self) -> List[Any]:
1313        """
1314        First securelinux section from the build
1315        type bootloader section
1316
1317        :return: <securelinux> section reference
1318
1319        :rtype: xml_parse::securelinux
1320        """
1321        bootloader_section = self.get_build_type_bootloader_section()
1322        bootloader_securelinux_section = []
1323        if bootloader_section and bootloader_section.get_securelinux():
1324            bootloader_securelinux_section = \
1325                bootloader_section.get_securelinux()
1326        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]:
1328    def get_build_type_bootloader_environment_variables(self) -> List[str]:
1329        """
1330        List of bootloader variables from the build
1331        type > bootloader > bootloadersettings section
1332        """
1333        variable_list = []
1334        bootloader_settings_section = \
1335            self.get_build_type_bootloader_settings_section()
1336        if bootloader_settings_section:
1337            environment = bootloader_settings_section.get_environment()
1338            if environment and environment[0].get_env():
1339                for env in environment[0].get_env():
1340                    variable_list.append(
1341                        '{}{}'.format(
1342                            env.get_name(),
1343                            f'={env.get_value()}' if env.get_value() else ''
1344                        )
1345                    )
1346        return variable_list

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

def get_bootloader_options(self, option_type: str) -> List[str]:
1348    def get_bootloader_options(self, option_type: str) -> List[str]:
1349        """
1350        List of custom options used in the process to
1351        run bootloader setup workloads
1352        """
1353        result: List[str] = []
1354        bootloader_settings = self.get_build_type_bootloader_settings_section()
1355        if bootloader_settings:
1356            options = []
1357            if option_type == 'shim':
1358                options = bootloader_settings.get_shimoption()
1359            elif option_type == 'install':
1360                options = bootloader_settings.get_installoption()
1361            elif option_type == 'config':
1362                options = bootloader_settings.get_configoption()
1363            for option in options:
1364                result.append(option.get_name())
1365                if option.get_value():
1366                    result.append(option.get_value())
1367        return result

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

def get_bootloader_shim_options(self) -> List[str]:
1369    def get_bootloader_shim_options(self) -> List[str]:
1370        """
1371        List of custom options used in the process to setup secure boot
1372        """
1373        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]:
1375    def get_bootloader_install_options(self) -> List[str]:
1376        """
1377        List of custom options used in the bootloader installation
1378        """
1379        return self.get_bootloader_options('install')

List of custom options used in the bootloader installation

def get_bootloader_config_options(self) -> List[str]:
1381    def get_bootloader_config_options(self) -> List[str]:
1382        """
1383        List of custom options used in the bootloader configuration
1384        """
1385        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:
1387    def get_build_type_bootloader_use_disk_password(self) -> bool:
1388        """
1389        Indicate whether the bootloader configuration should use the
1390        password protecting the encrypted root volume.
1391
1392        :return: True|False
1393
1394        :rtype: bool
1395        """
1396        bootloader = self.get_build_type_bootloader_section()
1397        if bootloader:
1398            return bootloader.get_use_disk_password()
1399        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:
1401    def get_build_type_oemconfig_section(self) -> Any:
1402        """
1403        First oemconfig section from the build type section
1404
1405        :return: <oemconfig> section reference
1406
1407        :rtype: xml_parse::oemconfig
1408        """
1409        oemconfig_sections = self.build_type.get_oemconfig()
1410        if oemconfig_sections:
1411            return oemconfig_sections[0]
1412        return None

First oemconfig section from the build type section

Returns

section reference

def get_oemconfig_oem_resize(self) -> bool:
1414    def get_oemconfig_oem_resize(self) -> bool:
1415        """
1416        State value to activate/deactivate disk resize. Returns a
1417        boolean value if specified or True to set resize on by default
1418
1419        :return: Content of <oem-resize> section value
1420
1421        :rtype: bool
1422        """
1423        oemconfig = self.get_build_type_oemconfig_section()
1424        if oemconfig and oemconfig.get_oem_resize():
1425            return oemconfig.get_oem_resize()[0]
1426        else:
1427            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:
1429    def get_oemconfig_oem_systemsize(self) -> int:
1430        """
1431        State value to retrieve root partition size
1432
1433        :return: Content of <oem-systemsize> section value
1434
1435        :rtype: int
1436        """
1437        oemconfig = self.get_build_type_oemconfig_section()
1438        if oemconfig and oemconfig.get_oem_systemsize():
1439            return int(oemconfig.get_oem_systemsize()[0])
1440        else:
1441            return 0

State value to retrieve root partition size

Returns

Content of section value

def get_oemconfig_oem_multipath_scan(self) -> bool:
1443    def get_oemconfig_oem_multipath_scan(self) -> bool:
1444        """
1445        State value to activate multipath maps. Returns a boolean
1446        value if specified or False
1447
1448        :return: Content of <oem-multipath-scan> section value
1449
1450        :rtype: bool
1451        """
1452        oemconfig = self.get_build_type_oemconfig_section()
1453        if oemconfig and oemconfig.get_oem_multipath_scan():
1454            return oemconfig.get_oem_multipath_scan()[0]
1455        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]:
1457    def get_oemconfig_swap_mbytes(self) -> Optional[int]:
1458        """
1459        Return swapsize in MB if requested or None
1460
1461        Operates on the value of oem-swap and if set to true
1462        returns the given size or the default value.
1463
1464        :return: Content of <oem-swapsize> section value or default
1465
1466        :rtype: int
1467        """
1468        oemconfig = self.get_build_type_oemconfig_section()
1469        if oemconfig and oemconfig.get_oem_swap():
1470            swap_requested = oemconfig.get_oem_swap()[0]
1471            if swap_requested:
1472                swapsize = oemconfig.get_oem_swapsize()
1473                if swapsize:
1474                    return swapsize[0]
1475                else:
1476                    return Defaults.get_swapsize_mbytes()
1477        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:
1479    def get_oemconfig_swap_name(self) -> str:
1480        """
1481        Return the swap space name
1482
1483        Operates on the value of oem-swapname and if set
1484        returns the configured name or the default name: LVSwap
1485
1486        The name of the swap space is used only if the
1487        image is configured to use the LVM volume manager.
1488        In this case swap is a volume and the volume takes
1489        a name. In any other case the given name will have
1490        no effect.
1491
1492        :return: Content of <oem-swapname> section value or default
1493
1494        :rtype: str
1495        """
1496        oemconfig = self.get_build_type_oemconfig_section()
1497        if oemconfig and oemconfig.get_oem_swapname():
1498            return oemconfig.get_oem_swapname()[0]
1499        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:
1501    def get_build_type_containerconfig_section(self) -> Any:
1502        """
1503        First containerconfig section from the build type section
1504
1505        :return: <containerconfig> section reference
1506
1507        :rtype: xml_parse::containerconfig
1508        """
1509        container_config_sections = self.build_type.get_containerconfig()
1510        if container_config_sections:
1511            return container_config_sections[0]
1512        return None

First containerconfig section from the build type section

Returns

section reference

def get_dracut_config(self, action: str) -> DracutT:
1514    def get_dracut_config(self, action: str) -> DracutT:
1515        """
1516        Get dracut initrd config for the specified action
1517        """
1518        uefi = False
1519        modules = []
1520        drivers = []
1521        initrd_sections = self.build_type.get_initrd()
1522        for initrd_section in initrd_sections:
1523            if initrd_section.get_action() == action:
1524                for dracut in initrd_section.get_dracut():
1525                    uefi = bool(dracut.get_uefi())
1526                    if dracut.get_module():
1527                        modules.append(dracut.get_module())
1528                    if dracut.get_driver():
1529                        drivers.append(dracut.get_driver())
1530        return DracutT(
1531            uefi=uefi, modules=modules, drivers=drivers
1532        )

Get dracut initrd config for the specified action

def get_installmedia_initrd_modules(self, action: str) -> List[str]:
1534    def get_installmedia_initrd_modules(self, action: str) -> List[str]:
1535        """
1536        Gets the list of modules to append in installation initrds
1537
1538        :return: a list of dracut module names
1539
1540        :rtype: list
1541        """
1542        modules: List[str] = []
1543        installmedia = self.build_type.get_installmedia()
1544        if not installmedia:
1545            return modules
1546        initrd_sections = installmedia[0].get_initrd()
1547        for initrd_section in initrd_sections:
1548            if initrd_section.get_action() == action:
1549                for module in initrd_section.get_dracut():
1550                    if module.get_module():
1551                        modules.append(module.get_module())
1552        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]:
1554    def get_installmedia_initrd_drivers(self, action: str) -> List[str]:
1555        """
1556        Gets the list of drivers to append in installation initrds
1557
1558        :return: a list of dracut driver names
1559
1560        :rtype: list
1561        """
1562        drivers: List[str] = []
1563        installmedia = self.build_type.get_installmedia()
1564        if not installmedia:
1565            return drivers
1566        initrd_sections = installmedia[0].get_initrd()
1567        for initrd_section in initrd_sections:
1568            if initrd_section.get_action() == action:
1569                for driver in initrd_section.get_dracut():
1570                    if driver.get_driver():
1571                        drivers.append(driver.get_driver())
1572        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]:
1574    def get_build_type_size(
1575        self, include_unpartitioned: bool = False
1576    ) -> Optional[size_type]:
1577        """
1578        Size information from the build type section.
1579        If no unit is set the value is treated as mbytes
1580
1581        :param bool include_unpartitioned: sets if the unpartitioned area
1582            should be included in the computed size or not
1583
1584        :return: mbytes
1585
1586        :rtype: int
1587        """
1588        size_section = self.build_type.get_size()
1589        if size_section:
1590            unit = size_section[0].get_unit()
1591            additive = size_section[0].get_additive()
1592            unpartitioned = size_section[0].get_unpartitioned()
1593            value = int(size_section[0].get_valueOf_())
1594            if not include_unpartitioned and unpartitioned is not None:
1595                value -= unpartitioned
1596            if unit == 'G':
1597                value *= 1024
1598            return size_type(
1599                mbytes=value, additive=additive
1600            )
1601        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:
1603    def get_build_type_unpartitioned_bytes(self) -> int:
1604        """
1605        Size of the unpartitioned area for image in megabytes
1606
1607        :return: mbytes
1608
1609        :rtype: int
1610        """
1611        size_section = self.build_type.get_size()
1612        if size_section:
1613            unit = size_section[0].get_unit() or 'M'
1614            unpartitioned = size_section[0].get_unpartitioned() or 0
1615            return StringToSize.to_bytes('{0}{1}'.format(unpartitioned, unit))
1616        return 0

Size of the unpartitioned area for image in megabytes

Returns

mbytes

def get_disk_start_sector(self) -> int:
1618    def get_disk_start_sector(self) -> int:
1619        """
1620        First disk sector number to be used by the first disk partition.
1621
1622        :return: number
1623
1624        :rtype: int
1625        """
1626        disk_start_sector = self.build_type.get_disk_start_sector()
1627        if disk_start_sector is None:
1628            disk_start_sector = Defaults.get_default_disk_start_sector()
1629        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]:
1631    def get_build_type_spare_part_size(self) -> Optional[int]:
1632        """
1633        Size information for the spare_part size from the build
1634        type. If no unit is set the value is treated as mbytes
1635
1636        :return: mbytes
1637
1638        :rtype: int
1639        """
1640        spare_part_size = self.build_type.get_spare_part()
1641        if spare_part_size:
1642            return self._to_mega_byte(spare_part_size)
1643        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]:
1645    def get_build_type_spare_part_fs_attributes(self) -> Optional[List]:
1646        """
1647        Build type specific list of filesystem attributes applied to
1648        the spare partition.
1649
1650        :return: list of strings or empty list
1651
1652        :rtype: list
1653        """
1654        spare_part_attributes = self.build_type.get_spare_part_fs_attributes()
1655        if spare_part_attributes:
1656            return spare_part_attributes.strip().split(',')
1657        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:
1659    def get_build_type_format_options(self) -> Dict:
1660        """
1661        Disk format options returned as a dictionary
1662
1663        :return: format options
1664
1665        :rtype: dict
1666        """
1667        result = {}
1668        format_options = self.build_type.get_formatoptions()
1669        if format_options:
1670            for option in format_options.split(','):
1671                key_value_list = option.split('=')
1672                if len(key_value_list) == 2:
1673                    result[key_value_list[0]] = key_value_list[1]
1674                else:
1675                    result[key_value_list[0]] = None
1676        return result

Disk format options returned as a dictionary

Returns

format options

def get_volume_group_name(self) -> str:
1678    def get_volume_group_name(self) -> str:
1679        """
1680        Volume group name from selected <systemdisk> section
1681
1682        :return: volume group name
1683
1684        :rtype: str
1685        """
1686        systemdisk_section = self.get_build_type_system_disk_section()
1687        volume_group_name = None
1688        if systemdisk_section:
1689            volume_group_name = systemdisk_section.get_name()
1690        if not volume_group_name:
1691            volume_group_name = Defaults.get_default_volume_group_name()
1692        return volume_group_name

Volume group name from selected section

Returns

volume group name

def get_users(self) -> List:
1694    def get_users(self) -> List:
1695        """
1696        List of configured users.
1697
1698        Each entry in the list is a single xml_parse::user instance.
1699
1700        :return: list of <user> section reference(s)
1701
1702        :rtype: list
1703        """
1704        users_list = []
1705        users_names_added = []
1706        for users_section in self.get_users_sections():
1707            for user in users_section.get_user():
1708                if user.get_name() not in users_names_added:
1709                    users_list.append(user)
1710                    users_names_added.append(user.get_name())
1711
1712        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]:
1714    def get_user_groups(self, user_name) -> List[str]:
1715        """
1716        List of group names matching specified user
1717
1718        Each entry in the list is the name of a group and optionally its
1719        group ID separated by a colon, that the specified user belongs to.
1720        The first item in the list is the login or primary group. The
1721        list will be empty if no groups are specified in the
1722        description file.
1723
1724        :return: groups data for the given user
1725
1726        :rtype: list
1727        """
1728        groups_list = []
1729        for users_section in self.get_users_sections():
1730            for user in users_section.get_user():
1731                if user.get_name() == user_name:
1732                    user_groups = user.get_groups()
1733                    if user_groups:
1734                        groups_list += user.get_groups().split(',')
1735
1736        # order of list items matter, thus we don't use set() here
1737        # better faster, nicer solutions welcome :)
1738        result_group_list = []
1739        for item in groups_list:
1740            if item not in result_group_list:
1741                result_group_list.append(item)
1742
1743        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:
1745    def get_container_config(self) -> Dict:
1746        """
1747        Dictionary of containerconfig information
1748
1749        Takes attributes and subsection data from the selected
1750        <containerconfig> section and stores it in a dictionary
1751        """
1752        container_config = self._match_docker_base_data()
1753        container_config.update(
1754            self._match_docker_entrypoint()
1755        )
1756        container_config.update(
1757            self._match_docker_subcommand()
1758        )
1759        container_config.update(
1760            self._match_docker_expose_ports()
1761        )
1762        container_config.update(
1763            self._match_docker_volumes()
1764        )
1765        container_config.update(
1766            self._match_docker_stopsignal()
1767        )
1768        container_config.update(
1769            self._match_docker_environment()
1770        )
1771        container_config.update(
1772            self._match_docker_labels()
1773        )
1774        container_config.update(
1775            self._match_docker_history()
1776        )
1777
1778        desc = self.get_description_section()
1779        author_contact = "{0} <{1}>".format(desc.author, desc.contact)
1780        if 'history' not in container_config:
1781            container_config['history'] = {}
1782        if 'author' not in container_config['history']:
1783            container_config['history']['author'] = author_contact
1784        if 'maintainer' not in container_config:
1785            container_config['maintainer'] = author_contact
1786
1787        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:
1789    def set_container_config_tag(self, tag: str) -> None:
1790        """
1791        Set new tag name in containerconfig section
1792
1793        In order to set a new tag value an existing containerconfig and
1794        tag setup is required
1795
1796        :param str tag: tag name
1797        """
1798        container_config_section = self.get_build_type_containerconfig_section()
1799        if container_config_section and container_config_section.get_tag():
1800            container_config_section.set_tag(tag)
1801        else:
1802            message = dedent('''\n
1803                No <containerconfig> section and/or tag attribute configured
1804
1805                In order to set the tag {0} as new container tag,
1806                an initial containerconfig section including a tag
1807                setup is required
1808            ''')
1809            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:
1811    def add_container_config_label(self, label_name: str, value: str) -> None:
1812        """
1813        Adds a new label in the containerconfig section, if a label with the
1814        same name is already defined in containerconfig it gets overwritten by
1815        this method.
1816
1817        :param str label_name: the string representing the label name
1818        :param str value: the value of the label
1819        """
1820        if self.get_build_type_name() not in ['docker', 'oci']:
1821            message = dedent('''\n
1822                Labels can only be configured for container image types
1823                docker and oci.
1824            ''')
1825            log.warning(message)
1826            return
1827
1828        container_config_section = self.get_build_type_containerconfig_section()
1829        if not container_config_section:
1830            container_config_section = xml_parse.containerconfig(
1831                name=Defaults.get_default_container_name(),
1832                tag=Defaults.get_default_container_tag()
1833            )
1834            self.build_type.set_containerconfig([container_config_section])
1835
1836        labels = container_config_section.get_labels()
1837        if not labels:
1838            labels = [xml_parse.labels()]
1839
1840        label_names = []
1841        for label in labels[0].get_label():
1842            label_names.append(label.get_name())
1843
1844        if label_name in label_names:
1845            labels[0].replace_label_at(
1846                label_names.index(label_name),
1847                xml_parse.label(label_name, value)
1848            )
1849        else:
1850            labels[0].add_label(xml_parse.label(label_name, value))
1851
1852        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]:
1854    def get_partitions(self) -> Dict[str, ptable_entry_type]:
1855        """
1856        Dictionary of configured partitions.
1857
1858        Each entry in the dict references a ptable_entry_type
1859        Each key in the dict references the name of the
1860        partition entry as handled by KIWI
1861
1862        :return:
1863            Contains dict of ptable_entry_type tuples
1864
1865            .. code:: python
1866
1867                {
1868                    'NAME': ptable_entry_type(
1869                        mbsize=int,
1870                        clone=int,
1871                        partition_name=str,
1872                        partition_type=str,
1873                        partition_id=Optional[int],
1874                        mountpoint=str,
1875                        filesystem=str,
1876                        label=str
1877                    )
1878                }
1879
1880        :rtype: dict
1881        """
1882        partitions: Dict[str, ptable_entry_type] = {}
1883        partitions_section = self.get_build_type_partitions_section()
1884        if not partitions_section:
1885            return partitions
1886        for partition in partitions_section.get_partition():
1887            name = partition.get_name()
1888            partition_name = partition.get_partition_name() or f'p.lx{name}'
1889            partitions[name] = ptable_entry_type(
1890                mbsize=self._to_mega_byte(partition.get_size()),
1891                clone=int(partition.get_clone()) if partition.get_clone() else 0,
1892                partition_name=partition_name,
1893                partition_type=partition.get_partition_type() or 't.linux',
1894                partition_id=partition.get_part_id(),
1895                mountpoint=partition.get_mountpoint(),
1896                filesystem=partition.get_filesystem(),
1897                label=partition.get_label() or ''
1898            )
1899        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]]]:
1901    def get_host_key_certificates(
1902        self
1903    ) -> Union[List[Dict[str, List[str]]], List[Dict[str, str]]]:
1904        cc_result = []
1905        cc_certificates: Dict[str, List[str]] = {}
1906        securelinux_list = \
1907            self.get_build_type_bootloader_securelinux_section()
1908        for securelinux in securelinux_list:
1909            cc_certificates = {
1910                'hkd_cert': [],
1911                'hkd_revocation_list': [],
1912                'hkd_ca_cert': securelinux.get_hkd_ca_cert(),
1913                'hkd_sign_cert': securelinux.get_hkd_sign_cert()
1914            }
1915            for hkd_cert in securelinux.get_hkd_cert():
1916                cc_certificates['hkd_cert'].append(hkd_cert.get_name())
1917            for hkd_revocation_list in securelinux.get_hkd_revocation_list():
1918                cc_certificates['hkd_revocation_list'].append(
1919                    hkd_revocation_list.get_name()
1920                )
1921            cc_result.append(cc_certificates)
1922        return cc_result
def get_containers(self) -> List[ContainerT]:
1924    def get_containers(self) -> List[ContainerT]:
1925        containers = []
1926
1927        def build_fetch_command(
1928            root_dir: str,
1929            container_uri: str = '',
1930            container_file_name: str = '',
1931            container_endpoint: str = ''
1932        ):
1933            pass  # pragma: nocover
1934        for containers_section in self.get_containers_sections():
1935            for container in containers_section.get_container():
1936                if self.container_matches_host_architecture(container):
1937                    fetch_command = build_fetch_command
1938                    load_command = []
1939                    container_tag = container.get_tag() or 'latest'
1940                    container_path = container.get_path() or ''
1941                    container_endpoint = os.path.normpath(
1942                        '{0}/{1}/{2}:{3}'.format(
1943                            containers_section.get_source(), container_path,
1944                            container.name, container_tag
1945                        )
1946                    )
1947                    container_file_name = '{0}/{1}_{2}'.format(
1948                        defaults.LOCAL_CONTAINERS, container.name, container_tag
1949                    )
1950                    container_backend = containers_section.get_backend() or ''
1951                    if container_backend in ['podman', 'docker', 'container-snap']:
1952                        if Defaults.is_buildservice_worker():
1953                            container_uri = Uri(
1954                                'obsrepositories:/{0}'.format(
1955                                    container_endpoint
1956                                ), 'container'
1957                            ).translate()
1958
1959                            def build_fetch_command(
1960                                root_dir: str,
1961                                container_uri: str = container_uri,
1962                                container_file_name: str = container_file_name,
1963                                container_endpoint: str = container_endpoint
1964                            ):
1965                                def perform():
1966                                    Command.run(
1967                                        [
1968                                            'cp', '{0}.ociarchive'.format(
1969                                                container_uri
1970                                            ), os.path.normpath(
1971                                                '{0}/{1}'.format(
1972                                                    root_dir,
1973                                                    container_file_name
1974                                                )
1975                                            )
1976                                        ]
1977                                    )
1978                                perform()
1979                            fetch_command = build_fetch_command
1980                        else:
1981
1982                            def build_fetch_command(
1983                                root_dir: str,
1984                                container_uri: str = '',
1985                                container_file_name: str = container_file_name,
1986                                container_endpoint: str = container_endpoint
1987                            ):
1988                                def perform():
1989                                    Command.run(
1990                                        [
1991                                            'chroot', root_dir,
1992                                            '/usr/bin/skopeo', 'copy',
1993                                            'docker://{0}'.format(
1994                                                container_endpoint
1995                                            ),
1996                                            'oci-archive:{0}:{1}'.format(
1997                                                container_file_name,
1998                                                container_endpoint
1999                                            )
2000                                        ]
2001                                    )
2002                                perform()
2003                            fetch_command = build_fetch_command
2004                        if not container.get_fetch_only():
2005                            load_command = [
2006                                f'/usr/bin/{container_backend}',
2007                                'load', '-i', container_file_name
2008                            ]
2009                    containers.append(
2010                        ContainerT(
2011                            name=f'{container.name}_{container_tag}',
2012                            backend=container_backend,
2013                            container_file=container_file_name,
2014                            fetch_only=bool(container.get_fetch_only()),
2015                            fetch_command=fetch_command,
2016                            load_command=load_command
2017                        )
2018                    )
2019        return containers
def get_volumes(self) -> List[volume_type]:
2021    def get_volumes(self) -> List[volume_type]:
2022        """
2023        List of configured systemdisk volumes.
2024
2025        Each entry in the list is a tuple with the following information
2026
2027        * name: name of the volume
2028        * size: size of the volume
2029        * realpath: system path to lookup volume data. If no mountpoint
2030          is set the volume name is used as data path.
2031        * mountpoint: volume mount point and volume data path
2032        * fullsize: takes all space True|False
2033        * attributes: list of volume attributes handled via chattr
2034
2035        :return:
2036            Contains list of volume_type tuples
2037
2038            .. code:: python
2039
2040                [
2041                    volume_type(
2042                        name=volume_name,
2043                        parent=volume_parent,
2044                        size=volume_size,
2045                        realpath=path,
2046                        mountpoint=path,
2047                        fullsize=True,
2048                        label=volume_label,
2049                        attributes=['no-copy-on-write'],
2050                        is_root_volume=True|False
2051                    )
2052                ]
2053
2054        :rtype: list
2055        """
2056        volume_type_list: List[volume_type] = []
2057        systemdisk_section = self.get_build_type_system_disk_section()
2058        selected_filesystem = self.build_type.get_filesystem()
2059        swap_mbytes = self.get_oemconfig_swap_mbytes()
2060        swap_name = self.get_oemconfig_swap_name()
2061        if not systemdisk_section:
2062            return volume_type_list
2063        volumes = systemdisk_section.get_volume()
2064        have_root_volume_setup = False
2065        have_full_size_volume = False
2066        if volumes:
2067            for volume in volumes:
2068                if not self.volume_matches_host_architecture(volume):
2069                    continue
2070                # volume setup for a full qualified volume with name and
2071                # mountpoint information. See below for exceptions
2072                name = volume.get_name()
2073                parent = volume.get_parent() or ''
2074                mountpoint = volume.get_mountpoint()
2075                realpath = mountpoint
2076                size = volume.get_size()
2077                freespace = volume.get_freespace()
2078                fullsize = False
2079                label = volume.get_label()
2080                attributes = []
2081                is_root_volume = False
2082
2083                if volume.get_quota():
2084                    attributes.append(f'quota={volume.get_quota()}')
2085
2086                if volume.get_copy_on_write() is False:
2087                    # by default copy-on-write is switched on for any
2088                    # filesystem. Thus only if no copy on write is requested
2089                    # the attribute is handled
2090                    attributes.append('no-copy-on-write')
2091
2092                if volume.get_filesystem_check() is True:
2093                    # by default filesystem check is switched off for any
2094                    # filesystem except the rootfs. Thus only if filesystem
2095                    # check is requested the attribute is handled
2096                    attributes.append('enable-for-filesystem-check')
2097
2098                if '@root' in name:
2099                    # setup root volume, it takes an optional volume
2100                    # name if specified as @root=name and has no specific
2101                    # mountpoint. The default name is set to
2102                    # defaults.ROOT_VOLUME_NAME if no other root volume
2103                    # name is provided
2104                    mountpoint = None
2105                    realpath = '/'
2106                    is_root_volume = True
2107                    root_volume_expression = re.match(
2108                        r'@root=(.+)', name
2109                    )
2110                    if root_volume_expression:
2111                        name = root_volume_expression.group(1)
2112                    else:
2113                        name = defaults.ROOT_VOLUME_NAME
2114                    have_root_volume_setup = True
2115                elif not mountpoint:
2116                    # setup volume without mountpoint. In this case the name
2117                    # attribute is used as mountpoint path and a name for the
2118                    # volume is created from that path information
2119                    mountpoint = name
2120                    realpath = mountpoint
2121                    name = self._to_volume_name(name)
2122
2123                if size:
2124                    size = 'size:' + format(
2125                        self._to_mega_byte(size)
2126                    )
2127                elif freespace:
2128                    size = 'freespace:' + format(
2129                        self._to_mega_byte(freespace)
2130                    )
2131                else:
2132                    size = 'freespace:' + format(
2133                        Defaults.get_min_volume_mbytes(selected_filesystem)
2134                    )
2135
2136                if ':all' in size:
2137                    size = None
2138                    fullsize = True
2139                    have_full_size_volume = True
2140
2141                volume_type_list.append(
2142                    volume_type(
2143                        name=name,
2144                        parent=parent,
2145                        size=size,
2146                        fullsize=fullsize,
2147                        mountpoint=mountpoint,
2148                        realpath=realpath,
2149                        label=label,
2150                        attributes=attributes,
2151                        is_root_volume=is_root_volume
2152                    )
2153                )
2154
2155        if not have_root_volume_setup:
2156            # There must always be a root volume setup. It will be the
2157            # full size volume if no other volume has this setup
2158            volume_management = self.get_volume_management()
2159            root_volume_name = \
2160                defaults.ROOT_VOLUME_NAME if volume_management == 'lvm' else ''
2161            if have_full_size_volume:
2162                size = 'freespace:' + format(
2163                    Defaults.get_min_volume_mbytes(selected_filesystem)
2164                )
2165                fullsize = False
2166            else:
2167                size = None
2168                fullsize = True
2169            volume_type_list.append(
2170                volume_type(
2171                    name=root_volume_name,
2172                    parent='',
2173                    size=size,
2174                    fullsize=fullsize,
2175                    mountpoint=None,
2176                    realpath='/',
2177                    label=None,
2178                    attributes=[],
2179                    is_root_volume=True
2180                )
2181            )
2182
2183        if swap_mbytes and self.get_volume_management() == 'lvm':
2184            volume_type_list.append(
2185                volume_type(
2186                    name=swap_name,
2187                    parent='',
2188                    size='size:{0}'.format(swap_mbytes),
2189                    fullsize=False,
2190                    mountpoint=None,
2191                    realpath='swap',
2192                    label='SWAP',
2193                    attributes=[],
2194                    is_root_volume=False
2195                )
2196            )
2197
2198        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]:
2200    def get_volume_management(self) -> Optional[str]:
2201        """
2202        Provides information which volume management system is used
2203
2204        :return: name of volume manager
2205
2206        :rtype: str
2207        """
2208        volume_filesystems = ['btrfs']
2209        selected_filesystem = self.build_type.get_filesystem()
2210        selected_system_disk = self.get_build_type_system_disk_section()
2211        volume_management = None
2212        if selected_system_disk and selected_system_disk.get_preferlvm():
2213            # LVM volume management is preferred, use it
2214            volume_management = 'lvm'
2215        elif selected_filesystem in volume_filesystems and selected_system_disk:
2216            # specified filesystem has its own volume management system
2217            volume_management = selected_filesystem
2218        elif selected_system_disk:
2219            # systemdisk section is specified with non volume capable
2220            # filesystem and no volume management preference. So let's
2221            # use LVM by default
2222            volume_management = 'lvm'
2223        return volume_management

Provides information which volume management system is used

Returns

name of volume manager

def get_drivers_list(self) -> List:
2225    def get_drivers_list(self) -> List:
2226        """
2227        List of driver names from all drivers sections matching
2228        configured profiles
2229
2230        :return: driver names
2231
2232        :rtype: list
2233        """
2234        drivers_sections = self._profiled(
2235            self.xml_data.get_drivers()
2236        )
2237        result = []
2238        if drivers_sections:
2239            for driver in drivers_sections:
2240                for file_section in driver.get_file():
2241                    result.append(file_section.get_name())
2242        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:
2244    def get_strip_list(self, section_type: str) -> List:
2245        """
2246        List of strip names matching the given section type
2247        and profiles
2248
2249        :param str section_type: type name from packages section
2250
2251        :return: strip names
2252
2253        :rtype: list
2254        """
2255        strip_sections = self._profiled(
2256            self.xml_data.get_strip()
2257        )
2258        result = []
2259        if strip_sections:
2260            for strip in strip_sections:
2261                if strip.get_type() == section_type:
2262                    for file_section in strip.get_file():
2263                        result.append(file_section.get_name())
2264        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:
2266    def get_strip_files_to_delete(self) -> List:
2267        """
2268        Items to delete from strip section
2269
2270        :return: item names
2271
2272        :rtype: list
2273        """
2274        return self.get_strip_list('delete')

Items to delete from strip section

Returns

item names

def get_strip_tools_to_keep(self) -> List:
2276    def get_strip_tools_to_keep(self) -> List:
2277        """
2278        Tools to keep from strip section
2279
2280        :return: tool names
2281
2282        :rtype: list
2283        """
2284        return self.get_strip_list('tools')

Tools to keep from strip section

Returns

tool names

def get_strip_libraries_to_keep(self) -> List:
2286    def get_strip_libraries_to_keep(self) -> List:
2287        """
2288        Libraries to keep from strip section
2289
2290        :return: librarie names
2291
2292        :rtype: list
2293        """
2294        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]:
2296    def get_include_section_reference_file_names(self) -> List[str]:
2297        """
2298        List of all <include> section file name references
2299
2300        :return: List[str]
2301
2302        :rtype: list
2303        """
2304        include_files = []
2305        for include in self.xml_data.get_include():
2306            include_files.append(include.get_from())
2307        return include_files

List of all section file name references

Returns

List[str]

def get_repository_sections(self) -> List:
2309    def get_repository_sections(self) -> List:
2310        """
2311        List of all repository sections for the selected profiles that
2312        matches the host architecture
2313
2314        :return: <repository> section reference(s)
2315
2316        :rtype: list
2317        """
2318        repository_list = []
2319        for repository in self._profiled(self.xml_data.get_repository()):
2320            if self.repository_matches_host_architecture(repository):
2321                repository_list.append(repository)
2322        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:
2324    def get_containers_sections(self) -> List:
2325        """
2326        List of all containers sections for the selected profiles that
2327        matches the host architecture
2328
2329        :return: <containers> section reference(s)
2330
2331        :rtype: list
2332        """
2333        containers_list = []
2334        for containers in self._profiled(self.xml_data.get_containers()):
2335            if self.containers_matches_host_architecture(containers):
2336                containers_list.append(containers)
2337        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:
2339    def get_repository_sections_used_for_build(self) -> List:
2340        """
2341        List of all repositorys sections used to build the image and
2342        matching configured profiles.
2343
2344        :return: <repository> section reference(s)
2345
2346        :rtype: list
2347        """
2348        repos = self.get_repository_sections()
2349        return list(
2350            repo for repo in repos if not repo.get_imageonly()
2351        )

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:
2353    def get_repository_sections_used_in_image(self) -> List:
2354        """
2355        List of all repositorys sections to be configured in the resulting
2356        image matching configured profiles.
2357
2358        :return: <repository> section reference(s)
2359
2360        :rtype: list
2361        """
2362        repos = self.get_repository_sections()
2363        return list(
2364            repo for repo in repos
2365            if repo.get_imageinclude() or repo.get_imageonly()
2366        )

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:
2368    def delete_repository_sections(self) -> None:
2369        """
2370        Delete all repository sections matching configured profiles
2371        """
2372        self.xml_data.set_repository([])

Delete all repository sections matching configured profiles

def delete_repository_sections_used_for_build(self) -> None:
2374    def delete_repository_sections_used_for_build(self) -> None:
2375        """
2376        Delete all repository sections used to build the image matching
2377        configured profiles
2378        """
2379        used_for_build = self.get_repository_sections_used_for_build()
2380        all_repos = self.get_repository_sections()
2381        self.xml_data.set_repository(
2382            [
2383                repo for repo in all_repos if repo not in used_for_build
2384            ]
2385        )

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

def get_repositories_signing_keys(self) -> List[str]:
2387    def get_repositories_signing_keys(self) -> List[str]:
2388        """
2389        Get list of signing keys specified on the repositories
2390        """
2391        key_file_list: List[str] = []
2392        release_version = self.get_release_version()
2393        release_vars = [
2394            '$releasever',
2395            '${releasever}'
2396        ]
2397        for repository in self.get_repository_sections() or []:
2398            for signing in repository.get_source().get_signing() or []:
2399                normalized_key_url = Uri(signing.get_key()).translate()
2400                if release_version:
2401                    for release_var in release_vars:
2402                        if release_var in normalized_key_url:
2403                            normalized_key_url = normalized_key_url.replace(
2404                                release_var, release_version
2405                            )
2406                if normalized_key_url not in key_file_list:
2407                    key_file_list.append(normalized_key_url)
2408        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:
2410    def set_repository(
2411        self, repo_source: str, repo_type: str, repo_alias: str,
2412        repo_prio: str, repo_imageinclude: bool = False,
2413        repo_package_gpgcheck: Optional[bool] = None,
2414        repo_signing_keys: List[str] = [], components: str = None,
2415        distribution: str = None, repo_gpgcheck: Optional[bool] = None,
2416        repo_sourcetype: str = None
2417    ) -> None:
2418        """
2419        Overwrite repository data of the first repository
2420
2421        :param str repo_source: repository URI
2422        :param str repo_type: type name defined by schema
2423        :param str repo_alias: alias name
2424        :param str repo_prio: priority number, package manager specific
2425        :param bool repo_imageinclude: setup repository inside of the image
2426        :param bool repo_package_gpgcheck: enable/disable package gpg checks
2427        :param list repo_signing_keys: list of signing key file names
2428        :param str components: component names for debian repos
2429        :param str distribution: base distribution name for debian repos
2430        :param bool repo_gpgcheck: enable/disable repo gpg checks
2431        """
2432        repository_sections = self.get_repository_sections()
2433        if repository_sections:
2434            repository = repository_sections[0]
2435            if repo_alias:
2436                repository.set_alias(repo_alias)
2437            if repo_type:
2438                repository.set_type(repo_type)
2439            if repo_source:
2440                repository.get_source().set_path(repo_source)
2441            if repo_prio:
2442                repository.set_priority(int(repo_prio))
2443            if repo_imageinclude:
2444                repository.set_imageinclude(repo_imageinclude)
2445            if repo_package_gpgcheck is not None:
2446                repository.set_package_gpgcheck(repo_package_gpgcheck)
2447            if repo_signing_keys:
2448                repository.get_source().set_signing(
2449                    [xml_parse.signing(key=k) for k in repo_signing_keys]
2450                )
2451            if components:
2452                repository.set_components(components)
2453            if distribution:
2454                repository.set_distribution(distribution)
2455            if repo_gpgcheck is not None:
2456                repository.set_repository_gpgcheck(repo_gpgcheck)
2457            if repo_sourcetype:
2458                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:
2460    def add_repository(
2461        self, repo_source: str, repo_type: str, repo_alias: str = None,
2462        repo_prio: str = '', repo_imageinclude: bool = False,
2463        repo_package_gpgcheck: Optional[bool] = None,
2464        repo_signing_keys: List[str] = [], components: str = None,
2465        distribution: str = None, repo_gpgcheck: Optional[bool] = None,
2466        repo_sourcetype: str = None
2467    ) -> None:
2468        """
2469        Add a new repository section at the end of the list
2470
2471        :param str repo_source: repository URI
2472        :param str repo_type: type name defined by schema
2473        :param str repo_alias: alias name
2474        :param str repo_prio: priority number, package manager specific
2475        :param bool repo_imageinclude: setup repository inside of the image
2476        :param bool repo_package_gpgcheck: enable/disable package gpg checks
2477        :param list repo_signing_keys: list of signing key file names
2478        :param str components: component names for debian repos
2479        :param str distribution: base distribution name for debian repos
2480        :param bool repo_gpgcheck: enable/disable repo gpg checks
2481        """
2482        priority_number: Optional[int] = None
2483        try:
2484            priority_number = int(repo_prio)
2485        except Exception:
2486            pass
2487
2488        self.xml_data.add_repository(
2489            xml_parse.repository(
2490                type_=repo_type,
2491                alias=repo_alias,
2492                priority=priority_number,
2493                source=xml_parse.source(
2494                    path=repo_source,
2495                    signing=[
2496                        xml_parse.signing(key=k) for k in repo_signing_keys
2497                    ]
2498                ),
2499                imageinclude=repo_imageinclude,
2500                package_gpgcheck=repo_package_gpgcheck,
2501                repository_gpgcheck=repo_gpgcheck,
2502                components=components,
2503                distribution=distribution,
2504                sourcetype=repo_sourcetype
2505            )
2506        )

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:
2508    def add_certificate(self, cert_file: str, target_distribution: str) -> None:
2509        """
2510        Add <certificate name="cert_file"> to main <certificates> section
2511        The main section will be created if it does not exist. Also
2512        setup the target_distribution in the resulting main section.
2513        """
2514        certificates_section = self._profiled(
2515            self.xml_data.get_certificates()
2516        )
2517        if not certificates_section:
2518            self.xml_data.set_certificates(
2519                [
2520                    xml_parse.certificates(
2521                        target_distribution=target_distribution,
2522                        certificate=[xml_parse.certificate(name=cert_file)]
2523                    )
2524                ]
2525            )
2526        else:
2527            certificates_section[0].set_target_distribution(
2528                target_distribution
2529            )
2530            certificates_section[0].add_certificate(
2531                xml_parse.certificate(
2532                    name=cert_file
2533                )
2534            )

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]:
2536    def get_certificates(self) -> List[str]:
2537        """
2538        Read list of certificates
2539        """
2540        cert_list = []
2541        certificates_section = self._profiled(
2542            self.xml_data.get_certificates()
2543        )
2544        if certificates_section:
2545            for certificate in certificates_section[0].get_certificate():
2546                cert_list.append(certificate.get_name())
2547        return sorted(list(set(cert_list)))

Read list of certificates

def get_certificates_target_distribution(self) -> str:
2549    def get_certificates_target_distribution(self) -> str:
2550        """
2551        Read CA target distribution
2552        """
2553        target_distribution = ''
2554        certificates_section = self._profiled(
2555            self.xml_data.get_certificates()
2556        )
2557        if certificates_section:
2558            target_distribution = \
2559                certificates_section[0].get_target_distribution()
2560        return target_distribution

Read CA target distribution

def resolve_this_path(self) -> None:
2562    def resolve_this_path(self) -> None:
2563        """
2564        Resolve any this:// repo source path into the path
2565        representing the target inside of the image description
2566        directory
2567        """
2568        for repository in self.get_repository_sections() or []:
2569            repo_source = repository.get_source()
2570            repo_path = repo_source.get_path()
2571            if repo_path.startswith('this://'):
2572                repo_path = repo_path.replace('this://', '')
2573                repo_source.set_path(
2574                    'dir://{0}'.format(
2575                        os.path.realpath(
2576                            os.path.join(
2577                                self.xml_data.description_dir, repo_path
2578                            )
2579                        )
2580                    )
2581                )

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:
2583    def copy_displayname(self, target_state: Any) -> None:
2584        """
2585        Copy image displayname from this xml state to the target xml state
2586
2587        :param object target_state: XMLState instance
2588        """
2589        displayname = self.xml_data.get_displayname()
2590        if displayname:
2591            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:
2593    def copy_name(self, target_state: Any) -> None:
2594        """
2595        Copy image name from this xml state to the target xml state
2596
2597        :param object target_state: XMLState instance
2598        """
2599        target_state.xml_data.set_name(
2600            self.xml_data.get_name()
2601        )

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:
2603    def copy_drivers_sections(self, target_state: Any) -> None:
2604        """
2605        Copy drivers sections from this xml state to the target xml state
2606
2607        :param object target_state: XMLState instance
2608        """
2609        drivers_sections = self._profiled(
2610            self.xml_data.get_drivers()
2611        )
2612        if drivers_sections:
2613            for drivers_section in drivers_sections:
2614                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:
2616    def copy_systemdisk_section(self, target_state: Any) -> None:
2617        """
2618        Copy systemdisk sections from this xml state to the target xml state
2619
2620        :param object target_state: XMLState instance
2621        """
2622        systemdisk_section = self.get_build_type_system_disk_section()
2623        if systemdisk_section:
2624            target_state.build_type.set_systemdisk(
2625                [systemdisk_section]
2626            )

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:
2628    def copy_strip_sections(self, target_state: Any) -> None:
2629        """
2630        Copy strip sections from this xml state to the target xml state
2631
2632        :param object target_state: XMLState instance
2633        """
2634        strip_sections = self._profiled(
2635            self.xml_data.get_strip()
2636        )
2637        if strip_sections:
2638            for strip_section in strip_sections:
2639                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:
2641    def copy_machine_section(self, target_state: Any) -> None:
2642        """
2643        Copy machine sections from this xml state to the target xml state
2644
2645        :param object target_state: XMLState instance
2646        """
2647        machine_section = self.get_build_type_machine_section()
2648        if machine_section:
2649            target_state.build_type.set_machine(
2650                [machine_section]
2651            )

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:
2653    def copy_bootloader_section(self, target_state: Any) -> None:
2654        """
2655        Copy bootloader section from this xml state to the target xml state
2656
2657        :param object target_state: XMLState instance
2658        """
2659        bootloader_section = self.get_build_type_bootloader_section()
2660        if bootloader_section:
2661            target_state.build_type.set_bootloader(
2662                [bootloader_section]
2663            )

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:
2665    def copy_oemconfig_section(self, target_state: Any) -> None:
2666        """
2667        Copy oemconfig sections from this xml state to the target xml state
2668
2669        :param object target_state: XMLState instance
2670        """
2671        oemconfig_section = self.get_build_type_oemconfig_section()
2672        if oemconfig_section:
2673            target_state.build_type.set_oemconfig(
2674                [oemconfig_section]
2675            )

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:
2677    def copy_repository_sections(
2678        self, target_state: Any, wipe: bool = False
2679    ) -> None:
2680        """
2681        Copy repository sections from this xml state to the target xml state
2682
2683        :param object target_state: XMLState instance
2684        :param bool wipe: delete all repos in target prior to copy
2685        """
2686        repository_sections = self._profiled(
2687            self.xml_data.get_repository()
2688        )
2689        if repository_sections:
2690            if wipe:
2691                target_state.xml_data.set_repository([])
2692            for repository_section in repository_sections:
2693                repository_copy = copy.deepcopy(repository_section)
2694                # profiles are not copied because they might not exist
2695                # in the target description
2696                repository_copy.set_profiles(None)
2697                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:
2699    def copy_preferences_subsections(
2700        self, section_names: List, target_state: Any
2701    ) -> None:
2702        """
2703        Copy subsections of the preferences sections, matching given
2704        section names, from this xml state to the target xml state
2705
2706        :param list section_names: preferences subsection names
2707        :param object target_state: XMLState instance
2708        """
2709        target_preferences_sections = target_state.get_preferences_sections()
2710        if target_preferences_sections:
2711            target_preferences_section = target_preferences_sections[0]
2712            for preferences_section in self.get_preferences_sections():
2713                for section_name in section_names:
2714                    get_section_method = getattr(
2715                        preferences_section, 'get_' + section_name
2716                    )
2717                    section = get_section_method()
2718                    if section:
2719                        set_section_method = getattr(
2720                            target_preferences_section, 'set_' + section_name
2721                        )
2722                        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:
2724    def copy_build_type_attributes(
2725        self, attribute_names: List, target_state: Any
2726    ) -> None:
2727        """
2728        Copy specified attributes from this build type section to the
2729        target xml state build type section
2730
2731        :param list attribute_names: type section attributes
2732        :param object target_state: XMLState instance
2733        """
2734        for attribute in attribute_names:
2735            get_type_method = getattr(
2736                self.build_type, 'get_' + attribute
2737            )
2738            attribute_value = get_type_method()
2739            if attribute_value:
2740                set_type_method = getattr(
2741                    target_state.build_type, 'set_' + attribute
2742                )
2743                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:
2745    def copy_bootincluded_packages(self, target_state: Any) -> None:
2746        """
2747        Copy packages marked as bootinclude to the packages
2748        type=bootstrap section in the target xml state. The package
2749        will also be removed from the packages type=delete section
2750        in the target xml state if present there
2751
2752        :param object target_state: XMLState instance
2753        """
2754        target_packages_sections = \
2755            target_state.get_bootstrap_packages_sections()
2756        if target_packages_sections:
2757            target_packages_section = \
2758                target_packages_sections[0]
2759            package_names_added = []
2760            packages_sections = self.get_packages_sections(
2761                ['image', 'bootstrap', self.get_build_type_name()]
2762            )
2763            package_list = self.get_package_sections(
2764                packages_sections
2765            )
2766            if package_list:
2767                for package in package_list:
2768                    if package.package_section.get_bootinclude():
2769                        target_packages_section.add_package(
2770                            xml_parse.package(
2771                                name=package.package_section.get_name()
2772                            )
2773                        )
2774                        package_names_added.append(
2775                            package.package_section.get_name()
2776                        )
2777            delete_packages_sections = target_state.get_packages_sections(
2778                ['delete']
2779            )
2780            package_list = self.get_package_sections(
2781                delete_packages_sections
2782            )
2783            if package_list:
2784                for package in package_list:
2785                    package_name = package.package_section.get_name()
2786                    if package_name in package_names_added:
2787                        package.packages_section.package.remove(
2788                            package.package_section
2789                        )

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:
2791    def copy_bootincluded_archives(self, target_state: Any) -> None:
2792        """
2793        Copy archives marked as bootinclude to the packages type=bootstrap
2794        section in the target xml state
2795
2796        :param object target_state: XMLState instance
2797        """
2798        target_bootstrap_packages_sections = \
2799            target_state.get_bootstrap_packages_sections()
2800        if target_bootstrap_packages_sections:
2801            target_bootstrap_packages_section = \
2802                target_bootstrap_packages_sections[0]
2803            packages_sections = self.get_packages_sections(
2804                ['image', 'bootstrap', self.get_build_type_name()]
2805            )
2806            for packages_section in packages_sections:
2807                archive_list = packages_section.get_archive()
2808                if archive_list:
2809                    for archive in archive_list:
2810                        if archive.get_bootinclude():
2811                            target_bootstrap_packages_section.add_archive(
2812                                xml_parse.archive(
2813                                    name=archive.get_name()
2814                                )
2815                            )

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:
2817    def copy_bootdelete_packages(self, target_state: Any) -> None:
2818        """
2819        Copy packages marked as bootdelete to the packages type=delete
2820        section in the target xml state
2821
2822        :param object target_state: XMLState instance
2823        """
2824        target_delete_packages_sections = target_state.get_packages_sections(
2825            ['delete']
2826        )
2827        if not target_delete_packages_sections:
2828            target_delete_packages_sections = [
2829                xml_parse.packages(type_='delete')
2830            ]
2831            target_state.xml_data.add_packages(
2832                target_delete_packages_sections[0]
2833            )
2834
2835        target_delete_packages_section = \
2836            target_delete_packages_sections[0]
2837        packages_sections = self.get_packages_sections(
2838            ['image', 'bootstrap', self.get_build_type_name()]
2839        )
2840        package_list = self.get_package_sections(
2841            packages_sections
2842        )
2843        if package_list:
2844            for package in package_list:
2845                if package.package_section.get_bootdelete():
2846                    target_delete_packages_section.add_package(
2847                        xml_parse.package(
2848                            name=package.package_section.get_name()
2849                        )
2850                    )

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:
2852    def get_distribution_name_from_boot_attribute(self) -> str:
2853        """
2854        Extract the distribution name from the boot attribute of the
2855        build type section.
2856
2857        If no boot attribute is configured or the contents does not
2858        match the kiwi defined naming schema for boot image descriptions,
2859        an exception is thrown
2860
2861        :return: lowercase distribution name
2862
2863        :rtype: str
2864        """
2865        boot_attribute = self.build_type.get_boot()
2866        if not boot_attribute:
2867            raise KiwiDistributionNameError(
2868                'No boot attribute to extract distribution name from found'
2869            )
2870        boot_attribute_format = '^.*-(.*)$'
2871        boot_attribute_expression = re.match(
2872            boot_attribute_format, boot_attribute
2873        )
2874        if not boot_attribute_expression:
2875            raise KiwiDistributionNameError(
2876                'Boot attribute "%s" does not match expected format %s' %
2877                (boot_attribute, boot_attribute_format)
2878            )
2879        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:
2881    def get_fs_mount_option_list(self) -> List:
2882        """
2883        List of root filesystem mount options
2884
2885        The list contains one element with the information from the
2886        fsmountoptions attribute. The value there is passed along to
2887        the -o mount option
2888
2889        :return: max one element list with mount option string
2890
2891        :rtype: list
2892        """
2893        option_list = []
2894        mount_options = self.build_type.get_fsmountoptions()
2895        if mount_options:
2896            option_list = [mount_options]
2897
2898        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:
2900    def get_fs_create_option_list(self) -> List:
2901        """
2902        List of root filesystem creation options
2903
2904        The list contains elements with the information from the
2905        fscreateoptions attribute string that got split into its
2906        substring components
2907
2908        :return: list with create options
2909
2910        :rtype: list
2911        """
2912        option_list = []
2913        create_options = self.build_type.get_fscreateoptions()
2914        if create_options:
2915            option_list = create_options.split()
2916
2917        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]:
2919    def get_luks_credentials(self) -> Optional[str]:
2920        """
2921        Return key or passphrase credentials to open the luks pool
2922
2923        :return: data
2924
2925        :rtype: str
2926        """
2927        data = self.build_type.get_luks()
2928        if data:
2929            keyfile_name = None
2930            try:
2931                # try to interpret data as an URI
2932                uri = Uri(data)
2933                if not uri.is_remote():
2934                    keyfile_name = uri.translate()
2935            except Exception:
2936                # this doesn't look like a valid URI, continue as just data
2937                pass
2938            if keyfile_name:
2939                try:
2940                    with open(keyfile_name) as keyfile:
2941                        return keyfile.read()
2942                except Exception as issue:
2943                    raise KiwiFileAccessError(
2944                        f'Failed to read from {keyfile_name!r}: {issue}'
2945                    )
2946        return data

Return key or passphrase credentials to open the luks pool

Returns

data

def get_luks_format_options(self) -> List[str]:
2948    def get_luks_format_options(self) -> List[str]:
2949        """
2950        Return list of luks format options
2951
2952        :return: list of options
2953
2954        :rtype: list
2955        """
2956        result = []
2957        luksversion = self.build_type.get_luks_version()
2958        luksformat = self.build_type.get_luksformat()
2959        luks_pbkdf = self.build_type.get_luks_pbkdf()
2960        if luksversion:
2961            result.append('--type')
2962            result.append(luksversion)
2963        if luksformat:
2964            for option in luksformat[0].get_option():
2965                result.append(option.get_name())
2966                if option.get_value():
2967                    result.append(option.get_value())
2968        if luks_pbkdf:
2969            # Allow to override the pbkdf algorithm that cryptsetup
2970            # uses by default. Cryptsetup may use argon2i by default,
2971            # which is not supported by all bootloaders.
2972            result.append('--pbkdf')
2973            result.append(luks_pbkdf)
2974        return result

Return list of luks format options

Returns

list of options

def get_derived_from_image_uri(self) -> List[kiwi.system.uri.Uri]:
2976    def get_derived_from_image_uri(self) -> List[Uri]:
2977        """
2978        Uri object(s) of derived image if configured
2979
2980        Specific image types can be based on one ore more derived
2981        images. This method returns the location of this image(s)
2982        when configured in the XML description
2983
2984        :return: List of Uri instances
2985
2986        :rtype: list
2987        """
2988        image_uris = []
2989        derived_images = self.build_type.get_derived_from()
2990        if derived_images:
2991            for derived_image in derived_images.split(','):
2992                image_uris.append(
2993                    Uri(derived_image, repo_type='container')
2994                )
2995        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:
2997    def set_derived_from_image_uri(self, uri: str) -> None:
2998        """
2999        Set derived_from attribute to a new value
3000
3001        In order to set a new value the derived_from attribute
3002        must be already present in the image configuration
3003
3004        :param str uri: URI
3005        """
3006        if self.build_type.get_derived_from():
3007            self.build_type.set_derived_from(uri)
3008        else:
3009            message = dedent('''\n
3010                No derived_from attribute configured in image <type>
3011
3012                In order to set the uri {0} as base container reference
3013                an initial derived_from attribute must be set in the
3014                type section
3015            ''')
3016            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:
3018    def set_root_partition_uuid(self, uuid: str) -> None:
3019        """
3020        Store PARTUUID provided in uuid as state information
3021
3022        :param str uuid: PARTUUID
3023        """
3024        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]:
3026    def get_root_partition_uuid(self) -> Optional[str]:
3027        """
3028        Return preserved PARTUUID
3029        """
3030        return self.root_partition_uuid

Return preserved PARTUUID

def set_root_filesystem_uuid(self, uuid: str) -> None:
3032    def set_root_filesystem_uuid(self, uuid: str) -> None:
3033        """
3034        Store UUID provided in uuid as state information
3035
3036        :param str uuid: UUID
3037        """
3038        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]:
3040    def get_root_filesystem_uuid(self) -> Optional[str]:
3041        """
3042        Return preserved UUID
3043        """
3044        return self.root_filesystem_uuid

Return preserved UUID

@staticmethod
def get_archives_target_dirs(packages_sections_names: Optional[List[kiwi.xml_parse.packages]]) -> Dict:
3046    @staticmethod
3047    def get_archives_target_dirs(
3048        packages_sections_names: Optional[List[xml_parse.packages]]
3049    ) -> Dict:
3050        """
3051        Dict of archive names and target dirs for packages section(s), if any
3052        :return: archive names and its target dir
3053        :rtype: dict
3054        """
3055        result = {}
3056        if packages_sections_names:
3057            for package_section_name in packages_sections_names:
3058                for archive in package_section_name.get_archive():
3059                    result[archive.get_name().strip()] = archive.get_target_dir()
3060
3061        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:
3063    def get_bootstrap_archives_target_dirs(self) -> Dict:
3064        """
3065        Dict of archive names and target dirs from the type="bootstrap"
3066        packages section(s)
3067        :return: archive names and its target dir
3068        :rtype: dict
3069        """
3070        return self.get_archives_target_dirs(
3071            self.get_packages_sections(['bootstrap'])
3072        )

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:
3074    def get_system_archives_target_dirs(self) -> Dict:
3075        """
3076        Dict of archive names and its target dir from the packages sections matching
3077        type="image" and type=build_type
3078        :return: archive names and its target dir
3079        :rtype: dict
3080        """
3081        return self.get_archives_target_dirs(
3082            self.get_packages_sections(['image', self.get_build_type_name()])
3083        )

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