curtin-dev team mailing list archive
-
curtin-dev team
-
Mailing list archive
-
Message #03823
[Merge] ~sjg1/curtin:boot2 into curtin:master
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)
Follow ups