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

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

Returns

file names

def get_system_files(self) -> Dict[str, FileT]:
685    def get_system_files(self) -> Dict[str, FileT]:
686        """
687        List of file names from the packages sections matching
688        type="image" and type=build_type
689
690        :return: file names
691
692        :rtype: dict
693        """
694        result = {}
695        image_packages_sections = self.get_packages_sections(
696            ['image', self.get_build_type_name()]
697        )
698        for packages in image_packages_sections:
699            for file in packages.get_file():
700                result[file.get_name()] = FileT(
701                    target=file.get_target() or '',
702                    owner=file.get_owner() or '',
703                    permissions=file.get_permissions() or ''
704                )
705        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:
707    def get_bootstrap_archives(self) -> List:
708        """
709        List of archive names from the type="bootstrap" packages section(s)
710
711        :return: archive names
712
713        :rtype: list
714        """
715        result = []
716        bootstrap_packages_sections = self.get_bootstrap_packages_sections()
717        if bootstrap_packages_sections:
718            for bootstrap_packages_section in bootstrap_packages_sections:
719                archive_list = bootstrap_packages_section.get_archive()
720                if archive_list:
721                    for archive in archive_list:
722                        result.append(archive.get_name().strip())
723        return sorted(result)

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

Returns

archive names

def get_system_archives(self) -> List:
725    def get_system_archives(self) -> List:
726        """
727        List of archive names from the packages sections matching
728        type="image" and type=build_type
729
730        :return: archive names
731
732        :rtype: list
733        """
734        result = []
735        image_packages_sections = self.get_packages_sections(
736            ['image', self.get_build_type_name()]
737        )
738        for packages in image_packages_sections:
739            for archive in packages.get_archive():
740                result.append(archive.get_name().strip())
741        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:
743    def get_ignore_packages(self, section_type: str) -> List:
744        """
745        List of ignore package names from the packages sections matching
746        section_type and type=build_type
747
748        :return: package names
749
750        :rtype: list
751        """
752        result = []
753        image_packages_sections = self.get_packages_sections(
754            [section_type, self.get_build_type_name()]
755        )
756        for packages in image_packages_sections:
757            for package in packages.get_ignore():
758                if self.package_matches_host_architecture(package):
759                    result.append(package.get_name().strip())
760        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]:
762    def get_system_files_ignore_packages(self) -> List[str]:
763        """
764        List of ignore package names from the type="systemfiles"
765        packages section(s)
766
767        :return: package names
768
769        :rtype: list
770        """
771        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:
773    def get_system_ignore_packages(self) -> List:
774        """
775        List of ignore package names from the packages sections matching
776        type="image" and type=build_type
777
778        :return: package names
779
780        :rtype: list
781        """
782        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:
784    def get_bootstrap_ignore_packages(self) -> List:
785        """
786        List of ignore package names from the packages sections matching
787        type="image" and type=build_type
788
789        :return: package names
790
791        :rtype: list
792        """
793        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:
795    def get_bootstrap_package_name(self) -> str:
796        """
797        bootstrap_package name from type="bootstrap" packages section
798
799        :return: bootstrap_package name
800
801        :rtype: str
802        """
803        typed_packages_sections = self.get_packages_sections(
804            ['bootstrap', self.get_build_type_name()]
805        )
806        bootstrap_package = ''
807        for packages in typed_packages_sections:
808            bootstrap_package = packages.get_bootstrap_package()
809            if bootstrap_package:
810                break
811        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:
813    def get_collection_type(self, section_type: str = 'image') -> str:
814        """
815        Collection type from packages sections matching given section
816        type.
817
818        If no collection type is specified the default collection
819        type is set to: onlyRequired
820
821        :param str section_type: type name from packages section
822
823        :return: collection type name
824
825        :rtype: str
826        """
827        typed_packages_sections = self.get_packages_sections(
828            [section_type, self.get_build_type_name()]
829        )
830        collection_type = 'onlyRequired'
831        for packages in typed_packages_sections:
832            packages_collection_type = packages.get_patternType()
833            if packages_collection_type:
834                collection_type = packages_collection_type
835                break
836        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:
838    def get_bootstrap_collection_type(self) -> str:
839        """
840        Collection type for packages sections matching type="bootstrap"
841
842        :return: collection type name
843
844        :rtype: str
845        """
846        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:
848    def get_system_collection_type(self) -> str:
849        """
850        Collection type for packages sections matching type="image"
851
852        :return: collection type name
853
854        :rtype: str
855        """
856        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]]:
858    def get_collection_modules(self) -> Dict[str, List[str]]:
859        """
860        Dict of collection modules to enable and/or disable
861
862        :return:
863            Dict of the form:
864
865            .. code:: python
866
867                {
868                    'enable': [
869                        "module:stream", "module"
870                    ],
871                    'disable': [
872                        "module"
873                    ]
874                }
875
876        :rtype: dict
877        """
878        modules: Dict[str, List[str]] = {
879            'disable': [],
880            'enable': []
881        }
882        for packages in self.get_bootstrap_packages_sections():
883            for collection_module in packages.get_collectionModule():
884                module_name = collection_module.get_name()
885                if collection_module.get_enable() is False:
886                    modules['disable'].append(module_name)
887                else:
888                    stream = collection_module.get_stream()
889                    if stream:
890                        modules['enable'].append(f'{module_name}:{stream}')
891                    else:
892                        modules['enable'].append(module_name)
893        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:
895    def get_collections(self, section_type: str = 'image') -> List:
896        """
897        List of collection names from the packages sections matching
898        type=section_type and type=build_type
899
900        :return: collection names
901
902        :rtype: list
903        """
904        result = []
905        typed_packages_sections = self.get_packages_sections(
906            [section_type, self.get_build_type_name()]
907        )
908        for packages in typed_packages_sections:
909            for collection in packages.get_namedCollection():
910                if self.collection_matches_host_architecture(collection):
911                    result.append(collection.get_name())
912        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:
914    def get_bootstrap_collections(self) -> List:
915        """
916        List of collection names from the packages sections
917        matching type="bootstrap"
918
919        :return: collection names
920
921        :rtype: list
922        """
923        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:
925    def get_system_collections(self) -> List:
926        """
927        List of collection names from the packages sections
928        matching type="image"
929
930        :return: collection names
931
932        :rtype: list
933        """
934        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:
936    def get_products(self, section_type: str = 'image') -> List:
937        """
938        List of product names from the packages sections matching
939        type=section_type and type=build_type
940
941        :param str section_type: type name from packages section
942
943        :return: product names
944
945        :rtype: list
946        """
947        result = []
948        typed_packages_sections = self.get_packages_sections(
949            [section_type, self.get_build_type_name()]
950        )
951        for packages in typed_packages_sections:
952            for product in packages.get_product():
953                result.append(product.get_name())
954        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:
956    def get_bootstrap_products(self) -> List:
957        """
958        List of product names from the packages sections
959        matching type="bootstrap"
960
961        :return: product names
962
963        :rtype: list
964        """
965        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:
967    def get_system_products(self) -> List:
968        """
969        List of product names from the packages sections
970        matching type="image"
971
972        :return: product names
973
974        :rtype: list
975        """
976        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:
978    def is_xen_server(self) -> bool:
979        """
980        Check if build type domain setup specifies a Xen Server (dom0)
981
982        :return: True or False
983
984        :rtype: bool
985        """
986        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:
 988    def is_xen_guest(self) -> bool:
 989        """
 990        Check if build type setup specifies a Xen Guest (domX)
 991        The check is based on the architecture, the firmware and
 992        xen_loader configuration values:
 993
 994        * We only support Xen setup on the x86_64 architecture
 995
 996        * Firmware pointing to ec2 means the image is targeted to run
 997          in Amazon EC2 which is a Xen guest
 998
 999        * Machine setup with a xen_loader attribute also indicates a
1000          Xen guest target
1001
1002        :return: True or False
1003
1004        :rtype: bool
1005        """
1006        if self.host_architecture != 'x86_64':
1007            # We only support Xen stuff on x86_64
1008            return False
1009        firmware = self.build_type.get_firmware()
1010        machine_section = self.get_build_type_machine_section()
1011        if firmware and firmware in Defaults.get_ec2_capable_firmware_names():
1012            # the image is targeted to run in Amazon EC2 which is a Xen system
1013            return True
1014        elif machine_section and machine_section.get_xen_loader():
1015            # the image provides a machine section with a guest loader setup
1016            return True
1017        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:
1019    def get_build_type_partitions_section(self) -> Any:
1020        """
1021        First partitions section from the build type section
1022
1023        :return: <partitions> section reference
1024
1025        :rtype: xml_parse::partitions
1026        """
1027        partitions_sections = self.build_type.get_partitions()
1028        if partitions_sections:
1029            return partitions_sections[0]
1030        return None

