Simon Glass has proposed merging ~sjg1/curtin:boot2 into curtin:master. Commit message: Curtin currently assumes that grub is used as the bootloader.
Generalise the schema to support multiple bootloaders TBD: Add extlinux support DO NOT SQUASH Requested reviews: curtin developers (curtin-dev) For more details, see: https://code.launchpad.net/~sjg1/curtin/+git/curtin/+merge/480712 The idea here is to allow MAAS to set up RISC-V clients without needing grub. They can use extlinux instead, meaning that U-Boot is enough to handle the full boot. I have made the schema changes but I need to figure out how to get the device and mount point for the root and boot devices. -- Your team curtin developers is requested to review the proposed merge of ~sjg1/curtin:boot2 into curtin:master.
diff --git a/.gitignore b/.gitignore index 8f343e7..1c6bd5e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__ .tox .coverage curtin.egg-info/ +doc/_build diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py index 82ebcab..9706494 100644 --- a/curtin/commands/curthooks.py +++ b/curtin/commands/curthooks.py @@ -32,6 +32,7 @@ from curtin.net import deps as ndeps from curtin.reporter import events from curtin.commands import apply_net, apt_config from curtin.commands.install_grub import install_grub +from curtin.commands.install_extlinux import install_extlinux from curtin.url_helper import get_maas_version from . import populate_one_subcmd @@ -439,7 +440,7 @@ def install_kernel(cfg, target): " System may not boot.", package) -def uefi_remove_old_loaders(grubcfg: config.GrubConfig, target: str): +def uefi_remove_old_loaders(bootcfg: config.BootCfg, target: str): """Removes the old UEFI loaders from efibootmgr.""" efi_state = util.get_efibootmgr(target) @@ -457,7 +458,7 @@ def uefi_remove_old_loaders(grubcfg: config.GrubConfig, target: str): if not old_efi_entries: return - if grubcfg.remove_old_uefi_loaders: + if bootcfg.remove_old_uefi_loaders: with util.ChrootableTarget(target) as in_chroot: for number, entry in old_efi_entries.items(): LOG.debug("removing old UEFI entry: %s", entry.name) @@ -532,7 +533,7 @@ def _reorder_new_entry( def uefi_reorder_loaders( - grubcfg: config.GrubConfig, + bootcfg: config.BootCfg, target: str, efi_orig_state: util.EFIBootState, variant: str, @@ -548,7 +549,7 @@ def uefi_reorder_loaders( is installed after the the previous first entry (before we installed grub). """ - if not grubcfg.reorder_uefi: + if not bootcfg.reorder_uefi: LOG.debug("Skipped reordering of UEFI boot methods.") LOG.debug("Currently booted UEFI loader might no longer boot.") return @@ -557,7 +558,7 @@ def uefi_reorder_loaders( LOG.debug('UEFI efibootmgr output after install:\n%s', efi_state) new_boot_order = None - if efi_state.current and not grubcfg.reorder_uefi_force_fallback: + if efi_state.current and not bootcfg.reorder_uefi_force_fallback: boot_order = list(efi_state.order) if efi_state.current in boot_order: boot_order.remove(efi_state.current) @@ -566,7 +567,7 @@ def uefi_reorder_loaders( "Setting currently booted %s as the first UEFI loader.", efi_state.current) else: - if grubcfg.reorder_uefi_force_fallback: + if bootcfg.reorder_uefi_force_fallback: reason = "config 'reorder_uefi_force_fallback' is True" else: reason = "missing 'BootCurrent' value" @@ -592,10 +593,10 @@ def uefi_reorder_loaders( def uefi_remove_duplicate_entries( - grubcfg: config.GrubConfig, + bootcfg: config.BootCfg, target: str, ) -> None: - if not grubcfg.remove_duplicate_entries: + if not bootcfg.remove_duplicate_entries: LOG.debug("Skipped removing duplicate UEFI boot entries per config.") return @@ -752,13 +753,7 @@ def setup_grub( from curtin.commands.block_meta import (extract_storage_ordered_dict, get_path_to_storage_volume) - grubcfg_d = cfg.get('grub', {}) - - # copy legacy top level name - if 'grub_install_devices' in cfg and 'install_devices' not in grubcfg_d: - grubcfg_d['install_devices'] = cfg['grub_install_devices'] - - grubcfg = config.fromdict(config.GrubConfig, grubcfg_d) + bootcfg = config.fromdict(config.BootCfg, cfg.get('boot', {})) LOG.debug("setup grub on target %s", target) # if there is storage config, look for devices tagged with 'grub_device' @@ -784,16 +779,16 @@ def setup_grub( get_path_to_storage_volume(item_id, storage_cfg_odict)) if len(storage_grub_devices) > 0: - if grubcfg.install_devices and \ - grubcfg.install_devices is not grubcfg.install_devices_default: + if bootcfg.install_devices and \ + bootcfg.install_devices is not bootcfg.install_devices_default: LOG.warn("Storage Config grub device config takes precedence " "over grub 'install_devices' value, ignoring: %s", - grubcfg['install_devices']) - grubcfg.install_devices = storage_grub_devices + bootcfg['install_devices']) + bootcfg.install_devices = storage_grub_devices - LOG.debug("install_devices: %s", grubcfg.install_devices) - if grubcfg.install_devices is not grubcfg.install_devices_default: - instdevs = grubcfg.install_devices + LOG.debug("install_devices: %s", bootcfg.install_devices) + if bootcfg.install_devices is not bootcfg.install_devices_default: + instdevs = bootcfg.install_devices if instdevs is None: LOG.debug("grub installation disabled by config") else: @@ -843,16 +838,70 @@ def setup_grub( else: instdevs = ["none"] - update_nvram = grubcfg.update_nvram + update_nvram = bootcfg.update_nvram if uefi_bootable and update_nvram: efi_orig_state = util.get_efibootmgr(target) - uefi_remove_old_loaders(grubcfg, target) + uefi_remove_old_loaders(bootcfg, target) - install_grub(instdevs, target, uefi=uefi_bootable, grubcfg=grubcfg) + install_grub(instdevs, target, uefi=uefi_bootable, bootcfg=bootcfg) if uefi_bootable and update_nvram: - uefi_reorder_loaders(grubcfg, target, efi_orig_state, variant) - uefi_remove_duplicate_entries(grubcfg, target) + uefi_reorder_loaders(bootcfg, target, efi_orig_state, variant) + uefi_remove_duplicate_entries(bootcfg, target) + + +def translate_old_grub_schema(cfg): + """Translate the old top-level 'grub' configure to the new 'boot' one""" + grub_cfg = cfg.get('grub', {}) + + # Use the 'boot' key, if present + if 'boot' in cfg: + if grub_cfg: + raise ValueError("Configuration has both 'grub' and 'boot' keys") + return + + # copy legacy top level name + if 'grub_install_devices' in cfg and 'install_devices' not in cfg: + grub_cfg['install_devices'] = cfg['grub_install_devices'] + + if 'grub' in cfg: + del cfg['grub'] + + # Create a bootloaders list with 'grub', which is implied in the old config + grub_cfg['bootloaders'] = ['grub'] + + cfg['boot'] = grub_cfg + + +def setup_boot( + cfg: dict, + target: str, + machine: str, + stack_prefix: str, + osfamily: str, + variant: str, + ) -> None: + translate_old_grub_schema(cfg) + + boot_cfg = cfg['boot'] + bootloaders = boot_cfg['bootloaders'] + + # For now we have a hard-coded mechanism to determine whether grub should + # be installed or not. Even if the grub info is present in the config, we + # check the machine to decide whether or not to install it. + if 'grub' in bootloaders and uses_grub(machine): + with events.ReportEventStack( + name=stack_prefix + '/install-grub', + reporting_enabled=True, level="INFO", + description="installing grub to target devices"): + setup_grub(cfg, target, osfamily=osfamily, variant=variant) + + if 'extlinux' in bootloaders: + with events.ReportEventStack( + name=stack_prefix + '/install-extlinux', + reporting_enabled=True, level="INFO", + description="installing extlinux to target devices"): + install_extlinux(cfg, target, osfamily=osfamily, variant=variant) def update_initramfs(target=None, all_kernels=False): @@ -898,12 +947,7 @@ def update_initramfs(target=None, all_kernels=False): # if the initrd file exists, then we only need to invoke # update-initramfs's -u (update) method. If the file does # not exist, then we need to run the -c (create) method. - for kernel in sorted(glob.glob(boot + '/vmlinu*-*')): - kfile = os.path.basename(kernel) - # handle vmlinux or vmlinuz - kprefix = kfile.split('-')[0] - version = kfile.replace(kprefix + '-', '') - initrd = kernel.replace(kprefix, 'initrd.img') + for kfile, initrd, version in paths.get_kernel_list(target): # -u == update, -c == create mode = '-u' if os.path.exists(initrd) else '-c' cmd = ['update-initramfs', mode, '-k', version] @@ -926,7 +970,7 @@ def update_initramfs(target=None, all_kernels=False): else: # Curtin only knows update-initramfs (provided by initramfs-tools) and # dracut. - if not glob.glob(boot + '/vmlinu*-*'): + if not list(paths.get_kernel_list(target)): LOG.debug("neither update-initramfs or dracut found in target %s" " but there is no initramfs to generate, so ignoring", target) @@ -2051,13 +2095,8 @@ def builtin_curthooks(cfg, target, state): reporting_enabled=True, level="INFO", description="configuring target system bootloader"): - if uses_grub(machine): - with events.ReportEventStack( - name=stack_prefix + '/install-grub', - reporting_enabled=True, level="INFO", - description="installing grub to target devices"): - setup_grub(cfg, target, osfamily=osfamily, - variant=distro_info.variant) + setup_boot(cfg, target, machine, stack_prefix, osfamily=osfamily, + variant=distro_info.variant) # Copy information from installation media with events.ReportEventStack( diff --git a/curtin/commands/install_extlinux.py b/curtin/commands/install_extlinux.py new file mode 100644 index 0000000..8e378c2 --- /dev/null +++ b/curtin/commands/install_extlinux.py @@ -0,0 +1,85 @@ +# This file is part of curtin. See LICENSE file for copyright and license info. + +"""This loosely follows the u-boot-update script in the u-boot-menu package""" + +import io +import os + +from curtin import config +from curtin import paths +from curtin.log import LOG + +EXTLINUX_DIR = '/boot/extlinux' + + +def build_content(target: str, bootcfg: config.BootCfg): + """Build the content of the extlinux.conf file + + For now this only supports x86, since it does not handle the 'fdt' option. + Rather than add that, the plan is to use a FIT (Flat Image Tree) which can + handle FDT selection automatically. This should avoid the complexity + associated with fdt and fdtdir options. + + We assume that the boot/ directory is in the root partition, rather than + being in a separate partition. TBD. + """ + def get_entry(label, params, menu_label_append=''): + return f'''\ +label {label} +\tmenu label {menu_label} {version}{menu_label_append} +\tlinux /{kernel_path} +\tinitrd /{initrd_path} +\tappend root={root} {params}''' + + buf = io.StringIO() + params = 'ro quiet' + alternatives = ['default', 'recovery'] + menu_label = 'Ubuntu 22.04.5 LTS' + root = '/dev/mapper/vgubuntu-root' # TODO + + # For the recovery option, remove 'quiet' and add 'single' + without_quiet = filter(lambda word: word != 'quiet', params.split()) + rec_params = ' '.join(list(without_quiet) + ['single']) + + print(f'''\ +## {EXTLINUX_DIR}/extlinux.conf +## +## IMPORTANT WARNING +## +## The configuration of this file is generated automatically. +## Do not edit this file manually, use: u-boot-update + +default l0 +menu title U-Boot menu +prompt 0 +timeout 50''', file=buf) + for seq, (kernel_path, full_initrd_path, version) in enumerate( + paths.get_kernel_list(target)): + LOG.debug('P: Writing config for %s...', kernel_path) + initrd_path = os.path.basename(full_initrd_path) + print(file=buf) + print(file=buf) + print(get_entry(f'l{seq}', params), file=buf) + + if 'recovery' in alternatives: + print(file=buf) + print(get_entry(f'l{seq}r', rec_params, ' (rescue target)'), + file=buf) + + return buf.getvalue() + + +def install_extlinux( + target: str, + bootcfg: config.BootCfg, + ): + """Install extlinux to the target chroot. + + :param: target: A string specifying the path to the chroot mountpoint. + :param: bootcfg: An config dict with grub config options. + """ + content = build_content(target, bootcfg) + extlinux_path = paths.target_path(target, '/boot/extlinux') + os.makedirs(extlinux_path, exist_ok=True) + with open(extlinux_path + '/extlinux.conf', 'w', encoding='utf-8') as outf: + outf.write(content) diff --git a/curtin/commands/install_grub.py b/curtin/commands/install_grub.py index 6d1d59e..e6fa1ef 100644 --- a/curtin/commands/install_grub.py +++ b/curtin/commands/install_grub.py @@ -2,7 +2,6 @@ import os import re import platform import shutil -import sys from typing import List, Optional from curtin import block @@ -11,8 +10,6 @@ from curtin import distro from curtin import util from curtin.log import LOG from curtin.paths import target_path -from curtin.reporter import events -from . import populate_one_subcmd CMD_ARGUMENTS = ( ((('-t', '--target'), @@ -209,15 +206,15 @@ def replace_grub_cmdline_linux_default(target, new_args): def write_grub_config( target: str, - grubcfg: config.GrubConfig, + bootcfg: config.BootCfg, grub_conf: str, new_params: str, ) -> None: - replace_default = grubcfg.replace_linux_default + replace_default = bootcfg.replace_linux_default if replace_default: replace_grub_cmdline_linux_default(target, new_params) - probe_os = grubcfg.probe_additional_os + probe_os = bootcfg.probe_additional_os if not probe_os: probe_content = [ ('# Curtin disable grub os prober that might find other ' @@ -228,7 +225,7 @@ def write_grub_config( "\n".join(probe_content), omode='a+') # if terminal is present in config, but unset, then don't - grub_terminal = grubcfg.terminal + grub_terminal = bootcfg.terminal if not grub_terminal.lower() == "unmodified": terminal_content = [ '# Curtin configured GRUB_TERMINAL value', @@ -402,7 +399,7 @@ def install_grub( devices: List[str], target: str, *, - grubcfg: config.GrubConfig, + bootcfg: config.BootCfg, uefi: Optional[bool] = None, ): """Install grub to devices inside target chroot. @@ -411,7 +408,7 @@ def install_grub( :param: target: A string specifying the path to the chroot mountpoint. :param: uefi: A boolean set to True if system is UEFI bootable otherwise False. - :param: grubcfg: An config dict with grub config options. + :param: bootcfg: An config dict with grub config options. """ if not devices: @@ -421,8 +418,8 @@ def install_grub( raise ValueError("Invalid parameter 'target': %s" % target) LOG.debug("installing grub to target=%s devices=%s [replace_defaults=%s]", - target, devices, grubcfg.replace_linux_default) - update_nvram = grubcfg.update_nvram + target, devices, bootcfg.replace_linux_default) + update_nvram = bootcfg.update_nvram distroinfo = distro.get_distroinfo(target=target) target_arch = distro.get_architecture(target=target) rhel_ver = (distro.rpm_get_dist_id(target) @@ -435,7 +432,7 @@ def install_grub( grub_conf = get_grub_config_file(target, distroinfo.family) new_params = get_carryover_params(distroinfo) prepare_grub_dir(target, grub_conf) - write_grub_config(target, grubcfg, grub_conf, new_params) + write_grub_config(target, bootcfg, grub_conf, new_params) grub_cmd = get_grub_install_command(uefi, distroinfo, target) if uefi: install_cmds, post_cmds = gen_uefi_install_commands( @@ -453,35 +450,4 @@ def install_grub( for cmd in install_cmds + post_cmds: in_chroot.subp(cmd, env=env, capture=True) - -def install_grub_main(args): - state = util.load_command_environment() - - if args.target is not None: - target = args.target - else: - target = state['target'] - - if target is None: - sys.stderr.write("Unable to find target. " - "Use --target or set TARGET_MOUNT_POINT\n") - sys.exit(2) - - cfg = config.load_command_config(args, state) - stack_prefix = state.get('report_stack_prefix', '') - uefi = util.is_uefi_bootable() - - util.EFIVarFSBug.apply_workaround_if_affected() - - grubcfg = config.fromdict(config.GrubConfig, cfg.get('grub')) - with events.ReportEventStack( - name=stack_prefix, reporting_enabled=True, level="INFO", - description="Installing grub to target devices"): - install_grub(args.devices, target, uefi=uefi, grubcfg=grubcfg) - sys.exit(0) - - -def POPULATE_SUBCMD(parser): - populate_one_subcmd(parser, CMD_ARGUMENTS, install_grub_main) - # vi: ts=4 expandtab syntax=python diff --git a/curtin/config.py b/curtin/config.py index e9f6516..da9e486 100644 --- a/curtin/config.py +++ b/curtin/config.py @@ -136,8 +136,21 @@ def _convert_install_devices(value): return value +def _check_bootloaders(inst, attr, vals): + if not isinstance(vals, list): + raise ValueError(f'bootloaders must be a list: {vals}') + if not vals: + raise ValueError(f'Empty bootloaders list: {vals}') + if len(vals) != len(set(vals)): + raise ValueError(f'bootloaders list contains duplicates: {vals}') + for val in vals: + if val not in ['grub', 'extlinux']: + raise ValueError(f'Unknown bootloader {val}: {vals}') + + @attr.s(auto_attribs=True) -class GrubConfig: +class BootCfg: + bootloaders: typing.List[str] = attr.ib(validator=_check_bootloaders) install_devices_default = object() install_devices: typing.Optional[typing.List[str]] = attr.ib( converter=_convert_install_devices, default=install_devices_default) diff --git a/curtin/paths.py b/curtin/paths.py index 064b060..98aa648 100644 --- a/curtin/paths.py +++ b/curtin/paths.py @@ -1,5 +1,8 @@ # This file is part of curtin. See LICENSE file for copyright and license info. +import glob import os +from packaging import version +import re try: string_types = (basestring,) @@ -31,4 +34,38 @@ def target_path(target, path=None): return os.path.join(target, path) + +def kernel_parse(fname): + """Function to extract version to use for sorting""" + m = re.search(r".*/vmlinu.-([\d.-]+)-.*", fname) + if not m: + raise ValueError(f"Cannot extract version from '{fname}'") + return version.parse(m.group(1)) + + +def get_kernel_list(target, full_initrd_path=True): + """yields [kernel filename, initrd path, version] for each kernel in target + + For example: + ('vmlinuz-6.8.0-48-generic', '/boot/initrd.img-6.8.0-48-generic', + '6.8.0-48-generic') + + If full_initrd_path is False, then only the basename of initrd is returned + """ + root_path = target_path(target) + boot = target_path(root_path, 'boot') + + for kernel in sorted(glob.glob(boot + '/vmlinu*-*'), key=kernel_parse, + reverse=True): + kfile = os.path.basename(kernel) + + # handle vmlinux or vmlinuz + kprefix = kfile.split('-')[0] + vers = kfile.replace(kprefix + '-', '') + initrd = kernel.replace(kprefix, 'initrd.img') + if not full_initrd_path: + initrd = os.path.basename(initrd) + yield kfile, initrd, vers + + # vi: ts=4 expandtab syntax=python diff --git a/doc/topics/config.rst b/doc/topics/config.rst index 4fbcc5a..fde668b 100644 --- a/doc/topics/config.rst +++ b/doc/topics/config.rst @@ -10,10 +10,14 @@ Configuration options --------------------- Curtin's top level config keys are as follows: +.. contents:: + :depth: 1 + :local: - apt_mirrors (``apt_mirrors``) - apt_proxy (``apt_proxy``) - block-meta (``block``) +- boot (``boot``) - curthooks (``curthooks``) - debconf_selections (``debconf_selections``) - disable_overlayroot (``disable_overlayroot``) @@ -112,81 +116,65 @@ Specify the filesystem label on the boot partition. label: my-boot-partition -curthooks -~~~~~~~~~ -Configure how Curtin determines what :ref:`curthooks` to run during the installation -process. - -**mode**: *<['auto', 'builtin', 'target']>* - -The default mode is ``auto``. - -In ``auto`` mode, curtin will execute curthooks within the image if present. -For images without curthooks inside, curtin will execute its built-in hooks. - -Currently the built-in curthooks support the following OS families: - -- Ubuntu -- Centos - -When specifying ``builtin``, curtin will only run the curthooks present in -Curtin ignoring any curthooks that may be present in the target operating -system. - -When specifying ``target``, curtin will attempt run the curthooks in the target -operating system. If the target does NOT contain any curthooks, then the -built-in curthooks will be run instead. - -Any errors during execution of curthooks (built-in or target) will fail the -installation. - -**Example**:: +boot +~~~~ - # ignore any target curthooks - curthooks: - mode: builtin +Configures which bootloader(s) Curtin installs and some associated options. +This is a list, which can contain up to two options: `grub` and `extlinux`. - # Only run target curthooks, fall back to built-in - curthooks: - mode: target +Two bootloaders are available: +- `GRUB <https://www.gnu.org/software/grub/>`_ (GRand Unified Bootloader) + installs itself on one or more block devices and takes care of booting. + Typically grub is built as an EFI application. Curtin controls aspects of + grub's configuration-file (/boot/grub/grub.cfg) which tells grub which OS + options to present to the user. -debconf_selections -~~~~~~~~~~~~~~~~~~ -Curtin will update the target with debconf set-selection values. Users will -need to be familiar with the package debconf options. Users can probe a -packages' debconf settings by using ``debconf-get-selections``. +- `extlinux <https://wiki.syslinux.org/wiki/index.php?title=EXTLINUX>`_ + is really just a file format, similar to a grub configuration-file but much + less flexible. It specifies which OS options to present to the user. -**selection_name**: *<debconf-set-selections input>* +One or both can be installed. -``debconf-set-selections`` is in the form:: +The following properties are used for both bootloaders: - <packagename> <packagename/option-name> <type> <value> +**bootloaders**: *<list of bootloaders>* -**Example**:: +Selects the bootloaders to use. Valid options are "grub" and "extlinux". - debconf_selections: - set1: | - cloud-init cloud-init/datasources multiselect MAAS - lxd lxd/bridge-name string lxdbr0 - set2: lxd lxd/setup-bridge boolean true +**replace_linux_default**: *<boolean: default True>* +Controls whether grub-install will update the Linux Default target +value during installation. +**terminal**: *<['unmodified', 'console', ...]>* -disable_overlayroot -~~~~~~~~~~~~~~~~~~~ -Curtin disables overlayroot in the target by default. +For grub, this configures the target-system grub-option GRUB_TERMINAL +``terminal`` value which is written to +/etc/default/grub.d/50-curtin-settings.cfg. Curtin does not attempt to validate +this string, grub2 has many values that it accepts and the list is platform +dependent. -**disable_overlayroot**: *<boolean: default True>* +For extlinux, this puts the console string in an APPEND line for each OS. -**Example**:: +If ``terminal`` is not provided, Curtin will set the value to 'console'. If the +``terminal`` value is 'unmodified' then Curtin will not set any value at all and +will use Grub defaults. - disable_overlayroot: False +extlinux +"""""""" +Curtin can add an ``extlinux.conf`` file to a filesystem. This contains a list +of possible kernels, etc. similar to grub. This is somewhat more flexible on +ARM/RISC-V since it can use `FIT <https://fitspec.osfw.foundation/>`_ and deal +with devicetree, verified boot, etc. automatically. It also avoids specifying +which bootloader must be used, since extlinux is supported by U-Boot, for +example. grub -~~~~ -Curtin configures grub as the target machine's boot loader. Users +"""" + +Curtin can configure grub as the target machine's grub boot loader. Users can control a few options to tailor how the system will boot after installation. @@ -194,11 +182,6 @@ installation. Specify a list of devices onto which grub will attempt to install. -**replace_linux_default**: *<boolean: default True>* - -Controls whether grub-install will update the Linux Default target -value during installation. - **update_nvram**: *<boolean: default True>* Certain platforms, like ``uefi`` and ``prep`` systems utilize @@ -217,16 +200,6 @@ When False, curtin writes "GRUB_DISABLE_OS_PROBER=true" to target system in /etc/default/grub.d/50-curtin-settings.cfg. If True, curtin won't modify the grub configuration value in the target system. -**terminal**: *<['unmodified', 'console', ...]>* - -Configure target system grub option GRUB_TERMINAL ``terminal`` value -which is written to /etc/default/grub.d/50-curtin-settings.cfg. Curtin -does not attempt to validate this string, grub2 has many values that -it accepts and the list is platform dependent. If ``terminal`` is -not provided, Curtin will set the value to 'console'. If the ``terminal`` -value is 'unmodified' then Curtin will not set any value at all and will -use Grub defaults. - **reorder_uefi**: *<boolean: default True>* Curtin is typically used with MAAS where the systems are configured to boot @@ -266,7 +239,9 @@ This setting is ignored if *update_nvram* is False. **Example**:: - grub: + boot: + bootloaders: + - grub install_devices: - /dev/sda1 replace_linux_default: False @@ -276,38 +251,138 @@ This setting is ignored if *update_nvram* is False. **Default terminal value, GRUB_TERMINAL=console**:: - grub: + boot: + bootloaders: + - grub install_devices: - /dev/sda1 **Don't set GRUB_TERMINAL in target**:: - grub: + boot: + bootloaders: + - grub install_devices: - /dev/sda1 terminal: unmodified **Allow grub to probe for additional OSes**:: - grub: - install_devices: - - /dev/sda1 + boot: + bootloaders: + - grub + install_devices: + - /dev/sda1 probe_additional_os: True **Avoid writting any settings to etc/default/grub.d/50-curtin-settings.cfg**:: - grub: - install_devices: - - /dev/sda1 + boot: + bootloaders: + - grub + install_devices: + - /dev/sda1 probe_additional_os: True terminal: unmodified **Enable Fallback UEFI Reordering**:: - grub: + boot: + bootloaders: + - grub reorder_uefi: true reorder_uefi_force_fallback: true +extlinux +"""""""" + +There are no options specific to extlinux. + +**Example**:: + + boot: + bootloaders: + - grub + - extlinux + install_devices: + - /dev/sda1 + +curthooks +~~~~~~~~~ +Configure how Curtin determines what :ref:`curthooks` to run during the installation +process. + +**mode**: *<['auto', 'builtin', 'target']>* + +The default mode is ``auto``. + +In ``auto`` mode, curtin will execute curthooks within the image if present. +For images without curthooks inside, curtin will execute its built-in hooks. + +Currently the built-in curthooks support the following OS families: + +- Ubuntu +- Centos + +When specifying ``builtin``, curtin will only run the curthooks present in +Curtin ignoring any curthooks that may be present in the target operating +system. + +When specifying ``target``, curtin will attempt run the curthooks in the target +operating system. If the target does NOT contain any curthooks, then the +built-in curthooks will be run instead. + +Any errors during execution of curthooks (built-in or target) will fail the +installation. + +**Example**:: + + # ignore any target curthooks + curthooks: + mode: builtin + + # Only run target curthooks, fall back to built-in + curthooks: + mode: target + + +debconf_selections +~~~~~~~~~~~~~~~~~~ +Curtin will update the target with debconf set-selection values. Users will +need to be familiar with the package debconf options. Users can probe a +packages' debconf settings by using ``debconf-get-selections``. + +**selection_name**: *<debconf-set-selections input>* + +``debconf-set-selections`` is in the form:: + + <packagename> <packagename/option-name> <type> <value> + +**Example**:: + + debconf_selections: + set1: | + cloud-init cloud-init/datasources multiselect MAAS + lxd lxd/bridge-name string lxdbr0 + set2: lxd lxd/setup-bridge boolean true + + + +disable_overlayroot +~~~~~~~~~~~~~~~~~~~ +Curtin disables overlayroot in the target by default. + +**disable_overlayroot**: *<boolean: default True>* + +**Example**:: + + disable_overlayroot: False + +grub +~~~~ + +This is an alias for **boot** with *bootloader* set to "grub". It is provided +to maintain backwards compatibility http_proxy ~~~~~~~~~~ diff --git a/doc/topics/storage.rst b/doc/topics/storage.rst index f3d465f..5503d73 100644 --- a/doc/topics/storage.rst +++ b/doc/topics/storage.rst @@ -1170,6 +1170,7 @@ keystore system. When **encryption_style** is not null, **keyfile** is required. This works as follows: + * A LUKS device is created as a ZFS dataset in the ZPool. * The supplied passphrase (see **keyfile**) is used to encrypt the LUKS device. * The real key for the ZFS dataset is contained in the LUKS device as a simple diff --git a/examples/tests/no-grub-file.yaml b/examples/tests/no-grub-file.yaml index d5ba698..240dbb9 100644 --- a/examples/tests/no-grub-file.yaml +++ b/examples/tests/no-grub-file.yaml @@ -4,6 +4,7 @@ placeholder_simple_install: unused # configure curtin so it does not emit a grub config file # in etc/default/grub/grub.d -grub: +boot: + bootloaders: ['grub'] probe_additional_os: true terminal: unmodified diff --git a/tests/unittests/test_commands_install_extlinux.py b/tests/unittests/test_commands_install_extlinux.py new file mode 100644 index 0000000..3e310eb --- /dev/null +++ b/tests/unittests/test_commands_install_extlinux.py @@ -0,0 +1,133 @@ +# This file is part of curtin. See LICENSE file for copyright and license info. + +import os +from pathlib import Path +import tempfile + +from .helpers import CiTestCase + +from curtin import config +from curtin import paths +from curtin.commands import install_extlinux + + +USE_EXTLINUX = ['extlinux'] + +EXPECT_HDR = '''\ +## /boot/extlinux/extlinux.conf +## +## IMPORTANT WARNING +## +## The configuration of this file is generated automatically. +## Do not edit this file manually, use: u-boot-update + +default l0 +menu title U-Boot menu +prompt 0 +timeout 50 +''' + +EXPECT_BODY = ''' + +label l0 +\tmenu label Ubuntu 22.04.5 LTS 6.8.0-48-generic +\tlinux /vmlinuz-6.8.0-48-generic +\tinitrd /initrd.img-6.8.0-48-generic +\tappend root=/dev/mapper/vgubuntu-root ro quiet + +label l0r +\tmenu label Ubuntu 22.04.5 LTS 6.8.0-48-generic (rescue target) +\tlinux /vmlinuz-6.8.0-48-generic +\tinitrd /initrd.img-6.8.0-48-generic +\tappend root=/dev/mapper/vgubuntu-root ro single + + +label l1 +\tmenu label Ubuntu 22.04.5 LTS 6.8.0-40-generic +\tlinux /vmlinuz-6.8.0-40-generic +\tinitrd /initrd.img-6.8.0-40-generic +\tappend root=/dev/mapper/vgubuntu-root ro quiet + +label l1r +\tmenu label Ubuntu 22.04.5 LTS 6.8.0-40-generic (rescue target) +\tlinux /vmlinuz-6.8.0-40-generic +\tinitrd /initrd.img-6.8.0-40-generic +\tappend root=/dev/mapper/vgubuntu-root ro single + + +label l2 +\tmenu label Ubuntu 22.04.5 LTS 5.15.0-127-generic +\tlinux /vmlinuz-5.15.0-127-generic +\tinitrd /initrd.img-5.15.0-127-generic +\tappend root=/dev/mapper/vgubuntu-root ro quiet + +label l2r +\tmenu label Ubuntu 22.04.5 LTS 5.15.0-127-generic (rescue target) +\tlinux /vmlinuz-5.15.0-127-generic +\tinitrd /initrd.img-5.15.0-127-generic +\tappend root=/dev/mapper/vgubuntu-root ro single +''' + + +class TestInstallExtlinux(CiTestCase): + def setUp(self): + self.tmpdir = tempfile.TemporaryDirectory(suffix='-curtin') + self.target = self.tmpdir.name + + versions = ['6.8.0-40', '5.15.0-127', '6.8.0-48'] + boot = os.path.join(self.target, 'boot') + Path(f'{boot}').mkdir() + os.system(f'ls {boot}') + for ver in versions: + Path(f'{boot}/config-{ver}-generic').touch() + Path(f'{boot}/initrd.img-{ver}-generic').touch() + Path(f'{boot}/vmlinuz-{ver}-generic').touch() + + Path(f'{self.target}/empty-dir').mkdir() + self.maxDiff = None + + def test_get_kernel_list(self): + iter = paths.get_kernel_list(self.target, full_initrd_path=False) + self.assertEqual( + ('vmlinuz-6.8.0-48-generic', 'initrd.img-6.8.0-48-generic', + '6.8.0-48-generic'), + next(iter)) + self.assertEqual( + ('vmlinuz-6.8.0-40-generic', 'initrd.img-6.8.0-40-generic', + '6.8.0-40-generic'), + next(iter)) + self.assertEqual( + ('vmlinuz-5.15.0-127-generic', 'initrd.img-5.15.0-127-generic', + '5.15.0-127-generic'), + next(iter)) + try: + val = next(iter) + raise ValueError(f'Extra value {val}') + except StopIteration: + pass + + def test_empty(self): + out = install_extlinux.build_content(f'{self.target}/empty-dir', + config.BootCfg(USE_EXTLINUX)) + self.assertEqual(out, EXPECT_HDR) + + def test_normal(self): + out = install_extlinux.build_content(self.target, + config.BootCfg(USE_EXTLINUX)) + self.assertEqual(EXPECT_HDR + EXPECT_BODY, out) + + def test_no_recovery(self): + out = install_extlinux.build_content(self.target, + config.BootCfg(USE_EXTLINUX)) + self.assertEqual(EXPECT_HDR + EXPECT_BODY, out) + + def test_install(self): + install_extlinux.install_extlinux(self.target, + config.BootCfg(USE_EXTLINUX)) + extlinux_path = self.target + '/boot/extlinux' + self.assertTrue(os.path.exists(extlinux_path)) + extlinux_file = extlinux_path + '/extlinux.conf' + self.assertTrue(os.path.exists(extlinux_file)) + + +# vi: ts=4 expandtab syntax=python diff --git a/tests/unittests/test_commands_install_grub.py b/tests/unittests/test_commands_install_grub.py index 7b7b547..0adeb89 100644 --- a/tests/unittests/test_commands_install_grub.py +++ b/tests/unittests/test_commands_install_grub.py @@ -10,6 +10,8 @@ from .helpers import CiTestCase from unittest import mock import os +USE_GRUB = ['grub'] + class TestGetGrubPackageName(CiTestCase): @@ -204,7 +206,7 @@ class TestGetGrubPackageName(CiTestCase): osfamily=osfamily) -class TestGetGrubConfigFile(CiTestCase): +class TestGetBootCfgFile(CiTestCase): @mock.patch('curtin.commands.install_grub.distro.os_release') def test_grub_config_redhat(self, mock_os_release): @@ -435,10 +437,10 @@ class TestReplaceGrubCmdlineLinuxDefault(CiTestCase): self.assertEqual(expected, found) -class TestWriteGrubConfig(CiTestCase): +class TestWriteBootCfg(CiTestCase): def setUp(self): - super(TestWriteGrubConfig, self).setUp() + super(TestWriteBootCfg, self).setUp() self.target = self.tmp_dir() self.grubdefault = "/etc/default/grub" self.grubconf = "/etc/default/grub.d/50-curtin.cfg" @@ -457,7 +459,7 @@ class TestWriteGrubConfig(CiTestCase): self.assertEqual(expected, found) def test_write_grub_config_defaults(self): - grubcfg = config.GrubConfig() + bootcfg = config.BootCfg(USE_GRUB) new_params = ['foo=bar', 'wark=1'] expected_default = "\n".join([ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', '']) @@ -469,12 +471,12 @@ class TestWriteGrubConfig(CiTestCase): 'GRUB_TERMINAL="console"']) install_grub.write_grub_config( - self.target, grubcfg, self.grubconf, new_params) + self.target, bootcfg, self.grubconf, new_params) self._verify_expected(expected_default, expected_curtin) def test_write_grub_config_no_replace(self): - grubcfg = config.GrubConfig(replace_linux_default=False) + bootcfg = config.BootCfg(USE_GRUB, replace_linux_default=False) new_params = ['foo=bar', 'wark=1'] expected_default = "\n".join([]) expected_curtin = "\n".join([ @@ -485,12 +487,12 @@ class TestWriteGrubConfig(CiTestCase): 'GRUB_TERMINAL="console"']) install_grub.write_grub_config( - self.target, grubcfg, self.grubconf, new_params) + self.target, bootcfg, self.grubconf, new_params) self._verify_expected(expected_default, expected_curtin) def test_write_grub_config_disable_probe(self): - grubcfg = config.GrubConfig(probe_additional_os=False) + bootcfg = config.BootCfg(USE_GRUB, probe_additional_os=False) new_params = ['foo=bar', 'wark=1'] expected_default = "\n".join([ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', '']) @@ -502,12 +504,12 @@ class TestWriteGrubConfig(CiTestCase): 'GRUB_TERMINAL="console"']) install_grub.write_grub_config( - self.target, grubcfg, self.grubconf, new_params) + self.target, bootcfg, self.grubconf, new_params) self._verify_expected(expected_default, expected_curtin) def test_write_grub_config_enable_probe(self): - grubcfg = config.GrubConfig(probe_additional_os=True) + bootcfg = config.BootCfg(USE_GRUB, probe_additional_os=True) new_params = ['foo=bar', 'wark=1'] expected_default = "\n".join([ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', '']) @@ -516,23 +518,23 @@ class TestWriteGrubConfig(CiTestCase): 'GRUB_TERMINAL="console"']) install_grub.write_grub_config( - self.target, grubcfg, self.grubconf, new_params) + self.target, bootcfg, self.grubconf, new_params) self._verify_expected(expected_default, expected_curtin) def test_write_grub_config_no_grub_settings_file(self): - grubcfg = config.GrubConfig( + bootcfg = config.BootCfg( + USE_GRUB, probe_additional_os=True, terminal='unmodified') new_params = [] install_grub.write_grub_config( - self.target, grubcfg, self.grubconf, new_params) + self.target, bootcfg, self.grubconf, new_params) self.assertTrue(os.path.exists(self.target_grubdefault)) self.assertFalse(os.path.exists(self.target_grubconf)) def test_write_grub_config_specify_terminal(self): - grubcfg = config.GrubConfig( - terminal='serial') + bootcfg = config.BootCfg(USE_GRUB, terminal='serial') new_params = ['foo=bar', 'wark=1'] expected_default = "\n".join([ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', '']) @@ -544,12 +546,12 @@ class TestWriteGrubConfig(CiTestCase): 'GRUB_TERMINAL="serial"']) install_grub.write_grub_config( - self.target, grubcfg, self.grubconf, new_params) + self.target, bootcfg, self.grubconf, new_params) self._verify_expected(expected_default, expected_curtin) def test_write_grub_config_terminal_unmodified(self): - grubcfg = config.GrubConfig(terminal='unmodified') + bootcfg = config.BootCfg(USE_GRUB, terminal='unmodified') new_params = ['foo=bar', 'wark=1'] expected_default = "\n".join([ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', '']) @@ -559,7 +561,7 @@ class TestWriteGrubConfig(CiTestCase): 'GRUB_DISABLE_OS_PROBER="true"', '']) install_grub.write_grub_config( - self.target, grubcfg, self.grubconf, new_params) + self.target, bootcfg, self.grubconf, new_params) self._verify_expected(expected_default, expected_curtin) @@ -1638,13 +1640,14 @@ class TestInstallGrub(CiTestCase): devices = [] with self.assertRaises(ValueError): install_grub.install_grub( - devices, self.target, uefi=False, grubcfg=config.GrubConfig()) + devices, self.target, uefi=False, + bootcfg=config.BootCfg(USE_GRUB)) def test_grub_install_raise_exception_on_no_target(self): devices = ['foobar'] with self.assertRaises(ValueError): install_grub.install_grub( - devices, None, uefi=False, grubcfg=config.GrubConfig()) + devices, None, uefi=False, bootcfg=config.BootCfg(USE_GRUB)) def test_grub_install_raise_exception_on_s390x(self): self.m_distro_get_architecture.return_value = 's390x' @@ -1652,7 +1655,8 @@ class TestInstallGrub(CiTestCase): devices = ['foobar'] with self.assertRaises(RuntimeError): install_grub.install_grub( - devices, self.target, uefi=False, grubcfg=config.GrubConfig()) + devices, self.target, uefi=False, + bootcfg=config.BootCfg(USE_GRUB)) def test_grub_install_raise_exception_on_armv7(self): self.m_distro_get_architecture.return_value = 'armhf' @@ -1660,7 +1664,8 @@ class TestInstallGrub(CiTestCase): devices = ['foobar'] with self.assertRaises(RuntimeError): install_grub.install_grub( - devices, self.target, uefi=False, grubcfg=config.GrubConfig()) + devices, self.target, uefi=False, + bootcfg=config.BootCfg(USE_GRUB)) def test_grub_install_raise_exception_on_arm64_no_uefi(self): self.m_distro_get_architecture.return_value = 'arm64' @@ -1668,12 +1673,13 @@ class TestInstallGrub(CiTestCase): devices = ['foobar'] with self.assertRaises(RuntimeError): install_grub.install_grub( - devices, self.target, uefi=False, grubcfg=config.GrubConfig()) + devices, self.target, uefi=False, + bootcfg=config.BootCfg(USE_GRUB)) def test_grub_install_ubuntu(self): devices = ['/dev/disk-a-part1'] uefi = False - grubcfg = config.GrubConfig() + bootcfg = config.BootCfg(USE_GRUB) grub_conf = self.tmp_path('grubconf') new_params = [] self.m_get_grub_package_name.return_value = ('grub-pc', 'i386-pc') @@ -1684,7 +1690,7 @@ class TestInstallGrub(CiTestCase): [['/bin/true']], [['/bin/false']]) install_grub.install_grub( - devices, self.target, uefi=uefi, grubcfg=grubcfg) + devices, self.target, uefi=uefi, bootcfg=bootcfg) self.m_distro_get_distroinfo.assert_called_with(target=self.target) self.m_distro_get_architecture.assert_called_with(target=self.target) @@ -1695,7 +1701,7 @@ class TestInstallGrub(CiTestCase): self.distroinfo.family) self.m_get_carryover_params.assert_called_with(self.distroinfo) self.m_prepare_grub_dir.assert_called_with(self.target, grub_conf) - self.m_write_grub_config.assert_called_with(self.target, grubcfg, + self.m_write_grub_config.assert_called_with(self.target, bootcfg, grub_conf, new_params) self.m_get_grub_install_command.assert_called_with( uefi, self.distroinfo, self.target) @@ -1712,7 +1718,7 @@ class TestInstallGrub(CiTestCase): def test_uefi_grub_install_ubuntu(self): devices = ['/dev/disk-a-part1'] uefi = True - grubcfg = config.GrubConfig(update_nvram=True) + bootcfg = config.BootCfg(USE_GRUB, update_nvram=True) grub_conf = self.tmp_path('grubconf') new_params = [] grub_name = 'grub-efi-amd64' @@ -1726,7 +1732,7 @@ class TestInstallGrub(CiTestCase): [['/bin/true']], [['/bin/false']]) install_grub.install_grub( - devices, self.target, uefi=uefi, grubcfg=grubcfg) + devices, self.target, uefi=uefi, bootcfg=bootcfg) self.m_distro_get_distroinfo.assert_called_with(target=self.target) self.m_distro_get_architecture.assert_called_with(target=self.target) @@ -1737,12 +1743,12 @@ class TestInstallGrub(CiTestCase): self.distroinfo.family) self.m_get_carryover_params.assert_called_with(self.distroinfo) self.m_prepare_grub_dir.assert_called_with(self.target, grub_conf) - self.m_write_grub_config.assert_called_with(self.target, grubcfg, + self.m_write_grub_config.assert_called_with(self.target, bootcfg, grub_conf, new_params) self.m_get_grub_install_command.assert_called_with( uefi, self.distroinfo, self.target) self.m_gen_uefi_install_commands.assert_called_with( - grub_name, grub_target, grub_cmd, grubcfg.update_nvram, + grub_name, grub_target, grub_cmd, bootcfg.update_nvram, self.distroinfo, devices, self.target) self.m_subp.assert_has_calls([ @@ -1755,7 +1761,7 @@ class TestInstallGrub(CiTestCase): def test_uefi_grub_install_ubuntu_multiple_esp(self): devices = ['/dev/disk-a-part1'] uefi = True - grubcfg = config.GrubConfig(update_nvram=True) + bootcfg = config.BootCfg(USE_GRUB, update_nvram=True) grub_conf = self.tmp_path('grubconf') new_params = [] grub_name = 'grub-efi-amd64' @@ -1769,7 +1775,7 @@ class TestInstallGrub(CiTestCase): [['/bin/true']], [['/bin/false']]) install_grub.install_grub( - devices, self.target, uefi=uefi, grubcfg=grubcfg) + devices, self.target, uefi=uefi, bootcfg=bootcfg) self.m_distro_get_distroinfo.assert_called_with(target=self.target) self.m_distro_get_architecture.assert_called_with(target=self.target) @@ -1780,12 +1786,12 @@ class TestInstallGrub(CiTestCase): self.distroinfo.family) self.m_get_carryover_params.assert_called_with(self.distroinfo) self.m_prepare_grub_dir.assert_called_with(self.target, grub_conf) - self.m_write_grub_config.assert_called_with(self.target, grubcfg, + self.m_write_grub_config.assert_called_with(self.target, bootcfg, grub_conf, new_params) self.m_get_grub_install_command.assert_called_with( uefi, self.distroinfo, self.target) self.m_gen_uefi_install_commands.assert_called_with( - grub_name, grub_target, grub_cmd, grubcfg.update_nvram, + grub_name, grub_target, grub_cmd, bootcfg.update_nvram, self.distroinfo, devices, self.target) self.m_subp.assert_has_calls([ diff --git a/tests/unittests/test_config.py b/tests/unittests/test_config.py index 1db3191..e25cdeb 100644 --- a/tests/unittests/test_config.py +++ b/tests/unittests/test_config.py @@ -226,4 +226,39 @@ class TestDeserializer(CiTestCase): deserializer.deserialize(UnionClass, {"val": None})) +class TestBootCfg(CiTestCase): + def test_empty(self): + with self.assertRaises(TypeError) as exc: + config.BootCfg() + self.assertIn("missing 1 required positional argument: 'bootloaders'", + str(exc.exception)) + + def test_not_list(self): + with self.assertRaises(ValueError) as exc: + config.BootCfg('invalid') + self.assertIn("bootloaders must be a list: invalid", str(exc.exception)) + + def test_empty_list(self): + with self.assertRaises(ValueError) as exc: + config.BootCfg([]) + self.assertIn("Empty bootloaders list:", str(exc.exception)) + + def test_duplicate(self): + with self.assertRaises(ValueError) as exc: + config.BootCfg(['grub', 'grub']) + self.assertIn("bootloaders list contains duplicates: ['grub', 'grub']", + str(exc.exception)) + + def test_invalid(self): + with self.assertRaises(ValueError) as exc: + config.BootCfg(['fred']) + self.assertIn("Unknown bootloader fred: ['fred']", str(exc.exception)) + + def test_valid(self): + config.BootCfg(['grub']) + config.BootCfg(['extlinux']) + config.BootCfg(['grub', 'extlinux']) + config.BootCfg(['extlinux', 'grub']) + + # vi: ts=4 expandtab syntax=python diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py index e76eb54..e4a8483 100644 --- a/tests/unittests/test_curthooks.py +++ b/tests/unittests/test_curthooks.py @@ -447,7 +447,7 @@ class TestUpdateInitramfs(CiTestCase): def test_fails_if_no_tool_to_update_initramfs(self): with patch("curtin.commands.curthooks.glob.glob", return_value=["/boot/vmlinuz"]): - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): curthooks.update_initramfs(self.target) with patch("curtin.commands.curthooks.glob.glob", return_value=[]): @@ -489,10 +489,10 @@ class TestUpdateInitramfs(CiTestCase): call(['update-initramfs', '-c', '-k', kversion3], target=self.target)) subp_calls += self._subp_calls( - call(['update-initramfs', '-c', '-k', self.kversion], + call(['update-initramfs', '-c', '-k', kversion2], target=self.target)) subp_calls += self._subp_calls( - call(['update-initramfs', '-c', '-k', kversion2], + call(['update-initramfs', '-c', '-k', self.kversion], target=self.target)) self.mock_subp.assert_has_calls(subp_calls) self.assertEqual(24, self.mock_subp.call_count) @@ -513,10 +513,10 @@ class TestUpdateInitramfs(CiTestCase): subp_calls = self._subp_calls( call(['dpkg-divert', '--list'], capture=True, target=self.target)) subp_calls += self._subp_calls( - call(['update-initramfs', '-u', '-k', kversion2], + call(['update-initramfs', '-c', '-k', self.kversion], target=self.target)) subp_calls += self._subp_calls( - call(['update-initramfs', '-c', '-k', self.kversion], + call(['update-initramfs', '-u', '-k', kversion2], target=self.target)) self.mock_subp.assert_has_calls(subp_calls) self.assertEqual(18, self.mock_subp.call_count) @@ -845,15 +845,18 @@ class TestSetupGrub(CiTestCase): cfg = { 'grub_install_devices': ['/dev/vdb'] } - curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family, - variant=self.variant) + curthooks.setup_boot(cfg, self.target, machine='amd64', + stack_prefix='stack_prefix', + osfamily=self.distro_family, variant=self.variant) self.m_install_grub.assert_called_with( ['/dev/vdb'], self.target, uefi=False, - grubcfg=config.GrubConfig(install_devices=['/dev/vdb'])) + bootcfg=config.BootCfg(bootloaders=['grub'], + install_devices=['/dev/vdb'])) - def test_uses_install_devices_in_grubcfg(self): + def test_uses_install_devices_in_bootcfg(self): cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], }, } @@ -862,13 +865,55 @@ class TestSetupGrub(CiTestCase): osfamily=self.distro_family, variant=self.variant) self.m_install_grub.assert_called_with( ['/dev/vdb'], self.target, uefi=False, - grubcfg=config.fromdict(config.GrubConfig, cfg.get('grub'))) + bootcfg=config.fromdict(config.BootCfg, cfg.get('boot'))) + + def test_uses_old_schema_install_devices_in_grubcfg(self): + cfg = { + 'grub': { + 'install_devices': ['/dev/vdb'], + }, + } + curthooks.setup_boot( + cfg, self.target, machine='amd64', stack_prefix='stack_prefix', + osfamily=self.distro_family, variant=self.variant) + self.m_install_grub.assert_called_with( + ['/dev/vdb'], self.target, uefi=False, + bootcfg=config.fromdict(config.BootCfg, cfg.get('boot'))) + + def test_calls_install_grub(self): + cfg = { + 'boot': { + 'bootloaders': ['grub'], + 'install_devices': ['/dev/vdb'], + }, + } + curthooks.setup_boot( + cfg, self.target, 'amd64', '/testing', + osfamily=self.distro_family, variant=self.variant) + self.m_install_grub.assert_called_with( + ['/dev/vdb'], self.target, uefi=False, + bootcfg=config.fromdict(config.BootCfg, cfg.get('boot'))) + + def test_skips_install_grub(self): + cfg = { + 'boot': { + 'bootloaders': ['grub'], + 'install_devices': ['/dev/vdb'], + }, + } + curthooks.setup_boot( + cfg, self.target, 'aarch64', '/testing', + osfamily=self.distro_family, variant=self.variant) + self.m_install_grub.assert_not_called() @patch('curtin.commands.block_meta.multipath') @patch('curtin.commands.curthooks.os.path.exists') def test_uses_grub_install_on_storage_config(self, m_exists, m_multipath): m_multipath.is_mpath_member.return_value = False cfg = { + 'boot': { + 'bootloaders': ['grub'], + }, 'storage': { 'version': 1, 'config': [ @@ -886,7 +931,8 @@ class TestSetupGrub(CiTestCase): variant=self.variant) self.m_install_grub.assert_called_with( ['/dev/vdb'], self.target, uefi=False, - grubcfg=config.GrubConfig(install_devices=['/dev/vdb'])) + bootcfg=config.BootCfg(bootloaders=['grub'], + install_devices=['/dev/vdb'])) @patch('curtin.commands.block_meta.multipath') @patch('curtin.block.is_valid_device') @@ -927,7 +973,8 @@ class TestSetupGrub(CiTestCase): }, ] }, - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'update_nvram': False, }, } @@ -937,13 +984,15 @@ class TestSetupGrub(CiTestCase): variant='centos') self.m_install_grub.assert_called_with( ['/dev/vdb1'], self.target, uefi=True, - grubcfg=config.GrubConfig( + bootcfg=config.BootCfg( + bootloaders=['grub'], update_nvram=False, install_devices=['/dev/vdb1'])) def test_grub_install_installs_to_none_if_install_devices_None(self): cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': None, }, } @@ -951,7 +1000,7 @@ class TestSetupGrub(CiTestCase): variant=self.variant) self.m_install_grub.assert_called_with( ['none'], self.target, uefi=False, - grubcfg=config.GrubConfig(install_devices=None), + bootcfg=config.BootCfg(bootloaders=['grub'], install_devices=None), ) @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) @@ -961,7 +1010,8 @@ class TestSetupGrub(CiTestCase): self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr') self.mock_is_uefi_bootable.return_value = True cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], 'update_nvram': True, 'remove_old_uefi_loaders': False, @@ -982,7 +1032,7 @@ class TestSetupGrub(CiTestCase): variant=self.variant) self.m_install_grub.assert_called_with( ['/dev/vdb'], self.target, uefi=True, - grubcfg=config.fromdict(config.GrubConfig, cfg.get('grub')) + bootcfg=config.fromdict(config.BootCfg, cfg.get('boot')) ) @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) @@ -992,7 +1042,8 @@ class TestSetupGrub(CiTestCase): self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr') self.mock_is_uefi_bootable.return_value = True cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], 'update_nvram': True, 'remove_old_uefi_loaders': True, @@ -1034,7 +1085,8 @@ class TestSetupGrub(CiTestCase): self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr') self.mock_is_uefi_bootable.return_value = True cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], 'update_nvram': True, 'remove_old_uefi_loaders': False, @@ -1070,7 +1122,8 @@ class TestSetupGrub(CiTestCase): self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr') self.mock_is_uefi_bootable.return_value = True cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], 'update_nvram': True, 'remove_old_uefi_loaders': False, @@ -1116,7 +1169,8 @@ class TestSetupGrub(CiTestCase): 'mock_remove_old_loaders') self.mock_is_uefi_bootable.return_value = True cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], 'update_nvram': True, 'remove_old_uefi_loaders': False, @@ -1168,7 +1222,8 @@ class TestSetupGrub(CiTestCase): self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr') self.mock_is_uefi_bootable.return_value = True cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], 'update_nvram': True, 'remove_old_uefi_loaders': True, @@ -1223,7 +1278,8 @@ class TestSetupGrub(CiTestCase): self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr') self.mock_is_uefi_bootable.return_value = True cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], 'update_nvram': True, 'remove_old_uefi_loaders': True, @@ -1311,8 +1367,8 @@ class TestUefiRemoveDuplicateEntries(CiTestCase): @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) def test_uefi_remove_duplicate_entries(self): - grubcfg = config.GrubConfig() - curthooks.uefi_remove_duplicate_entries(grubcfg, self.target) + bootcfg = config.BootCfg(['grub']) + curthooks.uefi_remove_duplicate_entries(bootcfg, self.target) self.assertEqual([ call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'], target=self.target), @@ -1322,11 +1378,11 @@ class TestUefiRemoveDuplicateEntries(CiTestCase): @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) def test_uefi_remove_duplicate_entries_no_bootcurrent(self): - grubcfg = config.GrubConfig() + bootcfg = config.BootCfg(['grub']) efiout = copy_efi_state(self.efibootmgr_output) efiout.current = '' self.m_efibootmgr.return_value = efiout - curthooks.uefi_remove_duplicate_entries(grubcfg, self.target) + curthooks.uefi_remove_duplicate_entries(bootcfg, self.target) self.assertEqual([ call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'], target=self.target), @@ -1336,19 +1392,17 @@ class TestUefiRemoveDuplicateEntries(CiTestCase): @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) def test_uefi_remove_duplicate_entries_disabled(self): - grubcfg = config.GrubConfig( - remove_duplicate_entries=False, - ) - curthooks.uefi_remove_duplicate_entries(grubcfg, self.target) + bootcfg = config.BootCfg(['grub'], remove_duplicate_entries=False) + curthooks.uefi_remove_duplicate_entries(bootcfg, self.target) self.assertEqual([], self.m_subp.call_args_list) @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) def test_uefi_remove_duplicate_entries_skip_bootcurrent(self): - grubcfg = config.GrubConfig() + bootcfg = config.BootCfg(['grub']) efiout = copy_efi_state(self.efibootmgr_output) efiout.current = '0003' self.m_efibootmgr.return_value = efiout - curthooks.uefi_remove_duplicate_entries(grubcfg, self.target) + curthooks.uefi_remove_duplicate_entries(bootcfg, self.target) self.assertEqual([ call(['efibootmgr', '--bootnum=0000', '--delete-bootnum'], target=self.target), @@ -1358,7 +1412,7 @@ class TestUefiRemoveDuplicateEntries(CiTestCase): @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) def test_uefi_remove_duplicate_entries_no_change(self): - grubcfg = config.GrubConfig() + bootcfg = config.BootCfg(['grub']) self.m_efibootmgr.return_value = util.EFIBootState( order=[], timeout='', @@ -1377,7 +1431,7 @@ class TestUefiRemoveDuplicateEntries(CiTestCase): path='HD(1,GPT)/File(\\EFI\\sles\\shimx64.efi)', ), }) - curthooks.uefi_remove_duplicate_entries(grubcfg, self.target) + curthooks.uefi_remove_duplicate_entries(bootcfg, self.target) self.assertEqual([], self.m_subp.call_args_list)
-- Mailing list: https://launchpad.net/~curtin-dev Post to : curtin-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~curtin-dev More help : https://help.launchpad.net/ListHelp