def test_process_result_bundle_with_bundle_format( self, mock_exists, mock_path_create, mock_command, mock_load ): self.xml_state.profiles = None self.xml_state.host_architecture = 'x86_64' self.xml_state.get_build_type_name = Mock( return_value='oem' ) self.xml_state.xml_data.get_name = Mock( return_value='Leap-15.2' ) result = Result(self.xml_state) result.add_bundle_format('%N-%T:%M') result.add( key='disk_image', filename='/tmp/mytest/Leap-15.2.x86_64-1.15.2.raw', use_for_bundle=True, compress=False, shasum=False ) mock_exists.return_value = False mock_load.return_value = result self._init_command_args() self.task.process() mock_command.assert_called_once_with( [ 'cp', '/tmp/mytest/Leap-15.2.x86_64-1.15.2.raw', os.sep.join( [self.abs_bundle_dir, 'Leap-15.2-oem:1.raw'] ) ] )
class ContainerBuilder: """ **Container image builder** :param object xml_state: Instance of :class:`XMLState` :param str target_dir: target directory path name :param str root_dir: root directory path name :param dict custom_args: Custom processing arguments defined as hash keys: * xz_options: string of XZ compression parameters """ def __init__(self, xml_state: XMLState, target_dir: str, root_dir: str, custom_args: Dict = None): self.custom_args = custom_args or {} self.root_dir = root_dir self.target_dir = target_dir self.bundle_format = xml_state.get_build_type_bundle_format() self.container_config = xml_state.get_container_config() self.requested_container_type = xml_state.get_build_type_name() self.base_image = None self.base_image_md5 = None self.container_config['xz_options'] = \ self.custom_args.get('xz_options') self.container_config['metadata_path'] = \ xml_state.build_type.get_metadata_path() if xml_state.get_derived_from_image_uri(): # The base image is expected to be unpacked by the kiwi # prepare step and stored inside of the root_dir/image directory. # In addition a md5 file of the image is expected too self.base_image = Defaults.get_imported_root_image(self.root_dir) self.base_image_md5 = ''.join([self.base_image, '.md5']) if not os.path.exists(self.base_image): raise KiwiContainerBuilderError( 'Unpacked Base image {0} not found'.format( self.base_image)) if not os.path.exists(self.base_image_md5): raise KiwiContainerBuilderError( 'Base image MD5 sum {0} not found at'.format( self.base_image_md5)) self.system_setup = SystemSetup(xml_state=xml_state, root_dir=self.root_dir) self.filename = ''.join([ target_dir, '/', xml_state.xml_data.get_name(), '.' + Defaults.get_platform_name(), '-' + xml_state.get_image_version(), '.', self.requested_container_type, '.tar' if self.requested_container_type != 'appx' else '' ]) self.result = Result(xml_state) self.runtime_config = RuntimeConfig() def create(self) -> Result: """ Builds a container image which is usually a data archive including container specific metadata. Image types which triggers this builder are: * image="docker" * image="oci" * image="appx" :return: result :rtype: instance of :class:`Result` """ if not self.base_image: log.info('Setting up %s container', self.requested_container_type) container_setup = ContainerSetup.new(self.requested_container_type, self.root_dir, self.container_config) container_setup.setup() else: checksum = Checksum(self.base_image) if not checksum.matches(checksum.md5(), self.base_image_md5): raise KiwiContainerBuilderError( 'base image file {0} checksum validation failed'.format( self.base_image)) log.info('--> Creating container image') container_image = ContainerImage.new(self.requested_container_type, self.root_dir, self.container_config) self.filename = container_image.create(self.filename, self.base_image) Result.verify_image_size(self.runtime_config.get_max_size_constraint(), self.filename) if self.bundle_format: self.result.add_bundle_format(self.bundle_format) self.result.add( key='container', filename=self.filename, use_for_bundle=True, compress=self.runtime_config.get_container_compression(), shasum=True) self.result.add(key='image_packages', filename=self.system_setup.export_package_list( self.target_dir), use_for_bundle=True, compress=False, shasum=False) self.result.add(key='image_changes', filename=self.system_setup.export_package_changes( self.target_dir), use_for_bundle=True, compress=True, shasum=False) self.result.add(key='image_verified', filename=self.system_setup.export_package_verification( self.target_dir), use_for_bundle=True, compress=False, shasum=False) return self.result
class ArchiveBuilder: """ **Root archive image builder** :param object xml_state: Instance of :class:`XMLState` :param str target_dir: target directory path name :param str root_dir: root directory path name :param dict custom_args: Custom processing arguments defined as hash keys: * xz_options: string of XZ compression parameters """ def __init__(self, xml_state: XMLState, target_dir: str, root_dir: str, custom_args: Dict = None): self.root_dir = root_dir self.target_dir = target_dir self.xml_state = xml_state self.requested_archive_type = xml_state.get_build_type_name() self.bundle_format = xml_state.get_build_type_bundle_format() self.result = Result(xml_state) self.system_setup = SystemSetup(xml_state=xml_state, root_dir=self.root_dir) self.filename = self._target_file_for('tar.xz') self.xz_options = custom_args['xz_options'] if custom_args \ and 'xz_options' in custom_args else None self.runtime_config = RuntimeConfig() def create(self) -> Result: """ Create a root archive tarball Build a simple XZ compressed root tarball from the image root tree Image types which triggers this builder are: * image="tbz" :return: result :rtype: instance of :class:`Result` """ supported_archives = Defaults.get_archive_image_types() if self.requested_archive_type not in supported_archives: raise KiwiArchiveSetupError('Unknown archive type: %s' % self.requested_archive_type) if self.requested_archive_type == 'tbz': log.info('Creating XZ compressed tar archive') archive = ArchiveTar(self._target_file_for('tar')) archive.create_xz_compressed( self.root_dir, xz_options=self.xz_options, exclude=Defaults.get_exclude_list_for_root_data_sync() + Defaults.get_exclude_list_from_custom_exclude_files( self.root_dir)) Result.verify_image_size( self.runtime_config.get_max_size_constraint(), self.filename) if self.bundle_format: self.result.add_bundle_format(self.bundle_format) self.result.add(key='root_archive', filename=self.filename, use_for_bundle=True, compress=False, shasum=True) self.result.add(key='image_packages', filename=self.system_setup.export_package_list( self.target_dir), use_for_bundle=True, compress=False, shasum=False) self.result.add(key='image_changes', filename=self.system_setup.export_package_changes( self.target_dir), use_for_bundle=True, compress=True, shasum=False) self.result.add( key='image_verified', filename=self.system_setup.export_package_verification( self.target_dir), use_for_bundle=True, compress=False, shasum=False) return self.result def _target_file_for(self, suffix: str) -> str: return ''.join([ self.target_dir, '/', self.xml_state.xml_data.get_name(), '.' + Defaults.get_platform_name(), '-' + self.xml_state.get_image_version(), '.', suffix ])
class FileSystemBuilder: """ **Filesystem image builder** :param obsject xml_state: Instance of :class:`XMLState` :param str target_dir: target directory path name :param str root_dir: root directory path name :param dict custom_args: Custom processing arguments defined as hash keys: * None """ def __init__( self, xml_state: XMLState, target_dir: str, root_dir: str, custom_args: Dict = None ): self.label = None self.root_uuid = '' self.root_dir = root_dir self.target_dir = target_dir self.bundle_format = xml_state.get_build_type_bundle_format() self.requested_image_type = xml_state.get_build_type_name() if self.requested_image_type in Defaults.get_kis_image_types(): self.requested_filesystem = xml_state.build_type.get_filesystem() else: self.requested_filesystem = self.requested_image_type if not self.requested_filesystem: raise KiwiFileSystemSetupError( 'No filesystem configured in %s type' % self.requested_image_type ) self.filesystem_custom_parameters = { 'mount_options': xml_state.get_fs_mount_option_list(), 'create_options': xml_state.get_fs_create_option_list() } if self.requested_filesystem == 'squashfs': self.filesystem_custom_parameters['compression'] = \ xml_state.build_type.get_squashfscompression() self.system_setup = SystemSetup( xml_state=xml_state, root_dir=self.root_dir ) self.filename = ''.join( [ target_dir, '/', xml_state.xml_data.get_name(), '.' + Defaults.get_platform_name(), '-' + xml_state.get_image_version(), '.', self.requested_filesystem ] ) self.blocksize = xml_state.build_type.get_target_blocksize() self.filesystem_setup = FileSystemSetup(xml_state, root_dir) self.filesystems_no_device_node = [ 'squashfs' ] self.result = Result(xml_state) self.runtime_config = RuntimeConfig() def create(self) -> Result: """ Build a mountable filesystem image Image types which triggers this builder are: * image="ext2" * image="ext3" * image="ext4" * image="btrfs" * image="xfs" :return: result :rtype: instance of :class:`Result` """ log.info( 'Creating %s filesystem', self.requested_filesystem ) supported_filesystems = Defaults.get_filesystem_image_types() if self.requested_filesystem not in supported_filesystems: raise KiwiFileSystemSetupError( 'Unknown filesystem: %s' % self.requested_filesystem ) if self.requested_filesystem not in self.filesystems_no_device_node: self._operate_on_loop() else: self._operate_on_file() Result.verify_image_size( self.runtime_config.get_max_size_constraint(), self.filename ) if self.bundle_format: self.result.add_bundle_format(self.bundle_format) self.result.add( key='filesystem_image', filename=self.filename, use_for_bundle=True, compress=self.runtime_config.get_bundle_compression( default=True ), shasum=True ) self.result.add( key='image_packages', filename=self.system_setup.export_package_list( self.target_dir ), use_for_bundle=True, compress=False, shasum=False ) self.result.add( key='image_changes', filename=self.system_setup.export_package_changes( self.target_dir ), use_for_bundle=True, compress=True, shasum=False ) self.result.add( key='image_verified', filename=self.system_setup.export_package_verification( self.target_dir ), use_for_bundle=True, compress=False, shasum=False ) return self.result def _operate_on_loop(self) -> None: filesystem = None loop_provider = LoopDevice( self.filename, self.filesystem_setup.get_size_mbytes(), self.blocksize ) loop_provider.create() filesystem = FileSystem.new( self.requested_filesystem, loop_provider, self.root_dir + os.sep, self.filesystem_custom_parameters ) filesystem.create_on_device(self.label) self.root_uuid = loop_provider.get_uuid(loop_provider.get_device()) log.info( f'--> Syncing data to filesystem on {loop_provider.get_device()}' ) filesystem.sync_data( Defaults. get_exclude_list_for_root_data_sync() + Defaults. get_exclude_list_from_custom_exclude_files(self.root_dir) ) def _operate_on_file(self) -> None: default_provider = DeviceProvider() filesystem = FileSystem.new( self.requested_filesystem, default_provider, self.root_dir, self.filesystem_custom_parameters ) filesystem.create_on_file( self.filename, self.label, Defaults.get_exclude_list_for_root_data_sync() )
class TestResult: @fixture(autouse=True) def inject_fixtures(self, caplog): self._caplog = caplog def setup(self): self.xml_state = MagicMock() self.result = Result(self.xml_state) def setup_method(self, cls): self.setup() def test_add(self): assert self.result.add('foo', 'bar') is None result = self.result.get_results() assert isinstance(result, dict) assert result['foo'].filename == 'bar' assert result['foo'].use_for_bundle is True assert result['foo'].compress is False def test_print_results_no_data(self): assert self.result.print_results() is None assert not self._caplog.text def test_print_results_data(self): self.xml_state.get_image_version.return_value = '1.1.1' self.result.add('foo', 'bar') self.result.add_bundle_format('%N') with self._caplog.at_level(logging.INFO): self.result.print_results() @patch('pickle.dump') @patch('simplejson.dumps') def test_dump(self, mock_simplejson_dumps, mock_pickle_dump): m_open = mock_open() with patch('builtins.open', m_open, create=True): assert self.result.dump('kiwi.result') is None assert m_open.call_args_list == [ call('kiwi.result', 'wb'), call('kiwi.result.json', 'w') ] mock_pickle_dump.assert_called_once_with(self.result, m_open.return_value) mock_simplejson_dumps.assert_called_once_with(self.result.result_files, sort_keys=True, indent=4) @patch('pickle.dump') def test_dump_failed(self, mock_pickle_dump): mock_pickle_dump.side_effect = Exception with patch('builtins.open'): with raises(KiwiResultError): self.result.dump('kiwi.result') @patch('pickle.load') @patch('os.path.exists') def test_load(self, mock_exists, mock_pickle_load): mock_exists.return_value = True m_open = mock_open() mock_pickle_load.return_value = Result with patch('builtins.open', m_open, create=True): assert Result.load('kiwi.result') is Result m_open.assert_called_once_with('kiwi.result', 'rb') mock_pickle_load.assert_called_once_with(m_open.return_value) @patch('os.path.exists') def test_load_result_not_present(self, mock_exists): mock_exists.return_value = False with raises(KiwiResultError): Result.load('kiwi.result') @patch('pickle.load') @patch('os.path.exists') def test_load_failed(self, mock_exists, mock_pickle_load): mock_exists.return_value = True mock_pickle_load.side_effect = Exception with raises(KiwiResultError): Result.load('kiwi.result') @patch('os.path.getsize') def test_build_constraint(self, mock_getsize): mock_getsize.return_value = 524288000 assert self.result.verify_image_size(524288000, 'foo') is None @patch('os.path.getsize') def test_build_constraint_failure(self, mock_getsize): mock_getsize.return_value = 524288000 with raises(KiwiResultError): self.result.verify_image_size(524287999, 'foo')
class KisBuilder: """ **Filesystem based image builder.** :param object xml_state: instance of :class:`XMLState` :param str target_dir: target directory path name :param str root_dir: system image root directory :param dict custom_args: Custom processing arguments defined as hash keys: * signing_keys: list of package signing keys * xz_options: string of XZ compression parameters """ def __init__( self, xml_state: XMLState, target_dir: str, root_dir: str, custom_args: Dict = None ): self.target_dir = target_dir self.compressed = xml_state.build_type.get_compressed() self.xen_server = xml_state.is_xen_server() self.custom_cmdline = xml_state.build_type.get_kernelcmdline() self.filesystem = FileSystemBuilder( xml_state, target_dir, root_dir + '/' ) if xml_state.build_type.get_filesystem() else None self.system_setup = SystemSetup( xml_state=xml_state, root_dir=root_dir ) self.initrd_system = xml_state.get_initrd_system() self.boot_signing_keys = custom_args['signing_keys'] if custom_args \ and 'signing_keys' in custom_args else None self.xz_options = custom_args['xz_options'] if custom_args \ and 'xz_options' in custom_args else None self.boot_image_task = BootImage.new( xml_state, target_dir, root_dir, signing_keys=self.boot_signing_keys ) self.bundle_format = xml_state.get_build_type_bundle_format() self.image_name = ''.join( [ target_dir, '/', xml_state.xml_data.get_name(), '.' + Defaults.get_platform_name(), '-' + xml_state.get_image_version() ] ) self.image: str = '' self.append_file = ''.join([self.image_name, '.append']) self.archive_name = ''.join([self.image_name, '.tar']) self.checksum_name = ''.join([self.image_name, '.md5']) self.kernel_filename: str = '' self.hypervisor_filename: str = '' self.result = Result(xml_state) self.runtime_config = RuntimeConfig() if not self.boot_image_task.has_initrd_support(): log.warning('Building without initrd support !') def create(self) -> Result: """ Build a component image consisting out of a boot image(initrd) plus its appropriate kernel files and the root filesystem image with a checksum. Image types which triggers this builder are: * image="kis" * image="pxe" :raises KiwiKisBootImageError: if no kernel or hipervisor is found in boot image tree :return: result :rtype: instance of :class:`Result` """ if self.filesystem: log.info('Creating root filesystem image') self.filesystem.create() os.rename( self.filesystem.filename, self.image_name ) self.image = self.image_name if self.compressed: log.info('xz compressing root filesystem image') compress = Compress(self.image) compress.xz(self.xz_options) self.image = compress.compressed_filename log.info('Creating root filesystem MD5 checksum') checksum = Checksum(self.image) checksum.md5(self.checksum_name) # prepare initrd if self.boot_image_task.has_initrd_support(): log.info('Creating boot image') self.boot_image_task.prepare() # export modprobe configuration to boot image self.system_setup.export_modprobe_setup( self.boot_image_task.boot_root_directory ) # extract kernel from boot system kernel = Kernel(self.boot_image_task.boot_root_directory) kernel_data = kernel.get_kernel() if kernel_data: self.kernel_filename = ''.join( [ os.path.basename(self.image_name), '-', kernel_data.version, '.kernel' ] ) kernel.copy_kernel( self.target_dir, self.kernel_filename ) else: raise KiwiKisBootImageError( 'No kernel in boot image tree %s found' % self.boot_image_task.boot_root_directory ) # extract hypervisor from boot(initrd) root system if self.xen_server: hypervisor_data = kernel.get_xen_hypervisor() if hypervisor_data: self.hypervisor_filename = ''.join( [ os.path.basename(self.image_name), '-', hypervisor_data.name ] ) kernel.copy_xen_hypervisor( self.target_dir, self.hypervisor_filename ) self.result.add( key='xen_hypervisor', filename=self.target_dir + '/' + self.hypervisor_filename, use_for_bundle=True, compress=False, shasum=True ) else: raise KiwiKisBootImageError( 'No hypervisor in boot image tree %s found' % self.boot_image_task.boot_root_directory ) # create initrd if self.boot_image_task.has_initrd_support(): self.boot_image_task.create_initrd() # create append information # this information helps to configure the deployment infrastructure if self.filesystem and self.filesystem.root_uuid \ and self.initrd_system == 'dracut': cmdline = 'root=UUID={}'.format(self.filesystem.root_uuid) if self.custom_cmdline: cmdline += ' {}'.format(self.custom_cmdline) with open(self.append_file, 'w') as append: append.write(cmdline) # put results into a tarball if not self.xz_options: self.xz_options = Defaults.get_xz_compression_options() kis_tarball_files = [ self.kernel_filename, os.path.basename(self.checksum_name), ] if self.boot_image_task.initrd_filename: kis_tarball_files.append( os.path.basename(self.boot_image_task.initrd_filename) ) if self.image: kis_tarball_files.append(os.path.basename(self.image)) if self.filesystem and self.filesystem.root_uuid \ and self.initrd_system == 'dracut': kis_tarball_files.append(os.path.basename(self.append_file)) kis_tarball = ArchiveTar( self.archive_name, create_from_file_list=True, file_list=kis_tarball_files ) if self.compressed: self.archive_name = kis_tarball.create(self.target_dir) else: self.archive_name = kis_tarball.create_xz_compressed( self.target_dir, xz_options=self.xz_options ) Result.verify_image_size( self.runtime_config.get_max_size_constraint(), self.archive_name ) # store image bundle_format in result if self.bundle_format: self.result.add_bundle_format(self.bundle_format) # # store archive file name in result self.result.add( key='kis_archive', filename=self.archive_name, use_for_bundle=True, compress=self.runtime_config.get_bundle_compression( default=False ), shasum=True ) # create image root metadata self.result.add( key='image_packages', filename=self.system_setup.export_package_list( self.target_dir ), use_for_bundle=True, compress=False, shasum=False ) self.result.add( key='image_changes', filename=self.system_setup.export_package_changes( self.target_dir ), use_for_bundle=True, compress=True, shasum=False ) self.result.add( key='image_verified', filename=self.system_setup.export_package_verification( self.target_dir ), use_for_bundle=True, compress=False, shasum=False ) return self.result
class DiskBuilder: """ **Disk image builder** :param object xml_state: Instance of :class:`XMLState` :param str target_dir: Target directory path name :param str root_dir: Root directory path name :param dict custom_args: Custom processing arguments defined as hash keys: * signing_keys: list of package signing keys * xz_options: string of XZ compression parameters """ def __init__(self, xml_state: XMLState, target_dir: str, root_dir: str, custom_args: Dict = None): self.arch = Defaults.get_platform_name() self.root_dir = root_dir self.target_dir = target_dir self.xml_state = xml_state self.spare_part_mbsize = xml_state.get_build_type_spare_part_size() self.spare_part_fs = xml_state.build_type.get_spare_part_fs() self.spare_part_is_last = xml_state.build_type.get_spare_part_is_last() self.spare_part_mountpoint = \ xml_state.build_type.get_spare_part_mountpoint() self.persistency_type = xml_state.build_type.get_devicepersistency() self.root_filesystem_is_overlay = xml_state.build_type.get_overlayroot( ) self.custom_root_mount_args = xml_state.get_fs_mount_option_list() self.custom_root_creation_args = xml_state.get_fs_create_option_list() self.build_type_name = xml_state.get_build_type_name() self.image_format = xml_state.build_type.get_format() self.install_iso = xml_state.build_type.get_installiso() self.install_stick = xml_state.build_type.get_installstick() self.install_pxe = xml_state.build_type.get_installpxe() self.blocksize = xml_state.build_type.get_target_blocksize() self.volume_manager_name = xml_state.get_volume_management() self.volumes = xml_state.get_volumes() self.custom_partitions = xml_state.get_partitions() self.volume_group_name = xml_state.get_volume_group_name() self.mdraid = xml_state.build_type.get_mdraid() self.hybrid_mbr = xml_state.build_type.get_gpt_hybrid_mbr() self.force_mbr = xml_state.build_type.get_force_mbr() self.luks = xml_state.build_type.get_luks() self.luks_os = xml_state.build_type.get_luksOS() self.xen_server = xml_state.is_xen_server() self.requested_filesystem = xml_state.build_type.get_filesystem() self.requested_boot_filesystem = \ xml_state.build_type.get_bootfilesystem() self.bootloader = xml_state.get_build_type_bootloader_name() self.initrd_system = xml_state.get_initrd_system() self.target_removable = xml_state.build_type.get_target_removable() self.root_filesystem_is_multipath = \ xml_state.get_oemconfig_oem_multipath_scan() self.disk_resize_requested = \ xml_state.get_oemconfig_oem_resize() self.swap_mbytes = xml_state.get_oemconfig_swap_mbytes() self.disk_setup = DiskSetup(xml_state, root_dir) self.unpartitioned_bytes = \ xml_state.get_build_type_unpartitioned_bytes() self.custom_args = custom_args self.signing_keys = None if custom_args and 'signing_keys' in custom_args: self.signing_keys = custom_args['signing_keys'] self.boot_image = BootImage.new(xml_state, target_dir, root_dir, signing_keys=self.signing_keys) self.firmware = FirmWare(xml_state) self.system_setup = SystemSetup(xml_state=xml_state, root_dir=self.root_dir) self.bundle_format = xml_state.get_build_type_bundle_format() self.diskname = ''.join([ target_dir, '/', xml_state.xml_data.get_name(), '.' + self.arch, '-' + xml_state.get_image_version(), '.raw' ]) self.boot_is_crypto = True if self.luks and not \ self.disk_setup.need_boot_partition() else False self.install_media = self._install_image_requested() self.fstab = Fstab() # result store self.result = Result(xml_state) self.runtime_config = RuntimeConfig() if not self.boot_image.has_initrd_support(): log.warning('Building without initrd support !') def create(self) -> Result: """ Build a bootable disk image and optional installation image The installation image is a bootable hybrid ISO image which embeds the disk image and an image installer Image types which triggers this builder are: * image="oem" :return: result :rtype: instance of :class:`Result` """ result = self.create_disk() result = self.create_install_media(result) self.append_unpartitioned_space() return self.create_disk_format(result) def create_disk(self) -> Result: """ Build a bootable raw disk image :raises KiwiInstallMediaError: if install media is required and image type is not oem :raises KiwiVolumeManagerSetupError: root overlay at the same time volumes are defined is not supported :return: result :rtype: instance of :class:`Result` """ # an instance of a class with the sync_data capability # representing the entire image system except for the boot/ area # which could live on another part of the disk system: Any = None # an instance of a class with the sync_data capability # representing the boot/ area of the disk if not part of # self.system system_boot: Optional[FileSystemBase] = None # an instance of a class with the sync_data capability # representing the boot/efi area of the disk system_efi: Optional[FileSystemBase] = None # an instance of a class with the sync_data capability # representing the spare_part_mountpoint area of the disk system_spare: Optional[FileSystemBase] = None # a list of instances with the sync_data capability # representing the custom partitions area of the disk system_custom_parts: List[FileSystemBase] = [] if self.install_media and self.build_type_name != 'oem': raise KiwiInstallMediaError( 'Install media requires oem type setup, got {0}'.format( self.build_type_name)) if self.root_filesystem_is_overlay and self.volume_manager_name: raise KiwiVolumeManagerSetupError( 'Volume management together with root overlay is not supported' ) # setup recovery archive, cleanup and create archive if requested self.system_setup.create_recovery_archive() # prepare initrd if self.boot_image.has_initrd_support(): log.info('Preparing boot system') self.boot_image.prepare() # precalculate needed disk size disksize_mbytes = self.disk_setup.get_disksize_mbytes() # create the disk log.info('Creating raw disk image %s', self.diskname) loop_provider = LoopDevice(self.diskname, disksize_mbytes, self.blocksize) loop_provider.create() disk = Disk(self.firmware.get_partition_table_type(), loop_provider, self.xml_state.get_disk_start_sector()) # create the bootloader instance if self.bootloader != 'custom': self.bootloader_config = BootLoaderConfig.new( self.bootloader, self.xml_state, root_dir=self.root_dir, boot_dir=self.root_dir, custom_args={ 'targetbase': loop_provider.get_device(), 'grub_directory_name': Defaults.get_grub_boot_directory_name(self.root_dir), 'crypto_disk': True if self.luks is not None else False, 'boot_is_crypto': self.boot_is_crypto }) # create disk partitions and instance device map device_map = self._build_and_map_disk_partitions(disk, disksize_mbytes) # create raid on current root device if requested raid_root = None if self.mdraid: raid_root = RaidDevice(device_map['root']) raid_root.create_degraded_raid(raid_level=self.mdraid) device_map['root'] = raid_root.get_device() disk.public_partition_id_map['kiwi_RaidPart'] = \ disk.public_partition_id_map['kiwi_RootPart'] disk.public_partition_id_map['kiwi_RaidDev'] = \ device_map['root'].get_device() # create luks on current root device if requested luks_root = None if self.luks is not None: luks_root = LuksDevice(device_map['root']) self.luks_boot_keyname = '/.root.keyfile' self.luks_boot_keyfile = ''.join( [self.root_dir, self.luks_boot_keyname]) # use LUKS key file for the following conditions: # 1. /boot is encrypted # In this case grub needs to read from LUKS via the # cryptodisk module which at the moment always asks # for the passphrase even when empty. The keyfile # setup makes sure only one interaction on the grub # stage is needed # 2. LUKS passphrase is configured as empty string # In this case the keyfile allows to open the # LUKS pool without asking # luks_need_keyfile = \ True if self.boot_is_crypto or self.luks == '' else False luks_root.create_crypto_luks( passphrase=self.luks, os=self.luks_os, keyfile=self.luks_boot_keyfile if luks_need_keyfile else '') if luks_need_keyfile: self.luks_boot_keyfile_setup = ''.join( [self.root_dir, '/etc/dracut.conf.d/99-luks-boot.conf']) self.boot_image.write_system_config_file( config={'install_items': [self.luks_boot_keyname]}, config_file=self.luks_boot_keyfile_setup) self.boot_image.include_file( os.sep + os.path.basename(self.luks_boot_keyfile)) device_map['luks_root'] = device_map['root'] device_map['root'] = luks_root.get_device() # create spare filesystem on spare partition if present system_spare = self._build_spare_filesystem(device_map) system_custom_parts = self._build_custom_parts_filesystem( device_map, self.custom_partitions) # create filesystems on boot partition(s) if any system_boot, system_efi = self._build_boot_filesystems(device_map) # create volumes and filesystems for root system if self.volume_manager_name: volume_manager_custom_parameters = { 'fs_mount_options': self.custom_root_mount_args, 'fs_create_options': self.custom_root_creation_args, 'root_label': self.disk_setup.get_root_label(), 'root_is_snapshot': self.xml_state.build_type.get_btrfs_root_is_snapshot(), 'root_is_readonly_snapshot': self.xml_state.build_type.get_btrfs_root_is_readonly_snapshot( ), 'quota_groups': self.xml_state.build_type.get_btrfs_quota_groups(), 'resize_on_boot': self.disk_resize_requested } volume_manager = VolumeManager.new( self.volume_manager_name, device_map, self.root_dir + '/', self.volumes, volume_manager_custom_parameters) volume_manager.setup(self.volume_group_name) volume_manager.create_volumes(self.requested_filesystem) volume_manager.mount_volumes() system = volume_manager device_map['root'] = volume_manager.get_device().get('root') device_map['swap'] = volume_manager.get_device().get('swap') else: log.info('Creating root(%s) filesystem on %s', self.requested_filesystem, device_map['root'].get_device()) filesystem_custom_parameters = { 'mount_options': self.custom_root_mount_args, 'create_options': self.custom_root_creation_args } filesystem = FileSystem.new(self.requested_filesystem, device_map['root'], self.root_dir + '/', filesystem_custom_parameters) filesystem.create_on_device(label=self.disk_setup.get_root_label()) system = filesystem # create swap on current root device if requested if self.swap_mbytes: swap = FileSystem.new('swap', device_map['swap']) swap.create_on_device(label='SWAP') # store root partition/filesystem uuid for profile self._preserve_root_partition_uuid(device_map) self._preserve_root_filesystem_uuid(device_map) # create a random image identifier self.mbrid = SystemIdentifier() self.mbrid.calculate_id() # create first stage metadata to boot image self._write_partition_id_config_to_boot_image(disk) self._write_recovery_metadata_to_boot_image() self._write_raid_config_to_boot_image(raid_root) self._write_generic_fstab_to_boot_image(device_map, system) self.system_setup.export_modprobe_setup( self.boot_image.boot_root_directory) # create first stage metadata to system image self._write_image_identifier_to_system_image() self._write_crypttab_to_system_image(luks_root) self._write_generic_fstab_to_system_image(device_map, system) if self.initrd_system == 'dracut': if self.root_filesystem_is_multipath is False: self.boot_image.omit_module('multipath') if self.root_filesystem_is_overlay: self.boot_image.include_module('kiwi-overlay') self.boot_image.write_system_config_file( config={'modules': ['kiwi-overlay']}) if self.disk_resize_requested: self.boot_image.include_module('kiwi-repart') # create initrd if self.boot_image.has_initrd_support(): self.boot_image.create_initrd(self.mbrid) # create second stage metadata to system image self._copy_first_boot_files_to_system_image() self._write_bootloader_meta_data_to_system_image(device_map, disk) self.mbrid.write_to_disk(disk.storage_provider) # set SELinux file security contexts if context exists self._setup_selinux_file_contexts() # syncing system data to disk image self._sync_system_to_image(device_map, system, system_boot, system_efi, system_spare, system_custom_parts) # run post sync script hook if self.system_setup.script_exists(defaults.POST_DISK_SYNC_SCRIPT): disk_system = SystemSetup(self.xml_state, system.get_mountpoint()) disk_system.import_description() disk_system.call_disk_script() disk_system.cleanup() # install boot loader self._install_bootloader(device_map, disk, system) # set root filesystem properties self._setup_property_root_is_readonly_snapshot(system) Result.verify_image_size(self.runtime_config.get_max_size_constraint(), self.diskname) # store image bundle_format in result if self.bundle_format: self.result.add_bundle_format(self.bundle_format) # store image file name in result compression = self.runtime_config.get_bundle_compression(default=True) if self.luks is not None: compression = False self.result.add( key='disk_image', filename=self.diskname, use_for_bundle=True if not self.image_format else False, compress=compression, shasum=True) # create image root metadata self.result.add(key='image_packages', filename=self.system_setup.export_package_list( self.target_dir), use_for_bundle=True, compress=False, shasum=False) self.result.add(key='image_changes', filename=self.system_setup.export_package_changes( self.target_dir), use_for_bundle=True, compress=True, shasum=False) self.result.add(key='image_verified', filename=self.system_setup.export_package_verification( self.target_dir), use_for_bundle=True, compress=False, shasum=False) return self.result def create_disk_format(self, result_instance: Result) -> Result: """ Create a bootable disk format from a previously created raw disk image :param object result_instance: instance of :class:`Result` :return: updated result_instance :rtype: instance of :class:`Result` """ if self.image_format: log.info('Creating %s Disk Format', self.image_format) disk_format = DiskFormat.new(self.image_format, self.xml_state, self.root_dir, self.target_dir) disk_format.create_image_format() disk_format.store_to_result(result_instance) return result_instance def append_unpartitioned_space(self) -> None: """ Extends the raw disk if an unpartitioned area is specified """ if self.unpartitioned_bytes: log.info('Expanding disk with %d bytes of unpartitioned space', self.unpartitioned_bytes) disk_format = DiskFormat.new('raw', self.xml_state, self.root_dir, self.target_dir) disk_format.resize_raw_disk(self.unpartitioned_bytes, append=True) firmware = FirmWare(self.xml_state) loop_provider = LoopDevice(disk_format.diskname) loop_provider.create(overwrite=False) partitioner = Partitioner.new(firmware.get_partition_table_type(), loop_provider) partitioner.resize_table() def create_install_media(self, result_instance: Result) -> Result: """ Build an installation image. The installation image is a bootable hybrid ISO image which embeds the raw disk image and an image installer :param object result_instance: instance of :class:`Result` :return: updated result_instance with installation media :rtype: instance of :class:`Result` """ if self.install_media: boot_image = None if self.initrd_system == 'kiwi': boot_image = self.boot_image install_image = InstallImageBuilder(self.xml_state, self.root_dir, self.target_dir, boot_image, self.custom_args) if self.install_iso or self.install_stick: log.info('Creating hybrid ISO installation image') install_image.create_install_iso() result_instance.add(key='installation_image', filename=install_image.isoname, use_for_bundle=True, compress=False, shasum=True) if self.install_pxe: log.info('Creating PXE installation archive') install_image.create_install_pxe_archive() result_instance.add(key='installation_pxe_archive', filename=install_image.pxetarball, use_for_bundle=True, compress=False, shasum=True) return result_instance def _setup_selinux_file_contexts(self) -> None: security_context = '/etc/selinux/targeted/contexts/files/file_contexts' if os.path.exists(self.root_dir + security_context): self.system_setup.set_selinux_file_contexts(security_context) def _install_image_requested(self) -> bool: return bool(self.install_iso or self.install_stick or self.install_pxe) def _get_exclude_list_for_root_data_sync(self, device_map: Dict) -> list: exclude_list = Defaults.\ get_exclude_list_for_root_data_sync() + Defaults.\ get_exclude_list_from_custom_exclude_files(self.root_dir) if 'spare' in device_map and self.spare_part_mountpoint: exclude_list.append('{0}/*'.format( self.spare_part_mountpoint.lstrip(os.sep))) exclude_list.append('{0}/.*'.format( self.spare_part_mountpoint.lstrip(os.sep))) if 'boot' in device_map and 's390' in self.arch: exclude_list.append('boot/zipl/*') exclude_list.append('boot/zipl/.*') elif 'boot' in device_map: exclude_list.append('boot/*') exclude_list.append('boot/.*') if 'efi' in device_map: exclude_list.append('boot/efi/*') exclude_list.append('boot/efi/.*') if self.custom_partitions: for map_name in sorted(self.custom_partitions.keys()): if map_name in device_map: mountpoint = os.path.normpath( self.custom_partitions[map_name].mountpoint).lstrip( os.sep) exclude_list.append(f'{mountpoint}/*') exclude_list.append(f'{mountpoint}/.*') return exclude_list @staticmethod def _get_exclude_list_for_boot_data_sync() -> list: return ['efi/*'] def _build_custom_parts_filesystem( self, device_map: Dict, custom_partitions: Dict['str', ptable_entry_type]) -> List[FileSystemBase]: filesystem_list = [] if custom_partitions: for map_name in sorted(custom_partitions.keys()): if map_name in device_map: ptable_entry = custom_partitions[map_name] filesystem = FileSystem.new( ptable_entry.filesystem, device_map[map_name], f'{self.root_dir}{ptable_entry.mountpoint}/') filesystem.create_on_device(label=map_name.upper()) filesystem_list.append(filesystem) return filesystem_list def _build_spare_filesystem(self, device_map: Dict) -> Optional[FileSystemBase]: if 'spare' in device_map and self.spare_part_fs: spare_part_data_path = None spare_part_custom_parameters = { 'fs_attributes': self.xml_state.get_build_type_spare_part_fs_attributes() } if self.spare_part_mountpoint: spare_part_data_path = self.root_dir + '{0}/'.format( self.spare_part_mountpoint) filesystem = FileSystem.new(self.spare_part_fs, device_map['spare'], spare_part_data_path, spare_part_custom_parameters) filesystem.create_on_device(label='SPARE') return filesystem return None def _build_boot_filesystems( self, device_map: Dict ) -> Tuple[Optional[FileSystemBase], Optional[FileSystemBase]]: system_boot = None system_efi = None if 'efi' in device_map: log.info('Creating EFI(fat16) filesystem on %s', device_map['efi'].get_device()) filesystem = FileSystem.new('fat16', device_map['efi'], self.root_dir + '/boot/efi/') filesystem.create_on_device(label=self.disk_setup.get_efi_label()) system_efi = filesystem if 'boot' in device_map: boot_filesystem = self.requested_boot_filesystem if not boot_filesystem: boot_filesystem = self.requested_filesystem boot_directory = self.root_dir + '/boot/' if 's390' in self.arch: boot_directory = self.root_dir + '/boot/zipl/' log.info('Creating boot(%s) filesystem on %s', boot_filesystem, device_map['boot'].get_device()) filesystem = FileSystem.new(boot_filesystem, device_map['boot'], boot_directory) filesystem.create_on_device(label=self.disk_setup.get_boot_label()) system_boot = filesystem return system_boot, system_efi def _build_and_map_disk_partitions(self, disk: Disk, disksize_mbytes: float) -> Dict: disk.wipe() disksize_used_mbytes = 0 if self.firmware.legacy_bios_mode(): log.info('--> creating EFI CSM(legacy bios) partition') partition_mbsize = self.firmware.get_legacy_bios_partition_size() disk.create_efi_csm_partition(partition_mbsize) disksize_used_mbytes += partition_mbsize if self.firmware.efi_mode(): log.info('--> creating EFI partition') partition_mbsize = self.firmware.get_efi_partition_size() disk.create_efi_partition(partition_mbsize) disksize_used_mbytes += partition_mbsize if self.firmware.ofw_mode(): log.info('--> creating PReP partition') partition_mbsize = self.firmware.get_prep_partition_size() disk.create_prep_partition(partition_mbsize) disksize_used_mbytes += partition_mbsize if self.disk_setup.need_boot_partition(): log.info('--> creating boot partition') partition_mbsize = self.disk_setup.boot_partition_size() disk.create_boot_partition(partition_mbsize) disksize_used_mbytes += partition_mbsize if self.swap_mbytes: if not self.volume_manager_name or self.volume_manager_name != 'lvm': log.info('--> creating SWAP partition') disk.create_swap_partition(f'{self.swap_mbytes}') disksize_used_mbytes += self.swap_mbytes if self.custom_partitions: log.info('--> creating custom partition(s): {0}'.format( sorted(self.custom_partitions.keys()))) disk.create_custom_partitions(self.custom_partitions) if self.spare_part_mbsize and not self.spare_part_is_last: log.info('--> creating spare partition') disk.create_spare_partition(f'{self.spare_part_mbsize}') if self.root_filesystem_is_overlay: log.info('--> creating readonly root partition') squashed_root_file = Temporary().new_file() squashed_root = FileSystemSquashFs( device_provider=DeviceProvider(), root_dir=self.root_dir, custom_args={ 'compression': self.xml_state.build_type.get_squashfscompression() }) squashed_root.create_on_file( filename=squashed_root_file.name, exclude=[Defaults.get_shared_cache_location()]) squashed_rootfs_mbsize = int( os.path.getsize(squashed_root_file.name) / 1048576) + Defaults.get_min_partition_mbytes() disk.create_root_readonly_partition(squashed_rootfs_mbsize) disksize_used_mbytes += squashed_rootfs_mbsize if self.spare_part_mbsize and self.spare_part_is_last: rootfs_mbsize = disksize_mbytes - disksize_used_mbytes - \ self.spare_part_mbsize - Defaults.get_min_partition_mbytes() else: rootfs_mbsize = 'all_free' if self.volume_manager_name and self.volume_manager_name == 'lvm': log.info('--> creating LVM root partition') disk.create_root_lvm_partition(rootfs_mbsize) elif self.mdraid: log.info('--> creating mdraid root partition') disk.create_root_raid_partition(rootfs_mbsize) else: log.info('--> creating root partition') disk.create_root_partition(rootfs_mbsize) if self.spare_part_mbsize and self.spare_part_is_last: log.info('--> creating spare partition') disk.create_spare_partition('all_free') if self.firmware.bios_mode(): log.info('--> setting active flag to primary boot partition') disk.activate_boot_partition() if self.firmware.ofw_mode(): log.info('--> setting active flag to primary PReP partition') disk.activate_boot_partition() if self.firmware.efi_mode(): if self.force_mbr: log.info('--> converting partition table to MBR') disk.create_mbr() elif self.hybrid_mbr: log.info('--> converting partition table to hybrid GPT/MBR') disk.create_hybrid_mbr() disk.map_partitions() return disk.get_device() def _write_partition_id_config_to_boot_image(self, disk: Disk) -> None: log.info('Creating config.partids in boot system') filename = ''.join( [self.boot_image.boot_root_directory, '/config.partids']) partition_id_map = disk.get_public_partition_id_map() with open(filename, 'w') as partids: for id_name, id_value in list(partition_id_map.items()): partids.write('{0}="{1}"{2}'.format(id_name, id_value, os.linesep)) self.boot_image.include_file(os.sep + os.path.basename(filename)) def _write_raid_config_to_boot_image( self, raid_root: Optional[RaidDevice]) -> None: if raid_root is not None: log.info('Creating etc/mdadm.conf in boot system') filename = ''.join( [self.boot_image.boot_root_directory, '/etc/mdadm.conf']) raid_root.create_raid_config(filename) self.boot_image.include_file( os.sep + os.sep.join(['etc', os.path.basename(filename)])) def _write_crypttab_to_system_image( self, luks_root: Optional[LuksDevice]) -> None: if luks_root is not None: log.info('Creating etc/crypttab') filename = ''.join([self.root_dir, '/etc/crypttab']) luks_root.create_crypttab(filename) self.boot_image.include_file( os.sep + os.sep.join(['etc', os.path.basename(filename)])) def _write_generic_fstab_to_system_image(self, device_map: Dict, system: Any) -> None: log.info('Creating generic system etc/fstab') self._write_generic_fstab(device_map, self.system_setup, system) def _write_generic_fstab_to_boot_image(self, device_map: Dict, system: Any) -> None: if self.initrd_system == 'kiwi': log.info('Creating generic boot image etc/fstab') self._write_generic_fstab(device_map, self.boot_image.setup, system) def _write_generic_fstab(self, device_map: Dict, setup: SystemSetup, system: Any) -> None: root_is_snapshot = \ self.xml_state.build_type.get_btrfs_root_is_snapshot() root_is_readonly_snapshot = \ self.xml_state.build_type.get_btrfs_root_is_readonly_snapshot() fs_check_interval = '0 1' custom_root_mount_args = list(self.custom_root_mount_args) if root_is_snapshot and root_is_readonly_snapshot: custom_root_mount_args += ['ro'] fs_check_interval = '0 0' self._add_fstab_entry(device_map['root'].get_device(), '/', custom_root_mount_args, fs_check_interval) if device_map.get('boot'): if 's390' in self.arch: boot_mount_point = '/boot/zipl' else: boot_mount_point = '/boot' self._add_fstab_entry(device_map['boot'].get_device(), boot_mount_point) if device_map.get('efi'): self._add_fstab_entry(device_map['efi'].get_device(), '/boot/efi') if self.volume_manager_name: volume_fstab_entries = system.get_fstab(self.persistency_type, self.requested_filesystem) for volume_fstab_entry in volume_fstab_entries: self.fstab.add_entry(volume_fstab_entry) if device_map.get('spare') and \ self.spare_part_fs and self.spare_part_mountpoint: self._add_fstab_entry(device_map['spare'].get_device(), self.spare_part_mountpoint) if device_map.get('swap'): self._add_fstab_entry(device_map['swap'].get_device(), 'swap') if self.custom_partitions: for map_name in sorted(self.custom_partitions.keys()): if device_map.get(map_name): self._add_fstab_entry( device_map[map_name].get_device(), self.custom_partitions[map_name].mountpoint) setup.create_fstab(self.fstab) def _add_fstab_entry(self, device: str, mount_point: str, options: List = None, check: str = '0 0') -> None: if not options: options = ['defaults'] block_operation = BlockID(device) if self.volume_manager_name and self.volume_manager_name == 'lvm' \ and (mount_point == '/' or mount_point == 'swap'): fstab_entry = ' '.join([ device, mount_point, block_operation.get_filesystem(), ','.join(options), check ]) else: blkid_type = 'LABEL' if self.persistency_type == 'by-label' \ else 'UUID' device_id = block_operation.get_blkid(blkid_type) fstab_entry = ' '.join([ blkid_type + '=' + device_id, mount_point, block_operation.get_filesystem(), ','.join(options), check ]) self.fstab.add_entry(fstab_entry) def _preserve_root_partition_uuid(self, device_map: Dict) -> None: block_operation = BlockID(device_map['root'].get_device()) partition_uuid = block_operation.get_blkid('PARTUUID') if partition_uuid: self.xml_state.set_root_partition_uuid(partition_uuid) def _preserve_root_filesystem_uuid(self, device_map: Dict) -> None: block_operation = BlockID(device_map['root'].get_device()) rootfs_uuid = block_operation.get_blkid('UUID') if rootfs_uuid: self.xml_state.set_root_filesystem_uuid(rootfs_uuid) def _write_image_identifier_to_system_image(self) -> None: log.info('Creating image identifier: %s', self.mbrid.get_id()) self.mbrid.write(self.root_dir + '/boot/mbrid') def _write_recovery_metadata_to_boot_image(self) -> None: if os.path.exists(self.root_dir + '/recovery.partition.size'): log.info('Copying recovery metadata to boot image') recovery_metadata = ''.join( [self.root_dir, '/recovery.partition.size']) Command.run( ['cp', recovery_metadata, self.boot_image.boot_root_directory]) self.boot_image.include_file(os.sep + os.path.basename(recovery_metadata)) def _write_bootloader_meta_data_to_system_image(self, device_map: Dict, disk: Disk) -> None: if self.bootloader != 'custom': log.info('Creating %s bootloader configuration', self.bootloader) boot_options = [] if self.mdraid: boot_options.append('rd.auto') root_device = device_map['root'] boot_device = root_device if 'boot' in device_map: boot_device = device_map['boot'] boot_uuid = disk.get_uuid(boot_device.get_device()) boot_uuid_unmapped = disk.get_uuid( device_map['luks_root'].get_device( )) if self.luks else boot_uuid self.bootloader_config.setup_disk_boot_images(boot_uuid_unmapped) self.bootloader_config.write_meta_data( root_device=device_map['root'].get_device(), boot_options=' '.join(boot_options)) log.info('Creating config.bootoptions') filename = ''.join( [self.boot_image.boot_root_directory, '/config.bootoptions']) kexec_boot_options = ' '.join([ self.bootloader_config.get_boot_cmdline( device_map['root'].get_device()) ] + boot_options) with open(filename, 'w') as boot_optionsfp: boot_optionsfp.write('{0}{1}'.format(kexec_boot_options, os.linesep)) partition_id_map = disk.get_public_partition_id_map() boot_partition_id = partition_id_map['kiwi_RootPart'] if 'kiwi_BootPart' in partition_id_map: boot_partition_id = partition_id_map['kiwi_BootPart'] self.system_setup.call_edit_boot_config_script( self.requested_filesystem, int(boot_partition_id)) def _sync_system_to_image( self, device_map: Dict, system: Any, system_boot: Optional[FileSystemBase], system_efi: Optional[FileSystemBase], system_spare: Optional[FileSystemBase], system_custom_parts: List[FileSystemBase]) -> None: log.info('Syncing system to image') if system_spare: log.info('--> Syncing spare partition data') system_spare.sync_data() for system_custom_part in system_custom_parts: log.info('--> Syncing custom partition(s) data') system_custom_part.sync_data() if system_efi: log.info('--> Syncing EFI boot data to EFI partition') system_efi.sync_data() if system_boot: log.info('--> Syncing boot data at extra partition') system_boot.sync_data(self._get_exclude_list_for_boot_data_sync()) log.info('--> Syncing root filesystem data') if self.root_filesystem_is_overlay: squashed_root_file = Temporary().new_file() squashed_root = FileSystemSquashFs( device_provider=DeviceProvider(), root_dir=self.root_dir, custom_args={ 'compression': self.xml_state.build_type.get_squashfscompression() }) squashed_root.create_on_file( filename=squashed_root_file.name, exclude=self._get_exclude_list_for_root_data_sync(device_map)) Command.run([ 'dd', 'if=%s' % squashed_root_file.name, 'of=%s' % device_map['readonly'].get_device() ]) else: system.sync_data( self._get_exclude_list_for_root_data_sync(device_map)) def _install_bootloader(self, device_map: Dict, disk, system: Any) -> None: root_device = device_map['root'] boot_device = root_device if 'boot' in device_map: boot_device = device_map['boot'] if 'readonly' in device_map: root_device = device_map['readonly'] custom_install_arguments = { 'boot_device': boot_device.get_device(), 'root_device': root_device.get_device(), 'firmware': self.firmware, 'target_removable': self.target_removable } if 'efi' in device_map: efi_device = device_map['efi'] custom_install_arguments.update( {'efi_device': efi_device.get_device()}) if 'prep' in device_map: prep_device = device_map['prep'] custom_install_arguments.update( {'prep_device': prep_device.get_device()}) if self.volume_manager_name: system.umount_volumes() custom_install_arguments.update( {'system_volumes': system.get_volumes()}) if self.bootloader != 'custom': # create bootloader config prior bootloader installation self.bootloader_config.setup_disk_image_config( boot_options=custom_install_arguments) if 's390' in self.arch: self.bootloader_config.write() # cleanup bootloader config resources taken prior to next steps del self.bootloader_config log.debug("custom arguments for bootloader installation %s", custom_install_arguments) bootloader = BootLoaderInstall.new(self.bootloader, self.root_dir, disk.storage_provider, custom_install_arguments) if bootloader.install_required(): bootloader.install() bootloader.secure_boot_install() self.system_setup.call_edit_boot_install_script( self.diskname, boot_device.get_device()) def _setup_property_root_is_readonly_snapshot(self, system: Any) -> None: if self.volume_manager_name: root_is_snapshot = \ self.xml_state.build_type.get_btrfs_root_is_snapshot() root_is_readonly_snapshot = \ self.xml_state.build_type.get_btrfs_root_is_readonly_snapshot() if root_is_snapshot and root_is_readonly_snapshot: log.info('Setting root filesystem into read-only mode') system.mount_volumes() system.set_property_readonly_root() system.umount_volumes() def _copy_first_boot_files_to_system_image(self) -> None: boot_names = self.boot_image.get_boot_names() if self.initrd_system == 'kiwi': log.info('Copy boot files to system image') kernel = Kernel(self.boot_image.boot_root_directory) log.info('--> boot image kernel as %s', boot_names.kernel_name) kernel.copy_kernel(self.root_dir, ''.join(['/boot/', boot_names.kernel_name])) if self.xen_server: if kernel.get_xen_hypervisor(): log.info('--> boot image Xen hypervisor as xen.gz') kernel.copy_xen_hypervisor(self.root_dir, '/boot/xen.gz') else: raise KiwiDiskBootImageError( 'No hypervisor in boot image tree %s found' % self.boot_image.boot_root_directory) if self.boot_image.initrd_filename: log.info('--> initrd archive as %s', boot_names.initrd_name) Command.run([ 'mv', self.boot_image.initrd_filename, self.root_dir + ''.join(['/boot/', boot_names.initrd_name]) ])
class LiveImageBuilder: """ **Live image builder** :param object xml_state: instance of :class:`XMLState` :param str target_dir: target directory path name :param str root_dir: root directory path name :param dict custom_args: Custom processing arguments """ def __init__(self, xml_state: XMLState, target_dir: str, root_dir: str, custom_args: Dict = None): self.arch = Defaults.get_platform_name() self.root_dir = root_dir self.target_dir = target_dir self.xml_state = xml_state self.live_type = xml_state.build_type.get_flags() self.volume_id = xml_state.build_type.get_volid() or \ Defaults.get_volume_id() self.mbrid = SystemIdentifier() self.mbrid.calculate_id() self.publisher = xml_state.build_type.get_publisher() or \ Defaults.get_publisher() self.custom_args = custom_args if not self.live_type: self.live_type = Defaults.get_default_live_iso_type() self.boot_image = BootImageDracut(xml_state, target_dir, self.root_dir) self.firmware = FirmWare(xml_state) self.system_setup = SystemSetup(xml_state=xml_state, root_dir=self.root_dir) self.bundle_format = xml_state.get_build_type_bundle_format() self.isoname = ''.join([ target_dir, '/', xml_state.xml_data.get_name(), '.' + Defaults.get_platform_name(), '-' + xml_state.get_image_version(), '.iso' ]) self.result = Result(xml_state) self.runtime_config = RuntimeConfig() def create(self) -> Result: """ Build a bootable hybrid live ISO image Image types which triggers this builder are: * image="iso" :raises KiwiLiveBootImageError: if no kernel or hipervisor is found in boot image tree :return: result :rtype: instance of :class:`Result` """ # media dir to store CD contents self.media_dir = Temporary(prefix='live-media.', path=self.target_dir).new_dir() # unpack cdroot user files to media dir self.system_setup.import_cdroot_files(self.media_dir.name) rootsize = SystemSize(self.media_dir.name) # custom iso metadata log.info('Using following live ISO metadata:') log.info('--> Application id: {0}'.format(self.mbrid.get_id())) log.info('--> Publisher: {0}'.format(self.publisher)) log.info('--> Volume id: {0}'.format(self.volume_id)) custom_iso_args = { 'meta_data': { 'publisher': self.publisher, 'preparer': Defaults.get_preparer(), 'volume_id': self.volume_id, 'mbr_id': self.mbrid.get_id(), 'efi_mode': self.firmware.efi_mode() } } log.info('Setting up live image bootloader configuration') if self.firmware.efi_mode(): # setup bootloader config to boot the ISO via EFI # This also embedds an MBR and the respective BIOS modules # for compat boot. The complete bootloader setup will be # based on grub bootloader_config = BootLoaderConfig.new( 'grub2', self.xml_state, root_dir=self.root_dir, boot_dir=self.media_dir.name, custom_args={ 'grub_directory_name': Defaults.get_grub_boot_directory_name(self.root_dir) }) bootloader_config.setup_live_boot_images(mbrid=self.mbrid, lookup_path=self.root_dir) else: # setup bootloader config to boot the ISO via isolinux. # This allows for booting on x86 platforms in BIOS mode # only. bootloader_config = BootLoaderConfig.new( 'isolinux', self.xml_state, root_dir=self.root_dir, boot_dir=self.media_dir.name) IsoToolsBase.setup_media_loader_directory( self.boot_image.boot_root_directory, self.media_dir.name, bootloader_config.get_boot_theme()) bootloader_config.write_meta_data() bootloader_config.setup_live_image_config(mbrid=self.mbrid) bootloader_config.write() # call custom editbootconfig script if present self.system_setup.call_edit_boot_config_script( filesystem='iso:{0}'.format(self.media_dir.name), boot_part_id=1, working_directory=self.root_dir) # prepare dracut initrd call self.boot_image.prepare() # create dracut initrd for live image log.info('Creating live ISO boot image') live_dracut_modules = Defaults.get_live_dracut_modules_from_flag( self.live_type) live_dracut_modules.append('pollcdrom') for dracut_module in live_dracut_modules: self.boot_image.include_module(dracut_module) self.boot_image.omit_module('multipath') self.boot_image.write_system_config_file( config={ 'modules': live_dracut_modules, 'omit_modules': ['multipath'] }, config_file=self.root_dir + '/etc/dracut.conf.d/02-livecd.conf') self.boot_image.create_initrd(self.mbrid) # setup kernel file(s) and initrd in ISO boot layout log.info('Setting up kernel file(s) and boot image in ISO boot layout') self._setup_live_iso_kernel_and_initrd() # calculate size and decide if we need UDF if rootsize.accumulate_mbyte_file_sizes() > 4096: log.info('ISO exceeds 4G size, using UDF filesystem') custom_iso_args['meta_data']['udf'] = True # pack system into live boot structure as expected by dracut log.info('Packing system into dracut live ISO type: {0}'.format( self.live_type)) root_filesystem = Defaults.get_default_live_iso_root_filesystem() filesystem_custom_parameters = { 'mount_options': self.xml_state.get_fs_mount_option_list(), 'create_options': self.xml_state.get_fs_create_option_list() } filesystem_setup = FileSystemSetup(self.xml_state, self.root_dir) root_image = Temporary().new_file() loop_provider = LoopDevice( root_image.name, filesystem_setup.get_size_mbytes(root_filesystem), self.xml_state.build_type.get_target_blocksize()) loop_provider.create() live_filesystem = FileSystem.new( name=root_filesystem, device_provider=loop_provider, root_dir=self.root_dir + os.sep, custom_args=filesystem_custom_parameters) live_filesystem.create_on_device() log.info('--> Syncing data to {0} root image'.format(root_filesystem)) live_filesystem.sync_data( Defaults.get_exclude_list_for_root_data_sync() + Defaults.get_exclude_list_from_custom_exclude_files(self.root_dir)) live_filesystem.umount() log.info('--> Creating squashfs container for root image') self.live_container_dir = Temporary(prefix='live-container.', path=self.target_dir).new_dir() Path.create(self.live_container_dir.name + '/LiveOS') shutil.copy(root_image.name, self.live_container_dir.name + '/LiveOS/rootfs.img') live_container_image = FileSystem.new( name='squashfs', device_provider=DeviceProvider(), root_dir=self.live_container_dir.name, custom_args={ 'compression': self.xml_state.build_type.get_squashfscompression() }) container_image = Temporary().new_file() live_container_image.create_on_file(container_image.name) Path.create(self.media_dir.name + '/LiveOS') shutil.copy(container_image.name, self.media_dir.name + '/LiveOS/squashfs.img') # create iso filesystem from media_dir log.info('Creating live ISO image') iso_image = FileSystemIsoFs(device_provider=DeviceProvider(), root_dir=self.media_dir.name, custom_args=custom_iso_args) iso_image.create_on_file(self.isoname) # include metadata for checkmedia tool if self.xml_state.build_type.get_mediacheck() is True: Iso.set_media_tag(self.isoname) Result.verify_image_size(self.runtime_config.get_max_size_constraint(), self.isoname) if self.bundle_format: self.result.add_bundle_format(self.bundle_format) self.result.add(key='live_image', filename=self.isoname, use_for_bundle=True, compress=False, shasum=True) self.result.add(key='image_packages', filename=self.system_setup.export_package_list( self.target_dir), use_for_bundle=True, compress=False, shasum=False) self.result.add(key='image_changes', filename=self.system_setup.export_package_changes( self.target_dir), use_for_bundle=True, compress=True, shasum=False) self.result.add(key='image_verified', filename=self.system_setup.export_package_verification( self.target_dir), use_for_bundle=True, compress=False, shasum=False) return self.result def _setup_live_iso_kernel_and_initrd(self) -> None: """ Copy kernel and initrd from the root tree into the iso boot structure """ boot_path = ''.join( [self.media_dir.name, '/boot/', self.arch, '/loader']) Path.create(boot_path) # Move kernel files to iso filesystem structure kernel = Kernel(self.boot_image.boot_root_directory) if kernel.get_kernel(): kernel.copy_kernel(boot_path, '/linux') else: raise KiwiLiveBootImageError( 'No kernel in boot image tree {0} found'.format( self.boot_image.boot_root_directory)) if self.xml_state.is_xen_server(): if kernel.get_xen_hypervisor(): kernel.copy_xen_hypervisor(boot_path, '/xen.gz') else: raise KiwiLiveBootImageError( 'No hypervisor in boot image tree {0} found'.format( self.boot_image.boot_root_directory)) # Move initrd to iso filesystem structure if os.path.exists(self.boot_image.initrd_filename): shutil.move(self.boot_image.initrd_filename, boot_path + '/initrd') else: raise KiwiLiveBootImageError( 'No boot image {0} in boot image tree {1} found'.format( self.boot_image.initrd_filename, self.boot_image.boot_root_directory))