First partitions section from the build type section

Returns

section reference

def get_build_type_system_disk_section(self) -> Any:
1032    def get_build_type_system_disk_section(self) -> Any:
1033        """
1034        First system disk section from the build type section
1035
1036        :return: <systemdisk> section reference
1037
1038        :rtype: xml_parse::systemdisk
1039        """
1040        systemdisk_sections = self.build_type.get_systemdisk()
1041        if systemdisk_sections:
1042            return systemdisk_sections[0]
1043        return None

First system disk section from the build type section

Returns

section reference

def get_build_type_machine_section(self) -> Any:
1045    def get_build_type_machine_section(self) -> Any:
1046        """
1047        First machine section from the build type section
1048
1049        :return: <machine> section reference
1050
1051        :rtype: xml_parse::machine
1052        """
1053        machine_sections = self.build_type.get_machine()
1054        if machine_sections:
1055            return machine_sections[0]
1056        return None

First machine section from the build type section

Returns

section reference

def get_build_type_vagrant_config_section(self) -> Any:
1058    def get_build_type_vagrant_config_section(self) -> Any:
1059        """
1060        First vagrantconfig section from the build type section
1061
1062        :return: <vagrantconfig> section reference
1063
1064        :rtype: xml_parse::vagrantconfig
1065        """
1066        vagrant_config_sections = self.build_type.get_vagrantconfig()
1067        if vagrant_config_sections:
1068            return vagrant_config_sections[0]
1069        return None

First vagrantconfig section from the build type section

Returns

section reference

def get_vagrant_config_virtualbox_guest_additions(self) -> bool:
1071    def get_vagrant_config_virtualbox_guest_additions(self) -> bool:
1072        """
1073        Attribute virtualbox_guest_additions_present from the first
1074        vagrantconfig section.
1075
1076        :return: True|False
1077
1078        :rtype: bool
1079        """
1080        vagrant_config_sections = self.get_build_type_vagrant_config_section()
1081        if not vagrant_config_sections.virtualbox_guest_additions_present:
1082            return Defaults.get_vagrant_config_virtualbox_guest_additions()
1083        else:
1084            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:
1086    def get_build_type_vmdisk_section(self) -> Any:
1087        """
1088        First vmdisk section from the first machine section in the
1089        build type section
1090
1091        :return: <vmdisk> section reference
1092
1093        :rtype: xml_parse::vmdisk
1094        """
1095        machine_section = self.get_build_type_machine_section()
1096        if machine_section:
1097            vmdisk_sections = machine_section.get_vmdisk()
1098            if vmdisk_sections:
1099                return vmdisk_sections[0]
1100        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:
1102    def get_build_type_vmnic_entries(self) -> List:
1103        """
1104        vmnic section(s) from the first machine section in the
1105        build type section
1106
1107        :return: list of <vmnic> section reference(s)
1108
1109        :rtype: list
1110        """
1111        machine_section = self.get_build_type_machine_section()
1112        if machine_section:
1113            return machine_section.get_vmnic()
1114        else:
1115            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:
1117    def get_build_type_vmdvd_section(self) -> Any:
1118        """
1119        First vmdvd section from the first machine section in the
1120        build type section
1121
1122        :return: <vmdvd> section reference
1123
1124        :rtype: xml_parse::vmdvd
1125        """
1126        machine_section = self.get_build_type_machine_section()
1127        if machine_section:
1128            vmdvd_sections = machine_section.get_vmdvd()
1129            if vmdvd_sections:
1130                return vmdvd_sections[0]
1131        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:
1133    def get_build_type_vmconfig_entries(self) -> List:
1134        """
1135        List of vmconfig-entry section values from the first
1136        machine section in the build type section
1137
1138        :return: <vmconfig_entry> section reference(s)
1139
1140        :rtype: list
1141        """
1142        machine_section = self.get_build_type_machine_section()
1143        if machine_section:
1144            vmconfig_entries = machine_section.get_vmconfig_entry()
1145            if vmconfig_entries:
1146                return vmconfig_entries
1147
1148        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:
1150    def get_build_type_bootloader_section(self) -> Any:
1151        """
1152        First bootloader section from the build type section
1153
1154        :return: <bootloader> section reference
1155
1156        :rtype: xml_parse::bootloader
1157        """
1158        bootloader_sections = self.build_type.get_bootloader()
1159        if bootloader_sections:
1160            return bootloader_sections[0]
1161        return None

First bootloader section from the build type section

Returns

section reference

def get_build_type_bootloader_name(self) -> str:
1163    def get_build_type_bootloader_name(self) -> str:
1164        """
1165        Return bootloader name for selected build type
1166
1167        :return: bootloader name
1168
1169        :rtype: str
1170        """
1171        bootloader = self.get_build_type_bootloader_section()
1172        return bootloader.get_name() if bootloader else \
1173            Defaults.get_default_bootloader()

Return bootloader name for selected build type

Returns

bootloader name

def get_build_type_bootloader_bls(self) -> bool:
1175    def get_build_type_bootloader_bls(self) -> bool:
1176        """
1177        Return bootloader bls setting for selected build type
1178
1179        :return: True or False
1180
1181        :rtype: bool
1182        """
1183        bootloader = self.get_build_type_bootloader_section()
1184        if bootloader and bootloader.get_bls() is not None:
1185            return bootloader.get_bls()
1186        return True

Return bootloader bls setting for selected build type

Returns

True or False

