Compare commits

...

1 Commits

Author SHA1 Message Date
f5799d404d Backport zipl bootloader support from upstream kiwi v10
Port standalone zipl bootloader support for s390x from the main branch
(kiwi v10) to the el8 branch (kiwi v9), enabling direct zipl boot
without the grub2_s390x_emu wrapper. This includes BLS entry generation,
secure execution support (genprotimg/pvattest), GPT partition table
support for s390, and the corresponding XML schema extensions.

New files:
- kiwi/bootloader/config/zipl.py (BootLoaderZipl config class)
- kiwi/bootloader/install/zipl.py (BootLoaderInstallZipl stub)
- kiwi/bootloader/template/zipl.py (zipl.conf/BLS templates)
- kiwi/utils/os_release.py (OsRelease parser for BLS entry names)

Modified:
- Register zipl in bootloader config/install factories
- Update bootloader_spec_base with entries_dir, firmware, cmdline
  accumulation, and get_entry_name() for BLS support
- Condition /boot/zipl mount paths on grub2_s390x_emu (not all s390)
- Add GPT target type for s390 in firmware.py
- Add BLS loader entries dir to defaults.py
- Add securelinux/hkd schema elements and zipl bootloader name to
  kiwi.rnc/kiwi.rng
- Add get_host_key_certificates() to xml_state.py
- Add KiwiOSReleaseImportError to exceptions.py

Test fixes:
- Fix test_mount_system_s390 for new grub2_s390x_emu conditional
- Fix systemd_boot_test setup to properly mock FirmWare dependencies
- Fix clone_device_test missing setup_method bridge
2026-03-13 00:53:55 +01:00
22 changed files with 1263 additions and 21 deletions

3
.gitignore vendored
View File

@ -90,3 +90,6 @@ target/
# Rust
Cargo.lock
vendor/
*.rpm
kiwi-descriptions

View File

@ -49,7 +49,8 @@ class BootLoaderConfig(metaclass=ABCMeta):
'grub2': {'grub2': 'BootLoaderConfigGrub2'},
'grub2_s390x_emu': {'grub2': 'BootLoaderConfigGrub2'},
'isolinux': {'isolinux': 'BootLoaderConfigIsoLinux'},
'systemd_boot': {'systemd_boot': 'BootLoaderSystemdBoot'}
'systemd_boot': {'systemd_boot': 'BootLoaderSystemdBoot'},
'zipl': {'zipl': 'BootLoaderZipl'}
}
try:
(bootloader_namespace, bootloader_name) = \

View File

