diff --git a/kiwi/bootloader/config/grub2.py b/kiwi/bootloader/config/grub2.py index 673442d7e6f..f85a24455be 100644 --- a/kiwi/bootloader/config/grub2.py +++ b/kiwi/bootloader/config/grub2.py @@ -319,7 +319,9 @@ def setup_disk_image_config( # be deleted... if self.xml_state.build_type.get_overlayroot_write_partition() is not False: self._fix_grub_root_device_reference(config_file, boot_options) - self._fix_grub_loader_entries_boot_cmdline() + self._fix_grub_loader_entries_boot_cmdline( + keep_existing_options=True if Defaults.is_ostree(self.root_dir) else False + ) self._fix_grub_loader_entries_linux_and_initrd_paths() if self.firmware.efi_mode() and self.early_boot_script_efi: @@ -418,6 +420,25 @@ def setup_live_image_config( has_graphics = True if 'serial' in self.terminal_output or 'serial' in self.terminal_input: has_serial = True + + # Find the ostree=... parameter from the BLS config and add it to the boot options + # TODO: ostree: This code looks like it is required for any ostree based boot process and should live in its own scope e.g. an OSTree class + if Defaults.is_ostree(self.root_dir): # pragma: nocover + loader_entries_pattern = os.sep.join( + [ + self.root_mount.mountpoint, + 'boot', 'loader', 'entries', '*.conf' + ] + ) + ostree_options_pattern = r'options (ostree=.*)' + for menu_entry_file in glob.iglob(loader_entries_pattern): + with open(menu_entry_file) as grub_menu_entry_file: + for line in grub_menu_entry_file: + options_match = re.match(ostree_options_pattern, line) + if options_match: + self.live_boot_options += options_match.group(1) + break + parameters = { 'search_params': '--file --set=root /boot/' + mbrid.get_id(), 'default_boot': '0', @@ -1459,7 +1480,9 @@ def _fix_grub_root_device_reference(self, config_file, boot_options): with open(vendor_grubenv_file, 'w') as vendor_grubenv: vendor_grubenv.write(grubenv) - def _fix_grub_loader_entries_boot_cmdline(self): + def _fix_grub_loader_entries_boot_cmdline( + self, keep_existing_options=False + ): if self.cmdline: # For distributions that follows the bootloader spec here: # https://www.freedesktop.org/wiki/Specifications/BootLoaderSpec @@ -1483,11 +1506,18 @@ def _fix_grub_loader_entries_boot_cmdline(self): for menu_entry_file in glob.iglob(loader_entries_pattern): with open(menu_entry_file) as grub_menu_entry_file: menu_entry = grub_menu_entry_file.read() - menu_entry = re.sub( - r'options (.*)', - r'options {0}'.format(self.cmdline), - menu_entry - ) + if keep_existing_options: + menu_entry = re.sub( + r'options (.*)', + r'options \1 {0}'.format(self.cmdline), + menu_entry + ) + else: + menu_entry = re.sub( + r'options (.*)', + r'options {0}'.format(self.cmdline), + menu_entry + ) with open(menu_entry_file, 'w') as grub_menu_entry_file: grub_menu_entry_file.write(menu_entry) diff --git a/kiwi/builder/live.py b/kiwi/builder/live.py index c4dc7e988c0..b5f04645e8f 100644 --- a/kiwi/builder/live.py +++ b/kiwi/builder/live.py @@ -19,6 +19,7 @@ import logging from typing import Dict import shutil +import re # project from kiwi.utils.temporary import Temporary @@ -172,41 +173,58 @@ def create(self) -> Result: working_directory=self.root_dir ) - # prepare dracut initrd call - self.boot_image.prepare() + if not Defaults.is_ostree(self.root_dir): + # prepare dracut initrd call + self.boot_image.prepare() - # create dracut initrd for live image - log.info('Creating live ISO boot image') - live_dracut_modules = Defaults.get_live_dracut_modules_from_flag( - self.live_type - ) - live_dracut_modules.append('pollcdrom') - for dracut_module in live_dracut_modules: - self.boot_image.include_module(dracut_module) - self.boot_image.omit_module('multipath') - self.boot_image.write_system_config_file( - config={ - 'modules': live_dracut_modules, - 'omit_modules': ['multipath'] - }, - config_file=self.root_dir + '/etc/dracut.conf.d/02-livecd.conf' - ) - self.boot_image.create_initrd(self.mbrid) - # Clean up leftover dracut config file (which can break installs) - os.unlink(self.root_dir + '/etc/dracut.conf.d/02-livecd.conf') - if self.bootloader == 'systemd_boot': - # make sure the initrd name follows the dracut - # naming conventions - boot_names = self.boot_image.get_boot_names() - if self.boot_image.initrd_filename: - Command.run( - [ - 'mv', self.boot_image.initrd_filename, - self.root_dir + ''.join( - ['/boot/', boot_names.initrd_name] - ) - ] - ) + # create dracut initrd for live image + log.info('Creating live ISO boot image') + live_dracut_modules = Defaults.get_live_dracut_modules_from_flag( + self.live_type + ) + live_dracut_modules.append('pollcdrom') + for dracut_module in live_dracut_modules: + self.boot_image.include_module(dracut_module) + self.boot_image.omit_module('multipath') + self.boot_image.write_system_config_file( + config={ + 'modules': live_dracut_modules, + 'omit_modules': ['multipath'] + }, + config_file=self.root_dir + '/etc/dracut.conf.d/02-livecd.conf' + ) + self.boot_image.create_initrd(self.mbrid) + # Clean up leftover dracut config file (which can break installs) + os.unlink(self.root_dir + '/etc/dracut.conf.d/02-livecd.conf') + if self.bootloader == 'systemd_boot': + # make sure the initrd name follows the dracut + # naming conventions + boot_names = self.boot_image.get_boot_names() + if self.boot_image.initrd_filename: + Command.run( + [ + 'mv', self.boot_image.initrd_filename, + self.root_dir + ''.join( + ['/boot/', boot_names.initrd_name] + ) + ] + ) + else: # pragma: nocover + # TODO: ostree: this code should be part of an OSTree class find the existing initrd in the ostree case + boot_ostree_dir = os.sep.join([self.root_dir, 'boot/ostree']) + initramfs_ostree_pattern = '.*/boot/ostree/.*/initramfs-(.*)' + if os.path.isdir(boot_ostree_dir): + for deployment in sorted(os.listdir(boot_ostree_dir)): + deployment_dir = os.sep.join([self.root_dir, 'boot/ostree', deployment]) + if os.path.isdir(deployment_dir): + files = sorted(os.listdir(deployment_dir)) + for f in files: + initramfs_file = os.sep.join([self.root_dir, 'boot/ostree', deployment, f]) + version_match = re.match(initramfs_ostree_pattern, initramfs_file) + if version_match: + initrd_dest = os.sep.join([self.root_dir, 'boot/initramfs']) + Command.run(['cp', initramfs_file, initrd_dest]) + self.boot_image.initrd_filename = initrd_dest # create EFI FAT image if self.firmware.efi_mode(): diff --git a/kiwi/defaults.py b/kiwi/defaults.py index 202f2d97521..e91a1805ebb 100644 --- a/kiwi/defaults.py +++ b/kiwi/defaults.py @@ -2079,3 +2079,14 @@ def to_profile(self, profile): cur_profile = profile.dot_profile if key not in cur_profile or cur_profile[key] is None: profile.add(key, self.get(key)) + + @staticmethod + def is_ostree(root_dir: str) -> bool: + """ + Returns true if the system being installed appears to be ostree managed. + This is a heuristic that should ideally be replaced by a key in the config instead. + + :return: if the system is ostree managed + :rtype: bool + """ + return os.path.isdir(os.sep.join([root_dir, 'boot', 'ostree'])) diff --git a/kiwi/system/kernel.py b/kiwi/system/kernel.py index 27f904c251e..5bad4f8b422 100644 --- a/kiwi/system/kernel.py +++ b/kiwi/system/kernel.py @@ -23,6 +23,7 @@ # project from kiwi.command import Command +from kiwi.defaults import Defaults from kiwi.exceptions import KiwiKernelLookupError @@ -90,6 +91,29 @@ def get_kernel( filename=kernel_file, version=version ) + + if Defaults.is_ostree(self.root_dir): # pragma: nocover + # TODO: ostree: This code looks similar to the one in builder/live and should imho exist only once in a OSTree class + boot_ostree_dir = os.sep.join([self.root_dir, 'boot/ostree']) + kernel_ostree_pattern = '.*/boot/ostree/.*/vmlinuz-(.*)' + if os.path.isdir(boot_ostree_dir): + for deployment in sorted(os.listdir(boot_ostree_dir)): + deployment_dir = os.sep.join([self.root_dir, 'boot/ostree', deployment]) + if os.path.isdir(deployment_dir): + files = sorted(os.listdir(deployment_dir)) + for f in files: + kernel_file = os.sep.join([self.root_dir, 'boot/ostree', deployment, f]) + version_match = re.match(kernel_ostree_pattern, kernel_file) + if version_match: + version = version_match.group(1) + return kernel_type( + name=os.path.basename( + os.path.realpath(kernel_file) + ), + filename=kernel_file, + version=version + ) + if raise_on_not_found: raise KiwiKernelLookupError( 'No kernel found in {0}, searched for {1}'.format( diff --git a/test/unit/bootloader/config/grub2_test.py b/test/unit/bootloader/config/grub2_test.py index 6c39a769607..d59145e74ac 100644 --- a/test/unit/bootloader/config/grub2_test.py +++ b/test/unit/bootloader/config/grub2_test.py @@ -885,7 +885,11 @@ def test_setup_sysconfig_bootloader_no_secure( ] @patch('os.path.exists') - def test_setup_live_image_config_custom_template(self, mock_exists): + @patch('kiwi.defaults.Defaults.is_ostree') + def test_setup_live_image_config_custom_template( + self, mock_is_ostree, mock_exists + ): + mock_is_ostree.return_value = False bootloader = Mock() bootloader.get_grub_template.return_value = "example.template" self.bootloader.xml_state.build_type.bootloader.append(bootloader) @@ -989,11 +993,13 @@ def test_setup_install_image_config_multiboot(self): @patch('kiwi.bootloader.config.grub2.Path.which') @patch('kiwi.defaults.Defaults.get_vendor_grubenv') @patch('glob.iglob') + @patch('kiwi.defaults.Defaults.is_ostree') def test_setup_disk_image_config( - self, mock_iglob, mock_get_vendor_grubenv, mock_Path_which, - mock_Command_run, mock_copy_grub_config_to_efi_path, + self, mock_is_ostree, mock_iglob, mock_get_vendor_grubenv, + mock_Path_which, mock_Command_run, mock_copy_grub_config_to_efi_path, mock_mount_system ): + mock_is_ostree.return_value = False mock_iglob.return_value = ['some_entry.conf'] mock_get_vendor_grubenv.return_value = 'grubenv' mock_Path_which.return_value = '/path/to/grub2-mkconfig' @@ -1085,11 +1091,25 @@ def open_file(filename, mode=None): } ) + assert 'options some-cmdline root=UUID=foo' in \ + file_handle_menu.write.call_args_list[0][0][0].split(os.linesep) assert 'linux /vmlinuz' in \ file_handle_menu.write.call_args_list[1][0][0].split(os.linesep) assert 'initrd /initrd' in \ file_handle_menu.write.call_args_list[1][0][0].split(os.linesep) + mock_is_ostree.return_value = True + file_handle_menu.reset_mock() + + self.bootloader.setup_disk_image_config( + boot_options={ + 'root_device': 'rootdev', 'boot_device': 'bootdev' + } + ) + + assert 'options foo some-cmdline root=UUID=foo' in \ + file_handle_menu.write.call_args_list[0][0][0].split(os.linesep) + @patch.object(BootLoaderConfigGrub2, '_copy_grub_config_to_efi_path') def test_setup_install_image_config_standard( self, mock_copy_grub_config_to_efi_path