def get_build_type_bootloader_console(self) -> List[str]:
1188    def get_build_type_bootloader_console(self) -> List[str]:
1189        """
1190        Return bootloader console setting for selected build type
1191
1192        :return:
1193            list of console settings for output (first element)
1194            and input (second element)
1195
1196        :rtype: list
1197        """
1198        result = ['', '']
1199        bootloader = self.get_build_type_bootloader_section()
1200        if bootloader:
1201            console_out = bootloader.get_output_console()
1202            console_in = bootloader.get_input_console()
1203            console_in = console_in if console_in else console_out
1204            result = [
1205                console_out if console_out and console_out != 'none' else '',
1206                console_in if console_in and console_in != 'none' else ''
1207            ]
1208        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]:
1210    def get_build_type_bootloader_serial_line_setup(self) -> Optional[str]:
1211        """
1212        Return bootloader serial line setup parameters for the
1213        selected build type
1214
1215        :return: serial line setup
1216
1217        :rtype: str
1218        """
1219        bootloader = self.get_build_type_bootloader_section()
1220        if bootloader:
1221            return bootloader.get_serial_line()
1222        return None

Return bootloader serial line setup parameters for the selected build type

Returns

serial line setup

def get_build_type_bootloader_timeout(self) -> Optional[str]:
1224    def get_build_type_bootloader_timeout(self) -> Optional[str]:
1225        """
1226        Return bootloader timeout setting for selected build type
1227
1228        :return: timeout string
1229
1230        :rtype: str
1231        """
1232        bootloader = self.get_build_type_bootloader_section()
1233        if bootloader:
1234            return bootloader.get_timeout()
1235        return None

Return bootloader timeout setting for selected build type

Returns

timeout string

def get_build_type_bootloader_timeout_style(self) -> Optional[str]:
1237    def get_build_type_bootloader_timeout_style(self) -> Optional[str]:
1238        """
1239        Return bootloader timeout style setting for selected build type
1240
1241        :return: timeout_style string
1242
1243        :rtype: str
1244        """
1245        bootloader = self.get_build_type_bootloader_section()
1246        if bootloader:
1247            return bootloader.get_timeout_style()
1248        return None

Return bootloader timeout style setting for selected build type

Returns

timeout_style string

def get_build_type_bootloader_targettype(self) -> Optional[str]:
1250    def get_build_type_bootloader_targettype(self) -> Optional[str]:
1251        """
1252        Return bootloader target type setting. Only relevant for
1253        the zipl bootloader because zipl is installed differently
1254        depending on the storage target it runs later
1255
1256        :return: target type string
1257
1258        :rtype: str
1259        """
1260        bootloader = self.get_build_type_bootloader_section()
1261        if bootloader:
1262            return bootloader.get_targettype()
1263        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:
1265    def get_build_type_bootloader_settings_section(self) -> Any:
1266        """
1267        First bootloadersettings section from the build
1268        type bootloader section
1269
1270        :return: <bootloadersettings> section reference
1271
1272        :rtype: xml_parse::bootloadersettings
1273        """
1274        bootloader_section = self.get_build_type_bootloader_section()
1275        bootloader_settings_section = None
1276        if bootloader_section and bootloader_section.get_bootloadersettings():
1277            bootloader_settings_section = \
1278                bootloader_section.get_bootloadersettings()[0]
1279        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]:
1281    def get_build_type_bootloader_securelinux_section(self) -> List[Any]:
1282        """
1283        First securelinux section from the build
1284        type bootloader section
1285
1286        :return: <securelinux> section reference
1287
1288        :rtype: xml_parse::securelinux
1289        """
1290        bootloader_section = self.get_build_type_bootloader_section()
1291        bootloader_securelinux_section = []
1292        if bootloader_section and bootloader_section.get_securelinux():
1293            bootloader_securelinux_section = \
1294                bootloader_section.get_securelinux()
1295        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]:
1297    def get_build_type_bootloader_environment_variables(self) -> List[str]:
1298        """
1299        List of bootloader variables from the build
1300        type > bootloader > bootloadersettings section
1301        """
1302        variable_list = []
1303        bootloader_settings_section = \
1304            self.get_build_type_bootloader_settings_section()
1305        if bootloader_settings_section:
1306            environment = bootloader_settings_section.get_environment()
1307            if environment and environment[0].get_env():
1308                for env in environment[0].get_env():
1309                    variable_list.append(
1310                        '{}{}'.format(
1311                            env.get_name(),
1312                            f'={env.get_value()}' if env.get_value() else ''
1313                        )
1314                    )
1315        return variable_list

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

def get_bootloader_options(self, option_type: str) -> List[str]:
1317    def get_bootloader_options(self, option_type: str) -> List[str]:
1318        """
1319        List of custom options used in the process to
1320        run bootloader setup workloads
1321        """
1322        result: List[str] = []
1323        bootloader_settings = self.get_build_type_bootloader_settings_section()
1324        if bootloader_settings:
1325            options = []
1326            if option_type == 'shim':
1327                options = bootloader_settings.get_shimoption()
1328            elif option_type == 'install':
1329                options = bootloader_settings.get_installoption()
1330            elif option_type == 'config':
1331                options = bootloader_settings.get_configoption()
1332            for option in options:
1333                result.append(option.get_name())
1334                if option.get_value():
1335                    result.append(option.get_value())
1336        return result

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

def get_bootloader_shim_options(self) -> List[str]:
1338    def get_bootloader_shim_options(self) -> List[str]:
1339        """
1340        List of custom options used in the process to setup secure boot
1341        """
1342        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]:
1344    def get_bootloader_install_options(self) -> List[str]:
1345        """
1346        List of custom options used in the bootloader installation
1347        """
1348        return self.get_bootloader_options('install')

List of custom options used in the bootloader installation

def get_bootloader_config_options(self) -> List[str]:
1350    def get_bootloader_config_options(self) -> List[str]:
1351        """
1352        List of custom options used in the bootloader configuration
1353        """
1354        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:
1356    def get_build_type_bootloader_use_disk_password(self) -> bool:
1357        """
1358        Indicate whether the bootloader configuration should use the
1359        password protecting the encrypted root volume.
1360
1361        :return: True|False
1362
1363        :rtype: bool
1364        """
1365        bootloader = self.get_build_type_bootloader_section()
1366        if bootloader:
1367            return bootloader.get_use_disk_password()
1368        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:
1370    def get_build_type_oemconfig_section(self) -> Any:
1371        """
1372        First oemconfig section from the build type section
1373
1374        :return: <oemconfig> section reference
1375
1376        :rtype: xml_parse::oemconfig
1377        """
1378        oemconfig_sections = self.build_type.get_oemconfig()
1379        if oemconfig_sections:
1380            return oemconfig_sections[0]
1381        return None

First oemconfig section from the build type section

Returns

section reference

def get_oemconfig_oem_resize(self) -> bool:
1383    def get_oemconfig_oem_resize(self) -> bool:
1384        """
1385        State value to activate/deactivate disk resize. Returns a
1386        boolean value if specified or True to set resize on by default
1387
1388        :return: Content of <oem-resize> section value
1389
1390        :rtype: bool
1391        """
1392        oemconfig = self.get_build_type_oemconfig_section()
1393        if oemconfig and oemconfig.get_oem_resize():
1394            return oemconfig.get_oem_resize()[0]
1395        else:
1396            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:
1398    def get_oemconfig_oem_systemsize(self) -> int:
1399        """
1400        State value to retrieve root partition size
1401
1402        :return: Content of <oem-systemsize> section value
1403
1404        :rtype: int
1405        """
1406        oemconfig = self.get_build_type_oemconfig_section()
1407        if oemconfig and oemconfig.get_oem_systemsize():
1408            return int(oemconfig.get_oem_systemsize()[0])
1409        else:
1410            return 0

State value to retrieve root partition size

Returns

Content of section value