@ -46,6 +46,7 @@ class BootLoaderConfigBase:
self.root_dir = root_dir
self.boot_dir = boot_dir or root_dir
self.xml_state = xml_state
self.bootloader = xml_state.get_build_type_bootloader_name()
self.arch = Defaults.get_platform_name()
self.volumes_mount = []
@ -380,10 +381,13 @@ class BootLoaderConfigBase:
disk_setup = DiskSetup(self.xml_state, self.boot_dir)
need_boot_partition = disk_setup.need_boot_partition()
if need_boot_partition:
# if an extra boot partition is used we will find the
# data directly in the root of this partition and not
# below the boot/ directory
bootpath = '/'
if self.bootloader != 'zipl':
# if an extra boot partition is used we will find the
# data directly in the root of this partition and not
# below the boot/ directory. An exception to this case
# is the zipl bootloader which finds its target
# according to the mount path.
bootpath = '/'
if target == 'disk':
if not need_boot_partition:
@ -507,7 +511,7 @@ class BootLoaderConfigBase:
self.root_mount = MountManager(
device=root_device
)
if 's390' in self.arch:
if 's390' in self.arch and self.bootloader == 'grub2_s390x_emu':
self.boot_mount = MountManager(
device=boot_device,
mountpoint=self.root_mount.mountpoint + '/boot/zipl'

View File

@ -15,12 +15,18 @@
# You should have received a copy of the GNU General Public License
# along with kiwi. If not, see <http://www.gnu.org/licenses/>
#
import os
import glob
from typing import (
Dict, NamedTuple
)
# project
from kiwi.firmware import FirmWare
from kiwi.bootloader.config.base import BootLoaderConfigBase
from kiwi.defaults import Defaults
from kiwi.utils.os_release import OsRelease
from kiwi.exceptions import KiwiKernelLookupError
target_type = NamedTuple(
'target_type', [
@ -56,11 +62,15 @@ class BootLoaderSpecBase(BootLoaderConfigBase):
Store custom arguments in an instance dict and initialize
target identifiers
"""
self.entries_dir = Defaults.get_bls_loader_entries_dir()
self.target = target_type(
disk='disk', install='install(iso)', live='live(iso)'
)
self.custom_args = custom_args
self.timeout = self.get_boot_timeout_seconds()
self.disk_type = self.xml_state.get_build_type_bootloader_targettype()
self.disk_blocksize = self.xml_state.build_type.get_target_blocksize()
self.firmware = FirmWare(self.xml_state)
self.cmdline = ''
def write(self) -> None:
@ -77,6 +87,19 @@ class BootLoaderSpecBase(BootLoaderConfigBase):
"""
pass
def write_meta_data(
self, root_device=None, write_device=None, boot_options=''
):
"""
For bootloaders following the bootloader spec take over
the cmdline options and store them for later use
:param string root_device: unused
:param string write_device: unused
:param string boot_options: kernel options as string
"""
self.cmdline = f'{self.cmdline} {boot_options}'.strip()
def setup_disk_image_config(
self, boot_uuid: str = '', root_uuid: str = '', hypervisor: str = '',
kernel: str = '', initrd: str = '', boot_options: Dict = {}
@ -99,7 +122,7 @@ class BootLoaderSpecBase(BootLoaderConfigBase):
self.custom_args['kernel'] = kernel
self.custom_args['initrd'] = initrd
self.custom_args['boot_options'] = boot_options
self.cmdline = ' '.join(
plus_cmdline = ' '.join(
[
self.get_boot_cmdline(
boot_options.get('root_device'),
@ -107,6 +130,7 @@ class BootLoaderSpecBase(BootLoaderConfigBase):
)
]
)
self.cmdline = f'{self.cmdline} {plus_cmdline}'.strip()
self.setup_loader(self.target.disk)
def setup_install_image_config(
@ -142,12 +166,13 @@ class BootLoaderSpecBase(BootLoaderConfigBase):
pass
def setup_disk_boot_images(
self, boot_uuid: str, lookup_path: str = ''
self, boot_uuid: str, efi_uuid: str = '', lookup_path: str = ''
) -> None:
"""
Create bootloader image(s) for disk boot
:param string mbrid: unused
:param string boot_uuid: unused
:param string efi_uuid: unused
:param str lookup_path: unused
Targeted to bootloader spec interface
@ -218,3 +243,30 @@ class BootLoaderSpecBase(BootLoaderConfigBase):
Implementation in specialized loader class
"""
raise NotImplementedError
def get_entry_name(self) -> str:
"""
Construct entry filename of the form
[OS_release_ID]-[kernel-version].conf or use the
naming policy of an existing entries file
:return: file basename
:rtype: str
"""
os_release = OsRelease(self.root_dir)
try:
kernel_name = os.path.basename(
list(glob.iglob(f'{self.root_dir}/lib/modules/*'))[0]
)
except Exception as issue:
raise KiwiKernelLookupError(
'Kernel lookup in {0}/lib/modules failed with: {1}'.format(
self.root_dir, issue
)
)
entry_filename = f'{os_release.get("ID")}-{kernel_name}.conf'
for dist_entry in glob.iglob(f'{self.root_dir}/{self.entries_dir}/*'):
entry_filename = os.path.basename(dist_entry)
break
return entry_filename

View File

@ -0,0 +1,359 @@
# Copyright (c) 2024 SUSE Software Solutions Germany GmbH
#
# This file is part of kiwi.
#
# kiwi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# kiwi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with kiwi. If not, see <http://www.gnu.org/licenses/>
#
import os
import copy
import logging
import shutil
from string import Template
from typing import Dict
# project
from kiwi.path import Path
from kiwi.boot.image.base import BootImageBase
from kiwi.bootloader.template.zipl import BootLoaderTemplateZipl
from kiwi.bootloader.config.bootloader_spec_base import BootLoaderSpecBase
from kiwi.command import Command
from kiwi.utils.temporary import Temporary
from kiwi.exceptions import (
KiwiTemplateError,
KiwiBootLoaderTargetError,
KiwiDiskGeometryError
)
log = logging.getLogger('kiwi')
class BootLoaderZipl(BootLoaderSpecBase):
def create_loader_image(self, target: str) -> None:
"""
Nothing to be done here for zipl
:param str target: unused
"""
pass # pragma: nocover
def setup_loader(self, target: str) -> None:
"""
Setup temporary zipl config and install zipl for supported targets.
Please note we are not touching the main zipl.conf file provided
by the distributors
:param str target:
target identifier, one of disk, live(iso) or install(iso)
Currently only the disk identifier is supported
"""
if target != self.target.disk:
raise KiwiBootLoaderTargetError(
'zipl is only supported with the disk image target'
)
boot_path = self.get_boot_path()
boot_options = self.custom_args['boot_options']
self._mount_system(
boot_options.get('root_device'),
boot_options.get('boot_device'),
boot_options.get('efi_device'),
boot_options.get('system_volumes'),
boot_options.get('system_root_volume')
)
root_dir = self.root_mount.mountpoint
kernel_info = BootImageBase(
self.xml_state, root_dir, root_dir
).get_boot_names()
self.custom_args['secure_linux'] = False
self.custom_args['kernel'] = \
f'{boot_path}/{os.path.basename(kernel_info.kernel_filename)}'
self.custom_args['initrd'] = \
f'{boot_path}/{kernel_info.initrd_name}'
host_key_certificates = self.xml_state.get_host_key_certificates()
cc_boot_image = f'{self.custom_args["kernel"]}.cc'
if host_key_certificates:
with Temporary(
path=self.root_mount.mountpoint, prefix='kiwi_zipl_parmfile_'
).new_file() as hkd_parm_file:
with open(hkd_parm_file.name, 'w') as parm_file:
parm_file.write(f'{self.cmdline}{os.linesep}')
genprotimg_init = [
'chroot', root_dir, 'genprotimg',
'--offline', '--verbose',
'-o', cc_boot_image,
'-i', self.custom_args['kernel'],
'-r', self.custom_args['initrd'],
'-p', hkd_parm_file.name.replace(root_dir, '')
]
for host_key_certificate in host_key_certificates:
genprotimg_host_key_check = copy.deepcopy(genprotimg_init)
genprotimg_host_key_check.append('--cert')
genprotimg_host_key_check.append(
host_key_certificates[0]['hkd_ca_cert']
)
genprotimg_host_key_check.append('--cert')
genprotimg_host_key_check.append(
host_key_certificate['hkd_sign_cert']
)
for host_key in host_key_certificate.get('hkd_cert'):
genprotimg_host_key_check.append('-k')
genprotimg_host_key_check.append(host_key)
for host_crl in host_key_certificate.get('hkd_revocation_list'):
genprotimg_host_key_check.append('--crl')
genprotimg_host_key_check.append(host_crl)
log.info(
'Checking HKDs for signing cert: {}'.format(
host_key_certificate['hkd_sign_cert']
)
)
Command.run(genprotimg_host_key_check)
os.unlink(f'{root_dir}/{cc_boot_image}')
genprotimg = genprotimg_init
genprotimg.append('--no-verify')
for host_key_certificate in host_key_certificates:
for host_key in host_key_certificate.get('hkd_cert'):
genprotimg.append('-k')
genprotimg.append(host_key)
for host_crl in host_key_certificate.get('hkd_revocation_list'):
genprotimg.append('--crl')
genprotimg.append(host_crl)
Command.run(genprotimg)
secure_execution_header = 'secure_execution_header.bin'
Command.run(
[
'chroot', root_dir, 'pvextract-hdr',
'-o', secure_execution_header,
cc_boot_image
]
)
shutil.move(
os.sep.join([root_dir, secure_execution_header]),
os.sep.join(
[
self.custom_args['target_dir'],
'{0}.{1}-{2}.{3}'.format(
self.xml_state.xml_data.get_name(),
self.arch,
self.xml_state.get_image_version(),
secure_execution_header
)
]
)
)
attestation_request = 'attestation_request.bin'
attestation_response_key = 'attestation_response.key'
pvattest = [
'chroot', root_dir, 'pvattest', 'create',
'--no-verify', '--verbose',
'-o', attestation_request,
'--arpk', attestation_response_key
]
for host_key_certificate in host_key_certificates:
for host_key in host_key_certificate.get('hkd_cert'):
pvattest.append('-k')
pvattest.append(host_key)
Command.run(pvattest)
shutil.move(
os.sep.join([root_dir, attestation_request]),
os.sep.join(
[
self.custom_args['target_dir'],
'{0}.{1}-{2}.{3}'.format(
self.xml_state.xml_data.get_name(),
self.arch,
self.xml_state.get_image_version(),
attestation_request
)
]
)
)
shutil.move(
os.sep.join([root_dir, attestation_response_key]),
os.sep.join(
[
self.custom_args['target_dir'],
'{0}.{1}-{2}.{3}'.format(
self.xml_state.xml_data.get_name(),
self.arch,
self.xml_state.get_image_version(),
attestation_response_key
)
]
)
)
self.custom_args['secure_linux'] = True
self.custom_args['secure_image_file'] = cc_boot_image
os.unlink(f'{root_dir}/{self.custom_args["kernel"]}')
os.unlink(f'{root_dir}/{self.custom_args["initrd"]}')
self.custom_args['kernel'] = ''
self.custom_args['initrd'] = ''
with Temporary(
path=self.root_mount.mountpoint, prefix='kiwi_zipl.conf_'
).new_file() as runtime_zipl_config_file:
BootLoaderZipl._write_config_file(
BootLoaderTemplateZipl().get_loader_template(),
runtime_zipl_config_file.name,
self._get_template_parameters()
)
self.set_loader_entry(
self.root_mount.mountpoint, self.target.disk
)
self._install_zipl(root_dir, runtime_zipl_config_file.name)
def set_loader_entry(self, root_dir: str, target: str) -> None:
"""
Setup/update loader entries of the form
{boot_path}/loader/entries/{get_entry_name}
:param str target:
target identifier, one of disk, live(iso) or install(iso)
"""
entry_name = self.get_entry_name()
BootLoaderZipl._write_config_file(
BootLoaderTemplateZipl().get_entry_template(
self.custom_args['secure_linux']
),
root_dir + f'{self.entries_dir}/{entry_name}',
self._get_template_parameters(entry_name)
)
def _install_zipl(self, root_dir: str, zipl_config: str) -> None:
"""
Install zipl on target
"""
zipl = [
'chroot', root_dir, 'zipl',
'--noninteractive',
'--config', zipl_config.replace(root_dir, ''),
'--blsdir', self.entries_dir,
'--verbose'
]
self.sys_mount.umount()
Command.run(zipl)
def _get_template_parameters(
self, default_entry: str = ''
) -> Dict[str, str]:
disk_type = self.disk_type or 'SCSI'
disk_type = disk_type if disk_type != 'GPT' else 'SCSI'
blocksize = self.disk_blocksize or 512
unsupported_for_target_geometry = ['FBA', 'SCSI']
targetbase = f'targetbase={self.custom_args.get("targetbase")}'
geometry = ''
if disk_type not in unsupported_for_target_geometry:
geometry = f'targetgeometry={self._get_disk_geometry()}'
return {
'secure_image_file': self.custom_args.get('secure_image_file') or '',
'kernel_file': self.custom_args['kernel'] or 'vmlinuz',
'initrd_file': self.custom_args['initrd'] or 'initrd',
'boot_options': self.cmdline,
'boot_timeout': self.timeout,
'bootpath': self.get_boot_path(),
'targetbase': targetbase,
'targettype': disk_type,
'targetblocksize': format(blocksize),
'targetoffset': self._get_partition_start(),
'targetgeometry': geometry,
'title': self.get_menu_entry_title(),
'default_entry': default_entry
}
def _get_disk_geometry(self) -> str:
target_table_type = self.firmware.get_partition_table_type()
disk_device = self.custom_args['targetbase']
disk_geometry = ''
if target_table_type == 'dasd':
disk_geometry = '{0},{1},{2}'.format(
self._get_dasd_disk_geometry_element(
disk_device, 'cylinders'
),
self._get_dasd_disk_geometry_element(
disk_device, 'tracks per cylinder'
),
self._get_dasd_disk_geometry_element(
disk_device, 'blocks per track'
)
)
return disk_geometry
def _get_partition_start(self) -> str:
target_table_type = self.firmware.get_partition_table_type()
disk_device = self.custom_args['targetbase']
if target_table_type == 'dasd':
blocks = self._get_dasd_disk_geometry_element(
disk_device, 'blocks per track'
)
fdasd_command = [
'fdasd', '-f', '-s', '-p', disk_device,
'|', 'grep', '"^ "',
'|', 'head', '-n', '1',
'|', 'tr', '-s', '" "'
]
fdasd_call = Command.run(
['bash', '-c', ' '.join(fdasd_command)]
)
fdasd_output = fdasd_call.output
try:
start_track = int(fdasd_output.split(' ')[2].lstrip())
except Exception:
raise KiwiDiskGeometryError(
f'unknown partition format: {fdasd_output}'
)
return '{0}'.format(start_track * blocks)
else:
sfdisk_command = ' '.join(
[
'sfdisk', '--dump', disk_device,
'|', 'grep', '"1 :"',
'|', 'cut', '-f1', '-d,',
'|', 'cut', '-f2', '-d='
]
)
return Command.run(
['bash', '-c', sfdisk_command]
).output.strip()
def _get_dasd_disk_geometry_element(self, disk_device, search) -> int:
fdasd = ['fdasd', '-f', '-p', disk_device]
bash_command = fdasd + ['|', 'grep', '"' + search + '"']
fdasd_call = Command.run(
['bash', '-c', ' '.join(bash_command)]
)
fdasd_output = fdasd_call.output
try:
return int(fdasd_output.split(':')[1].lstrip())
except Exception:
raise KiwiDiskGeometryError(
f'unknown format for disk geometry: {fdasd_output}'
)
@staticmethod
def _write_config_file(
template: Template, filename: str, parameters: Dict[str, str]
) -> None:
try:
config_data = template.substitute(parameters)
Path.create(os.path.dirname(filename))
with open(filename, 'w') as config:
config.write(config_data)
except Exception as issue:
raise KiwiTemplateError(
'{0}: {1}'.format(type(issue).__name__, issue)
)

View File

@ -48,7 +48,8 @@ class BootLoaderInstall(metaclass=ABCMeta):
name_map = {
'grub2': {'grub2': 'BootLoaderInstallGrub2'},
'grub2_s390x_emu': {'grub2': 'BootLoaderInstallGrub2'},
'systemd_boot': {'systemd_boot': 'BootLoaderInstallSystemdBoot'}
'systemd_boot': {'systemd_boot': 'BootLoaderInstallSystemdBoot'},
'zipl': {'zipl': 'BootLoaderInstallZipl'}
}
try:
(bootloader_namespace, bootloader_name) = \

View File

@ -0,0 +1,66 @@
# Copyright (c) 2024 SUSE Software Solutions Germany GmbH. All rights reserved.
#
# This file is part of kiwi.
#
# kiwi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# kiwi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with kiwi. If not, see <http://www.gnu.org/licenses/>
#
import logging
from typing import Dict
# project
from kiwi.bootloader.install.base import BootLoaderInstallBase
log = logging.getLogger('kiwi')
class BootLoaderInstallZipl(BootLoaderInstallBase):
"""
**zipl bootloader installation**
"""
def post_init(self, custom_args: Dict):
"""
zipl post initialization method
:param dict custom_args: unused
"""
self.custom_args = custom_args
def install_required(self) -> bool:
"""
Check if zipl needs to install boot code
zipl requires boot code installation, but it is done as
part of the BLS implementation in bootloader/config/zipl.py
Thus this method always returns: False
:return: False
:rtype: bool
"""
return False
def install(self):
"""
Not required for zipl, handled in config stage
"""
pass
def secure_boot_install(self):
"""
Run shim installation for secure boot setup
For zipl this is skipped since details for
secure boot are not yet clear.
"""
pass

View File

@ -0,0 +1,79 @@
# Copyright (c) 2024 SUSE Software Solutions Germany GmbH. All rights reserved.
#
# This file is part of kiwi.
#
# kiwi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# kiwi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with kiwi. If not, see <http://www.gnu.org/licenses/>
#
from string import Template
from textwrap import dedent
class BootLoaderTemplateZipl:
"""
**zipl configuraton file templates**
"""
def __init__(self):
self.cr = '\n'
self.main_conf = dedent('''
[defaultboot]
defaultauto
prompt=1
target=${bootpath}
${targetbase}
targettype=${targettype}
targetblocksize=${targetblocksize}
targetoffset=${targetoffset}
${targetgeometry}
timeout=${boot_timeout}
secure=auto
''').strip() + self.cr
self.entry_secure = dedent('''
title ${title}
options ${boot_options}
linux ${secure_image_file}
''').strip() + self.cr
self.entry_standard = dedent('''
title ${title}
options ${boot_options}
linux ${kernel_file}
initrd ${initrd_file}
''').strip() + self.cr
def get_loader_template(self) -> Template:
"""
Bootloader main configuration template
:return: instance of :class:`Template`
:rtype: Template
"""
template_data = self.main_conf
return Template(template_data)
def get_entry_template(self, secure: bool = False) -> Template:
"""
Bootloader entry configuration template
:return: instance of :class:`Template`
:rtype: Template
"""
if secure:
template_data = self.entry_secure
else:
template_data = self.entry_standard
return Template(template_data)

View File

@ -731,7 +731,8 @@ class DiskBuilder:
exclude_list.append(
'{0}/.*'.format(self.spare_part_mountpoint.lstrip(os.sep))
)
if 'boot' in device_map and 's390' in self.arch:
if 'boot' in device_map \
and 's390' in self.arch and self.bootloader == 'grub2_s390x_emu':
exclude_list.append('boot/zipl/*')
exclude_list.append('boot/zipl/.*')
elif 'boot' in device_map:
@ -846,7 +847,7 @@ class DiskBuilder:
if not boot_filesystem:
boot_filesystem = self.requested_filesystem
boot_directory = self.root_dir + '/boot/'
if 's390' in self.arch:
if 's390' in self.arch and self.bootloader == 'grub2_s390x_emu':
boot_directory = self.root_dir + '/boot/zipl/'
log.info(
'Creating boot(%s) filesystem on %s',
@ -1138,7 +1139,7 @@ class DiskBuilder:
custom_root_mount_args, fs_check_interval
)
if device_map.get('boot'):
if 's390' in self.arch:
if 's390' in self.arch and self.bootloader == 'grub2_s390x_emu':
boot_mount_point = '/boot/zipl'
else:
boot_mount_point = '/boot'

View File

@ -570,6 +570,17 @@ class Defaults:
"""
return 'grub2'
@staticmethod
def get_bls_loader_entries_dir() -> str:
"""
Provide default loader entries directory for BLS loaders
:return: directory name
:rtype: str
"""
return '/boot/loader/entries'
@staticmethod
def get_grub_boot_directory_name(lookup_path):
"""

View File

@ -379,6 +379,12 @@ class KiwiKernelLookupError(KiwiError):
"""
class KiwiOSReleaseImportError(KiwiError):
"""
Exception raised if the os-release file could not be imported
"""
class KiwiLiveBootImageError(KiwiError):
"""
Exception raised if an attempt was made to use an

View File

@ -65,6 +65,8 @@ class FirmWare:
if 's390' in self.arch:
if self.zipl_target_type and 'CDL' in self.zipl_target_type:
return 'dasd'
elif self.zipl_target_type and 'GPT' in self.zipl_target_type:
return 'gpt'
else:
return 'msdos'
elif 'ppc64' in self.arch:

View File

@ -2741,7 +2741,7 @@ div {
## user which can be done by using the editbootinstall and
## editbootconfig custom scripts
attribute name {
"grub2" | "isolinux" | "grub2_s390x_emu" | "systemd_boot" | "custom"
"grub2" | "isolinux" | "grub2_s390x_emu" | "systemd_boot" | "custom" | "zipl"
}
>> sch:pattern [ id = "loader_name" is-a = "bootloader_image_type"
sch:param [ name = "attr" value = "name" ]
@ -2785,7 +2785,7 @@ div {
attribute timeout { xsd:nonNegativeInteger }
>> sch:pattern [ id = "timeout" is-a = "bootloader_name_type"
sch:param [ name = "attr" value = "timeout" ]
sch:param [ name = "types" value = "grub2 isolinux grub2_s390x_emu systemd_boot" ]
sch:param [ name = "types" value = "grub2 isolinux grub2_s390x_emu systemd_boot zipl" ]
]
k.bootloader.timeout_style.attribute =
## Specifies the boot timeout style to control the way in which
@ -2805,11 +2805,11 @@ div {
## devices use SCSI, for emulated DASD devices use FBA,
## for 4k DASD devices use CDL
attribute targettype {
"FBA" | "SCSI" | "CDL"
"FBA" | "SCSI" | "CDL" | "GPT"
}
>> sch:pattern [ id = "targettype" is-a = "bootloader_name_type"
sch:param [ name = "attr" value = "targettype" ]
sch:param [ name = "types" value = "grub2_s390x_emu" ]
sch:param [ name = "types" value = "grub2_s390x_emu zipl" ]
]
k.bootloader.use_disk_password.attribute =
## When /boot is encrypted, make the boot loader store the
@ -2831,7 +2831,8 @@ div {
## and to provide configuration parameters for it
element bootloader {
k.bootloader.attlist &
k.bootloadersettings?
k.bootloadersettings? &
k.securelinux*
}
}
@ -3093,6 +3094,74 @@ div {
}
}
#==========================================
# main block: <securelinux>
#
div {
sch:pattern [
abstract = "true"
id = "securelinux_loader_requirement"
sch:rule [
context = "securelinux"
sch:assert [
test = "../@name='zipl' or ../@name='grub2-s390x-emu'"
"<securelinux> section only available for the "
"bootloader types: zipl and grub2-s390x-emu"
]
]
]
k.securelinux.hkd_ca_cert.attribute =
attribute hkd_ca_cert { text }
k.securelinux.hkd_sign_cert.attribute =
attribute hkd_sign_cert { text }
k.securelinux.attlist =
k.securelinux.hkd_ca_cert.attribute &
k.securelinux.hkd_sign_cert.attribute
k.securelinux =
## securelinux contains all elements to describe
## data required to setup a secure linux execution
## process for the individual architecture in the scope
## of the bootloader process
element securelinux {
k.securelinux.attlist &
k.hkd_cert+ &
k.hkd_revocation_list*
}
>> sch:pattern [
id = "bootloader_matches" is-a = "securelinux_loader_requirement"
]
}
#==========================================
# common element <hkd_cert>
#
div {
k.hkd_cert.name.attribute =
attribute name { text }
k.hkd_cert.attlist =
k.hkd_cert.name.attribute
k.hkd_cert =
element hkd_cert {
k.hkd_cert.attlist,
empty
}
}
#==========================================
# common element <hkd_revocation_list>
#
div {
k.hkd_revocation_list.name.attribute =
attribute name { text }
k.hkd_revocation_list.attlist =
k.hkd_revocation_list.name.attribute
k.hkd_revocation_list =
element hkd_revocation_list {
k.hkd_revocation_list.attlist,
empty
}
}
#==========================================
# main block: <environment>
#

View File

@ -4111,6 +4111,7 @@ editbootconfig custom scripts</a:documentation>
<value>grub2_s390x_emu</value>
<value>systemd_boot</value>
<value>custom</value>
<value>zipl</value>
</choice>
</attribute>
<sch:pattern id="loader_name" is-a="bootloader_image_type">
@ -4165,7 +4166,7 @@ to 10sec</a:documentation>
</attribute>
<sch:pattern id="timeout" is-a="bootloader_name_type">
<sch:param name="attr" value="timeout"/>
<sch:param name="types" value="grub2 isolinux grub2_s390x_emu systemd_boot"/>
<sch:param name="types" value="grub2 isolinux grub2_s390x_emu systemd_boot zipl"/>
</sch:pattern>
</define>
<define name="k.bootloader.timeout_style.attribute">
@ -4195,11 +4196,12 @@ for 4k DASD devices use CDL</a:documentation>
<value>FBA</value>
<value>SCSI</value>
<value>CDL</value>
<value>GPT</value>
</choice>
</attribute>
<sch:pattern id="targettype" is-a="bootloader_name_type">
<sch:param name="attr" value="targettype"/>
<sch:param name="types" value="grub2_s390x_emu"/>
<sch:param name="types" value="grub2_s390x_emu zipl"/>
</sch:pattern>
</define>
<define name="k.bootloader.use_disk_password.attribute">
@ -4245,6 +4247,9 @@ and to provide configuration parameters for it</a:documentation>
<optional>
<ref name="k.bootloadersettings"/>
</optional>
<zeroOrMore>
<ref name="k.securelinux"/>
</zeroOrMore>
</interleave>
</element>
</define>
@ -4612,6 +4617,74 @@ of the storage device</a:documentation>
</element>
</define>
</div>
<!--
==========================================
main block: <securelinux>
-->
<div>
<sch:pattern abstract="true" id="securelinux_loader_requirement">
<sch:rule context="securelinux">
<sch:assert test="../@name='zipl' or ../@name='grub2-s390x-emu'">&lt;securelinux&gt; section only available for the bootloader types: zipl and grub2-s390x-emu</sch:assert>
</sch:rule>
</sch:pattern>
<define name="k.securelinux.attlist">
<interleave>
<attribute name="hkd_ca_cert"/>
<attribute name="hkd_sign_cert"/>
</interleave>
</define>
<define name="k.securelinux">
<element name="securelinux">
<a:documentation>securelinux contains all elements to describe
data required to setup a secure linux execution
process for the individual architecture in the scope
of the bootloader process</a:documentation>
<interleave>
<ref name="k.securelinux.attlist"/>
<oneOrMore>
<ref name="k.hkd_cert"/>
</oneOrMore>
<zeroOrMore>
<ref name="k.hkd_revocation_list"/>
</zeroOrMore>
</interleave>
</element>
<sch:pattern id="bootloader_matches" is-a="securelinux_loader_requirement"/>
</define>
</div>
<!--
==========================================
common element <hkd_cert>
-->
<div>
<define name="k.hkd_cert.attlist">
<attribute name="name"/>
</define>
<define name="k.hkd_cert">
<element name="hkd_cert">
<ref name="k.hkd_cert.attlist"/>
<empty/>
</element>
</define>
</div>
<!--
==========================================
common element <hkd_revocation_list>
-->
<div>
<define name="k.hkd_revocation_list.attlist">
<attribute name="name"/>
</define>
<define name="k.hkd_revocation_list">
<element name="hkd_revocation_list">
<ref name="k.hkd_revocation_list.attlist"/>
<empty/>
</element>
</define>
</div>
<!--
==========================================
main block: <environment>

63
kiwi/utils/os_release.py Normal file
View File

@ -0,0 +1,63 @@
# Copyright (c) 2024 SUSE Software Solutions Germany GmbH. All rights reserved.
#
# This file is part of kiwi.
#
# kiwi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# kiwi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with kiwi. If not, see <http://www.gnu.org/licenses/>
#
import csv
from io import TextIOWrapper
from typing import Iterable
# project
from kiwi.exceptions import KiwiOSReleaseImportError
class OsRelease:
"""
**Read os-release information**
"""
def __init__(self, root_dir: str):
self.data = {}
os_release = root_dir + '/etc/os-release'
try:
with open(os_release) as osdata:
reader = csv.reader(OsRelease._rip(osdata), delimiter='=')
self.data = dict(reader)
except Exception as issue:
raise KiwiOSReleaseImportError(
f'Import of {os_release} failed with {issue}'
)
@staticmethod
def _is_comment(line: str) -> bool:
return line.startswith('#')
@staticmethod
def _is_whitespace(line: str) -> bool:
return line.isspace()
@staticmethod
def _rip(csvfile: TextIOWrapper) -> Iterable[str]:
for row in csvfile:
if not OsRelease._is_comment(row) \
and not OsRelease._is_whitespace(row):
yield row
def get(self, key: str) -> str:
"""
Return value for key or an empty string if not present
:param string key: key name from os-release
"""
return self.data.get(key) or ''

View File

@ -17,7 +17,7 @@
#
import os
from typing import (
List, Optional, Any, Dict, NamedTuple
List, Optional, Any, Dict, NamedTuple, Union
)
import re
import logging
@ -1123,6 +1123,47 @@ class XMLState:
return bootloader.get_use_disk_password()
return False
def get_build_type_bootloader_securelinux_section(self) -> List[Any]:
"""
First securelinux section from the build
type bootloader section
:return: <securelinux> section reference
:rtype: xml_parse::securelinux
"""
bootloader_section = self.get_build_type_bootloader_section()
bootloader_securelinux_section = []
get_securelinux = getattr(
bootloader_section, 'get_securelinux', None
)
if get_securelinux and get_securelinux():
bootloader_securelinux_section = get_securelinux()
return bootloader_securelinux_section
def get_host_key_certificates(
self
) -> Union[List[Dict[str, List[str]]], List[Dict[str, str]]]:
cc_result = []
cc_certificates: Dict[str, List[str]] = {}
securelinux_list = \
self.get_build_type_bootloader_securelinux_section()
for securelinux in securelinux_list:
cc_certificates = {
'hkd_cert': [],
'hkd_revocation_list': [],
'hkd_ca_cert': securelinux.get_hkd_ca_cert(),
'hkd_sign_cert': securelinux.get_hkd_sign_cert()
}
for hkd_cert in securelinux.get_hkd_cert():
cc_certificates['hkd_cert'].append(hkd_cert.get_name())
for hkd_revocation_list in securelinux.get_hkd_revocation_list():
cc_certificates['hkd_revocation_list'].append(
hkd_revocation_list.get_name()
)
cc_result.append(cc_certificates)
return cc_result
def get_build_type_oemconfig_section(self) -> Any:
"""
First oemconfig section from the build type section

View File

@ -353,6 +353,7 @@ class TestBootLoaderConfigBase:
return mount_managers.pop()
self.bootloader.arch = 's390x'
self.bootloader.bootloader = 'grub2_s390x_emu'
mock_MountManager.side_effect = mount_managers_effect
self.bootloader._mount_system(

View File

@ -17,6 +17,12 @@ class TestBootLoaderSystemdBoot:
self.state.xml_data.get_name.return_value = 'image-name'
self.state.get_image_version.return_value = 'image-version'
self.state.build_type.get_efifatimagesize.return_value = None
self.state.build_type.get_firmware.return_value = None
self.state.build_type.get_efipartsize.return_value = None
self.state.build_type.get_efiparttable.return_value = None
self.state.build_type.get_target_blocksize.return_value = None
self.state.get_build_type_bootloader_targettype.return_value = None
self.state.get_build_type_bootloader_name.return_value = 'systemd_boot'
self.bootloader = BootLoaderSystemdBoot(self.state, 'root_dir')
self.bootloader.custom_args['kernel'] = None
self.bootloader.custom_args['initrd'] = None

View File

@ -0,0 +1,344 @@
import io
from pytest import raises
from unittest.mock import (
Mock, patch, call, MagicMock
)
from kiwi.bootloader.config.zipl import BootLoaderZipl
from kiwi.bootloader.config.bootloader_spec_base import BootLoaderSpecBase
from kiwi.command import command_type
from kiwi.exceptions import (
KiwiTemplateError,
KiwiBootLoaderTargetError,
KiwiDiskGeometryError
)
class TestBootLoaderZipl:
@patch('kiwi.bootloader.config.bootloader_spec_base.FirmWare')
def setup(self, mock_FirmWare):
self.firmware = Mock()
mock_FirmWare.return_value = self.firmware
self.state = Mock()
self.state.get_host_key_certificates.return_value = [
{
'hkd_cert': ['/path/to/host.crt'],
'hkd_revocation_list': ['/path/to/revocation-list.crl'],
'hkd_ca_cert': '/path/to/DigiCertCA.crt',
'hkd_sign_cert': '/path/to/ibm-z-host-key-signing.crt'
}
]
self.state.get_build_type_bootloader_targettype.return_value = 'CDL'
self.state.build_type.get_target_blocksize.return_value = 4096
self.state.xml_data.get_name.return_value = 'image-name'
self.state.get_image_version.return_value = 'image-version'
self.state.build_type.get_efifatimagesize.return_value = None
self.bootloader = BootLoaderZipl(self.state, 'root_dir')
self.bootloader.cmdline = 'options'
self.bootloader.custom_args['kernel'] = None
self.bootloader.custom_args['initrd'] = None
self.bootloader.custom_args['boot_options'] = {}
self.bootloader.custom_args['targetbase'] = '/dev/disk'
self.bootloader.custom_args['target_dir'] = 'target_dir'
self.bootloader.sys_mount = Mock(
mountpoint='sys_mount'
)
self.bootloader.root_mount = Mock(
mountpoint='system_root_mount'
)
self.bootloader._mount_system = Mock()
self.bootloader.create_efi_path = Mock()
self.bootloader.get_boot_path = Mock(
return_value='bootpath'
)
self.bootloader.get_menu_entry_title = Mock(
return_value='title'
)
self.bootloader.arch = 's390x'
@patch('kiwi.bootloader.config.bootloader_spec_base.FirmWare')
def setup_method(self, cls, mock_FirmWare):
self.setup()
@patch('os.unlink')
def test_setup_loader_raises_invalid_target(self, mock_os_unlink):
with raises(KiwiBootLoaderTargetError):
self.bootloader.setup_loader('iso')
@patch('shutil.move')
@patch('os.unlink')
@patch('os.path.exists')
@patch('kiwi.bootloader.config.zipl.BootImageBase.get_boot_names')
@patch('kiwi.bootloader.config.zipl.Path.create')
@patch('kiwi.bootloader.config.zipl.Command.run')
@patch('kiwi.bootloader.config.zipl.BootLoaderTemplateZipl')
@patch('kiwi.bootloader.config.zipl.Temporary.new_file')
@patch.object(BootLoaderZipl, '_write_config_file')
@patch.object(BootLoaderZipl, '_get_template_parameters')
@patch.object(BootLoaderSpecBase, 'get_entry_name')
def test_setup_loader(
self, mock_get_entry_name, mock_get_template_parameters,
mock_write_config_file, mock_Temporary_new_file,
mock_BootLoaderTemplateZipl, mock_Command_run,
mock_Path_create, mock_BootImageBase_get_boot_names,
mock_os_path_exists, mock_os_unlink, mock_shutil_move
):
temporary = Mock()
temporary.name = 'system_root_mount/kiwi_zipl.conf_'
mock_Temporary_new_file.return_value.__enter__.return_value = temporary
mock_get_entry_name.return_value = \
'opensuse-leap-5.3.18-59.10-default.conf'
mock_get_template_parameters.return_value = {
'targetbase': '/dev/loop'
}
kernel_info = Mock()
kernel_info.kernel_version = 'kernel-version'
kernel_info.kernel_filename = 'kernel-filename'
kernel_info.initrd_name = 'initrd-name'
mock_os_path_exists.return_value = True
mock_BootImageBase_get_boot_names.return_value = kernel_info
with patch('builtins.open', create=True) as mock_open:
mock_open.return_value = MagicMock(spec=io.IOBase)
file_handle = mock_open.return_value.__enter__.return_value
self.bootloader.setup_loader('disk')
file_handle.write.assert_called_once_with('options\n')
assert mock_write_config_file.call_args_list == [
call(
mock_BootLoaderTemplateZipl.
return_value.get_loader_template.return_value,
'system_root_mount/kiwi_zipl.conf_',
mock_get_template_parameters.return_value
),
call(
mock_BootLoaderTemplateZipl.
return_value.get_entry_template.return_value,
'system_root_mount/boot/loader/entries/'
'opensuse-leap-5.3.18-59.10-default.conf',
mock_get_template_parameters.return_value
)
]
assert self.bootloader._mount_system.called
assert self.bootloader.sys_mount.umount.called
assert mock_Command_run.call_args_list == [
call(
[
'chroot', 'system_root_mount', 'genprotimg',
'--offline', '--verbose',
'-o', 'bootpath/kernel-filename.cc',
'-i', 'bootpath/kernel-filename',
'-r', 'bootpath/initrd-name',
'-p', temporary.name.replace('system_root_mount', ''),
'--cert', '/path/to/DigiCertCA.crt',
'--cert', '/path/to/ibm-z-host-key-signing.crt',
'-k', '/path/to/host.crt',
'--crl', '/path/to/revocation-list.crl'
]
),
call(
[
'chroot', 'system_root_mount', 'genprotimg',
'--offline', '--verbose',
'-o', 'bootpath/kernel-filename.cc',
'-i', 'bootpath/kernel-filename',
'-r', 'bootpath/initrd-name',
'-p', temporary.name.replace('system_root_mount', ''),
'--no-verify',
'-k', '/path/to/host.crt',
'--crl', '/path/to/revocation-list.crl',
]
),
call(
[
'chroot', 'system_root_mount', 'pvextract-hdr',
'-o', 'secure_execution_header.bin',
'bootpath/kernel-filename.cc'
]
),
call(
[
'chroot', 'system_root_mount', 'pvattest', 'create',
'--no-verify', '--verbose',
'-o', 'attestation_request.bin',
'--arpk', 'attestation_response.key',
'-k', '/path/to/host.crt'
]
),
call(
[
'chroot', 'system_root_mount', 'zipl',
'--noninteractive',
'--config', '/kiwi_zipl.conf_',
'--blsdir', '/boot/loader/entries',
'--verbose'
]
)
]
assert mock_shutil_move.call_args_list == [
call(
'system_root_mount/secure_execution_header.bin',
'target_dir/image-name.s390x-image-version.'
'secure_execution_header.bin'
),
call(
'system_root_mount/attestation_request.bin',
'target_dir/image-name.s390x-image-version.'
'attestation_request.bin'
),
call(
'system_root_mount/attestation_response.key',
'target_dir/image-name.s390x-image-version.'
'attestation_response.key'
)
]
assert mock_os_unlink.call_args_list == [
call('system_root_mount/bootpath/kernel-filename.cc'),
call('system_root_mount/bootpath/kernel-filename'),
call('system_root_mount/bootpath/initrd-name')
]
@patch.object(BootLoaderZipl, '_get_disk_geometry')
@patch.object(BootLoaderZipl, '_get_partition_start')
def test_get_template_parameters(
self, mock_get_partition_start, mock_get_disk_geometry
):
mock_get_partition_start.return_value = '42'
mock_get_disk_geometry.return_value = '123,53,9'
self.bootloader.timeout = 0
self.bootloader.disk_type = 'CDL'
self.bootloader.disk_blocksize = 4096
assert self.bootloader._get_template_parameters() == {
'secure_image_file': 'bootpath/kernel-filename.cc',
'kernel_file': 'vmlinuz',
'initrd_file': 'initrd',
'boot_options': 'options',
'boot_timeout': 0,
'bootpath': 'bootpath',
'targetbase': 'targetbase=/dev/disk',
'targettype': 'CDL',
'targetblocksize': '4096',
'targetoffset': '42',
'targetgeometry': 'targetgeometry=123,53,9',
'title': 'title',
'default_entry': ''
}
@patch('kiwi.bootloader.config.zipl.Path.create')
@patch.object(BootLoaderZipl, '_get_template_parameters')
def test_write_config_file(
self, mock_get_template_parameters, mock_Path_create
):
template = Mock()
template.substitute.return_value = 'data'
with patch('builtins.open', create=True) as mock_open:
mock_open.return_value = MagicMock(spec=io.IOBase)
file_handle = mock_open.return_value.__enter__.return_value
self.bootloader._write_config_file(template, 'path/some-file', {})
mock_Path_create.assert_called_once_with('path')
mock_open.assert_called_once_with('path/some-file', 'w')
file_handle.write.assert_called_once_with('data')
def test_write_config_file_raises(self):
template = Mock()
template.substitute.side_effect = Exception
with raises(KiwiTemplateError):
self.bootloader._write_config_file(template, 'some-file', {})
@patch('kiwi.bootloader.config.zipl.Command.run')
def test_get_disk_geometry(self, mock_Command_run):
command_return_values = [
command_type(
output=' cylinders ............: 10017\n',
error='', returncode=0
),
command_type(
output=' tracks per cylinder ..: 15\n',
error='', returncode=0
),
command_type(
output=' blocks per track .....: 12\n',
error='', returncode=0
)
]
def command_run(arg):
return command_return_values.pop(0)
self.firmware.get_partition_table_type.return_value = 'dasd'
mock_Command_run.side_effect = command_run
assert self.bootloader._get_disk_geometry() == '10017,15,12'
@patch('kiwi.bootloader.config.zipl.Command.run')
def test_get_partition_start_dasd(self, mock_Command_run):
self.firmware.get_partition_table_type.return_value = 'dasd'
command_return_values = [
command_type(
output=' blocks per track .....: 12\n',
error='', returncode=0
),
command_type(
output=' /dev/loop01 2 6401 6400 1 Linux native\n',
error='', returncode=0
)
]
def command_run(arg):
return command_return_values.pop(0)
mock_Command_run.side_effect = command_run
assert self.bootloader._get_partition_start() == '24'
assert mock_Command_run.call_args_list == [
call(
[
'bash', '-c',
'fdasd -f -p /dev/disk | grep "blocks per track"'
]
),
call(
[
'bash', '-c',
'fdasd -f -s -p /dev/disk | grep "^ " |'
' head -n 1 | tr -s " "'
]
)
]
@patch('kiwi.bootloader.config.zipl.Command.run')
def test_get_partition_start_not_dasd(self, mock_Command_run):
self.firmware.get_partition_table_type.return_value = 'msdos'
command = Mock()
command.output = ' 2048'
mock_Command_run.return_value = command
assert self.bootloader._get_partition_start() == '2048'
mock_Command_run.assert_called_once_with(
[
'bash', '-c',
'sfdisk --dump /dev/disk | grep "1 :" |'
' cut -f1 -d, | cut -f2 -d='
]
)
@patch('kiwi.bootloader.config.zipl.Command.run')
@patch.object(BootLoaderZipl, '_get_dasd_disk_geometry_element')
def test_get_partition_start_raises(
self, mock_get_dasd_disk_geometry_element, mock_Command_run
):
self.firmware.get_partition_table_type.return_value = 'dasd'
mock_Command_run.return_value = command_type(
output='bogus data', error='', returncode=0
)
with raises(KiwiDiskGeometryError):
self.bootloader._get_partition_start()
@patch('kiwi.bootloader.config.zipl.Command.run')
def test_get_dasd_disk_geometry_element_raises(
self, mock_Command_run
):
self.bootloader.target_table_type = 'dasd'
mock_Command_run.return_value = command_type(
output='bogus data', error='', returncode=0
)
with raises(KiwiDiskGeometryError):
self.bootloader._get_dasd_disk_geometry_element(
'/dev/disk', 'tracks per cylinder'
)

View File

@ -0,0 +1,22 @@
from unittest.mock import Mock
from kiwi.bootloader.install.zipl import BootLoaderInstallZipl
class TestBootLoaderInstallZipl:
def setup(self):
self.bootloader = BootLoaderInstallZipl(
'root_dir', Mock()
)
def setup_method(self, cls):
self.setup()
def test_install_required(self):
assert self.bootloader.install_required() is False
def test_install(self):
self.bootloader.install()
def test_secure_boot_install(self):
self.bootloader.secure_boot_install()

View File

@ -0,0 +1,35 @@
from kiwi.bootloader.template.zipl import BootLoaderTemplateZipl
class TestBootLoaderTemplateZipl:
def setup(self):
self.zipl = BootLoaderTemplateZipl()
def setup_method(self, cls):
self.setup()
def test_get_loader_template(self):
assert self.zipl.get_loader_template().substitute(
boot_timeout='10',
bootpath='/boot',
targetbase='',
targettype='SCSI',
targetblocksize='512',
targetoffset='2048',
targetgeometry=''
)
def test_get_entry_template_standard(self):
assert self.zipl.get_entry_template().substitute(
title='title',
boot_options='',
kernel_file='linux',
initrd_file='initrd'
)
def test_get_entry_template_secure(self):
assert self.zipl.get_entry_template(secure=True).substitute(
title='title',
boot_options='',
secure_image_file='linux'
)

View File

@ -24,6 +24,9 @@ class TestCloneDevice:
self.storage_device, 'root_dir'
)
def setup_method(self, cls):
self.setup()
@patch('kiwi.storage.clone_device.Command.run')
@patch('kiwi.storage.clone_device.BlockID')
@patch('kiwi.storage.clone_device.FileSystem.new')