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, target_dir, root_dir, custom_args=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.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.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(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.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() # 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 self.system = None # an instance of a class with the sync_data capability # representing the boot/ area of the disk if not part of # self.system self.system_boot = None # an instance of a class with the sync_data capability # representing the boot/efi area of the disk self.system_efi = None # an instance of a class with the sync_data capability # representing the spare_part_mountpoint area of the disk self.system_spare = None # result store self.result = Result(xml_state) self.runtime_config = RuntimeConfig() def create(self): """ 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" * image="vmx" :return: result :rtype: instance of :class:`Result` """ disk = DiskBuilder(self.xml_state, self.target_dir, self.root_dir, self.custom_args) result = disk.create_disk() # cleanup disk resources taken prior to next steps del disk disk_installer = DiskBuilder(self.xml_state, self.target_dir, self.root_dir) result = disk_installer.create_install_media(result) disk_format = DiskBuilder(self.xml_state, self.target_dir, self.root_dir) disk_format.append_unpartitioned_space() result = disk_format.create_disk_format(result) return result def create_disk(self): # noqa: C901 """ 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` """ if self.install_media and self.build_type_name != 'oem': raise KiwiInstallMediaError( 'Install media requires oem type setup, got %s' % 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 boot(initrd) root system 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) self.loop_provider = LoopDevice(self.diskname, disksize_mbytes, self.blocksize) self.loop_provider.create() self.disk = Disk(self.firmware.get_partition_table_type(), self.loop_provider, self.xml_state.get_disk_start_sector()) # create the bootloader instance self.bootloader_config = BootLoaderConfig( self.bootloader, self.xml_state, root_dir=self.root_dir, boot_dir=self.root_dir, custom_args={ 'targetbase': self.loop_provider.get_device(), 'grub_directory_name': Defaults.get_grub_boot_directory_name(self.root_dir), 'boot_is_crypto': self.boot_is_crypto }) # create disk partitions and instance device map device_map = self._build_and_map_disk_partitions(disksize_mbytes) # create raid on current root device if requested if self.mdraid: self.raid_root = RaidDevice(device_map['root']) self.raid_root.create_degraded_raid(raid_level=self.mdraid) device_map['root'] = self.raid_root.get_device() self.disk.public_partition_id_map['kiwi_RaidPart'] = \ self.disk.public_partition_id_map['kiwi_RootPart'] self.disk.public_partition_id_map['kiwi_RaidDev'] = \ device_map['root'].get_device() # create luks on current root device if requested if self.luks: self.luks_root = LuksDevice(device_map['root']) self.luks_boot_keyname = '/.root.keyfile' self.luks_boot_keyfile = ''.join( [self.root_dir, self.luks_boot_keyname]) self.luks_root.create_crypto_luks(passphrase=self.luks, os=self.luks_os, keyfile=self.luks_boot_keyfile if self.boot_is_crypto else None) if self.boot_is_crypto: 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'] = self.luks_root.get_device() # create spare filesystem on spare partition if present self._build_spare_filesystem(device_map) # create filesystems on boot partition(s) if any 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(), 'image_type': self.xml_state.get_build_type_name() } self.volume_manager = VolumeManager( self.volume_manager_name, device_map, self.root_dir + '/', self.volumes, volume_manager_custom_parameters) self.volume_manager.setup(self.volume_group_name) self.volume_manager.create_volumes(self.requested_filesystem) self.volume_manager.mount_volumes() self.system = self.volume_manager device_map['root'] = self.volume_manager.get_device().get('root') device_map['swap'] = self.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()) self.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() self._write_recovery_metadata_to_boot_image() self._write_raid_config_to_boot_image() self._write_generic_fstab_to_boot_image(device_map) 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() self._write_generic_fstab_to_system_image(device_map) 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.build_type_name == 'oem': self.boot_image.include_module('kiwi-repart') # create initrd cpio archive 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) self.mbrid.write_to_disk(self.disk.storage_provider) # set SELinux file security contexts if context exists self._setup_selinux_file_contexts() # syncing system data to disk image log.info('Syncing system to image') if self.system_spare: self.system_spare.sync_data() if self.system_efi: log.info('--> Syncing EFI boot data to EFI partition') self.system_efi.sync_data() if self.system_boot: log.info('--> Syncing boot data at extra partition') self.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 = NamedTemporaryFile() squashed_root = FileSystemSquashFs( device_provider=None, 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: self.system.sync_data( self._get_exclude_list_for_root_data_sync(device_map)) # run post sync script hook if self.system_setup.script_exists(defaults.POST_DISK_SYNC_SCRIPT): disk_system = SystemSetup(self.xml_state, self.system.get_mountpoint()) disk_system.import_description() disk_system.call_disk_script() disk_system.cleanup() # install boot loader self._install_bootloader(device_map) # set root filesystem properties self._setup_property_root_is_readonly_snapshot() # prepare for install media if requested if self.install_media: log.info('Saving boot image instance to file') self.boot_image.dump(self.target_dir + '/boot_image.pickledump') self.result.verify_image_size( self.runtime_config.get_max_size_constraint(), self.diskname) # store image file name in result self.result.add( key='disk_image', filename=self.diskname, use_for_bundle=True if not self.image_format else False, compress=self.runtime_config.get_bundle_compression(default=True), 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_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): """ 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(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): """ 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('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(firmware.get_partition_table_type(), loop_provider) partitioner.resize_table() def create_install_media(self, result_instance): """ 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: install_image = InstallImageBuilder( self.xml_state, self.root_dir, self.target_dir, self._load_boot_image_instance(), 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 _load_boot_image_instance(self): boot_image_dump_file = self.target_dir + '/boot_image.pickledump' if not os.path.exists(boot_image_dump_file): raise KiwiInstallMediaError( 'No boot image instance dump %s found' % boot_image_dump_file) try: with open(boot_image_dump_file, 'rb') as boot_image_dump: boot_image = pickle.load(boot_image_dump) Path.wipe(boot_image_dump_file) except Exception as e: raise KiwiInstallMediaError('Failed to load boot image dump: %s' % type(e).__name__) return boot_image def _setup_selinux_file_contexts(self): 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): if self.install_iso or self.install_stick or self.install_pxe: return True def _get_exclude_list_for_root_data_sync(self, device_map): exclude_list = Defaults.get_exclude_list_for_root_data_sync() 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 self.bootloader == 'grub2_s390x_emu': 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/.*') return exclude_list def _get_exclude_list_for_boot_data_sync(self): return ['efi/*'] def _build_spare_filesystem(self, device_map): 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') self.system_spare = filesystem def _build_boot_filesystems(self, device_map): 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()) self.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 self.bootloader == 'grub2_s390x_emu': boot_directory = self.root_dir + '/boot/zipl/' boot_filesystem = 'ext2' 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()) self.system_boot = filesystem def _build_and_map_disk_partitions(self, disksize_mbytes): # noqa: C901 self.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() self.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() self.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() self.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() self.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') self.disk.create_swap_partition(self.swap_mbytes) disksize_used_mbytes += self.swap_mbytes if self.spare_part_mbsize and not self.spare_part_is_last: log.info('--> creating spare partition') self.disk.create_spare_partition(self.spare_part_mbsize) if self.root_filesystem_is_overlay: log.info('--> creating readonly root partition') squashed_root_file = NamedTemporaryFile() squashed_root = FileSystemSquashFs( device_provider=None, 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() self.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') self.disk.create_root_lvm_partition(rootfs_mbsize) elif self.mdraid: log.info('--> creating mdraid root partition') self.disk.create_root_raid_partition(rootfs_mbsize) else: log.info('--> creating root partition') self.disk.create_root_partition(rootfs_mbsize) if self.spare_part_mbsize and self.spare_part_is_last: log.info('--> creating spare partition') self.disk.create_spare_partition('all_free') if self.firmware.bios_mode(): log.info('--> setting active flag to primary boot partition') self.disk.activate_boot_partition() if self.firmware.ofw_mode(): log.info('--> setting active flag to primary PReP partition') self.disk.activate_boot_partition() if self.firmware.efi_mode(): if self.force_mbr: log.info('--> converting partition table to MBR') self.disk.create_mbr() elif self.hybrid_mbr: log.info('--> converting partition table to hybrid GPT/MBR') self.disk.create_hybrid_mbr() self.disk.map_partitions() return self.disk.get_device() def _write_partition_id_config_to_boot_image(self): log.info('Creating config.partids in boot system') filename = ''.join( [self.boot_image.boot_root_directory, '/config.partids']) partition_id_map = self.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): if self.mdraid: log.info('Creating etc/mdadm.conf in boot system') filename = ''.join( [self.boot_image.boot_root_directory, '/etc/mdadm.conf']) self.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): if self.luks: log.info('Creating etc/crypttab') filename = ''.join([self.root_dir, '/etc/crypttab']) self.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): log.info('Creating generic system etc/fstab') self._write_generic_fstab(device_map, self.system_setup) def _write_generic_fstab_to_boot_image(self, device_map): if self.initrd_system == 'kiwi': log.info('Creating generic boot image etc/fstab') self._write_generic_fstab(device_map, self.boot_image.setup) def _write_generic_fstab(self, device_map, setup): 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 = '1 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_generic_fstab_entry(device_map['root'].get_device(), '/', custom_root_mount_args, fs_check_interval) if device_map.get('boot'): if self.bootloader == 'grub2_s390x_emu': boot_mount_point = '/boot/zipl' else: boot_mount_point = '/boot' self._add_generic_fstab_entry(device_map['boot'].get_device(), boot_mount_point) if device_map.get('efi'): self._add_generic_fstab_entry(device_map['efi'].get_device(), '/boot/efi') if self.volume_manager_name: volume_fstab_entries = self.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_generic_fstab_entry(device_map['spare'].get_device(), self.spare_part_mountpoint) if device_map.get('swap'): if self.volume_manager_name and self.volume_manager_name == 'lvm': self._add_simple_fstab_entry(device_map['swap'].get_device(), 'swap', 'swap') else: self._add_generic_fstab_entry(device_map['swap'].get_device(), 'swap') setup.create_fstab(self.fstab) def _add_simple_fstab_entry(self, device, mount_point, filesystem, options=None, check='0 0'): if not options: options = ['defaults'] fstab_entry = ' '.join( [device, mount_point, filesystem, ','.join(options), check]) self.fstab.add_entry(fstab_entry) def _add_generic_fstab_entry(self, device, mount_point, options=None, check='0 0'): if not options: options = ['defaults'] block_operation = BlockID(device) 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): 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): 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): 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): 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): 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'] root_uuid = self.disk.get_uuid(device_map['root'].get_device()) boot_uuid = self.disk.get_uuid(boot_device.get_device()) boot_uuid_unmapped = self.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_uuid=root_uuid, 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(root_uuid)] + boot_options) with open(filename, 'w') as boot_options: boot_options.write('{0}{1}'.format(kexec_boot_options, os.linesep)) partition_id_map = self.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, boot_partition_id) def _install_bootloader(self, device_map): 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: self.system.umount_volumes() custom_install_arguments.update( {'system_volumes': self.system.get_volumes()}) # 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 if self.bootloader != 'custom': log.debug("custom arguments for bootloader installation %s", custom_install_arguments) bootloader = BootLoaderInstall(self.bootloader, self.root_dir, self.disk.storage_provider, custom_install_arguments) if bootloader.install_required(): bootloader.install() self.system_setup.call_edit_boot_install_script( self.diskname, boot_device.get_device()) def _setup_property_root_is_readonly_snapshot(self): 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') self.system.mount_volumes() self.system.set_property_readonly_root() self.system.umount_volumes() def _copy_first_boot_files_to_system_image(self): 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) 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 TestSystemSetup: @fixture(autouse=True) def inject_fixtures(self, caplog): self._caplog = caplog @patch('kiwi.system.setup.RuntimeConfig') def setup(self, mock_RuntimeConfig): Defaults.set_platform_name('x86_64') self.runtime_config = Mock() self.runtime_config.get_package_changes = Mock(return_value=True) mock_RuntimeConfig.return_value = self.runtime_config self.xml_state = MagicMock() self.xml_state.get_package_manager = Mock(return_value='zypper') self.xml_state.build_type.get_filesystem = Mock(return_value='ext3') self.xml_state.xml_data.get_name = Mock(return_value='some-image') self.xml_state.get_image_version = Mock(return_value='1.2.3') self.xml_state.xml_data.description_dir = 'description_dir' self.setup = SystemSetup(self.xml_state, 'root_dir') description = XMLDescription(description='../data/example_config.xml', derived_from='derived/description') self.description_dir = os.path.dirname(description.description_origin) self.setup_with_real_xml = SystemSetup(XMLState(description.load()), 'root_dir') command_run = namedtuple('command', ['output', 'error', 'returncode']) self.run_result = command_run(output='password-hash\n', error='stderr', returncode=0) def teardown(self): sys.argv = argv_kiwi_tests def test_setup_ix86(self): Defaults.set_platform_name('i686') setup = SystemSetup(MagicMock(), 'root_dir') assert setup.arch == 'ix86' @patch('kiwi.command.Command.run') @patch('os.path.exists') @patch('kiwi.system.setup.glob.iglob') def test_import_description(self, mock_iglob, mock_path, mock_command): mock_iglob.return_value = ['config-cdroot.tar.xz'] mock_path.return_value = True with patch('builtins.open'): self.setup_with_real_xml.import_description() mock_iglob.assert_called_once_with('{0}/config-cdroot.tar*'.format( self.description_dir)) assert mock_command.call_args_list == [ call([ 'cp', '{0}/config.sh'.format(self.description_dir), 'root_dir/image/config.sh' ]), call([ 'cp', '{0}/disk.sh'.format(self.description_dir), 'root_dir/image/disk.sh' ]), call([ 'cp', '{0}/my_edit_boot_script'.format(self.description_dir), 'root_dir/image/edit_boot_config.sh' ]), call([ 'cp', '/absolute/path/to/my_edit_boot_install', 'root_dir/image/edit_boot_install.sh' ]), call([ 'cp', '{0}/images.sh'.format(self.description_dir), 'root_dir/image/images.sh' ]), call([ 'cp', '{0}/post_bootstrap.sh'.format(self.description_dir), 'root_dir/image/post_bootstrap.sh' ]), call([ 'cp', Defaults.project_file('config/functions.sh'), 'root_dir/.kconfig' ]), call(['cp', '/absolute/path/to/image.tgz', 'root_dir/image/']), call([ 'cp', '{0}/bootstrap.tgz'.format(self.description_dir), 'root_dir/image/' ]), call(['cp', 'config-cdroot.tar.xz', 'root_dir/image/']) ] @patch('kiwi.path.Path.create') @patch('kiwi.command.Command.run') @patch('os.path.exists') def test_import_description_archive_from_derived(self, mock_path, mock_command, mock_create): path_return_values = [ True, False, True, True, True, True, True, True, True ] def side_effect(arg): return path_return_values.pop() mock_path.side_effect = side_effect with patch('builtins.open'): self.setup_with_real_xml.import_description() assert mock_command.call_args_list == [ call([ 'cp', '{0}/config.sh'.format(self.description_dir), 'root_dir/image/config.sh' ]), call([ 'cp', '{0}/disk.sh'.format(self.description_dir), 'root_dir/image/disk.sh' ]), call([ 'cp', '{0}/my_edit_boot_script'.format(self.description_dir), 'root_dir/image/edit_boot_config.sh' ]), call([ 'cp', '/absolute/path/to/my_edit_boot_install', 'root_dir/image/edit_boot_install.sh' ]), call([ 'cp', '{0}/images.sh'.format(self.description_dir), 'root_dir/image/images.sh' ]), call([ 'cp', '{0}/post_bootstrap.sh'.format(self.description_dir), 'root_dir/image/post_bootstrap.sh' ]), call([ 'cp', Defaults.project_file('config/functions.sh'), 'root_dir/.kconfig' ]), call(['cp', '/absolute/path/to/image.tgz', 'root_dir/image/']), call( ['cp', 'derived/description/bootstrap.tgz', 'root_dir/image/']) ] mock_create.assert_called_once_with('root_dir/image') @patch('kiwi.command.Command.run') @patch('os.path.exists') def test_import_description_configured_editboot_scripts_not_found( self, mock_path, mock_command): path_return_values = [False, True, True, True] def side_effect(arg): return path_return_values.pop() mock_path.side_effect = side_effect with patch('builtins.open'): with raises(KiwiImportDescriptionError): self.setup_with_real_xml.import_description() @patch('kiwi.path.Path.create') @patch('kiwi.command.Command.run') @patch('os.path.exists') def test_import_description_configured_archives_not_found( self, mock_path, mock_command, mock_create): path_return_values = [False, False, True, True, True, True, True, True] def side_effect(arg): return path_return_values.pop() mock_path.side_effect = side_effect with patch('builtins.open'): with raises(KiwiImportDescriptionError): self.setup_with_real_xml.import_description() @patch('kiwi.command.Command.run') def test_cleanup(self, mock_command): self.setup.cleanup() mock_command.assert_called_once_with( ['chroot', 'root_dir', 'rm', '-rf', '.kconfig', 'image']) @patch('kiwi.system.setup.ArchiveTar') @patch('kiwi.system.setup.glob.iglob') def test_import_cdroot_files(self, mock_iglob, mock_ArchiveTar): archive = Mock() mock_ArchiveTar.return_value = archive mock_iglob.return_value = ['config-cdroot.tar.xz'] self.setup.import_cdroot_files('target_dir') mock_iglob.assert_called_once_with( 'description_dir/config-cdroot.tar*') mock_ArchiveTar.assert_called_once_with('config-cdroot.tar.xz') archive.extract.assert_called_once_with('target_dir') @patch('kiwi.command.Command.run') @patch('kiwi.system.setup.DataSync') @patch('os.path.exists') def test_import_overlay_files_copy_links(self, mock_os_path, mock_DataSync, mock_command): data = Mock() mock_DataSync.return_value = data mock_os_path.return_value = True self.setup.import_overlay_files(follow_links=True, preserve_owner_group=True) mock_DataSync.assert_called_once_with('description_dir/root/', 'root_dir') data.sync_data.assert_called_once_with(options=[ '-r', '-p', '-t', '-D', '-H', '-X', '-A', '--one-file-system', '--copy-links', '-o', '-g' ]) @patch('kiwi.command.Command.run') @patch('kiwi.system.setup.DataSync') @patch('os.path.exists') def test_import_overlay_files_links(self, mock_os_path, mock_DataSync, mock_command): data = Mock() mock_DataSync.return_value = data mock_os_path.return_value = True self.setup.import_overlay_files(follow_links=False, preserve_owner_group=True) mock_DataSync.assert_called_once_with('description_dir/root/', 'root_dir') data.sync_data.assert_called_once_with(options=[ '-r', '-p', '-t', '-D', '-H', '-X', '-A', '--one-file-system', '--links', '-o', '-g' ]) @patch('kiwi.system.setup.ArchiveTar') @patch('os.path.exists') def test_import_overlay_files_from_archive(self, mock_os_path, mock_archive): archive = Mock() mock_archive.return_value = archive exists_results = [True, False] def side_effect(arg): return exists_results.pop() mock_os_path.side_effect = side_effect self.setup.import_overlay_files() mock_archive.assert_called_once_with('description_dir/root.tar.gz') archive.extract.assert_called_once_with('root_dir') @patch('kiwi.system.setup.DataSync') @patch('os.path.exists') def test_import_overlay_files_from_profile(self, mock_os_path, mock_DataSync): exists_results = [True, False, False] def side_effect(arg): return exists_results.pop() data = Mock() mock_DataSync.return_value = data mock_os_path.side_effect = side_effect self.xml_state.profiles = ['profile_root'] self.setup.import_overlay_files() mock_DataSync.assert_called_once_with('description_dir/profile_root/', 'root_dir') data.sync_data.assert_called_once_with(options=[ '-r', '-p', '-t', '-D', '-H', '-X', '-A', '--one-file-system', '--links' ]) @patch('kiwi.system.setup.Shell.run_common_function') @patch('kiwi.system.setup.Command.run') @patch('os.path.exists') def test_setup_keyboard_map(self, mock_path, mock_run, mock_shell): mock_path.return_value = True self.setup.preferences['keytable'] = 'keytable' self.setup.setup_keyboard_map() mock_shell.assert_called_once_with( 'baseUpdateSysConfig', ['root_dir/etc/sysconfig/keyboard', 'KEYTABLE', '"keytable"']) @patch('kiwi.system.setup.CommandCapabilities.has_option_in_help') @patch('kiwi.system.setup.Shell.run_common_function') @patch('kiwi.system.setup.Command.run') @patch('os.path.exists') def test_setup_keyboard_map_with_systemd(self, mock_path, mock_run, mock_shell, mock_caps): mock_caps.return_value = True mock_path.return_value = True self.setup.preferences['keytable'] = 'keytable' self.setup.setup_keyboard_map() mock_run.assert_has_calls([ call(['rm', '-r', '-f', 'root_dir/etc/vconsole.conf']), call([ 'chroot', 'root_dir', 'systemd-firstboot', '--keymap=keytable' ]) ]) @patch('os.path.exists') def test_setup_keyboard_skipped(self, mock_exists): mock_exists.return_value = False self.setup.preferences['keytable'] = 'keytable' with self._caplog.at_level(logging.WARNING): self.setup.setup_keyboard_map() @patch('kiwi.system.setup.CommandCapabilities.has_option_in_help') @patch('kiwi.system.setup.Command.run') @patch('os.path.exists') def test_setup_locale(self, mock_path, mock_run, mock_caps): mock_caps.return_valure = True mock_path.return_value = True self.setup.preferences['locale'] = 'locale1,locale2' self.setup.setup_locale() mock_run.assert_has_calls([ call(['rm', '-r', '-f', 'root_dir/etc/locale.conf']), call([ 'chroot', 'root_dir', 'systemd-firstboot', '--locale=locale1.UTF-8' ]) ]) @patch('kiwi.system.setup.CommandCapabilities.has_option_in_help') @patch('kiwi.system.setup.Command.run') @patch('os.path.exists') def test_setup_locale_POSIX(self, mock_path, mock_run, mock_caps): mock_caps.return_valure = True mock_path.return_value = True self.setup.preferences['locale'] = 'POSIX,locale2' self.setup.setup_locale() mock_run.assert_has_calls([ call(['rm', '-r', '-f', 'root_dir/etc/locale.conf']), call(['chroot', 'root_dir', 'systemd-firstboot', '--locale=POSIX']) ]) @patch('kiwi.system.setup.Command.run') def test_setup_timezone(self, mock_command): self.setup.preferences['timezone'] = 'timezone' self.setup.setup_timezone() mock_command.assert_has_calls([ call([ 'chroot', 'root_dir', 'ln', '-s', '-f', '/usr/share/zoneinfo/timezone', '/etc/localtime' ]) ]) @patch('kiwi.system.setup.CommandCapabilities.has_option_in_help') @patch('kiwi.system.setup.Command.run') def test_setup_timezone_with_systemd(self, mock_command, mock_caps): mock_caps.return_value = True self.setup.preferences['timezone'] = 'timezone' self.setup.setup_timezone() mock_command.assert_has_calls([ call([ 'chroot', 'root_dir', 'systemd-firstboot', '--timezone=timezone' ]) ]) @patch('kiwi.system.setup.Users') def test_setup_groups(self, mock_users): users = Mock() users.group_exists = Mock(return_value=False) mock_users.return_value = users self.setup_with_real_xml.setup_groups() calls = [call('users'), call('kiwi'), call('admin')] users.group_exists.assert_has_calls(calls) calls = [call('users', []), call('kiwi', []), call('admin', [])] users.group_add.assert_has_calls(calls) @patch('kiwi.system.setup.Users') @patch('kiwi.system.setup.Command.run') def test_setup_users_add(self, mock_command, mock_users): users = Mock() users.user_exists = Mock(return_value=False) mock_users.return_value = users mock_command.return_value = self.run_result self.setup_with_real_xml.setup_users() calls = [call('root'), call('tux'), call('kiwi')] users.user_exists.assert_has_calls(calls) calls = [ call('root', [ '-p', 'password-hash', '-s', '/bin/bash', '-u', '815', '-c', 'Bob', '-m', '-d', '/root' ]), call('tux', [ '-p', 'password-hash', '-g', 'users', '-m', '-d', '/home/tux' ]), call('kiwi', [ '-p', 'password-hash', '-g', 'kiwi', '-G', 'admin,users', '-m' ]) ] users.user_add.assert_has_calls(calls) mock_command.assert_called_with( ['openssl', 'passwd', '-1', '-salt', 'xyz', 'mypwd']) @patch('kiwi.system.setup.Users') @patch('kiwi.system.setup.Command.run') def test_setup_users_modify(self, mock_command, mock_users): users = Mock() users.user_exists = Mock(return_value=True) mock_users.return_value = users mock_command.return_value = self.run_result self.setup_with_real_xml.setup_users() calls = [call('root'), call('tux'), call('kiwi')] users.user_exists.assert_has_calls(calls) calls = [ call('root', [ '-p', 'password-hash', '-s', '/bin/bash', '-u', '815', '-c', 'Bob' ]), call('tux', ['-p', 'password-hash', '-g', 'users']), call('kiwi', ['-p', 'password-hash', '-g', 'kiwi', '-G', 'admin,users']) ] users.user_modify.assert_has_calls(calls) @patch('kiwi.system.setup.Path.which') @patch('kiwi.system.setup.Command.run') def test_setup_plymouth_splash(self, mock_command, mock_which): mock_which.return_value = 'plymouth-set-default-theme' preferences = Mock() preferences.get_bootsplash_theme = Mock(return_value=['some-theme']) self.xml_state.get_preferences_sections = Mock( return_value=[preferences]) self.setup.setup_plymouth_splash() mock_which.assert_called_once_with( root_dir='root_dir', filename='plymouth-set-default-theme') mock_command.assert_called_once_with( ['chroot', 'root_dir', 'plymouth-set-default-theme', 'some-theme']) @patch('os.path.exists') def test_import_image_identifier(self, mock_os_path): self.xml_state.xml_data.get_id = Mock(return_value='42') mock_os_path.return_value = True m_open = mock_open() with patch('builtins.open', m_open, create=True): self.setup.import_image_identifier() m_open.assert_called_once_with('root_dir/etc/ImageID', 'w') m_open.return_value.write.assert_called_once_with('42\n') @patch('kiwi.system.setup.Profile') @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') @patch('os.stat') @patch('os.access') @patch('copy.deepcopy') def test_call_non_excutable_config_script(self, mock_copy_deepcopy, mock_access, mock_stat, mock_os_path, mock_watch, mock_command, mock_Profile): mock_copy_deepcopy.return_value = {} profile = Mock() mock_Profile.return_value = profile profile.get_settings.return_value = {} result_type = namedtuple('result', ['stderr', 'returncode']) mock_result = result_type(stderr='stderr', returncode=0) mock_os_path.return_value = True mock_watch.return_value = mock_result mock_access.return_value = False self.setup.call_config_script() mock_copy_deepcopy.assert_called_once_with(os.environ) mock_command.assert_called_once_with( ['chroot', 'root_dir', 'bash', 'image/config.sh'], {}) @patch('kiwi.system.setup.Profile') @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') @patch('os.stat') @patch('os.access') @patch('copy.deepcopy') def test_call_excutable_config_script(self, mock_copy_deepcopy, mock_access, mock_stat, mock_os_path, mock_watch, mock_command, mock_Profile): mock_copy_deepcopy.return_value = {} profile = Mock() mock_Profile.return_value = profile profile.get_settings.return_value = {} result_type = namedtuple('result', ['stderr', 'returncode']) mock_result = result_type(stderr='stderr', returncode=0) mock_os_path.return_value = True mock_watch.return_value = mock_result # pretend that the script is executable mock_access.return_value = True self.setup.call_config_script() mock_copy_deepcopy.assert_called_once_with(os.environ) mock_command.assert_called_once_with( ['chroot', 'root_dir', 'image/config.sh'], {}) @patch('kiwi.system.setup.Profile') @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') @patch('os.stat') @patch('os.access') @patch('copy.deepcopy') def test_call_excutable_post_bootstrap_script(self, mock_copy_deepcopy, mock_access, mock_stat, mock_os_path, mock_watch, mock_command, mock_Profile): mock_copy_deepcopy.return_value = {} profile = Mock() mock_Profile.return_value = profile profile.get_settings.return_value = {} result_type = namedtuple('result', ['stderr', 'returncode']) mock_result = result_type(stderr='stderr', returncode=0) mock_os_path.return_value = True mock_watch.return_value = mock_result # pretend that the script is executable mock_access.return_value = True self.setup.call_post_bootstrap_script() mock_copy_deepcopy.assert_called_once_with(os.environ) mock_command.assert_called_once_with( ['chroot', 'root_dir', 'image/post_bootstrap.sh'], {}) @patch('kiwi.system.setup.Defaults.is_buildservice_worker') @patch('kiwi.logger.Logger.getLogLevel') @patch('kiwi.system.setup.Profile') @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') @patch('os.stat') @patch('os.access') @patch('copy.deepcopy') def test_call_disk_script(self, mock_copy_deepcopy, mock_access, mock_stat, mock_os_path, mock_watch, mock_command, mock_Profile, mock_getLogLevel, mock_is_buildservice_worker): mock_is_buildservice_worker.return_value = False mock_getLogLevel.return_value = logging.DEBUG mock_copy_deepcopy.return_value = {} profile = Mock() mock_Profile.return_value = profile profile.get_settings.return_value = {} result_type = namedtuple('result_type', ['stderr', 'returncode']) mock_result = result_type(stderr='stderr', returncode=0) mock_os_path.return_value = True mock_watch.return_value = mock_result mock_access.return_value = False self.setup.call_disk_script() mock_copy_deepcopy.assert_called_once_with(os.environ) mock_command.assert_called_once_with([ 'screen', '-t', '-X', 'chroot', 'root_dir', 'bash', 'image/disk.sh' ], {}) @patch('kiwi.system.setup.Profile') @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') @patch('os.stat') @patch('os.access') @patch('copy.deepcopy') def test_call_image_script(self, mock_copy_deepcopy, mock_access, mock_stat, mock_os_path, mock_watch, mock_command, mock_Profile): mock_copy_deepcopy.return_value = {} profile = Mock() mock_Profile.return_value = profile profile.get_settings.return_value = {} result_type = namedtuple('result_type', ['stderr', 'returncode']) mock_result = result_type(stderr='stderr', returncode=0) mock_os_path.return_value = True mock_watch.return_value = mock_result mock_access.return_value = False self.setup.call_image_script() mock_copy_deepcopy.assert_called_once_with(os.environ) mock_command.assert_called_once_with( ['chroot', 'root_dir', 'bash', 'image/images.sh'], {}) @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') @patch('os.path.abspath') def test_call_edit_boot_config_script(self, mock_abspath, mock_exists, mock_watch, mock_command): result_type = namedtuple('result_type', ['stderr', 'returncode']) mock_result = result_type(stderr='stderr', returncode=0) mock_exists.return_value = True mock_abspath.return_value = '/root_dir/image/edit_boot_config.sh' mock_watch.return_value = mock_result self.setup.call_edit_boot_config_script('ext4', 1) mock_abspath.assert_called_once_with( 'root_dir/image/edit_boot_config.sh') mock_command.assert_called_once_with([ 'bash', '-c', 'cd root_dir && bash --norc /root_dir/image/edit_boot_config.sh ' 'ext4 1' ]) @patch('kiwi.system.setup.Defaults.is_buildservice_worker') @patch('kiwi.logger.Logger.getLogLevel') @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') @patch('os.path.abspath') def test_call_edit_boot_install_script(self, mock_abspath, mock_exists, mock_watch, mock_command, mock_getLogLevel, mock_is_buildservice_worker): mock_is_buildservice_worker.return_value = False mock_getLogLevel.return_value = logging.DEBUG result_type = namedtuple('result_type', ['stderr', 'returncode']) mock_result = result_type(stderr='stderr', returncode=0) mock_exists.return_value = True mock_abspath.return_value = '/root_dir/image/edit_boot_install.sh' mock_watch.return_value = mock_result self.setup.call_edit_boot_install_script('my_image.raw', '/dev/mapper/loop0p1') mock_abspath.assert_called_once_with( 'root_dir/image/edit_boot_install.sh') mock_command.assert_called_once_with([ 'screen', '-t', '-X', 'bash', '-c', 'cd root_dir && bash --norc ' '/root_dir/image/edit_boot_install.sh ' 'my_image.raw /dev/mapper/loop0p1' ]) @patch('kiwi.system.setup.Profile') @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') @patch('os.stat') @patch('os.access') def test_call_image_script_raises(self, mock_access, mock_stat, mock_os_path, mock_watch, mock_command, mock_Profile): profile = Mock() mock_Profile.return_value = profile profile.get_settings.return_value = {} result_type = namedtuple('result_type', ['stderr', 'returncode']) mock_result = result_type(stderr='stderr', returncode=1) mock_os_path.return_value = True mock_watch.return_value = mock_result mock_access.return_value = False with raises(KiwiScriptFailed): self.setup.call_image_script() @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') def test_call_edit_boot_install_script_raises(self, mock_os_path, mock_watch, mock_command): result_type = namedtuple('result_type', ['stderr', 'returncode']) mock_result = result_type(stderr='stderr', returncode=1) mock_os_path.return_value = True mock_watch.return_value = mock_result with raises(KiwiScriptFailed): self.setup.call_edit_boot_install_script('my_image.raw', '/dev/mapper/loop0p1') @patch('kiwi.command.Command.run') def test_create_init_link_from_linuxrc(self, mock_command): self.setup.create_init_link_from_linuxrc() mock_command.assert_called_once_with( ['ln', 'root_dir/linuxrc', 'root_dir/init']) @patch('kiwi.command.Command.run') def test_create_recovery_archive_cleanup_only(self, mock_command): self.setup.oemconfig['recovery'] = False self.setup.create_recovery_archive() assert mock_command.call_args_list[0] == call( ['bash', '-c', 'rm -f root_dir/recovery.*']) @patch('os.path.exists') @patch('kiwi.system.setup.Path.wipe') @patch('kiwi.command.Command.run') def test_create_fstab(self, mock_command, mock_wipe, mock_exists): fstab = Mock() mock_exists.return_value = True m_open = mock_open(read_data='append_entry') with patch('builtins.open', m_open, create=True): self.setup.create_fstab(fstab) fstab.export.assert_called_once_with('root_dir/etc/fstab') assert m_open.call_args_list == [ call('root_dir/etc/fstab', 'a'), call('root_dir/etc/fstab.append', 'r') ] assert m_open.return_value.write.call_args_list == [ call('append_entry') ] assert mock_command.call_args_list == [ call(['patch', 'root_dir/etc/fstab', 'root_dir/etc/fstab.patch']), call(['chroot', 'root_dir', '/etc/fstab.script']) ] assert mock_wipe.call_args_list == [ call('root_dir/etc/fstab.append'), call('root_dir/etc/fstab.patch'), call('root_dir/etc/fstab.script') ] @patch('kiwi.command.Command.run') @patch('pathlib.Path.touch') @patch('kiwi.system.setup.ArchiveTar') @patch('kiwi.system.setup.Compress') @patch('os.path.getsize') @patch('kiwi.system.setup.Path.wipe') def test_create_recovery_archive(self, mock_wipe, mock_getsize, mock_compress, mock_archive, mock_pathlib_Path_touch, mock_command): mock_getsize.return_value = 42 compress = Mock() mock_compress.return_value = compress archive = Mock() mock_archive.return_value = archive self.setup.oemconfig['recovery'] = True self.setup.oemconfig['recovery_inplace'] = True m_open = mock_open() with patch('builtins.open', m_open, create=True): self.setup.create_recovery_archive() assert mock_command.call_args_list[0] == call( ['bash', '-c', 'rm -f root_dir/recovery.*']) mock_archive.assert_called_once_with(create_from_file_list=False, filename='root_dir/recovery.tar') archive.create.assert_called_once_with(exclude=['dev', 'proc', 'sys'], options=[ '--numeric-owner', '--hard-dereference', '--preserve-permissions' ], source_dir='root_dir') assert m_open.call_args_list[0] == call( 'root_dir/recovery.tar.filesystem', 'w') assert m_open.return_value.write.call_args_list[0] == call('ext3') assert mock_command.call_args_list[1] == call( ['bash', '-c', 'tar -tf root_dir/recovery.tar | wc -l']) assert m_open.call_args_list[1] == call('root_dir/recovery.tar.files', 'w') assert mock_getsize.call_args_list[0] == call('root_dir/recovery.tar') assert m_open.return_value.write.call_args_list[1] == call('1\n') assert m_open.call_args_list[2] == call('root_dir/recovery.tar.size', 'w') assert m_open.return_value.write.call_args_list[2] == call('42') mock_compress.assert_called_once_with('root_dir/recovery.tar') compress.gzip.assert_called_once_with() assert mock_getsize.call_args_list[1] == call( 'root_dir/recovery.tar.gz') assert m_open.call_args_list[3] == call( 'root_dir/recovery.partition.size', 'w') assert m_open.return_value.write.call_args_list[3] == call('300') mock_wipe.assert_called_once_with('root_dir/recovery.tar.gz') @patch('kiwi.system.setup.Command.run') @patch('kiwi.system.setup.Path.create') @patch('kiwi.system.setup.DataSync') @patch('os.path.exists') def test_export_modprobe_setup(self, mock_exists, mock_DataSync, mock_path, mock_command): data = Mock() mock_DataSync.return_value = data mock_exists.return_value = True self.setup.export_modprobe_setup('target_root_dir') mock_path.assert_called_once_with('target_root_dir/etc') mock_DataSync.assert_called_once_with('root_dir/etc/modprobe.d', 'target_root_dir/etc/') data.sync_data.assert_called_once_with(options=['-a']) @patch('kiwi.defaults.Defaults.get_default_packager_tool') def test_export_package_list_unknown_packager( self, mock_get_default_packager_tool): assert self.setup.export_package_list('target_dir') == '' @patch('kiwi.defaults.Defaults.get_default_packager_tool') def test_export_package_changes_unknown_packager( self, mock_get_default_packager_tool): assert self.setup.export_package_changes('target_dir') == '' @patch('kiwi.defaults.Defaults.get_default_packager_tool') def test_export_package_verification_unknown_packager( self, mock_get_default_packager_tool): assert self.setup.export_package_verification('target_dir') == '' @patch('kiwi.system.setup.Command.run') @patch('kiwi.system.setup.RpmDataBase') @patch('kiwi.system.setup.MountManager') def test_export_package_list_rpm(self, mock_MountManager, mock_RpmDataBase, mock_command): rpmdb = Mock() rpmdb.rpmdb_image.expand_query.return_value = 'image_dbpath' rpmdb.rpmdb_host.expand_query.return_value = 'host_dbpath' rpmdb.has_rpm.return_value = True mock_RpmDataBase.return_value = rpmdb command = Mock() command.output = 'packages_data' mock_command.return_value = command with patch('builtins.open') as m_open: result = self.setup.export_package_list('target_dir') m_open.assert_called_once_with( 'target_dir/some-image.x86_64-1.2.3.packages', 'w', encoding='utf-8') assert result == 'target_dir/some-image.x86_64-1.2.3.packages' mock_command.assert_called_once_with([ 'rpm', '--root', 'root_dir', '-qa', '--qf', '%{NAME}|%{EPOCH}|%{VERSION}|%{RELEASE}|%{ARCH}|' '%{DISTURL}|%{LICENSE}\\n', '--dbpath', 'image_dbpath' ]) rpmdb.has_rpm.return_value = False mock_command.reset_mock() with patch('builtins.open'): result = self.setup.export_package_list('target_dir') assert result == 'target_dir/some-image.x86_64-1.2.3.packages' mock_command.assert_called_once_with([ 'rpm', '--root', 'root_dir', '-qa', '--qf', '%{NAME}|%{EPOCH}|%{VERSION}|%{RELEASE}|%{ARCH}|' '%{DISTURL}|%{LICENSE}\\n', '--dbpath', 'host_dbpath' ]) @patch('kiwi.system.setup.os.path.exists') def test_setup_machine_id(self, mock_path_exists): mock_path_exists.return_value = True with patch('builtins.open') as m_open: self.setup.setup_machine_id() m_open.assert_called_once_with('root_dir/etc/machine-id', 'w') mock_path_exists.return_value = False self.setup.setup_machine_id() @patch('kiwi.system.setup.Command.run') @patch('kiwi.system.setup.Path.which') def test_setup_permissions(self, mock_path_which, mock_command): mock_path_which.return_value = 'chkstat' self.setup.setup_permissions() mock_command.assert_called_once_with( ['chroot', 'root_dir', 'chkstat', '--system', '--set']) mock_path_which.return_value = None with self._caplog.at_level(logging.WARNING): self.setup.setup_permissions() @patch('kiwi.system.setup.Command.run') def test_export_package_list_dpkg(self, mock_command): command = Mock() command.output = 'packages_data' mock_command.return_value = command self.xml_state.get_package_manager = Mock(return_value='apt') with patch('builtins.open') as m_open: result = self.setup.export_package_list('target_dir') m_open.assert_called_once_with( 'target_dir/some-image.x86_64-1.2.3.packages', 'w', encoding='utf-8') assert result == 'target_dir/some-image.x86_64-1.2.3.packages' mock_command.assert_called_once_with([ 'dpkg-query', '--admindir', 'root_dir/var/lib/dpkg', '-W', '-f', '${Package}|None|${Version}|None|${Architecture}|None|None\\n' ]) @patch('kiwi.system.setup.Command.run') def test_export_package_list_pacman(self, mock_command): command = Mock() command.output = 'packages_data' mock_command.return_value = command self.xml_state.get_package_manager = Mock(return_value='pacman') with patch('builtins.open') as m_open: result = self.setup.export_package_list('target_dir') m_open.assert_called_once_with( 'target_dir/some-image.x86_64-1.2.3.packages', 'w') assert result == 'target_dir/some-image.x86_64-1.2.3.packages' mock_command.assert_called_once_with( ['pacman', '--query', '--dbpath', 'root_dir/var/lib/pacman']) @patch('kiwi.system.setup.Command.run') @patch('kiwi.system.setup.RpmDataBase') @patch('kiwi.system.setup.MountManager') def test_export_package_changes_rpm(self, mock_MountManager, mock_RpmDataBase, mock_command): rpmdb = Mock() rpmdb.rpmdb_image.expand_query.return_value = 'image_dbpath' rpmdb.rpmdb_host.expand_query.return_value = 'host_dbpath' rpmdb.has_rpm.return_value = True mock_RpmDataBase.return_value = rpmdb command = Mock() command.output = 'package|\nchanges' mock_command.return_value = command with patch('builtins.open', create=True) as mock_open: mock_open.return_value = MagicMock(spec=io.IOBase) result = self.setup.export_package_changes('target_dir') mock_open.assert_called_once_with( 'target_dir/some-image.x86_64-1.2.3.changes', 'w', encoding='utf-8') assert result == 'target_dir/some-image.x86_64-1.2.3.changes' mock_command.assert_called_once_with([ 'rpm', '--root', 'root_dir', '-qa', '--qf', '%{NAME}|\\n', '--changelog', '--dbpath', 'image_dbpath' ]) self.runtime_config.get_package_changes.assert_called_once_with() @patch('kiwi.system.setup.Command.run') @patch('os.path.exists') @patch('os.listdir') def test_export_package_changes_dpkg(self, mock_os_listdir, mock_os_path_exists, mock_command): command = Mock() command.output = 'changes' mock_command.return_value = command self.xml_state.get_package_manager = Mock(return_value='apt') mock_os_listdir.return_value = ['package_b', 'package_a'] mock_os_path_exists.return_value = True with patch('builtins.open', create=True) as mock_open: mock_open.return_value = MagicMock(spec=io.IOBase) result = self.setup.export_package_changes('target_dir') file_handle = mock_open.return_value.__enter__.return_value mock_open.assert_called_once_with( 'target_dir/some-image.x86_64-1.2.3.changes', 'w', encoding='utf-8') assert result == 'target_dir/some-image.x86_64-1.2.3.changes' assert file_handle.write.call_args_list == [ call('package_a|\n'), call('changes\n'), call('package_b|\n'), call('changes\n') ] self.runtime_config.get_package_changes.assert_called_once_with() @patch('kiwi.system.setup.Command.run') @patch('kiwi.system.setup.RpmDataBase') @patch('kiwi.system.setup.MountManager') def test_export_package_verification(self, mock_MountManager, mock_RpmDataBase, mock_command): is_mounted_return = [True, False] def is_mounted(): return is_mounted_return.pop() shared_mount = Mock() shared_mount.is_mounted.side_effect = is_mounted mock_MountManager.return_value = shared_mount rpmdb = Mock() rpmdb.rpmdb_image.expand_query.return_value = 'image_dbpath' rpmdb.rpmdb_host.expand_query.return_value = 'host_dbpath' rpmdb.has_rpm.return_value = True mock_RpmDataBase.return_value = rpmdb command = Mock() command.output = 'verification_data' mock_command.return_value = command with patch('builtins.open') as m_open: result = self.setup.export_package_verification('target_dir') m_open.assert_called_once_with( 'target_dir/some-image.x86_64-1.2.3.verified', 'w', encoding='utf-8') assert result == 'target_dir/some-image.x86_64-1.2.3.verified' mock_command.assert_called_once_with(command=[ 'rpm', '--root', 'root_dir', '-Va', '--dbpath', 'image_dbpath' ], raise_on_error=False) mock_MountManager.assert_called_once_with(device='/dev', mountpoint='root_dir/dev') shared_mount.bind_mount.assert_called_once_with() shared_mount.umount_lazy.assert_called_once_with() rpmdb.has_rpm.return_value = False is_mounted_return = [True, False] mock_command.reset_mock() shared_mount.reset_mock() with patch('builtins.open'): result = self.setup.export_package_verification('target_dir') assert result == 'target_dir/some-image.x86_64-1.2.3.verified' mock_command.assert_called_once_with(command=[ 'rpm', '--root', 'root_dir', '-Va', '--dbpath', 'host_dbpath' ], raise_on_error=False) shared_mount.bind_mount.assert_called_once_with() shared_mount.umount_lazy.assert_called_once_with() @patch('kiwi.system.setup.Command.run') def test_export_package_verification_dpkg(self, mock_command): command = Mock() command.output = 'verification_data' mock_command.return_value = command self.xml_state.get_package_manager = Mock(return_value='apt') with patch('builtins.open') as m_open: result = self.setup.export_package_verification('target_dir') m_open.assert_called_once_with( 'target_dir/some-image.x86_64-1.2.3.verified', 'w', encoding='utf-8') assert result == 'target_dir/some-image.x86_64-1.2.3.verified' mock_command.assert_called_once_with(command=[ 'dpkg', '--root', 'root_dir', '-V', '--verify-format', 'rpm' ], raise_on_error=False) @patch('kiwi.system.setup.Command.run') def test_set_selinux_file_contexts(self, mock_command): self.setup.set_selinux_file_contexts('security_context_file') mock_command.assert_called_once_with([ 'chroot', 'root_dir', 'setfiles', 'security_context_file', '/', '-v' ]) @patch('kiwi.system.setup.Repository.new') @patch('kiwi.system.setup.Uri') def test_import_repositories_marked_as_imageinclude( self, mock_uri, mock_repo): uri = Mock() mock_uri.return_value = uri uri.translate = Mock(return_value="uri") uri.alias = Mock(return_value="uri-alias") uri.credentials_file_name = Mock(return_value='kiwiRepoCredentials') mock_uri.return_value = uri repo = Mock() mock_repo.return_value = repo self.setup_with_real_xml.import_repositories_marked_as_imageinclude() assert repo.add_repo.call_args_list[0] == call( 'uri-alias', 'uri', 'rpm-md', None, None, None, None, None, 'kiwiRepoCredentials', None, None, None, False, '../data/script') @patch('os.path.exists') def test_script_exists(self, mock_path_exists): assert self.setup.script_exists('some-script') == \ mock_path_exists.return_value