def get_oemconfig_oem_multipath_scan(self) -> bool:
1412    def get_oemconfig_oem_multipath_scan(self) -> bool:
1413        """
1414        State value to activate multipath maps. Returns a boolean
1415        value if specified or False
1416
1417        :return: Content of <oem-multipath-scan> section value
1418
1419        :rtype: bool
1420        """
1421        oemconfig = self.get_build_type_oemconfig_section()
1422        if oemconfig and oemconfig.get_oem_multipath_scan():
1423            return oemconfig.get_oem_multipath_scan()[0]
1424        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]:
1426    def get_oemconfig_swap_mbytes(self) -> Optional[int]:
1427        """
1428        Return swapsize in MB if requested or None
1429
1430        Operates on the value of oem-swap and if set to true
1431        returns the given size or the default value.
1432
1433        :return: Content of <oem-swapsize> section value or default
1434
1435        :rtype: int
1436        """
1437        oemconfig = self.get_build_type_oemconfig_section()
1438        if oemconfig and oemconfig.get_oem_swap():
1439            swap_requested = oemconfig.get_oem_swap()[0]
1440            if swap_requested:
1441                swapsize = oemconfig.get_oem_swapsize()
1442                if swapsize:
1443                    return swapsize[0]
1444                else:
1445                    return Defaults.get_swapsize_mbytes()
1446        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:
1448    def get_oemconfig_swap_name(self) -> str:
1449        """
1450        Return the swap space name
1451
1452        Operates on the value of oem-swapname and if set
1453        returns the configured name or the default name: LVSwap
1454
1455        The name of the swap space is used only if the
1456        image is configured to use the LVM volume manager.
1457        In this case swap is a volume and the volume takes
1458        a name. In any other case the given name will have
1459        no effect.
1460
1461        :return: Content of <oem-swapname> section value or default
1462
1463        :rtype: str
1464        """
1465        oemconfig = self.get_build_type_oemconfig_section()
1466        if oemconfig and oemconfig.get_oem_swapname():
1467            return oemconfig.get_oem_swapname()[0]
1468        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:
1470    def get_build_type_containerconfig_section(self) -> Any:
1471        """
1472        First containerconfig section from the build type section
1473
1474        :return: <containerconfig> section reference
1475
1476        :rtype: xml_parse::containerconfig
1477        """
1478        container_config_sections = self.build_type.get_containerconfig()
1479        if container_config_sections:
1480            return container_config_sections[0]
1481        return None

First containerconfig section from the build type section

Returns

section reference

def get_dracut_config(self, action: str) -> DracutT:
1483    def get_dracut_config(self, action: str) -> DracutT:
1484        """
1485        Get dracut initrd config for the specified action
1486        """
1487        uefi = False
1488        modules = []
1489        drivers = []
1490        initrd_sections = self.build_type.get_initrd()
1491        for initrd_section in initrd_sections:
1492            if initrd_section.get_action() == action:
1493                for dracut in initrd_section.get_dracut():
1494                    uefi = bool(dracut.get_uefi())
1495                    if dracut.get_module():
1496                        modules.append(dracut.get_module())
1497                    if dracut.get_driver():
1498                        drivers.append(dracut.get_driver())
1499        return DracutT(
1500            uefi=uefi, modules=modules, drivers=drivers
1501        )

Get dracut initrd config for the specified action

def get_installmedia_initrd_modules(self, action: str) -> List[str]:
1503    def get_installmedia_initrd_modules(self, action: str) -> List[str]:
1504        """
1505        Gets the list of modules to append in installation initrds
1506
1507        :return: a list of dracut module names
1508
1509        :rtype: list
1510        """
1511        modules: List[str] = []
1512        installmedia = self.build_type.get_installmedia()
1513        if not installmedia:
1514            return modules
1515        initrd_sections = installmedia[0].get_initrd()
1516        for initrd_section in initrd_sections:
1517            if initrd_section.get_action() == action:
1518                for module in initrd_section.get_dracut():
1519                    if module.get_module():
1520                        modules.append(module.get_module())
1521        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]:
1523    def get_installmedia_initrd_drivers(self, action: str) -> List[str]:
1524        """
1525        Gets the list of drivers to append in installation initrds
1526
1527        :return: a list of dracut driver names
1528
1529        :rtype: list
1530        """
1531        drivers: List[str] = []
1532        installmedia = self.build_type.get_installmedia()
1533        if not installmedia:
1534            return drivers
1535        initrd_sections = installmedia[0].get_initrd()
1536        for initrd_section in initrd_sections:
1537            if initrd_section.get_action() == action:
1538                for driver in initrd_section.get_dracut():
1539                    if driver.get_driver():
1540                        drivers.append(driver.get_driver())
1541        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]:
1543    def get_build_type_size(
1544        self, include_unpartitioned: bool = False
1545    ) -> Optional[size_type]:
1546        """
1547        Size information from the build type section.
1548        If no unit is set the value is treated as mbytes
1549
1550        :param bool include_unpartitioned: sets if the unpartitioned area
1551            should be included in the computed size or not
1552
1553        :return: mbytes
1554
1555        :rtype: int
1556        """
1557        size_section = self.build_type.get_size()
1558        if size_section:
1559            unit = size_section[0].get_unit()
1560            additive = size_section[0].get_additive()
1561            unpartitioned = size_section[0].get_unpartitioned()
1562            value = int(size_section[0].get_valueOf_())
1563            if not include_unpartitioned and unpartitioned is not None:
1564                value -= unpartitioned
1565            if unit == 'G':
1566                value *= 1024
1567            return size_type(
1568                mbytes=value, additive=additive
1569            )
1570        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:
1572    def get_build_type_unpartitioned_bytes(self) -> int:
1573        """
1574        Size of the unpartitioned area for image in megabytes
1575
1576        :return: mbytes
1577
1578        :rtype: int
1579        """
1580        size_section = self.build_type.get_size()
1581        if size_section:
1582            unit = size_section[0].get_unit() or 'M'
1583            unpartitioned = size_section[0].get_unpartitioned() or 0
1584            return StringToSize.to_bytes('{0}{1}'.format(unpartitioned, unit))
1585        return 0

Size of the unpartitioned area for image in megabytes

Returns

mbytes

def get_disk_start_sector(self) -> int:
1587    def get_disk_start_sector(self) -> int:
1588        """
1589        First disk sector number to be used by the first disk partition.
1590
1591        :return: number
1592
1593        :rtype: int
1594        """
1595        disk_start_sector = self.build_type.get_disk_start_sector()
1596        if disk_start_sector is None:
1597            disk_start_sector = Defaults.get_default_disk_start_sector()
1598        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]:
1600    def get_build_type_spare_part_size(self) -> Optional[int]:
1601        """
1602        Size information for the spare_part size from the build
1603        type. If no unit is set the value is treated as mbytes
1604
1605        :return: mbytes
1606
1607        :rtype: int
1608        """
1609        spare_part_size = self.build_type.get_spare_part()
1610        if spare_part_size:
1611            return self._to_mega_byte(spare_part_size)
1612        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]:
1614    def get_build_type_spare_part_fs_attributes(self) -> Optional[List]:
1615        """
1616        Build type specific list of filesystem attributes applied to
1617        the spare partition.
1618
1619        :return: list of strings or empty list
1620
1621        :rtype: list
1622        """
1623        spare_part_attributes = self.build_type.get_spare_part_fs_attributes()
1624        if spare_part_attributes:
1625            return spare_part_attributes.strip().split(',')
1626        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:
1628    def get_build_type_format_options(self) -> Dict:
1629        """
1630        Disk format options returned as a dictionary
1631
1632        :return: format options
1633
1634        :rtype: dict
1635        """
1636        result = {}
1637        format_options = self.build_type.get_formatoptions()
1638        if format_options:
1639            for option in format_options.split(','):
1640                key_value_list = option.split('=')
1641                if len(key_value_list) == 2:
1642                    result[key_value_list[0]] = key_value_list[1]
1643                else:
1644                    result[key_value_list[0]] = None
1645        return result

