/
base.py
479 lines (420 loc) · 15.8 KB
/
base.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
# Copyright (c) 2015 SUSE Linux 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 re
import os
import logging
import glob
from typing import (
Optional, List, Dict, NamedTuple
)
# project
from kiwi.defaults import Defaults
from kiwi.system.setup import SystemSetup
from kiwi.system.identifier import SystemIdentifier
from kiwi.xml_description import XMLDescription
from kiwi.xml_state import XMLState
from kiwi.path import Path
from kiwi.system.kernel import Kernel
from kiwi.exceptions import (
KiwiTargetDirectoryNotFound,
KiwiConfigFileNotFound,
KiwiDiskBootImageError
)
boot_names_type = NamedTuple(
'boot_names_type', [
('kernel_name', str),
('initrd_name', str)
]
)
log = logging.getLogger('kiwi')
class BootImageBase:
"""
**Base class for boot image(initrd) task**
"""
def __init__(
self, xml_state: XMLState, target_dir: str,
root_dir: Optional[str] = None,
signing_keys: Optional[List[str]] = None,
target_arch: Optional[str] = None
) -> None:
"""
Create a base class instance of a BootImage
The base class instance only support the common operations
for boot images. The actual preparation and creation of an
initrd is specific to the used initrd creation toolchain
:param XMLState xml_state: Instance of :class:`XMLState`
:param str target_dir: target dir to store the initrd
:param str root_dir: system image root directory
:param List[str] signing_keys: list of package signing keys
:param str target_arch: target architecture
"""
self.xml_state = xml_state
self.target_dir = target_dir
self.initrd_filename = ''
self.boot_xml_state: Optional[XMLState] = None
self.setup: Optional[SystemSetup] = None
self.signing_keys = signing_keys
self.target_arch = target_arch
self.boot_root_directory = root_dir or ''
if not os.path.exists(target_dir):
raise KiwiTargetDirectoryNotFound(
'target directory %s not found' % target_dir
)
self.initrd_base_name = ''.join(
[
self.xml_state.xml_data.get_name(),
'.' + Defaults.get_platform_name(),
'-' + self.xml_state.get_image_version(),
'.initrd'
]
)
self.post_init()
def post_init(self) -> None:
"""
Post initialization method
Implementation in specialized boot image class
"""
pass
@staticmethod
def has_initrd_support() -> bool:
"""
Indicates if this instance supports actual creation of an initrd
The method needs to be overwritten by the subclass
implementing preparation and creation of an initrd
"""
return False
def include_file(self, filename: str, install_media: bool = False) -> None:
"""
Include file to boot image
For kiwi boot images this is done by adding package or
archive definitions with the bootinclude attribute. Thus
for kiwi boot images the method is a noop
:param str filename: file path name
:param bool install_media: include also for installation media initrd
"""
pass
def include_module(self, module: str, install_media: bool = False) -> None:
"""
Include module to boot image
For kiwi boot no modules configuration is required. Thus in
such a case this method is a noop.
:param str module: module to include
:param bool install_media: include the module for install initrds
"""
pass
def omit_module(self, module: str, install_media: bool = False) -> None:
"""
Omit module to boot image
For kiwi boot no modules configuration is required. Thus in
such a case this method is a noop.
:param str module: module to omit
:param bool install_media: omit the module for install initrds
"""
pass
def set_static_modules(
self, modules: List[str], install_media: bool = False
) -> None:
"""
Set static modules list for boot image
For kiwi boot no modules configuration is required. Thus in
such a case this method is a noop.
:param list modules: list of modules to include
:param bool install_media: lists the modules for install initrds
"""
pass
def write_system_config_file(
self, config: Dict, config_file: Optional[str] = None
) -> None:
"""
Writes relevant boot image configuration into configuration file
that will be part of the system image.
This is used to configure any further boot image rebuilds after
deployment. For instance, initrds recreated on kernel update.
For kiwi boot no specific configuration is required for initrds
recreation, thus this method is a noop in that case.
:param dict config: dictonary including configuration parameters
:param str config_file: configuration file to write
"""
pass
def get_boot_names(self) -> boot_names_type:
"""
Provides kernel and initrd names for the boot image
:return:
Contains boot_names_type tuple
.. code:: python
boot_names_type(
kernel_name='INSTALLED_KERNEL',
initrd_name='DRACUT_OUTPUT_NAME'
)
:rtype: boot_names_type
"""
kernel = Kernel(
self.boot_root_directory
)
kernel_info = kernel.get_kernel()
if not kernel_info:
if self.xml_state.get_initrd_system() == 'none':
return boot_names_type(kernel_name='none', initrd_name='none')
raise KiwiDiskBootImageError(
'No kernel in boot image tree %s found' %
self.boot_root_directory
)
dracut_output_format = self._get_boot_image_output_file_format(
kernel_info.version
)
return boot_names_type(
kernel_name=kernel_info.name,
initrd_name=dracut_output_format.format(
kernel_version=kernel_info.version
)
)
def prepare(self) -> None:
"""
Prepare new root system to create initrd from. Implementation
is only needed if there is no other root system available
Implementation in specialized boot image class
"""
raise NotImplementedError
def create_initrd(
self, mbrid: Optional[SystemIdentifier] = None,
basename: Optional[str] = None, install_initrd: bool = False
) -> None:
"""
Implements creation of the initrd
:param SystemIdentifier mbrid: instance of SystemIdentifier
:param str basename: base initrd file name
:param bool install_initrd: installation media initrd
Implementation in specialized boot image class
"""
raise NotImplementedError
def is_prepared(self) -> bool:
"""
Check if initrd system is prepared.
:return: True or False
:rtype: bool
"""
return bool(os.listdir(self.boot_root_directory))
def load_boot_xml_description(self) -> None:
"""
Load the boot image description referenced by the system image
description boot attribute
"""
log.info('Loading Boot XML description')
boot_description_directory = self.get_boot_description_directory()
if not boot_description_directory:
raise KiwiConfigFileNotFound(
'no boot reference specified in XML description'
)
boot_config_file = boot_description_directory + '/config.xml'
if not os.path.exists(boot_config_file):
raise KiwiConfigFileNotFound(
'no Boot XML description found in %s' %
boot_description_directory
)
boot_description = XMLDescription(
description=boot_config_file,
derived_from=self.xml_state.xml_data.description_dir
)
boot_image_profile = self.xml_state.build_type.get_bootprofile()
if not boot_image_profile:
boot_image_profile = 'default'
boot_kernel_profile = self.xml_state.build_type.get_bootkernel()
if not boot_kernel_profile:
boot_kernel_profile = 'std'
self.boot_xml_state = XMLState(
boot_description.load(), [boot_image_profile, boot_kernel_profile]
)
log.info('--> loaded %s', boot_config_file)
if self.boot_xml_state.build_type:
log.info(
'--> Selected build type: %s',
self.boot_xml_state.get_build_type_name()
)
if self.boot_xml_state.profiles:
log.info(
'--> Selected boot profiles: image: %s, kernel: %s',
boot_image_profile, boot_kernel_profile
)
def import_system_description_elements(self) -> None:
"""
Copy information from the system image relevant to create the
boot image to the boot image state XML description
"""
self.xml_state.copy_displayname(
self.boot_xml_state
)
self.xml_state.copy_name(
self.boot_xml_state
)
self.xml_state.copy_repository_sections(
target_state=self.boot_xml_state,
wipe=True
)
self.xml_state.copy_drivers_sections(
self.boot_xml_state
)
strip_description = XMLDescription(
Defaults.get_boot_image_strip_file()
)
strip_xml_state = XMLState(strip_description.load())
strip_xml_state.copy_strip_sections(
self.boot_xml_state
)
self.xml_state.copy_strip_sections(
self.boot_xml_state
)
preferences_subsection_names = [
'bootloader_theme',
'bootsplash_theme',
'locale',
'packagemanager',
'rpm_check_signatures',
'rpm_excludedocs',
'showlicense'
]
self.xml_state.copy_preferences_subsections(
preferences_subsection_names, self.boot_xml_state
)
self.xml_state.copy_bootincluded_packages(
self.boot_xml_state
)
self.xml_state.copy_bootincluded_archives(
self.boot_xml_state
)
self.xml_state.copy_bootdelete_packages(
self.boot_xml_state
)
type_attributes = [
'bootkernel',
'bootprofile',
'btrfs_root_is_snapshot',
'gpt_hybrid_mbr',
'devicepersistency',
'filesystem',
'firmware',
'fsmountoptions',
'hybridpersistent',
'hybridpersistent_filesystem',
'initrd_system',
'installboot',
'installprovidefailsafe',
'kernelcmdline',
'ramonly',
'target_removable',
'vga',
'wwid_wait_timeout'
]
self.xml_state.copy_build_type_attributes(
type_attributes, self.boot_xml_state
)
self.xml_state.copy_bootloader_section(
self.boot_xml_state
)
self.xml_state.copy_systemdisk_section(
self.boot_xml_state
)
self.xml_state.copy_machine_section(
self.boot_xml_state
)
self.xml_state.copy_oemconfig_section(
self.boot_xml_state
)
def get_boot_description_directory(self) -> Optional[str]:
"""
Provide path to the boot image XML description
:return: path name
:rtype: str
"""
boot_description = self.xml_state.build_type.get_boot()
if boot_description:
if not boot_description[0] == '/':
boot_description = \
Defaults.get_boot_image_description_path() + '/' + \
boot_description
return boot_description
def cleanup(self) -> None:
"""
Cleanup temporary boot image data if any
"""
pass
def _get_boot_image_output_file_format(self, kernel_version: str) -> str:
"""
The initrd output file format varies between
the different Linux distributions. Tools like lsinitrd, and also
grub2 rely on the initrd output file to be in that format. Thus
kiwi should use the same file format to stay compatible
with the distributions. The format is determined in three
stages:
a) check for an existing initrd file and use this naming schema
b) if no initrd file is found check the dracut binary for its
default output file name schema
c) if no output file schema could be detected from the dracut
binary return a default output file format which is
initramfs-{kernel_version}.img
Let's hope all this mess can be deleted once all distros just
can agree on one initrd file name schema
"""
if self.xml_state.get_initrd_system() == 'kiwi':
# The custom kiwi initrd system is used only on SUSE systems.
# The initrd environment does not provide dracut and thus the
# outfile format cannot be determined. on SUSE systems the
# initrd format is different than on upstream and therefore
# it can be explicitly specified. Note that the custom initrd
# system will become obsolete in the future.
default_outfile_format = 'initrd-{kernel_version}'
else:
default_outfile_format = 'initramfs-{kernel_version}.img'
outfile_format = \
self._get_boot_image_output_file_format_from_existing_file(
kernel_version
)
if not outfile_format:
outfile_format = \
self._get_boot_image_output_file_format_from_dracut_code()
if outfile_format:
return outfile_format
else:
log.warning('Could not detect dracut output file format')
log.warning(
'Using default initrd file name format {0}'.format(
default_outfile_format
)
)
return default_outfile_format
def _get_boot_image_output_file_format_from_dracut_code(
self
) -> Optional[str]:
dracut_tool = Path.which(
'dracut', root_dir=self.boot_root_directory, access_mode=os.X_OK
)
if dracut_tool:
outfile_expression = r'outfile="/boot/(init.*\$kernel.*)"'
with open(dracut_tool) as dracut:
matches = re.findall(outfile_expression, dracut.read())
if matches:
return matches[0].replace('$kernel', '{kernel_version}')
return None
def _get_boot_image_output_file_format_from_existing_file(
self, kernel_version: str
) -> Optional[str]:
for initrd_file in glob.iglob(self.boot_root_directory + '/boot/init*'):
if not os.path.islink(initrd_file):
return os.path.basename(initrd_file).replace(
kernel_version, '{kernel_version}'
)
return None