Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for ostree based systems #2557

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 37 additions & 7 deletions kiwi/bootloader/config/grub2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open for the Friday conversation

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',
Expand Down Expand Up @@ -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
Expand All @@ -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)

Expand Down
86 changes: 52 additions & 34 deletions kiwi/builder/live.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import logging
from typing import Dict
import shutil
import re

# project
from kiwi.utils.temporary import Temporary
Expand Down Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open for the Friday conversation

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():
Expand Down
11 changes: 11 additions & 0 deletions kiwi/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']))
24 changes: 24 additions & 0 deletions kiwi/system/kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

# project
from kiwi.command import Command
from kiwi.defaults import Defaults

from kiwi.exceptions import KiwiKernelLookupError

Expand Down Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open for the Friday conversation

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(
Expand Down
26 changes: 23 additions & 3 deletions test/unit/bootloader/config/grub2_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down
Loading