Disk format options returned as a dictionary

Returns

format options

def get_volume_group_name(self) -> str:
1647    def get_volume_group_name(self) -> str:
1648        """
1649        Volume group name from selected <systemdisk> section
1650
1651        :return: volume group name
1652
1653        :rtype: str
1654        """
1655        systemdisk_section = self.get_build_type_system_disk_section()
1656        volume_group_name = None
1657        if systemdisk_section:
1658            volume_group_name = systemdisk_section.get_name()
1659        if not volume_group_name:
1660            volume_group_name = Defaults.get_default_volume_group_name()
1661        return volume_group_name

Volume group name from selected section

Returns

volume group name

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

Provides information which volume management system is used

Returns

name of volume manager

def get_drivers_list(self) -> List:
2194    def get_drivers_list(self) -> List:
2195        """
2196        List of driver names from all drivers sections matching
2197        configured profiles
2198
2199        :return: driver names
2200
2201        :rtype: list
2202        """
2203        drivers_sections = self._profiled(
2204            self.xml_data.get_drivers()
2205        )
2206        result = []
2207        if drivers_sections:
2208            for driver in drivers_sections:
2209                for file_section in driver.get_file():
2210                    result.append(file_section.get_name())
2211        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:
2213    def get_strip_list(self, section_type: str) -> List:
2214        """
2215        List of strip names matching the given section type
2216        and profiles
2217
2218        :param str section_type: type name from packages section
2219
2220        :return: strip names
2221
2222        :rtype: list
2223        """
2224        strip_sections = self._profiled(
2225            self.xml_data.get_strip()
2226        )
2227        result = []
2228        if strip_sections:
2229            for strip in strip_sections:
2230                if strip.get_type() == section_type:
2231                    for file_section in strip.get_file():
2232                        result.append(file_section.get_name())
2233        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:
2235    def get_strip_files_to_delete(self) -> List:
2236        """
2237        Items to delete from strip section
2238
2239        :return: item names
2240
2241        :rtype: list
2242        """
2243        return self.get_strip_list('delete')

Items to delete from strip section

Returns

item names

def get_strip_tools_to_keep(self) -> List:
2245    def get_strip_tools_to_keep(self) -> List:
2246        """
2247        Tools to keep from strip section
2248
2249        :return: tool names
2250
2251        :rtype: list
2252        """
2253        return self.get_strip_list('tools')

Tools to keep from strip section

Returns

tool names

def get_strip_libraries_to_keep(self) -> List:
2255    def get_strip_libraries_to_keep(self) -> List:
2256        """
2257        Libraries to keep from strip section
2258
2259        :return: librarie names
2260
2261        :rtype: list
2262        """
2263        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]:
2265    def get_include_section_reference_file_names(self) -> List[str]:
2266        """
2267        List of all <include> section file name references
2268
2269        :return: List[str]
2270
2271        :rtype: list
2272        """
2273        include_files = []
2274        for include in self.xml_data.get_include():
2275            include_files.append(include.get_from())
2276        return include_files

List of all section file name references

Returns

List[str]

def get_repository_sections(self) -> List:
2278    def get_repository_sections(self) -> List:
2279        """
2280        List of all repository sections for the selected profiles that
2281        matches the host architecture
2282
2283        :return: <repository> section reference(s)
2284
2285        :rtype: list
2286        """
2287        repository_list = []
2288        for repository in self._profiled(self.xml_data.get_repository()):
2289            if self.repository_matches_host_architecture(repository):
2290                repository_list.append(repository)
2291        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:
2293    def get_containers_sections(self) -> List:
2294        """
2295        List of all containers sections for the selected profiles that
2296        matches the host architecture
2297
2298        :return: <containers> section reference(s)
2299
2300        :rtype: list
2301        """
2302        containers_list = []
2303        for containers in self._profiled(self.xml_data.get_containers()):
2304            if self.containers_matches_host_architecture(containers):
2305                containers_list.append(containers)
2306        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:
2308    def get_repository_sections_used_for_build(self) -> List:
2309        """
2310        List of all repositorys sections used to build the image and
2311        matching configured profiles.
2312
2313        :return: <repository> section reference(s)
2314
2315        :rtype: list
2316        """
2317        repos = self.get_repository_sections()
2318        return list(
2319            repo for repo in repos if not repo.get_imageonly()
2320        )

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:
2322    def get_repository_sections_used_in_image(self) -> List:
2323        """
2324        List of all repositorys sections to be configured in the resulting
2325        image matching configured profiles.
2326
2327        :return: <repository> section reference(s)
2328
2329        :rtype: list
2330        """
2331        repos = self.get_repository_sections()
2332        return list(
2333            repo for repo in repos
2334            if repo.get_imageinclude() or repo.get_imageonly()
2335        )

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:
2337    def delete_repository_sections(self) -> None:
2338        """
2339        Delete all repository sections matching configured profiles
2340        """
2341        self.xml_data.set_repository([])

Delete all repository sections matching configured profiles

def delete_repository_sections_used_for_build(self) -> None:
2343    def delete_repository_sections_used_for_build(self) -> None:
2344        """
2345        Delete all repository sections used to build the image matching
2346        configured profiles
2347        """
2348        used_for_build = self.get_repository_sections_used_for_build()
2349        all_repos = self.get_repository_sections()
2350        self.xml_data.set_repository(
2351            [
2352                repo for repo in all_repos if repo not in used_for_build
2353            ]
2354        )

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

def get_repositories_signing_keys(self) -> List[str]:
2356    def get_repositories_signing_keys(self) -> List[str]:
2357        """
2358        Get list of signing keys specified on the repositories
2359        """
2360        key_file_list: List[str] = []
2361        release_version = self.get_release_version()
2362        release_vars = [
2363            '$releasever',
2364            '${releasever}'
2365        ]
2366        for repository in self.get_repository_sections() or []:
2367            for signing in repository.get_source().get_signing() or []:
2368                normalized_key_url = Uri(signing.get_key()).translate()
2369                if release_version:
2370                    for release_var in release_vars:
2371                        if release_var in normalized_key_url:
2372                            normalized_key_url = normalized_key_url.replace(
2373                                release_var, release_version
2374                            )
2375                if normalized_key_url not in key_file_list:
2376                    key_file_list.append(normalized_key_url)
2377        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:
2379    def set_repository(
2380        self, repo_source: str, repo_type: str, repo_alias: str,
2381        repo_prio: str, repo_imageinclude: bool = False,
2382        repo_package_gpgcheck: Optional[bool] = None,
2383        repo_signing_keys: List[str] = [], components: str = None,
2384        distribution: str = None, repo_gpgcheck: Optional[bool] = None,
2385        repo_sourcetype: str = None
2386    ) -> None:
2387        """
2388        Overwrite repository data of the first repository
2389
2390        :param str repo_source: repository URI
2391        :param str repo_type: type name defined by schema
2392        :param str repo_alias: alias name
2393        :param str repo_prio: priority number, package manager specific
2394        :param bool repo_imageinclude: setup repository inside of the image
2395        :param bool repo_package_gpgcheck: enable/disable package gpg checks
2396        :param list repo_signing_keys: list of signing key file names
2397        :param str components: component names for debian repos
2398        :param str distribution: base distribution name for debian repos
2399        :param bool repo_gpgcheck: enable/disable repo gpg checks
2400        """
2401        repository_sections = self.get_repository_sections()
2402        if repository_sections:
2403            repository = repository_sections[0]
2404            if repo_alias:
2405                repository.set_alias(repo_alias)
2406            if repo_type:
2407                repository.set_type(repo_type)
2408            if repo_source:
2409                repository.get_source().set_path(repo_source)
2410            if repo_prio:
2411                repository.set_priority(int(repo_prio))
2412            if repo_imageinclude:
2413                repository.set_imageinclude(repo_imageinclude)
2414            if repo_package_gpgcheck is not None:
2415                repository.set_package_gpgcheck(repo_package_gpgcheck)
2416            if repo_signing_keys:
2417                repository.get_source().set_signing(
2418                    [xml_parse.signing(key=k) for k in repo_signing_keys]
2419                )
2420            if components:
2421                repository.set_components(components)
2422            if distribution:
2423                repository.set_distribution(distribution)
2424            if repo_gpgcheck is not None:
2425                repository.set_repository_gpgcheck(repo_gpgcheck)
2426            if repo_sourcetype:
2427                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:
2429    def add_repository(
2430        self, repo_source: str, repo_type: str, repo_alias: str = None,
2431        repo_prio: str = '', repo_imageinclude: bool = False,
2432        repo_package_gpgcheck: Optional[bool] = None,
2433        repo_signing_keys: List[str] = [], components: str = None,
2434        distribution: str = None, repo_gpgcheck: Optional[bool] = None,
2435        repo_sourcetype: str = None
2436    ) -> None:
2437        """
2438        Add a new repository section at the end of the list
2439
2440        :param str repo_source: repository URI
2441        :param str repo_type: type name defined by schema
2442        :param str repo_alias: alias name
2443        :param str repo_prio: priority number, package manager specific
2444        :param bool repo_imageinclude: setup repository inside of the image
2445        :param bool repo_package_gpgcheck: enable/disable package gpg checks
2446        :param list repo_signing_keys: list of signing key file names
2447        :param str components: component names for debian repos
2448        :param str distribution: base distribution name for debian repos
2449        :param bool repo_gpgcheck: enable/disable repo gpg checks
2450        """
2451        priority_number: Optional[int] = None
2452        try:
2453            priority_number = int(repo_prio)
2454        except Exception:
2455            pass
2456
2457        self.xml_data.add_repository(
2458            xml_parse.repository(
2459                type_=repo_type,
2460                alias=repo_alias,
2461                priority=priority_number,
2462                source=xml_parse.source(
2463                    path=repo_source,
2464                    signing=[
2465                        xml_parse.signing(key=k) for k in repo_signing_keys
2466                    ]
2467                ),
2468                imageinclude=repo_imageinclude,
2469                package_gpgcheck=repo_package_gpgcheck,
2470                repository_gpgcheck=repo_gpgcheck,
2471                components=components,
2472                distribution=distribution,
2473                sourcetype=repo_sourcetype
2474            )
2475        )

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:
2477    def add_certificate(self, cert_file: str, target_distribution: str) -> None:
2478        """
2479        Add <certificate name="cert_file"> to main <certificates> section
2480        The main section will be created if it does not exist. Also
2481        setup the target_distribution in the resulting main section.
2482        """
2483        certificates_section = self._profiled(
2484            self.xml_data.get_certificates()
2485        )
2486        if not certificates_section:
2487            self.xml_data.set_certificates(
2488                [
2489                    xml_parse.certificates(
2490                        target_distribution=target_distribution,
2491                        certificate=[xml_parse.certificate(name=cert_file)]
2492                    )
2493                ]
2494            )
2495        else:
2496            certificates_section[0].set_target_distribution(
2497                target_distribution
2498            )
2499            certificates_section[0].add_certificate(
2500                xml_parse.certificate(
2501                    name=cert_file
2502                )
2503            )

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]:
2505    def get_certificates(self) -> List[str]:
2506        """
2507        Read list of certificates
2508        """
2509        cert_list = []
2510        certificates_section = self._profiled(
2511            self.xml_data.get_certificates()
2512        )
2513        if certificates_section:
2514            for certificate in certificates_section[0].get_certificate():
2515                cert_list.append(certificate.get_name())
2516        return sorted(list(set(cert_list)))

Read list of certificates

def get_certificates_target_distribution(self) -> str:
2518    def get_certificates_target_distribution(self) -> str:
2519        """
2520        Read CA target distribution
2521        """
2522        target_distribution = ''
2523        certificates_section = self._profiled(
2524            self.xml_data.get_certificates()
2525        )
2526        if certificates_section:
2527            target_distribution = \
2528                certificates_section[0].get_target_distribution()
2529        return target_distribution

Read CA target distribution

def resolve_this_path(self) -> None:
2531    def resolve_this_path(self) -> None:
2532        """
2533        Resolve any this:// repo source path into the path
2534        representing the target inside of the image description
2535        directory
2536        """
2537        for repository in self.get_repository_sections() or []:
2538            repo_source = repository.get_source()
2539            repo_path = repo_source.get_path()
2540            if repo_path.startswith('this://'):
2541                repo_path = repo_path.replace('this://', '')
2542                repo_source.set_path(
2543                    'dir://{0}'.format(
2544                        os.path.realpath(
2545                            os.path.join(
2546                                self.xml_data.description_dir, repo_path
2547                            )
2548                        )
2549                    )
2550                )

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:
2552    def copy_displayname(self, target_state: Any) -> None:
2553        """
2554        Copy image displayname from this xml state to the target xml state
2555
2556        :param object target_state: XMLState instance
2557        """
2558        displayname = self.xml_data.get_displayname()
2559        if displayname:
2560            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:
2562    def copy_name(self, target_state: Any) -> None:
2563        """
2564        Copy image name from this xml state to the target xml state
2565
2566        :param object target_state: XMLState instance
2567        """
2568        target_state.xml_data.set_name(
2569            self.xml_data.get_name()
2570        )

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:
2572    def copy_drivers_sections(self, target_state: Any) -> None:
2573        """
2574        Copy drivers sections from this xml state to the target xml state
2575
2576        :param object target_state: XMLState instance
2577        """
2578        drivers_sections = self._profiled(
2579            self.xml_data.get_drivers()
2580        )
2581        if drivers_sections:
2582            for drivers_section in drivers_sections:
2583                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:
2585    def copy_systemdisk_section(self, target_state: Any) -> None:
2586        """
2587        Copy systemdisk sections from this xml state to the target xml state
2588
2589        :param object target_state: XMLState instance
2590        """
2591        systemdisk_section = self.get_build_type_system_disk_section()
2592        if systemdisk_section:
2593            target_state.build_type.set_systemdisk(
2594                [systemdisk_section]
2595            )

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:
2597    def copy_strip_sections(self, target_state: Any) -> None:
2598        """
2599        Copy strip sections from this xml state to the target xml state
2600
2601        :param object target_state: XMLState instance
2602        """
2603        strip_sections = self._profiled(
2604            self.xml_data.get_strip()
2605        )
2606        if strip_sections:
2607            for strip_section in strip_sections:
2608                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:
2610    def copy_machine_section(self, target_state: Any) -> None:
2611        """
2612        Copy machine sections from this xml state to the target xml state
2613
2614        :param object target_state: XMLState instance
2615        """
2616        machine_section = self.get_build_type_machine_section()
2617        if machine_section:
2618            target_state.build_type.set_machine(
2619                [machine_section]
2620            )

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:
2622    def copy_bootloader_section(self, target_state: Any) -> None:
2623        """
2624        Copy bootloader section from this xml state to the target xml state
2625
2626        :param object target_state: XMLState instance
2627        """
2628        bootloader_section = self.get_build_type_bootloader_section()
2629        if bootloader_section:
2630            target_state.build_type.set_bootloader(
2631                [bootloader_section]
2632            )

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:
2634    def copy_oemconfig_section(self, target_state: Any) -> None:
2635        """
2636        Copy oemconfig sections from this xml state to the target xml state
2637
2638        :param object target_state: XMLState instance
2639        """
2640        oemconfig_section = self.get_build_type_oemconfig_section()
2641        if oemconfig_section:
2642            target_state.build_type.set_oemconfig(
2643                [oemconfig_section]
2644            )

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:
2646    def copy_repository_sections(
2647        self, target_state: Any, wipe: bool = False
2648    ) -> None:
2649        """
2650        Copy repository sections from this xml state to the target xml state
2651
2652        :param object target_state: XMLState instance
2653        :param bool wipe: delete all repos in target prior to copy
2654        """
2655        repository_sections = self._profiled(
2656            self.xml_data.get_repository()
2657        )
2658        if repository_sections:
2659            if wipe:
2660                target_state.xml_data.set_repository([])
2661            for repository_section in repository_sections:
2662                repository_copy = copy.deepcopy(repository_section)
2663                # profiles are not copied because they might not exist
2664                # in the target description
2665                repository_copy.set_profiles(None)
2666                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:
2668    def copy_preferences_subsections(
2669        self, section_names: List, target_state: Any
2670    ) -> None:
2671        """
2672        Copy subsections of the preferences sections, matching given
2673        section names, from this xml state to the target xml state
2674
2675        :param list section_names: preferences subsection names
2676        :param object target_state: XMLState instance
2677        """
2678        target_preferences_sections = target_state.get_preferences_sections()
2679        if target_preferences_sections:
2680            target_preferences_section = target_preferences_sections[0]
2681            for preferences_section in self.get_preferences_sections():
2682                for section_name in section_names:
2683                    get_section_method = getattr(
2684                        preferences_section, 'get_' + section_name
2685                    )
2686                    section = get_section_method()
2687                    if section:
2688                        set_section_method = getattr(
2689                            target_preferences_section, 'set_' + section_name
2690                        )
2691                        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:
2693    def copy_build_type_attributes(
2694        self, attribute_names: List, target_state: Any
2695    ) -> None:
2696        """
2697        Copy specified attributes from this build type section to the
2698        target xml state build type section
2699
2700        :param list attribute_names: type section attributes
2701        :param object target_state: XMLState instance
2702        """
2703        for attribute in attribute_names:
2704            get_type_method = getattr(
2705                self.build_type, 'get_' + attribute
2706            )
2707            attribute_value = get_type_method()
2708            if attribute_value:
2709                set_type_method = getattr(
2710                    target_state.build_type, 'set_' + attribute
2711                )
2712                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:
2714    def copy_bootincluded_packages(self, target_state: Any) -> None:
2715        """
2716        Copy packages marked as bootinclude to the packages
2717        type=bootstrap section in the target xml state. The package
2718        will also be removed from the packages type=delete section
2719        in the target xml state if present there
2720
2721        :param object target_state: XMLState instance
2722        """
2723        target_packages_sections = \
2724            target_state.get_bootstrap_packages_sections()
2725        if target_packages_sections:
2726            target_packages_section = \
2727                target_packages_sections[0]
2728            package_names_added = []
2729            packages_sections = self.get_packages_sections(
2730                ['image', 'bootstrap', self.get_build_type_name()]
2731            )
2732            package_list = self.get_package_sections(
2733                packages_sections
2734            )
2735            if package_list:
2736                for package in package_list:
2737                    if package.package_section.get_bootinclude():
2738                        target_packages_section.add_package(
2739                            xml_parse.package(
2740                                name=package.package_section.get_name()
2741                            )
2742                        )
2743                        package_names_added.append(
2744                            package.package_section.get_name()
2745                        )
2746            delete_packages_sections = target_state.get_packages_sections(
2747                ['delete']
2748            )
2749            package_list = self.get_package_sections(
2750                delete_packages_sections
2751            )
2752            if package_list:
2753                for package in package_list:
2754                    package_name = package.package_section.get_name()
2755                    if package_name in package_names_added:
2756                        package.packages_section.package.remove(
2757                            package.package_section
2758                        )

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:
2760    def copy_bootincluded_archives(self, target_state: Any) -> None:
2761        """
2762        Copy archives marked as bootinclude to the packages type=bootstrap
2763        section in the target xml state
2764
2765        :param object target_state: XMLState instance
2766        """
2767        target_bootstrap_packages_sections = \
2768            target_state.get_bootstrap_packages_sections()
2769        if target_bootstrap_packages_sections:
2770            target_bootstrap_packages_section = \
2771                target_bootstrap_packages_sections[0]
2772            packages_sections = self.get_packages_sections(
2773                ['image', 'bootstrap', self.get_build_type_name()]
2774            )
2775            for packages_section in packages_sections:
2776                archive_list = packages_section.get_archive()
2777                if archive_list:
2778                    for archive in archive_list:
2779                        if archive.get_bootinclude():
2780                            target_bootstrap_packages_section.add_archive(
2781                                xml_parse.archive(
2782                                    name=archive.get_name()
2783                                )
2784                            )

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:
2786    def copy_bootdelete_packages(self, target_state: Any) -> None:
2787        """
2788        Copy packages marked as bootdelete to the packages type=delete
2789        section in the target xml state
2790
2791        :param object target_state: XMLState instance
2792        """
2793        target_delete_packages_sections = target_state.get_packages_sections(
2794            ['delete']
2795        )
2796        if not target_delete_packages_sections:
2797            target_delete_packages_sections = [
2798                xml_parse.packages(type_='delete')
2799            ]
2800            target_state.xml_data.add_packages(
2801                target_delete_packages_sections[0]
2802            )
2803
2804        target_delete_packages_section = \
2805            target_delete_packages_sections[0]
2806        packages_sections = self.get_packages_sections(
2807            ['image', 'bootstrap', self.get_build_type_name()]
2808        )
2809        package_list = self.get_package_sections(
2810            packages_sections
2811        )
2812        if package_list:
2813            for package in package_list:
2814                if package.package_section.get_bootdelete():
2815                    target_delete_packages_section.add_package(
2816                        xml_parse.package(
2817                            name=package.package_section.get_name()
2818                        )
2819                    )

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:
2821    def get_distribution_name_from_boot_attribute(self) -> str:
2822        """
2823        Extract the distribution name from the boot attribute of the
2824        build type section.
2825
2826        If no boot attribute is configured or the contents does not
2827        match the kiwi defined naming schema for boot image descriptions,
2828        an exception is thrown
2829
2830        :return: lowercase distribution name
2831
2832        :rtype: str
2833        """
2834        boot_attribute = self.build_type.get_boot()
2835        if not boot_attribute:
2836            raise KiwiDistributionNameError(
2837                'No boot attribute to extract distribution name from found'
2838            )
2839        boot_attribute_format = '^.*-(.*)$'
2840        boot_attribute_expression = re.match(
2841            boot_attribute_format, boot_attribute
2842        )
2843        if not boot_attribute_expression:
2844            raise KiwiDistributionNameError(
2845                'Boot attribute "%s" does not match expected format %s' %
2846                (boot_attribute, boot_attribute_format)
2847            )
2848        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:
2850    def get_fs_mount_option_list(self) -> List:
2851        """
2852        List of root filesystem mount options
2853
2854        The list contains one element with the information from the
2855        fsmountoptions attribute. The value there is passed along to
2856        the -o mount option
2857
2858        :return: max one element list with mount option string
2859
2860        :rtype: list
2861        """
2862        option_list = []
2863        mount_options = self.build_type.get_fsmountoptions()
2864        if mount_options:
2865            option_list = [mount_options]
2866
2867        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:
2869    def get_fs_create_option_list(self) -> List:
2870        """
2871        List of root filesystem creation options
2872
2873        The list contains elements with the information from the
2874        fscreateoptions attribute string that got split into its
2875        substring components
2876
2877        :return: list with create options
2878
2879        :rtype: list
2880        """
2881        option_list = []
2882        create_options = self.build_type.get_fscreateoptions()
2883        if create_options:
2884            option_list = create_options.split()
2885
2886        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]:
2888    def get_luks_credentials(self) -> Optional[str]:
2889        """
2890        Return key or passphrase credentials to open the luks pool
2891
2892        :return: data
2893
2894        :rtype: str
2895        """
2896        data = self.build_type.get_luks()
2897        if data:
2898            keyfile_name = None
2899            try:
2900                # try to interpret data as an URI
2901                uri = Uri(data)
2902                if not uri.is_remote():
2903                    keyfile_name = uri.translate()
2904            except Exception:
2905                # this doesn't look like a valid URI, continue as just data
2906                pass
2907            if keyfile_name:
2908                try:
2909                    with open(keyfile_name) as keyfile:
2910                        return keyfile.read()
2911                except Exception as issue:
2912                    raise KiwiFileAccessError(
2913                        f'Failed to read from {keyfile_name!r}: {issue}'
2914                    )
2915        return data

Return key or passphrase credentials to open the luks pool

Returns

data

def get_luks_format_options(self) -> List[str]:
2917    def get_luks_format_options(self) -> List[str]:
2918        """
2919        Return list of luks format options
2920
2921        :return: list of options
2922
2923        :rtype: list
2924        """
2925        result = []
2926        luksversion = self.build_type.get_luks_version()
2927        luksformat = self.build_type.get_luksformat()
2928        luks_pbkdf = self.build_type.get_luks_pbkdf()
2929        if luksversion:
2930            result.append('--type')
2931            result.append(luksversion)
2932        if luksformat:
2933            for option in luksformat[0].get_option():
2934                result.append(option.get_name())
2935                if option.get_value():
2936                    result.append(option.get_value())
2937        if luks_pbkdf:
2938            # Allow to override the pbkdf algorithm that cryptsetup
2939            # uses by default. Cryptsetup may use argon2i by default,
2940            # which is not supported by all bootloaders.
2941            result.append('--pbkdf')
2942            result.append(luks_pbkdf)
2943        return result

Return list of luks format options

Returns

list of options

def get_derived_from_image_uri(self) -> List[kiwi.system.uri.Uri]:
2945    def get_derived_from_image_uri(self) -> List[Uri]:
2946        """
2947        Uri object(s) of derived image if configured
2948
2949        Specific image types can be based on one ore more derived
2950        images. This method returns the location of this image(s)
2951        when configured in the XML description
2952
2953        :return: List of Uri instances
2954
2955        :rtype: list
2956        """
2957        image_uris = []
2958        derived_images = self.build_type.get_derived_from()
2959        if derived_images:
2960            for derived_image in derived_images.split(','):
2961                image_uris.append(
2962                    Uri(derived_image, repo_type='container')
2963                )
2964        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:
2966    def set_derived_from_image_uri(self, uri: str) -> None:
2967        """
2968        Set derived_from attribute to a new value
2969
2970        In order to set a new value the derived_from attribute
2971        must be already present in the image configuration
2972
2973        :param str uri: URI
2974        """
2975        if self.build_type.get_derived_from():
2976            self.build_type.set_derived_from(uri)
2977        else:
2978            message = dedent('''\n
2979                No derived_from attribute configured in image <type>
2980
2981                In order to set the uri {0} as base container reference
2982                an initial derived_from attribute must be set in the
2983                type section
2984            ''')
2985            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:
2987    def set_root_partition_uuid(self, uuid: str) -> None:
2988        """
2989        Store PARTUUID provided in uuid as state information
2990
2991        :param str uuid: PARTUUID
2992        """
2993        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]:
2995    def get_root_partition_uuid(self) -> Optional[str]:
2996        """
2997        Return preserved PARTUUID
2998        """
2999        return self.root_partition_uuid

Return preserved PARTUUID

def set_root_filesystem_uuid(self, uuid: str) -> None:
3001    def set_root_filesystem_uuid(self, uuid: str) -> None:
3002        """
3003        Store UUID provided in uuid as state information
3004
3005        :param str uuid: UUID
3006        """
3007        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]:
3009    def get_root_filesystem_uuid(self) -> Optional[str]:
3010        """
3011        Return preserved UUID
3012        """
3013        return self.root_filesystem_uuid

Return preserved UUID

@staticmethod
def get_archives_target_dirs(packages_sections_names: Optional[List[kiwi.xml_parse.packages]]) -> Dict:
3015    @staticmethod
3016    def get_archives_target_dirs(
3017        packages_sections_names: Optional[List[xml_parse.packages]]
3018    ) -> Dict:
3019        """
3020        Dict of archive names and target dirs for packages section(s), if any
3021        :return: archive names and its target dir
3022        :rtype: dict
3023        """
3024        result = {}
3025        if packages_sections_names:
3026            for package_section_name in packages_sections_names:
3027                for archive in package_section_name.get_archive():
3028                    result[archive.get_name().strip()] = archive.get_target_dir()
3029
3030        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:
3032    def get_bootstrap_archives_target_dirs(self) -> Dict:
3033        """
3034        Dict of archive names and target dirs from the type="bootstrap"
3035        packages section(s)
3036        :return: archive names and its target dir
3037        :rtype: dict
3038        """
3039        return self.get_archives_target_dirs(
3040            self.get_packages_sections(['bootstrap'])
3041        )

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:
3043    def get_system_archives_target_dirs(self) -> Dict:
3044        """
3045        Dict of archive names and its target dir from the packages sections matching
3046        type="image" and type=build_type
3047        :return: archive names and its target dir
3048        :rtype: dict
3049        """
3050        return self.get_archives_target_dirs(
3051            self.get_packages_sections(['image', self.get_build_type_name()])
3052        )

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