def setup(self, mock_machine): mock_machine.return_value = 'x86_64' self.xml_state = mock.MagicMock() self.xml_state.build_type.get_filesystem = mock.Mock( return_value='ext3' ) self.xml_state.xml_data.get_name = mock.Mock( return_value='some-image' ) self.xml_state.get_image_version = mock.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.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 prepare(self): """ Prepare kiwi profile environment to be included in dracut initrd """ profile = Profile(self.xml_state) defaults = Defaults() defaults.to_profile(profile) setup = SystemSetup( self.xml_state, self.boot_root_directory ) setup.import_shell_environment(profile) self.dracut_options.append('--install') self.dracut_options.append('/.profile')
def process(self): """ Create a system image from the specified root directory the root directory is the result of a system prepare command """ self.manual = Help() if self._help(): return Privileges.check_for_root_permissions() abs_target_dir_path = os.path.abspath( self.command_args['--target-dir'] ) abs_root_path = os.path.abspath(self.command_args['--root']) self.load_xml_description( abs_root_path ) self.runtime_checker.check_target_directory_not_in_shared_cache( abs_target_dir_path ) log.info('Creating system image') if not os.path.exists(abs_target_dir_path): Path.create(abs_target_dir_path) setup = SystemSetup( xml_state=self.xml_state, root_dir=abs_root_path ) setup.call_image_script() image_builder = ImageBuilder( self.xml_state, abs_target_dir_path, abs_root_path, custom_args={ 'signing_keys': self.command_args['--signing-key'], 'xz_options': self.runtime_config.get_xz_options() } ) result = image_builder.create() result.print_results() result.dump( os.sep.join([abs_target_dir_path, 'kiwi.result']) )
def __init__(self, xml_state, target_dir, root_dir, custom_args=None): self.target_dir = target_dir self.compressed = xml_state.build_type.get_compressed() self.xen_server = xml_state.is_xen_server() self.pxedeploy = xml_state.get_build_type_pxedeploy_section() self.filesystem = FileSystemBuilder( xml_state, target_dir, root_dir + '/' ) self.system_setup = SystemSetup( xml_state=xml_state, root_dir=root_dir ) 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( xml_state, target_dir, signing_keys=self.boot_signing_keys, custom_args=custom_args ) self.image_name = ''.join( [ target_dir, '/', xml_state.xml_data.get_name(), '.' + platform.machine(), '-' + xml_state.get_image_version() ] ) self.archive_name = ''.join([self.image_name, '.tar.xz']) self.kernel_filename = None self.hypervisor_filename = None self.result = Result(xml_state) self.runtime_config = RuntimeConfig()
def __init__(self, xml_state, target_dir, root_dir, custom_args=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.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.checksum = self._target_file_for('md5') self.xz_options = custom_args['xz_options'] if custom_args \ and 'xz_options' in custom_args else None self.runtime_config = RuntimeConfig()
def __init__(self, xml_state, target_dir, root_dir, custom_args=None): self.root_dir = root_dir self.target_dir = target_dir 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'] = custom_args['xz_options'] \ if custom_args and 'xz_options' in custom_args else None 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(), '.' + platform.machine(), '-' + xml_state.get_image_version(), '.', self.requested_container_type, '.tar' ] ) self.result = Result(xml_state) self.runtime_config = RuntimeConfig()
def __init__(self, xml_state, target_dir, root_dir, custom_args=None): self.media_dir = None self.live_container_dir = None self.arch = platform.machine() if self.arch == 'i686' or self.arch == 'i586': self.arch = 'ix86' 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.filesystem_custom_parameters = { 'mount_options': xml_state.get_fs_mount_option_list() } self.publisher = xml_state.build_type.get_publisher() or \ Defaults.get_publisher() 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.isoname = ''.join( [ target_dir, '/', xml_state.xml_data.get_name(), '.' + platform.machine(), '-' + xml_state.get_image_version(), '.iso' ] ) self.result = Result(xml_state) self.runtime_config = RuntimeConfig()
def __init__(self, xml_state, target_dir, root_dir): self.label = None self.root_dir = root_dir self.target_dir = target_dir self.requested_image_type = xml_state.get_build_type_name() if self.requested_image_type == 'pxe': 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() } self.system_setup = SystemSetup( xml_state=xml_state, root_dir=self.root_dir ) self.filename = ''.join( [ target_dir, '/', xml_state.xml_data.get_name(), '.' + platform.machine(), '-' + 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_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 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) # 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) # 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
class ArchiveBuilder(object): """ Root archive image builder Attributes * :attr:`xml_state` Instance of XMLState * :attr:`target_dir` target directory path name * :attr:`root_dir` root directory path name * :attr:`custom_args` Custom processing arguments defined as hash keys: * xz_options: string of XZ compression parameters """ def __init__(self, xml_state, target_dir, root_dir, custom_args=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.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.checksum = self._target_file_for('md5') 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): """ 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" """ 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() ) checksum = Checksum(self.filename) log.info('--> Creating archive checksum') checksum.md5(self.checksum) self.result.verify_image_size( self.runtime_config.get_max_size_constraint(), self.filename ) self.result.add( key='root_archive', filename=self.filename, use_for_bundle=True, compress=False, shasum=True ) self.result.add( key='root_archive_md5', filename=self.checksum, use_for_bundle=False ) 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 _target_file_for(self, suffix): return ''.join( [ self.target_dir, '/', self.xml_state.xml_data.get_name(), '.' + platform.machine(), '-' + self.xml_state.get_image_version(), '.', suffix ] )
class FileSystemBuilder(object): """ **Filesystem image builder** :param str label: filesystem label :param str root_dir: root directory path name :param str target_dir: target directory path name :param str requested_image_type: configured image type :param str requested_filesystem: requested filesystem name :param obejct system_setup: instance of :class:`SystemSetup` :param str filename: file name of the filesystem image :param int blocksize: configured disk blocksize :param object filesystem_setup: instance of :class:`FileSystemSetup` :param object filesystems_no_device_node: List of filesystems which are created from a data tree and do not require a block device e.g loop :param dict filesystem_custom_parameters: Configured custom filesystem mount and creation arguments :param object result: instance of :class:`Result` """ def __init__(self, xml_state, target_dir, root_dir): self.label = None self.root_dir = root_dir self.target_dir = target_dir self.requested_image_type = xml_state.get_build_type_name() if self.requested_image_type == 'pxe': 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() } self.system_setup = SystemSetup( xml_state=xml_state, root_dir=self.root_dir ) self.filename = ''.join( [ target_dir, '/', xml_state.xml_data.get_name(), '.' + platform.machine(), '-' + 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): """ 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() self.result.verify_image_size( self.runtime_config.get_max_size_constraint(), self.filename ) self.result.add( key='filesystem_image', filename=self.filename, use_for_bundle=True, compress=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_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): filesystem = None loop_provider = LoopDevice( self.filename, self.filesystem_setup.get_size_mbytes(), self.blocksize ) loop_provider.create() filesystem = FileSystem( self.requested_filesystem, loop_provider, self.root_dir, self.filesystem_custom_parameters ) filesystem.create_on_device(self.label) log.info( '--> Syncing data to filesystem on %s', loop_provider.get_device() ) exclude_list = [ 'image', '.profile', '.kconfig', Defaults.get_shared_cache_location() ] filesystem.sync_data(exclude_list) def _operate_on_file(self): default_provider = DeviceProvider() filesystem = FileSystem( self.requested_filesystem, default_provider, self.root_dir, self.filesystem_custom_parameters ) filesystem.create_on_file( self.filename, self.label )
def __init__(self, xml_state, target_dir, root_dir, custom_args=None): self.arch = platform.machine() if self.arch == 'i686' or self.arch == 'i586': self.arch = 'ix86' 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.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.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.build_type.get_bootloader() 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_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, custom_args=self.custom_args ) 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.install_media = self._install_image_requested() self.generic_fstab_entries = [] # 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 # result store self.result = Result(xml_state) self.runtime_config = RuntimeConfig()
def test_setup_ix86(self): Defaults.set_platform_name('i686') setup = SystemSetup( MagicMock(), 'root_dir' ) assert setup.arch == 'ix86'
def process(self): # noqa: C901 """ Build a system image from the specified description. The build command combines the prepare and create commands """ self.manual = Help() if self._help(): return Privileges.check_for_root_permissions() abs_target_dir_path = os.path.abspath( self.command_args['--target-dir'] ) build_dir = os.sep.join([abs_target_dir_path, 'build']) image_root = os.sep.join([build_dir, 'image-root']) Path.create(build_dir) if not self.global_args['--logfile']: log.set_logfile( os.sep.join([abs_target_dir_path, 'build', 'image-root.log']) ) self.load_xml_description( self.command_args['--description'] ) self.runtime_checker.check_efi_mode_for_disk_overlay_correctly_setup() self.runtime_checker.check_boot_description_exists() self.runtime_checker.check_consistent_kernel_in_boot_and_system_image() self.runtime_checker.check_docker_tool_chain_installed() self.runtime_checker.check_volume_setup_has_no_root_definition() self.runtime_checker.check_xen_uniquely_setup_as_server_or_guest() self.runtime_checker.check_target_directory_not_in_shared_cache( abs_target_dir_path ) self.runtime_checker.check_mediacheck_only_for_x86_arch() self.runtime_checker.check_dracut_module_for_live_iso_in_package_list() self.runtime_checker.check_dracut_module_for_disk_overlay_in_package_list() self.runtime_checker.check_dracut_module_for_disk_oem_in_package_list() self.runtime_checker.check_dracut_module_for_oem_install_in_package_list() if self.command_args['--ignore-repos']: self.xml_state.delete_repository_sections() elif self.command_args['--ignore-repos-used-for-build']: self.xml_state.delete_repository_sections_used_for_build() if self.command_args['--set-repo']: self.xml_state.set_repository( *self.sextuple_token(self.command_args['--set-repo']) ) if self.command_args['--add-repo']: for add_repo in self.command_args['--add-repo']: self.xml_state.add_repository( *self.sextuple_token(add_repo) ) if self.command_args['--set-container-tag']: self.xml_state.set_container_config_tag( self.command_args['--set-container-tag'] ) if self.command_args['--set-container-derived-from']: self.xml_state.set_derived_from_image_uri( self.command_args['--set-container-derived-from'] ) self.runtime_checker.check_repositories_configured() self.runtime_checker.check_image_include_repos_publicly_resolvable() log.info('Preparing new root system') system = SystemPrepare( self.xml_state, image_root, self.command_args['--allow-existing-root'] ) manager = system.setup_repositories( self.command_args['--clear-cache'], self.command_args['--signing-key'] ) system.install_bootstrap(manager) system.install_system( manager ) if self.command_args['--add-package']: system.install_packages( manager, self.command_args['--add-package'] ) if self.command_args['--delete-package']: system.delete_packages( manager, self.command_args['--delete-package'] ) profile = Profile(self.xml_state) defaults = Defaults() defaults.to_profile(profile) setup = SystemSetup( self.xml_state, image_root ) setup.import_shell_environment(profile) setup.import_description() setup.import_overlay_files() setup.import_image_identifier() setup.setup_groups() setup.setup_users() setup.setup_keyboard_map() setup.setup_locale() setup.setup_plymouth_splash() setup.setup_timezone() # make sure manager instance is cleaned up now del manager # setup permanent image repositories after cleanup setup.import_repositories_marked_as_imageinclude() setup.call_config_script() # handle uninstall package requests, gracefully uninstall # with dependency cleanup system.pinch_system(force=False) # handle delete package requests, forced uninstall without # any dependency resolution system.pinch_system(force=True) # make sure system instance is cleaned up now del system setup.call_image_script() # make sure setup instance is cleaned up now del setup log.info('Creating system image') image_builder = ImageBuilder( self.xml_state, abs_target_dir_path, image_root, custom_args={ 'signing_keys': self.command_args['--signing-key'], 'xz_options': self.runtime_config.get_xz_options() } ) result = image_builder.create() result.print_results() result.dump( os.sep.join([abs_target_dir_path, 'kiwi.result']) )
def __init__( self, xml_state, root_dir, target_dir, boot_image_task, custom_args=None ): self.arch = platform.machine() if self.arch == 'i686' or self.arch == 'i586': self.arch = 'ix86' self.root_dir = root_dir self.target_dir = target_dir self.boot_image_task = boot_image_task self.xml_state = xml_state self.root_filesystem_is_multipath = \ xml_state.get_oemconfig_oem_multipath_scan() self.initrd_system = xml_state.get_initrd_system() self.firmware = FirmWare(xml_state) self.setup = SystemSetup( self.xml_state, self.root_dir ) self.diskname = ''.join( [ target_dir, '/', xml_state.xml_data.get_name(), '.' + self.arch, '-' + xml_state.get_image_version(), '.raw' ] ) self.isoname = ''.join( [ target_dir, '/', xml_state.xml_data.get_name(), '.' + self.arch, '-' + xml_state.get_image_version(), '.install.iso' ] ) self.pxename = ''.join( [ target_dir, '/', xml_state.xml_data.get_name(), '.' + self.arch, '-' + xml_state.get_image_version(), '.install.tar.xz' ] ) self.dracut_config_file = ''.join( [self.root_dir, Defaults.get_dracut_conf_name()] ) self.squashed_diskname = ''.join( [xml_state.xml_data.get_name(), '.raw'] ) self.md5name = ''.join( [xml_state.xml_data.get_name(), '.md5'] ) self.xz_options = custom_args['xz_options'] if custom_args \ and 'xz_options' in custom_args else None self.mbrid = SystemIdentifier() self.mbrid.calculate_id() self.media_dir = None self.pxe_dir = None self.squashed_contents = None self.custom_iso_args = None
class ArchiveBuilder: """ **Root archive 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: * xz_options: string of XZ compression parameters """ def __init__(self, xml_state, target_dir, root_dir, custom_args=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.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): """ 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()) self.result.verify_image_size( self.runtime_config.get_max_size_constraint(), self.filename) 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_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): return ''.join([ self.target_dir, '/', self.xml_state.xml_data.get_name(), '.' + Defaults.get_platform_name(), '-' + self.xml_state.get_image_version(), '.', suffix ])
class ContainerBuilder(object): """ Container image builder Attributes * :attr:`xml_state` Instance of XMLState * :attr:`target_dir` target directory path name * :attr:`root_dir` root directory path name * :attr:`custom_args` Custom processing arguments defined as hash keys: * xz_options: string of XZ compression parameters """ def __init__(self, xml_state, target_dir, root_dir, custom_args=None): self.root_dir = root_dir self.target_dir = target_dir 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'] = custom_args['xz_options'] \ if custom_args and 'xz_options' in custom_args else None 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(), '.' + platform.machine(), '-' + xml_state.get_image_version(), '.', self.requested_container_type, '.tar.xz' ]) self.result = Result(xml_state) def create(self): """ Builds a container image which is usually a tarball including container specific metadata. Image types which triggers this builder are: * image="docker" """ if not self.base_image: log.info('Setting up %s container', self.requested_container_type) container_setup = ContainerSetup(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(self.requested_container_type, self.root_dir, self.container_config) container_image.create(self.filename, self.base_image) self.result.add(key='container', 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_verified', filename=self.system_setup.export_package_verification( self.target_dir), use_for_bundle=True, compress=False, shasum=False) return self.result
def __init__(self, xml_state, target_dir, root_dir, custom_args=None): self.arch = platform.machine() if self.arch == 'i686' or self.arch == 'i586': self.arch = 'ix86' 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.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.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.build_type.get_bootloader() self.initrd_system = xml_state.get_initrd_system() self.target_removable = xml_state.build_type.get_target_removable() self.disk_setup = DiskSetup(xml_state, root_dir) 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, custom_args=self.custom_args) 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.install_media = self._install_image_requested() self.generic_fstab_entries = [] # 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 # result store self.result = Result(xml_state) self.runtime_config = RuntimeConfig()
class BootImageKiwi(BootImageBase): """ Implements preparation and creation of kiwi boot(initrd) images The kiwi initrd is a customized first boot initrd which allows to control the first boot an appliance. The kiwi initrd replaces itself after first boot by the result of dracut. Attributes Inherited from BootImageBase """ def post_init(self): """ Post initialization method Creates custom directory to prepare the boot image root filesystem which is a separate image to create the initrd from """ self.boot_root_directory = mkdtemp(prefix='kiwi_boot_root.', dir=self.target_dir) self.temp_directories.append(self.boot_root_directory) def prepare(self): """ Prepare new root system suitable to create a kiwi initrd from it """ self.load_boot_xml_description() boot_image_name = self.boot_xml_state.xml_data.get_name() self.import_system_description_elements() log.info('Preparing boot image') system = SystemPrepare(xml_state=self.boot_xml_state, root_dir=self.boot_root_directory, allow_existing=True) manager = system.setup_repositories(signing_keys=self.signing_keys) system.install_bootstrap(manager) system.install_system(manager) profile = Profile(self.boot_xml_state) profile.add('kiwi_initrdname', boot_image_name) defaults = Defaults() defaults.to_profile(profile) self.setup = SystemSetup(self.boot_xml_state, self.boot_root_directory) self.setup.import_shell_environment(profile) self.setup.import_description() self.setup.import_overlay_files(follow_links=True) self.setup.call_config_script() system.pinch_system(manager=manager, force=True) # make sure system instance is cleaned up before setting up del system self.setup.call_image_script() self.setup.create_init_link_from_linuxrc() def create_initrd(self, mbrid=None): """ Create initrd from prepared boot system tree and compress the result :param object mbrid: instance of ImageIdentifier """ if self.is_prepared(): log.info('Creating initrd cpio archive') # we can't simply exclude boot when building the archive # because the file boot/mbrid must be preserved. Because of # that we create a copy of the boot directory and remove # everything in boot/ except for boot/mbrid. The original # boot directory should not be changed because we rely # on other data in boot/ e.g the kernel to be available # for the entire image building process temp_boot_root_directory = mkdtemp(prefix='kiwi_boot_root_copy.') self.temp_directories.append(temp_boot_root_directory) data = DataSync(self.boot_root_directory + '/', temp_boot_root_directory) data.sync_data(options=['-z', '-a']) boot_directory = temp_boot_root_directory + '/boot' Path.wipe(boot_directory) if mbrid: log.info('--> Importing mbrid: %s', mbrid.get_id()) Path.create(boot_directory) image_identifier = boot_directory + '/mbrid' mbrid.write(image_identifier) cpio = ArchiveCpio( os.sep.join([self.target_dir, self.initrd_base_name])) # the following is a list of directories which were needed # during the process of creating an image but not when the # image is actually booting with this initrd exclude_from_archive = [ '/' + Defaults.get_shared_cache_location(), '/image', '/usr/lib/grub*' ] # the following is a list of directories to exclude which # are not needed inside of the initrd exclude_from_archive += [ '/usr/share/doc', '/usr/share/man', '/home', '/media', '/srv' ] cpio.create(source_dir=temp_boot_root_directory, exclude=exclude_from_archive) log.info('--> xz compressing archive') compress = Compress( os.sep.join([self.target_dir, self.initrd_base_name])) compress.xz(['--check=crc32', '--lzma2=dict=1MiB', '--threads=0']) self.initrd_filename = compress.compressed_filename
class DiskBuilder(object): """ Disk image builder Attributes * :attr:`xml_state` Instance of XMLState * :attr:`target_dir` Target directory path name * :attr:`root_dir` Root directory path name * :attr:`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 = platform.machine() if self.arch == 'i686' or self.arch == 'i586': self.arch = 'ix86' 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.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.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.build_type.get_bootloader() self.initrd_system = xml_state.get_initrd_system() self.target_removable = xml_state.build_type.get_target_removable() self.disk_setup = DiskSetup(xml_state, root_dir) 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, custom_args=self.custom_args) 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.install_media = self._install_image_requested() self.generic_fstab_entries = [] # 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 # 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" """ 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) result = disk_format.create_disk_format(result) return result def create_disk(self): """ Build a bootable raw disk image """ 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) loop_provider = LoopDevice(self.diskname, disksize_mbytes, self.blocksize) loop_provider.create() self.disk = Disk(self.firmware.get_partition_table_type(), loop_provider) # create the bootloader instance self.bootloader_config = BootLoaderConfig( self.bootloader, self.xml_state, self.root_dir, { 'targetbase': loop_provider.get_device(), 'grub_directory_name': Defaults.get_grub_boot_directory_name(self.root_dir) }) # create disk partitions and instance device map device_map = self._build_and_map_disk_partitions() # 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_root.create_crypto_luks(passphrase=self.luks, os=self.luks_os) device_map['root'] = self.luks_root.get_device() # 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, '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( ), 'image_type': self.xml_state.get_build_type_name() } volume_manager = VolumeManager(self.volume_manager_name, device_map['root'], 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() self.generic_fstab_entries += volume_manager.get_fstab( self.persistency_type, self.requested_filesystem) self.system = volume_manager device_map['root'] = volume_manager.get_device()['root'] 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 } filesystem = FileSystem(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 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': self._create_dracut_config() # create initrd cpio archive self.boot_image.create_initrd(self.mbrid) # create dracut config omitting one time kiwi dracut modules if self.initrd_system == 'dracut': self._create_system_dracut_config() # create second stage metadata to system image self._copy_first_boot_files_to_system_image() self._write_bootloader_config_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_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) 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)) # 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=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 """ 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 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 """ 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.pxename, 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) boot_image.enable_cleanup() 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 '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_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('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(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): # noqa: C901 self.disk.wipe() if self.firmware.legacy_bios_mode(): log.info('--> creating EFI CSM(legacy bios) partition') self.disk.create_efi_csm_partition( self.firmware.get_legacy_bios_partition_size()) if self.firmware.efi_mode(): log.info('--> creating EFI partition') self.disk.create_efi_partition( self.firmware.get_efi_partition_size()) if self.firmware.ofw_mode(): log.info('--> creating PReP partition') self.disk.create_prep_partition( self.firmware.get_prep_partition_size()) if self.disk_setup.need_boot_partition(): log.info('--> creating boot partition') self.disk.create_boot_partition( self.disk_setup.boot_partition_size()) if self.spare_part_mbsize: 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) squashed_root.create_on_file( filename=squashed_root_file.name, exclude=[Defaults.get_shared_cache_location()]) squashed_rootfs_mbsize = os.path.getsize( squashed_root_file.name) / 1048576 self.disk.create_root_readonly_partition( int(squashed_rootfs_mbsize + 50)) if self.volume_manager_name and self.volume_manager_name == 'lvm': log.info('--> creating LVM root partition') self.disk.create_root_lvm_partition('all_free') elif self.mdraid: log.info('--> creating mdraid root partition') self.disk.create_root_raid_partition('all_free') else: log.info('--> creating root partition') self.disk.create_root_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 _create_dracut_config(self): dracut_config = ['hostonly="no"', 'dracut_rescue_image="no"'] dracut_modules = [] dracut_modules_omit = ['kiwi-live', 'kiwi-dump'] if self.root_filesystem_is_overlay: dracut_modules.append('kiwi-overlay') else: dracut_modules_omit.append('kiwi-overlay') if self.build_type_name == 'oem': dracut_modules.append('kiwi-lib') dracut_modules.append('kiwi-repart') self._write_dracut_config(config=dracut_config, modules=dracut_modules, omit_modules=dracut_modules_omit) def _create_system_dracut_config(self): dracut_modules = [] dracut_modules_omit = ['kiwi-live', 'kiwi-dump', 'kiwi-repart'] if self.root_filesystem_is_overlay: dracut_modules.append('kiwi-overlay') else: dracut_modules_omit.append('kiwi-overlay') self._write_dracut_config(config=[], modules=dracut_modules, omit_modules=dracut_modules_omit) def _write_dracut_config(self, config, modules, omit_modules): dracut_config_file = ''.join( [self.root_dir, Defaults.get_dracut_conf_name()]) if modules: config.append('add_dracutmodules+=" {0} "'.format( ' '.join(modules))) if omit_modules: config.append('omit_dracutmodules+=" {0} "'.format( ' '.join(omit_modules))) with open(dracut_config_file, 'w') as dracut_config: for entry in config: dracut_config.write(entry + os.linesep) 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 'boot' in device_map: 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 'efi' in device_map: self._add_generic_fstab_entry(device_map['efi'].get_device(), '/boot/efi') setup.create_fstab(self.generic_fstab_entries) 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 ]) if fstab_entry not in self.generic_fstab_entries: self.generic_fstab_entries.append(fstab_entry) 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_config_to_system_image(self, device_map): if self.bootloader is not 'custom': log.info('Creating %s bootloader configuration', self.bootloader) boot_options = [] if self.mdraid: boot_options.append('rd.auto') boot_names = self.boot_image.get_boot_names() boot_device = device_map['root'] 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()) self.bootloader_config.setup_disk_boot_images(boot_uuid) self.bootloader_config.setup_disk_image_config( boot_uuid=boot_uuid, root_uuid=root_uuid, kernel=boot_names.kernel_name, initrd=boot_names.initrd_name, boot_options=' '.join(boot_options)) self.bootloader_config.write() 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()}) if self.bootloader is not '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]) ])
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.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 !')
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.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 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) # 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) # 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/.*') return exclude_list @staticmethod def _get_exclude_list_for_boot_data_sync() -> list: return ['efi/*'] 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.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') 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]) -> None: log.info('Syncing system to image') if system_spare: system_spare.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 PxeBuilder: """ **Filesystem based PXE 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, target_dir, root_dir, custom_args=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 + '/') 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(xml_state, target_dir, root_dir, signing_keys=self.boot_signing_keys) self.image_name = ''.join([ target_dir, '/', xml_state.xml_data.get_name(), '.' + platform.machine(), '-' + xml_state.get_image_version() ]) 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 = None self.hypervisor_filename = None self.result = Result(xml_state) self.runtime_config = RuntimeConfig() def create(self): """ Build a pxe image set consisting out of a boot image(initrd) plus its appropriate kernel files and the root filesystem image with a checksum. The result can be used within the kiwi PXE boot infrastructure Image types which triggers this builder are: * image="pxe" :raises KiwiPxeBootImageError: if no kernel or hipervisor is found in boot image tree :return: result :rtype: instance of :class:`Result` """ log.info('Creating PXE 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 PXE root filesystem MD5 checksum') checksum = Checksum(self.image) checksum.md5(self.checksum_name) # prepare boot(initrd) root system log.info('Creating PXE 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(initrd) root 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 KiwiPxeBootImageError( '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: kernel_data = kernel.get_xen_hypervisor() if kernel_data: self.hypervisor_filename = ''.join( [os.path.basename(self.image_name), '-', kernel_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 KiwiPxeBootImageError( 'No hypervisor in boot image tree %s found' % self.boot_image_task.boot_root_directory) # create initrd for pxe boot self.boot_image_task.create_initrd() # create pxe config append information # this information helps to configure the boot server correctly if 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() pxe_tarball_files = [ self.kernel_filename, os.path.basename(self.boot_image_task.initrd_filename), os.path.basename(self.image), os.path.basename(self.checksum_name), ] if self.filesystem.root_uuid and self.initrd_system == 'dracut': pxe_tarball_files.append(os.path.basename(self.append_file)) pxe_tarball = ArchiveTar(self.archive_name, create_from_file_list=True, file_list=pxe_tarball_files) if self.compressed: self.archive_name = pxe_tarball.create(self.target_dir) else: self.archive_name = pxe_tarball.create_xz_compressed( self.target_dir, xz_options=self.xz_options) self.result.verify_image_size( self.runtime_config.get_max_size_constraint(), self.archive_name) # store results self.result.add( key='pxe_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_verified', filename=self.system_setup.export_package_verification( self.target_dir), use_for_bundle=True, compress=False, shasum=False) return self.result
class LiveImageBuilder(object): """ Live image builder Attributes * :attr:`media_dir` Temporary directory to collect the install ISO contents * :attr:`arch` platform.machine * :attr:`root_dir` root directory path name * :attr:`target_dir` target directory path name * :attr:`xml_state` Instance of XMLState * :attr:`live_type` Configured live ISO type name * :attr:`types` List of supported live ISO types * :attr:`hybrid` Request for hybrid ISO: true|false * :attr:`volume_id` Configured ISO volume ID or default * :attr:`mbrid` Instance of SystemIdentifier * :attr:`filesystem_custom_parameters` Configured custom filesystem mount and creation arguments * :attr:`boot_image_task` Instance of BootImage * :attr:`firmware` Instance of FirmWare * :attr:`system_setup` Instance of SystemSetup * :attr:`isoname` File name of the live ISO image * :attr:`live_image_file` File name of compressed image on the ISO * :attr:`result` Instance of Result """ def __init__(self, xml_state, target_dir, root_dir, custom_args=None): self.media_dir = None self.arch = platform.machine() if self.arch == 'i686' or self.arch == 'i586': self.arch = 'ix86' 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.types = Defaults.get_live_iso_types() self.hybrid = xml_state.build_type.get_hybrid() self.volume_id = xml_state.build_type.get_volid() self.machine = xml_state.get_build_type_machine_section() self.mbrid = SystemIdentifier() self.mbrid.calculate_id() self.filesystem_custom_parameters = { 'mount_options': xml_state.get_fs_mount_option_list() } if not self.live_type: self.live_type = Defaults.get_default_live_iso_type() boot_signing_keys = None if custom_args and 'signing_keys' in custom_args: boot_signing_keys = custom_args['signing_keys'] self.boot_image_task = BootImage(xml_state, target_dir, signing_keys=boot_signing_keys) self.firmware = FirmWare(xml_state) self.system_setup = SystemSetup(xml_state=xml_state, root_dir=self.root_dir) self.isoname = ''.join([ target_dir, '/', xml_state.xml_data.get_name(), '.' + platform.machine(), '-' + xml_state.get_image_version(), '.iso' ]) self.live_image_file = ''.join([ target_dir, '/', xml_state.xml_data.get_name(), '-read-only.', self.arch, '-', xml_state.get_image_version() ]) self.result = Result(xml_state) def create(self): """ Build a bootable hybrid live ISO image Image types which triggers this builder are: * image="iso" """ # media dir to store CD contents self.media_dir = mkdtemp(prefix='live-media.', dir=self.target_dir) rootsize = SystemSize(self.media_dir) # custom iso metadata log.info('Using following live ISO metadata:') log.info('--> Application id: %s', self.mbrid.get_id()) log.info('--> Publisher: %s', Defaults.get_publisher()) custom_iso_args = { 'create_options': [ '-A', self.mbrid.get_id(), '-p', '"' + Defaults.get_preparer() + '"', '-publisher', '"' + Defaults.get_publisher() + '"' ] } if self.volume_id: log.info('--> Volume id: %s', self.volume_id) custom_iso_args['create_options'].append('-V') custom_iso_args['create_options'].append('"' + self.volume_id + '"') # prepare boot(initrd) root system log.info('Preparing live ISO boot system') self.boot_image_task.prepare() # export modprobe configuration to boot image self.system_setup.export_modprobe_setup( self.boot_image_task.boot_root_directory) # pack system into live boot structure log.info('Packing system into live ISO type: %s', self.live_type) if self.live_type in self.types: live_type_image = FileSystem( name=self.types[self.live_type], device_provider=None, root_dir=self.root_dir, custom_args=self.filesystem_custom_parameters) live_type_image.create_on_file(self.live_image_file) Command.run(['mv', self.live_image_file, self.media_dir]) self._create_live_iso_client_config(self.live_type) else: raise KiwiLiveBootImageError('live ISO type "%s" not supported' % self.live_type) # setup bootloader config to boot the ISO via isolinux log.info('Setting up isolinux bootloader configuration') bootloader_config_isolinux = BootLoaderConfig('isolinux', self.xml_state, self.media_dir) bootloader_config_isolinux.setup_live_boot_images( mbrid=None, lookup_path=self.boot_image_task.boot_root_directory) bootloader_config_isolinux.setup_live_image_config(mbrid=None) bootloader_config_isolinux.write() self.system_setup.call_edit_boot_config_script( filesystem=self.types[self.live_type], boot_part_id=1, working_directory=self.media_dir) # setup bootloader config to boot the ISO via EFI if self.firmware.efi_mode(): log.info('Setting up EFI grub bootloader configuration') bootloader_config_grub = BootLoaderConfig( 'grub2', self.xml_state, self.media_dir, { 'grub_directory_name': Defaults.get_grub_boot_directory_name(self.root_dir) }) bootloader_config_grub.setup_live_boot_images( mbrid=self.mbrid, lookup_path=self.boot_image_task.boot_root_directory) bootloader_config_grub.setup_live_image_config(mbrid=self.mbrid) bootloader_config_grub.write() # create initrd for live image log.info('Creating live ISO boot image') self._create_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['create_options'].append('-iso-level') custom_iso_args['create_options'].append('3') custom_iso_args['create_options'].append('-udf') # create iso filesystem from media_dir log.info('Creating live ISO image') iso_image = FileSystemIsoFs(device_provider=None, root_dir=self.media_dir, custom_args=custom_iso_args) iso_header_offset = iso_image.create_on_file(self.isoname) # make it hybrid if self.hybrid: Iso.create_hybrid(iso_header_offset, self.mbrid, self.isoname) 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_rpm_package_list( self.target_dir), use_for_bundle=True, compress=False, shasum=False) self.result.add( key='image_verified', filename=self.system_setup.export_rpm_package_verification( self.target_dir), use_for_bundle=True, compress=False, shasum=False) return self.result def _create_live_iso_kernel_and_initrd(self): boot_path = self.media_dir + '/boot/' + self.arch + '/loader' Path.create(boot_path) kernel = Kernel(self.boot_image_task.boot_root_directory) if kernel.get_kernel(): kernel.copy_kernel(boot_path, '/linux') else: raise KiwiLiveBootImageError( 'No kernel in boot image tree %s found' % self.boot_image_task.boot_root_directory) if self.machine and self.machine.get_domain() == 'dom0': if kernel.get_xen_hypervisor(): kernel.copy_xen_hypervisor(boot_path, '/xen.gz') else: raise KiwiLiveBootImageError( 'No hypervisor in boot image tree %s found' % self.boot_image_task.boot_root_directory) self.boot_image_task.create_initrd(self.mbrid) Command.run([ 'mv', self.boot_image_task.initrd_filename, boot_path + '/initrd' ]) def _create_live_iso_client_config(self, iso_type): """ Setup IMAGE and UNIONFS_CONFIG variables as they are used in the kiwi isoboot code. Variable contents: + IMAGE=target_device;live_iso_name_definition + UNIONFS_CONFIG=rw_device,ro_device,union_type If no real block device is used or can be predefined the word 'loop' is set as a placeholder or indicator to use a loop device. For more details please refer to the kiwi shell boot code """ iso_client_config_file = self.media_dir + '/config.isoclient' iso_client_params = Defaults.get_live_iso_client_parameters() (system_device, union_device, union_type) = iso_client_params[iso_type] with open(iso_client_config_file, 'w') as config: config.write('IMAGE="%s;%s.%s;%s"\n' % (system_device, self.xml_state.xml_data.get_name(), self.arch, self.xml_state.get_image_version())) config.write('UNIONFS_CONFIG="%s,loop,%s"\n' % (union_device, union_type)) def __del__(self): if self.media_dir: log.info('Cleaning up %s instance', type(self).__name__) Path.wipe(self.media_dir)
class DiskBuilder(object): """ Disk image builder Attributes * :attr:`arch` platform.machine * :attr:`root_dir` root directory path name * :attr:`target_dir` target directory path name * :attr:`xml_state` Instance of XMLState * :attr:`custom_root_mount_args` Configured custom root mount arguments * :attr:`build_type_name` Configured build type name, oem or vmx * :attr:`image_format` Configured disk image format * :attr:`install_iso` Request for install ISO image * :attr:`install_stick` Request for install Stick image, converts into a request for the install ISO image because it can handle both cases by the hybrid ISO setup * :attr:`install_pxe` Request for install PXE archive * :attr:`blocksize` Configured disk blocksize * :attr:`volume_manager_name` Configured volume manager * :attr:`volumes` Configured disk volumes * :attr:`volume_group_name` Configured volume group name * :attr:`mdraid` Request for md raid, degraded setup * :attr:`hybrid_mbr` Request to convert partition table to hybrid GPT/MBR' * :attr:`luks` LUKS encryption credentials, also triggers to encrypt the disk * :attr:`luks_os` Target operating system name for LUKS encryption * :attr:`machine` Configured build type machine section * :attr:`requested_filesystem` Configured root filesystem * :attr:`requested_boot_filesystem` Configured boot filesystem * :attr:`bootloader` Configured boot loader * :attr:`initrd_system` Configured initrd system * :attr:`disk_setup` Instance of DiskSetup * :attr:`boot_image` Instance of BootImage * :attr:`firmware` Instance of FirmWare * :attr:`system_setup` Instance of SystemSetup * :attr:`diskname` File name of the disk image * :attr:`install_media` Build of install media requested true|false * :attr:`system` Instance of a class with the sync_data capability representing the entire image system except for the boot/ area which could live on other parts of the disk * :attr:`system_boot` Instance of a class with the sync_data capability representing the boot/ area of the disk if not part of system * :attr:`system_efi` Instance of a class with the sync_data capability representing the boot/efi area of the disk * :attr:`generic_fstab_entries` List of generic/persistent fstab entries * :attr:`result` Instance of Result """ def __init__(self, xml_state, target_dir, root_dir, custom_args=None): self.arch = platform.machine() if self.arch == 'i686' or self.arch == 'i586': self.arch = 'ix86' 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.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.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.machine = xml_state.get_build_type_machine_section() self.requested_filesystem = xml_state.build_type.get_filesystem() self.requested_boot_filesystem = \ xml_state.build_type.get_bootfilesystem() self.bootloader = xml_state.build_type.get_bootloader() self.initrd_system = xml_state.build_type.get_initrd_system() self.target_removable = xml_state.build_type.get_target_removable() self.disk_setup = DiskSetup( xml_state, root_dir ) 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.install_media = self._install_image_requested() self.generic_fstab_entries = [] # 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 # result store self.result = Result(xml_state) 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" """ 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 ) result = disk_format.create_disk_format(result) return result def create_disk(self): """ Build a bootable raw disk image """ 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) loop_provider = LoopDevice( self.diskname, disksize_mbytes, self.blocksize ) loop_provider.create() self.disk = Disk( self.firmware.get_partition_table_type(), loop_provider ) # create the bootloader instance self.bootloader_config = BootLoaderConfig( self.bootloader, self.xml_state, self.root_dir, { 'targetbase': loop_provider.get_device(), 'grub_directory_name': Defaults.get_grub_boot_directory_name(self.root_dir) } ) # create disk partitions and instance device map device_map = self._build_and_map_disk_partitions() # 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() # create luks on current root device if requested if self.luks: self.luks_root = LuksDevice(device_map['root']) self.luks_root.create_crypto_luks( passphrase=self.luks, os=self.luks_os ) device_map['root'] = self.luks_root.get_device() # 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, '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(), 'image_type': self.xml_state.get_build_type_name() } volume_manager = VolumeManager( self.volume_manager_name, device_map['root'], 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() self.generic_fstab_entries += volume_manager.get_fstab( self.persistency_type, self.requested_filesystem ) self.system = volume_manager device_map['root'] = volume_manager.get_device()['root'] 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 } filesystem = FileSystem( 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 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) # 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_config_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_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 ) 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) ) # 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: if self.initrd_system and self.initrd_system == 'dracut': # for the installation process we need a kiwi initrd # Therefore an extra install boot root system needs to # be prepared if dracut was set as the initrd system # to boot the system image log.info('Preparing extra install boot system') self.xml_state.build_type.set_initrd_system('kiwi') self.initrd_system = \ self.xml_state.build_type.get_initrd_system() self.boot_image = BootImageKiwi( self.xml_state, self.target_dir, signing_keys=self.signing_keys ) self.boot_image.prepare() # apply disk builder metadata also needed in the install initrd self._write_partition_id_config_to_boot_image() self._write_recovery_metadata_to_boot_image() self._write_raid_config_to_boot_image() self.system_setup.export_modprobe_setup( self.boot_image.boot_root_directory ) log.info('Saving boot image instance to file') self.boot_image.dump( self.target_dir + '/boot_image.pickledump' ) # 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=True, shasum=True ) # create image root metadata self.result.add( key='image_packages', filename=self.system_setup.export_rpm_package_list( self.target_dir ), use_for_bundle=True, compress=False, shasum=False ) self.result.add( key='image_verified', filename=self.system_setup.export_rpm_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 """ 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 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 """ if self.install_media: install_image = InstallImageBuilder( self.xml_state, self.root_dir, self.target_dir, self._load_boot_image_instance() ) 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.pxename, 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) boot_image.enable_cleanup() 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 = [ 'image', '.profile', '.kconfig', Defaults.get_shared_cache_location() ] 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_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( '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( 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): self.disk.wipe() if self.firmware.legacy_bios_mode(): log.info('--> creating EFI CSM(legacy bios) partition') self.disk.create_efi_csm_partition( self.firmware.get_legacy_bios_partition_size() ) if self.firmware.efi_mode(): log.info('--> creating EFI partition') self.disk.create_efi_partition( self.firmware.get_efi_partition_size() ) if self.firmware.ofw_mode(): log.info('--> creating PReP partition') self.disk.create_prep_partition( self.firmware.get_prep_partition_size() ) if self.disk_setup.need_boot_partition(): log.info('--> creating boot partition') self.disk.create_boot_partition( self.disk_setup.boot_partition_size() ) if self.spare_part_mbsize: 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 ) squashed_root.create_on_file( filename=squashed_root_file.name, exclude=[Defaults.get_shared_cache_location()] ) squashed_rootfs_mbsize = os.path.getsize( squashed_root_file.name ) / 1048576 self.disk.create_root_readonly_partition( int(squashed_rootfs_mbsize + 50) ) if self.volume_manager_name and self.volume_manager_name == 'lvm': log.info('--> creating LVM root partition') self.disk.create_root_lvm_partition('all_free') elif self.mdraid: log.info('--> creating mdraid root partition') self.disk.create_root_raid_partition('all_free') else: log.info('--> creating root partition') self.disk.create_root_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): if not self.initrd_system or self.initrd_system == 'kiwi': 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) ) def _write_raid_config_to_boot_image(self): if self.mdraid: log.info('Creating etc/mdadm.conf in boot system') self.raid_root.create_raid_config( self.boot_image.boot_root_directory + '/etc/mdadm.conf' ) def _write_crypttab_to_system_image(self): if self.luks: log.info('Creating etc/crypttab') self.luks_root.create_crypttab( self.root_dir + '/etc/crypttab' ) 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 not self.initrd_system or 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 'boot' in device_map: 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 'efi' in device_map: self._add_generic_fstab_entry( device_map['efi'].get_device(), '/boot/efi' ) setup.create_fstab( self.generic_fstab_entries ) 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 ] ) if fstab_entry not in self.generic_fstab_entries: self.generic_fstab_entries.append( fstab_entry ) 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') Command.run( [ 'cp', self.root_dir + '/recovery.partition.size', self.boot_image.boot_root_directory ] ) def _write_bootloader_config_to_system_image(self, device_map): if self.bootloader is not 'custom': log.info('Creating %s bootloader configuration', self.bootloader) boot_names = self._get_boot_names() boot_device = device_map['root'] 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() ) self.bootloader_config.setup_disk_boot_images(boot_uuid) self.bootloader_config.setup_disk_image_config( boot_uuid=boot_uuid, root_uuid=root_uuid, kernel=boot_names.kernel_name, initrd=boot_names.initrd_name ) self.bootloader_config.write() 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()} ) if self.bootloader is not '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._get_boot_names() if not self.initrd_system or 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.machine and self.machine.get_domain() == 'dom0': 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]) ] ) def _get_boot_names(self): boot_names_type = namedtuple( 'boot_names_type', ['kernel_name', 'initrd_name'] ) kernel = Kernel( self.boot_image.boot_root_directory ) kernel_info = kernel.get_kernel() if not kernel_info: raise KiwiDiskBootImageError( 'No kernel in boot image tree %s found' % self.boot_image.boot_root_directory ) if self.initrd_system and self.initrd_system == 'dracut': dracut_output_format = self._get_dracut_output_file_format() return boot_names_type( kernel_name=kernel_info.name, initrd_name=dracut_output_format.format( kernel_version=kernel_info.version ) ) else: return boot_names_type( kernel_name='linux.vmx', initrd_name='initrd.vmx' ) def _get_dracut_output_file_format(self): """ Unfortunately the dracut initrd output file format varies between the different Linux distributions. Tools like lsinitrd, and also grub2 rely on the initrd output file to be in that format. Thus when kiwi uses dracut the same file format should be used all over the place in order to stay compatible with what the distribution does """ default_outfile_format = 'initramfs-{kernel_version}.img' dracut_search_env = { 'PATH': os.sep.join([self.root_dir, 'usr', 'bin']) } dracut_tool = Path.which( 'dracut', custom_env=dracut_search_env, access_mode=os.X_OK ) if dracut_tool: outfile_expression = r'outfile="/boot/(init.*\$kernel.*)"' with open(dracut_tool) as dracut: outfile = re.findall(outfile_expression, dracut.read())[0] if outfile: return outfile.replace('$kernel', '{kernel_version}') log.warning('Could not detect dracut output file format') log.warning('Using default initrd file name format {0}'.format( default_outfile_format )) return default_outfile_format
class BootImageKiwi(BootImageBase): """ **Implements preparation and creation of kiwi boot(initrd) images** The kiwi initrd is a customized first boot initrd which allows to control the first boot an appliance. The kiwi initrd replaces itself after first boot by the result of dracut. """ @staticmethod def has_initrd_support() -> bool: """ This instance supports initrd preparation and creation """ return True def post_init(self) -> None: """ Post initialization method Creates custom directory to prepare the boot image root filesystem which is a separate image to create the initrd from """ # builtin kiwi initrd builds its own root tree to create # an initrd from. Thus there is no pre defined boot root # directory self.boot_root_directory = '' self.temp_directories: List[str] = [] self.load_boot_xml_description() def prepare(self) -> None: """ Prepare new root system suitable to create a kiwi initrd from it """ if self.boot_xml_state: self.boot_root_directory_temporary = Temporary( prefix='kiwi_boot_root.', dir=self.target_dir).new_dir() self.boot_root_directory = self.boot_root_directory_temporary.name self.temp_directories.append(self.boot_root_directory) boot_image_name = self.boot_xml_state.xml_data.get_name() self.import_system_description_elements() log.info('Preparing boot image') system = SystemPrepare(xml_state=self.boot_xml_state, root_dir=self.boot_root_directory, allow_existing=True) manager = system.setup_repositories(signing_keys=self.signing_keys, target_arch=self.target_arch) system.install_bootstrap(manager) system.install_system(manager) profile = Profile(self.boot_xml_state) profile.add('kiwi_initrdname', boot_image_name) defaults = Defaults() defaults.to_profile(profile) self.setup = SystemSetup(self.boot_xml_state, self.boot_root_directory) profile.create(Defaults.get_profile_file(self.boot_root_directory)) self.setup.import_description() self.setup.import_overlay_files(follow_links=True) self.setup.call_config_script() system.pinch_system(manager=manager, force=True) # make sure system instance is cleaned up before setting up del system self.setup.call_image_script() self.setup.create_init_link_from_linuxrc() def create_initrd(self, mbrid: Optional[SystemIdentifier] = None, basename: Optional[str] = None, install_initrd: bool = False) -> None: """ Create initrd from prepared boot system tree and compress the result :param SystemIdentifier mbrid: instance of ImageIdentifier :param str basename: base initrd file name :param bool install_initrd: installation media initrd """ if self.is_prepared(): log.info('Creating initrd cpio archive') # we can't simply exclude boot when building the archive # because the file boot/mbrid must be preserved. Because of # that we create a copy of the boot directory and remove # everything in boot/ except for boot/mbrid. The original # boot directory should not be changed because we rely # on other data in boot/ e.g the kernel to be available # for the entire image building process if basename: kiwi_initrd_basename = basename else: kiwi_initrd_basename = self.initrd_base_name temp_boot_root = Temporary(prefix='kiwi_boot_root_copy.').new_dir() temp_boot_root_directory = temp_boot_root.name os.chmod(temp_boot_root_directory, 0o755) data = DataSync(self.boot_root_directory + '/', temp_boot_root_directory) data.sync_data(options=['-a']) boot_directory = temp_boot_root_directory + '/boot' Path.wipe(boot_directory) if mbrid: log.info('--> Importing mbrid: %s', mbrid.get_id()) Path.create(boot_directory) image_identifier = boot_directory + '/mbrid' mbrid.write(image_identifier) cpio = ArchiveCpio( os.sep.join([self.target_dir, kiwi_initrd_basename])) # the following is a list of directories which were needed # during the process of creating an image but not when the # image is actually booting with this initrd exclude_from_archive = [ '/' + Defaults.get_shared_cache_location(), '/image', '/usr/lib/grub*' ] # the following is a list of directories to exclude which # are not needed inside of the initrd exclude_from_archive += [ '/usr/share/doc', '/usr/share/man', '/home', '/media', '/srv' ] cpio.create(source_dir=temp_boot_root_directory, exclude=exclude_from_archive) log.info('--> xz compressing archive') compress = Compress( os.sep.join([self.target_dir, kiwi_initrd_basename])) compress.xz(['--check=crc32', '--lzma2=dict=1MiB', '--threads=0']) self.initrd_filename = compress.compressed_filename def cleanup(self) -> None: for directory in self.temp_directories: if directory and os.path.exists(directory): Path.wipe(directory)
class InstallImageBuilder(object): """ **Installation image builder** :param object xml_state: instance of :class:`XMLState` :param str root_dir: system image root directory :param str target_dir: target directory path name :param object boot_image_task: instance of :class:`BootImage` :param dict custom_args: Custom processing arguments defined as hash keys: * xz_options: string of XZ compression parameters """ def __init__( self, xml_state, root_dir, target_dir, boot_image_task, custom_args=None ): self.arch = platform.machine() if self.arch == 'i686' or self.arch == 'i586': self.arch = 'ix86' self.root_dir = root_dir self.target_dir = target_dir self.boot_image_task = boot_image_task self.xml_state = xml_state self.root_filesystem_is_multipath = \ xml_state.get_oemconfig_oem_multipath_scan() self.initrd_system = xml_state.get_initrd_system() self.firmware = FirmWare(xml_state) self.setup = SystemSetup( self.xml_state, self.root_dir ) self.diskname = ''.join( [ target_dir, '/', xml_state.xml_data.get_name(), '.' + self.arch, '-' + xml_state.get_image_version(), '.raw' ] ) self.isoname = ''.join( [ target_dir, '/', xml_state.xml_data.get_name(), '.' + self.arch, '-' + xml_state.get_image_version(), '.install.iso' ] ) self.pxename = ''.join( [ target_dir, '/', xml_state.xml_data.get_name(), '.' + self.arch, '-' + xml_state.get_image_version(), '.install.tar.xz' ] ) self.dracut_config_file = ''.join( [self.root_dir, Defaults.get_dracut_conf_name()] ) self.squashed_diskname = ''.join( [xml_state.xml_data.get_name(), '.raw'] ) self.md5name = ''.join( [xml_state.xml_data.get_name(), '.md5'] ) self.xz_options = custom_args['xz_options'] if custom_args \ and 'xz_options' in custom_args else None self.mbrid = SystemIdentifier() self.mbrid.calculate_id() self.media_dir = None self.pxe_dir = None self.squashed_contents = None self.custom_iso_args = None def create_install_iso(self): """ Create an install ISO from the disk_image as hybrid ISO bootable via legacy BIOS, EFI and as disk from Stick Image types which triggers this builder are: * installiso="true|false" * installstick="true|false" """ self.media_dir = mkdtemp( prefix='kiwi_install_media.', dir=self.target_dir ) # unpack cdroot user files to media dir self.setup.import_cdroot_files(self.media_dir) # custom iso metadata self.custom_iso_args = { 'meta_data': { 'volume_id': Defaults.get_install_volume_id(), 'mbr_id': self.mbrid.get_id(), 'efi_mode': self.firmware.efi_mode() } } # the system image transfer is checked against a checksum log.info('Creating disk image checksum') self.squashed_contents = mkdtemp( prefix='kiwi_install_squashfs.', dir=self.target_dir ) checksum = Checksum(self.diskname) checksum.md5(self.squashed_contents + '/' + self.md5name) # the system image name is stored in a config file self._write_install_image_info_to_iso_image() if self.initrd_system == 'kiwi': self._write_install_image_info_to_boot_image() # the system image is stored as squashfs embedded file log.info('Creating squashfs embedded disk image') Command.run( [ 'cp', '-l', self.diskname, self.squashed_contents + '/' + self.squashed_diskname ] ) squashed_image_file = ''.join( [ self.target_dir, '/', self.squashed_diskname, '.squashfs' ] ) squashed_image = FileSystemSquashFs( device_provider=None, root_dir=self.squashed_contents ) squashed_image.create_on_file(squashed_image_file) Command.run( ['mv', squashed_image_file, self.media_dir] ) # setup bootloader config to boot the ISO via isolinux log.info('Setting up install image bootloader configuration') bootloader_config_isolinux = BootLoaderConfig( 'isolinux', self.xml_state, self.media_dir ) bootloader_config_isolinux.setup_install_boot_images( mbrid=None, lookup_path=self.boot_image_task.boot_root_directory ) bootloader_config_isolinux.setup_install_image_config( mbrid=None ) bootloader_config_isolinux.write() # setup bootloader config to boot the ISO via EFI bootloader_config_grub = BootLoaderConfig( 'grub2', self.xml_state, self.media_dir, { 'grub_directory_name': Defaults.get_grub_boot_directory_name(self.root_dir) } ) bootloader_config_grub.setup_install_boot_images( mbrid=self.mbrid, lookup_path=self.root_dir ) bootloader_config_grub.setup_install_image_config( mbrid=self.mbrid ) bootloader_config_grub.write() # create initrd for install image log.info('Creating install image boot image') self._create_iso_install_kernel_and_initrd() # the system image initrd is stored to allow kexec self._copy_system_image_initrd_to_iso_image() # create iso filesystem from media_dir log.info('Creating ISO filesystem') iso_image = FileSystemIsoFs( device_provider=None, root_dir=self.media_dir, custom_args=self.custom_iso_args ) iso_image.create_on_file(self.isoname) def create_install_pxe_archive(self): """ Create an oem install tar archive suitable for installing a disk image via the network using the PXE boot protocol. The archive contains: * The raw system image xz compressed * The raw system image checksum metadata file * The append file template for the boot server * The system image initrd for kexec * The install initrd * The kernel Image types which triggers this builder are: * installpxe="true|false" """ self.pxe_dir = mkdtemp( prefix='kiwi_pxe_install_media.', dir=self.target_dir ) # the system image is transfered as xz compressed variant log.info('xz compressing disk image') pxe_image_filename = ''.join( [ self.pxe_dir, '/', self.xml_state.xml_data.get_name(), '.xz' ] ) compress = Compress( source_filename=self.diskname, keep_source_on_compress=True ) compress.xz(self.xz_options) Command.run( ['mv', compress.compressed_filename, pxe_image_filename] ) # the system image transfer is checked against a checksum log.info('Creating disk image checksum') pxe_md5_filename = ''.join( [ self.pxe_dir, '/', self.xml_state.xml_data.get_name(), '.md5' ] ) checksum = Checksum(self.diskname) checksum.md5(pxe_md5_filename) # the install image name is stored in a config file if self.initrd_system == 'kiwi': self._write_install_image_info_to_boot_image() # the kexec required system image initrd is stored for dracut kiwi-dump if self.initrd_system == 'dracut': boot_names = self.boot_image_task.get_boot_names() system_image_initrd = os.sep.join( [self.root_dir, 'boot', boot_names.initrd_name] ) target_initrd_name = '{0}/{1}.initrd'.format( self.pxe_dir, self.xml_state.xml_data.get_name() ) shutil.copy( system_image_initrd, target_initrd_name ) os.chmod(target_initrd_name, 420) # create pxe config append information # this information helps to configure the boot server correctly append_filename = ''.join( [ self.pxe_dir, '/', self.xml_state.xml_data.get_name(), '.append' ] ) if self.initrd_system == 'kiwi': cmdline = 'pxe=1' else: cmdline = ' '.join( [ 'rd.kiwi.install.pxe', 'rd.kiwi.install.image=http://example.com/image.xz' ] ) custom_cmdline = self.xml_state.build_type.get_kernelcmdline() if custom_cmdline: cmdline += ' ' + custom_cmdline with open(append_filename, 'w') as append: append.write('%s\n' % cmdline) # create initrd for pxe install log.info('Creating pxe install boot image') self._create_pxe_install_kernel_and_initrd() # create pxe install tarball log.info('Creating pxe install archive') archive = ArchiveTar( self.pxename.replace('.xz', '') ) archive.create_xz_compressed( self.pxe_dir, xz_options=self.xz_options ) def _create_pxe_install_kernel_and_initrd(self): kernel = Kernel(self.boot_image_task.boot_root_directory) if kernel.get_kernel(): kernel.copy_kernel(self.pxe_dir, '/pxeboot.kernel') os.symlink( 'pxeboot.kernel', ''.join( [ self.pxe_dir, '/', self.xml_state.xml_data.get_name(), '.kernel' ] ) ) else: raise KiwiInstallBootImageError( 'No kernel in boot image tree %s found' % self.boot_image_task.boot_root_directory ) if self.xml_state.is_xen_server(): if kernel.get_xen_hypervisor(): kernel.copy_xen_hypervisor(self.pxe_dir, '/pxeboot.xen.gz') else: raise KiwiInstallBootImageError( 'No hypervisor in boot image tree %s found' % self.boot_image_task.boot_root_directory ) if self.initrd_system == 'dracut': self._create_dracut_install_config() self._add_system_image_boot_options_to_boot_image() self.boot_image_task.create_initrd(self.mbrid, 'initrd_kiwi_install') Command.run( [ 'mv', self.boot_image_task.initrd_filename, self.pxe_dir + '/pxeboot.initrd.xz' ] ) os.chmod(self.pxe_dir + '/pxeboot.initrd.xz', 420) def _create_iso_install_kernel_and_initrd(self): boot_path = self.media_dir + '/boot/' + self.arch + '/loader' Path.create(boot_path) kernel = Kernel(self.boot_image_task.boot_root_directory) if kernel.get_kernel(): kernel.copy_kernel(boot_path, '/linux') else: raise KiwiInstallBootImageError( 'No kernel in boot image tree %s found' % self.boot_image_task.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 KiwiInstallBootImageError( 'No hypervisor in boot image tree %s found' % self.boot_image_task.boot_root_directory ) if self.initrd_system == 'dracut': self._create_dracut_install_config() self._add_system_image_boot_options_to_boot_image() self.boot_image_task.create_initrd(self.mbrid, 'initrd_kiwi_install') Command.run( [ 'mv', self.boot_image_task.initrd_filename, boot_path + '/initrd' ] ) def _add_system_image_boot_options_to_boot_image(self): filename = ''.join( [self.boot_image_task.boot_root_directory, '/config.bootoptions'] ) self.boot_image_task.include_file( os.sep + os.path.basename(filename) ) def _copy_system_image_initrd_to_iso_image(self): boot_names = self.boot_image_task.get_boot_names() system_image_initrd = os.sep.join( [self.root_dir, 'boot', boot_names.initrd_name] ) shutil.copy( system_image_initrd, self.media_dir + '/initrd.system_image' ) def _write_install_image_info_to_iso_image(self): iso_trigger = self.media_dir + '/config.isoclient' with open(iso_trigger, 'w') as iso_system: iso_system.write('IMAGE="%s"\n' % self.squashed_diskname) def _write_install_image_info_to_boot_image(self): initrd_trigger = \ self.boot_image_task.boot_root_directory + '/config.vmxsystem' with open(initrd_trigger, 'w') as vmx_system: vmx_system.write('IMAGE="%s"\n' % self.squashed_diskname) def _create_dracut_install_config(self): dracut_config = [ 'hostonly="no"', 'dracut_rescue_image="no"' ] dracut_modules = ['kiwi-lib', 'kiwi-dump'] dracut_modules_omit = ['kiwi-overlay', 'kiwi-live', 'kiwi-repart'] if self.root_filesystem_is_multipath is False: dracut_modules_omit.append('multipath') dracut_config.append( 'add_dracutmodules+=" {0} "'.format(' '.join(dracut_modules)) ) dracut_config.append( 'omit_dracutmodules+=" {0} "'.format(' '.join(dracut_modules_omit)) ) with open(self.dracut_config_file, 'w') as config: for entry in dracut_config: config.write(entry + os.linesep) def _delete_dracut_install_config(self): if os.path.exists(self.dracut_config_file): os.remove(self.dracut_config_file) def __del__(self): log.info('Cleaning up %s instance', type(self).__name__) if self.initrd_system == 'dracut': self._delete_dracut_install_config() if self.media_dir: Path.wipe(self.media_dir) if self.pxe_dir: Path.wipe(self.pxe_dir) if self.squashed_contents: Path.wipe(self.squashed_contents)
class TestSystemSetup(object): @patch('platform.machine') def setup(self, mock_machine): mock_machine.return_value = 'x86_64' self.xml_state = mock.MagicMock() self.xml_state.build_type.get_filesystem = mock.Mock( return_value='ext3' ) self.xml_state.xml_data.get_name = mock.Mock( return_value='some-image' ) self.xml_state.get_image_version = mock.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.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 ) @patch('platform.machine') def test_setup_ix86(self, mock_machine): mock_machine.return_value = 'i686' setup = SystemSetup( mock.MagicMock(), 'root_dir' ) assert setup.arch == 'ix86' @patch('kiwi.command.Command.run') @patch_open @patch('os.path.exists') def test_import_description(self, mock_path, mock_open, mock_command): mock_path.return_value = True self.setup_with_real_xml.import_description() assert mock_command.call_args_list == [ call(['mkdir', '-p', 'root_dir/image']), call(['cp', '../data/config.sh', 'root_dir/image/config.sh']), call([ 'cp', '../data/my_edit_boot_script', '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', '../data/images.sh', 'root_dir/image/images.sh']), call([ 'cp', Defaults.project_file('config/functions.sh'), 'root_dir/.kconfig' ]), call(['cp', '/absolute/path/to/image.tgz', 'root_dir/image/']), call(['cp', '../data/bootstrap.tgz', 'root_dir/image/'])] @patch('kiwi.command.Command.run') @patch_open @patch('os.path.exists') def test_import_description_archive_from_derived( self, mock_path, mock_open, mock_command ): path_return_values = [ True, False, True, True, True, True, True ] def side_effect(arg): return path_return_values.pop() mock_path.side_effect = side_effect self.setup_with_real_xml.import_description() assert mock_command.call_args_list == [ call(['mkdir', '-p', 'root_dir/image']), call(['cp', '../data/config.sh', 'root_dir/image/config.sh']), call([ 'cp', '../data/my_edit_boot_script', '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', '../data/images.sh', 'root_dir/image/images.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/' ]) ] @patch('kiwi.command.Command.run') @patch_open @patch('os.path.exists') @raises(KiwiImportDescriptionError) def test_import_description_configured_editboot_scripts_not_found( self, mock_path, mock_open, mock_command ): path_return_values = [False, True, True] def side_effect(arg): return path_return_values.pop() mock_path.side_effect = side_effect self.setup_with_real_xml.import_description() @patch('kiwi.command.Command.run') @patch_open @patch('os.path.exists') @raises(KiwiImportDescriptionError) def test_import_description_configured_archives_not_found( self, mock_path, mock_open, mock_command ): path_return_values = [False, False, True, True, True, True] def side_effect(arg): return path_return_values.pop() mock_path.side_effect = side_effect 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( ['rm', '-r', '-f', '/.kconfig', '/image'] ) @patch_open def test_import_shell_environment(self, mock_open): mock_profile = mock.MagicMock() mock_profile.create = mock.Mock( return_value=['a'] ) context_manager_mock = mock.Mock() mock_open.return_value = context_manager_mock file_mock = mock.Mock() enter_mock = mock.Mock() exit_mock = mock.Mock() enter_mock.return_value = file_mock setattr(context_manager_mock, '__enter__', enter_mock) setattr(context_manager_mock, '__exit__', exit_mock) self.setup.import_shell_environment(mock_profile) mock_profile.create.assert_called_once_with() mock_open.assert_called_once_with('root_dir/.profile', 'w') file_mock.write.assert_called_once_with('a\n') @patch('kiwi.command.Command.run') @patch('os.path.exists') def test_import_overlay_files_copy_links(self, mock_os_path, mock_command): mock_os_path.return_value = True self.setup.import_overlay_files( follow_links=True, preserve_owner_group=True ) mock_command.assert_called_once_with( [ 'rsync', '-r', '-p', '-t', '-D', '-H', '-X', '-A', '--one-file-system', '--copy-links', '-o', '-g', 'description_dir/root/', 'root_dir' ] ) @patch('kiwi.command.Command.run') @patch('os.path.exists') def test_import_overlay_files_links(self, mock_os_path, mock_command): mock_os_path.return_value = True self.setup.import_overlay_files( follow_links=False, preserve_owner_group=True ) mock_command.assert_called_once_with( [ 'rsync', '-r', '-p', '-t', '-D', '-H', '-X', '-A', '--one-file-system', '--links', '-o', '-g', 'description_dir/root/', 'root_dir' ] ) @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() 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.Command.run') def test_setup_hardware_clock(self, mock_command): self.setup.preferences['hwclock'] = 'clock' self.setup.setup_hardware_clock() mock_command.assert_called_once_with( [ 'chroot', 'root_dir', 'hwclock', '--adjust', '--clock' ] ) @patch('kiwi.system.setup.Shell.run_common_function') @patch('os.path.exists') def test_setup_keyboard_map(self, mock_path, 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.logger.log.warning') @patch('os.path.exists') def test_setup_keyboard_skipped(self, mock_exists, mock_log_warn): mock_exists.return_value = False self.setup.preferences['keytable'] = 'keytable' self.setup.setup_keyboard_map() assert mock_log_warn.called @patch('kiwi.system.setup.Shell.run_common_function') @patch('os.path.exists') def test_setup_locale(self, mock_path, mock_shell): mock_path.return_value = True self.setup.preferences['locale'] = 'locale1,locale2' self.setup.setup_locale() mock_shell.assert_called_once_with( 'baseUpdateSysConfig', [ 'root_dir/etc/sysconfig/language', 'RC_LANG', 'locale1.UTF-8' ] ) @patch('kiwi.logger.log.warning') @patch('os.path.exists') def test_setup_locale_skipped(self, mock_exists, mock_log_warn): mock_exists.return_value = False self.setup.preferences['locale'] = 'locale1,locale2' self.setup.setup_locale() assert mock_log_warn.called @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_called_once_with([ 'chroot', 'root_dir', 'ln', '-s', '-f', '/usr/share/zoneinfo/timezone', '/etc/localtime' ]) @patch('kiwi.system.setup.Users') def test_setup_groups(self, mock_users): users = mock.Mock() users.group_exists = mock.Mock( return_value=False ) mock_users.return_value = users self.setup_with_real_xml.setup_groups() users.group_exists.assert_called_once_with('root') users.group_add.assert_called_once_with('root', ['-g', 42]) @patch('kiwi.system.setup.Users') @patch('kiwi.system.setup.Command.run') def test_setup_users_add(self, mock_command, mock_users): users = mock.Mock() users.user_exists = mock.Mock( return_value=False ) mock_users.return_value = users mock_command.return_value = self.run_result self.setup_with_real_xml.setup_users() users.user_exists.assert_called_once_with('root') users.user_add.assert_called_once_with( 'root', [ '-p', 'password-hash', '-s', '/bin/bash', '-g', 42, '-u', 815, '-c', 'Bob', '-m', '-d', '/root' ] ) mock_command.assert_called_once_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.Mock() users.user_exists = mock.Mock( return_value=True ) mock_users.return_value = users mock_command.return_value = self.run_result self.setup_with_real_xml.setup_users() users.user_exists.assert_called_once_with('root') users.user_modify.assert_called_once_with( 'root', [ '-p', 'password-hash', '-s', '/bin/bash', '-g', 42, '-u', 815, '-c', 'Bob' ] ) @patch('kiwi.system.setup.Users') @patch('kiwi.system.setup.Command.run') def test_setup_users_modify_group_name(self, mock_command, mock_users): # unset group id and expect use of group name now self.setup_with_real_xml.xml_state.xml_data.get_users()[0].set_id(None) users = mock.Mock() users.user_exists = mock.Mock( return_value=True ) mock_users.return_value = users mock_command.return_value = self.run_result self.setup_with_real_xml.setup_users() users.user_modify.assert_called_once_with( 'root', [ '-p', 'password-hash', '-s', '/bin/bash', '-g', 'root', '-u', 815, '-c', 'Bob' ] ) @patch_open @patch('os.path.exists') def test_import_image_identifier(self, mock_os_path, mock_open): self.xml_state.xml_data.get_id = mock.Mock( return_value='42' ) mock_os_path.return_value = True context_manager_mock = mock.Mock() mock_open.return_value = context_manager_mock file_mock = mock.Mock() enter_mock = mock.Mock() exit_mock = mock.Mock() enter_mock.return_value = file_mock setattr(context_manager_mock, '__enter__', enter_mock) setattr(context_manager_mock, '__exit__', exit_mock) self.setup.import_image_identifier() mock_open.assert_called_once_with('root_dir/etc/ImageID', 'w') file_mock.write.assert_called_once_with('42\n') @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') def test_call_config_script(self, mock_os_path, mock_watch, mock_command): 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 self.setup.call_config_script() mock_command.assert_called_once_with( ['chroot', 'root_dir', 'bash', '/image/config.sh'] ) @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') def test_call_image_script(self, mock_os_path, mock_watch, mock_command): 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 self.setup.call_image_script() 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') def test_call_edit_boot_config_script( self, mock_os_path, mock_watch, mock_command ): 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 self.setup.call_edit_boot_config_script('ext4', 1) mock_command.assert_called_once_with([ 'bash', '-c', 'cd root_dir && bash --norc image/edit_boot_config.sh ext4 1' ]) @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') def test_call_edit_boot_install_script( self, mock_os_path, mock_watch, mock_command ): 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 self.setup.call_edit_boot_install_script( 'my_image.raw', '/dev/mapper/loop0p1' ) mock_command.assert_called_once_with([ 'bash', '-c', 'cd root_dir && bash --norc image/edit_boot_install.sh my_image.raw /dev/mapper/loop0p1' ]) @raises(KiwiScriptFailed) @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') def test_call_image_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 self.setup.call_image_script() @raises(KiwiScriptFailed) @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 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('kiwi.command.Command.run') @patch('kiwi.system.setup.NamedTemporaryFile') @patch('kiwi.system.setup.ArchiveTar') @patch_open @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_open, mock_archive, mock_temp, mock_command ): context_manager_mock = mock.Mock() mock_open.return_value = context_manager_mock file_mock = mock.Mock() enter_mock = mock.Mock() exit_mock = mock.Mock() enter_mock.return_value = file_mock setattr(context_manager_mock, '__enter__', enter_mock) setattr(context_manager_mock, '__exit__', exit_mock) mock_getsize.return_value = 42 compress = mock.Mock() mock_compress.return_value = compress archive = mock.Mock() mock_archive.return_value = archive tmpdir = mock.Mock() tmpdir.name = 'tmpdir' mock_temp.return_value = tmpdir self.setup.oemconfig['recovery'] = True self.setup.oemconfig['recovery_inplace'] = 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='tmpdir' ) archive.create.assert_called_once_with( exclude=['dev', 'proc', 'sys'], options=[ '--numeric-owner', '--hard-dereference', '--preserve-permissions' ], source_dir='root_dir' ) assert mock_command.call_args_list[1] == call( ['mv', 'tmpdir', 'root_dir/recovery.tar'] ) assert mock_open.call_args_list[0] == call( 'root_dir/recovery.tar.filesystem', 'w' ) assert file_mock.write.call_args_list[0] == call('ext3') assert mock_command.call_args_list[2] == call( ['bash', '-c', 'tar -tf root_dir/recovery.tar | wc -l'] ) assert mock_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 file_mock.write.call_args_list[1] == call('1\n') assert mock_open.call_args_list[2] == call( 'root_dir/recovery.tar.size', 'w' ) assert file_mock.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 mock_open.call_args_list[3] == call( 'root_dir/recovery.partition.size', 'w' ) assert file_mock.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('os.path.exists') def test_export_modprobe_setup(self, mock_exists, mock_path, mock_command): mock_exists.return_value = True self.setup.export_modprobe_setup('target_root_dir') mock_path.assert_called_once_with('target_root_dir/etc') mock_command.assert_called_once_with( [ 'rsync', '-z', '-a', 'root_dir/etc/modprobe.d', 'target_root_dir/etc/' ] ) @patch('kiwi.system.setup.Command.run') @patch('os.path.exists') @patch_open def test_export_rpm_package_list( self, mock_open, mock_exists, mock_command ): command = mock.Mock() command.output = 'packages_data' mock_exists.return_value = True mock_command.return_value = command result = self.setup.export_rpm_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}|\\n' ]) mock_open.assert_called_once_with( 'target_dir/some-image.x86_64-1.2.3.packages', 'w' ) @patch('kiwi.system.setup.Command.run') @patch('os.path.exists') @patch_open def test_export_rpm_package_verification( self, mock_open, mock_exists, mock_command ): command = mock.Mock() command.output = 'verification_data' mock_exists.return_value = True mock_command.return_value = command result = self.setup.export_rpm_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'], raise_on_error=False ) mock_open.assert_called_once_with( 'target_dir/some-image.x86_64-1.2.3.verified', 'w' ) @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') def test_import_repositories_marked_as_imageinclude(self, mock_repo): repo = mock.Mock() mock_repo.return_value = repo self.setup_with_real_xml.import_repositories_marked_as_imageinclude() repo.delete_all_repos.assert_called_once_with() repo.add_repo.assert_called_once_with( '95811799a6d1889c5b2363d3886986de', 'http://download.opensuse.org/repositories/Devel:PubCloud:AmazonEC2/SLE_12_GA', 'rpm-md', None, None, None )
class DiskBuilder(object): """ **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 = platform.machine() if self.arch == 'i686' or self.arch == 'i586': self.arch = 'ix86' 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.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.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.build_type.get_bootloader() 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_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, custom_args=self.custom_args ) 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.install_media = self._install_image_requested() self.generic_fstab_entries = [] # 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 # 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): """ 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) loop_provider = LoopDevice( self.diskname, disksize_mbytes, self.blocksize ) loop_provider.create() self.disk = Disk( self.firmware.get_partition_table_type(), loop_provider, self.xml_state.get_disk_start_sector() ) # create the bootloader instance self.bootloader_config = BootLoaderConfig( self.bootloader, self.xml_state, self.root_dir, { 'targetbase': loop_provider.get_device(), 'grub_directory_name': Defaults.get_grub_boot_directory_name(self.root_dir) } ) # create disk partitions and instance device map device_map = self._build_and_map_disk_partitions() # 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_root.create_crypto_luks( passphrase=self.luks, os=self.luks_os ) device_map['root'] = self.luks_root.get_device() # 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, '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(), 'image_type': self.xml_state.get_build_type_name() } volume_manager = VolumeManager( self.volume_manager_name, device_map['root'], 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() self.generic_fstab_entries += volume_manager.get_fstab( self.persistency_type, self.requested_filesystem ) self.system = volume_manager device_map['root'] = volume_manager.get_device()['root'] 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 } filesystem = FileSystem( 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 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': self._create_dracut_config() # create initrd cpio archive self.boot_image.create_initrd(self.mbrid) # create dracut config omitting one time kiwi dracut modules if self.initrd_system == 'dracut': self._create_system_dracut_config() # create second stage metadata to system image self._copy_first_boot_files_to_system_image() self._write_bootloader_config_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_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 ) 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) ) # 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=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.pxename, 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) boot_image.enable_cleanup() 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 '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_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( '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( 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): # noqa: C901 self.disk.wipe() if self.firmware.legacy_bios_mode(): log.info('--> creating EFI CSM(legacy bios) partition') self.disk.create_efi_csm_partition( self.firmware.get_legacy_bios_partition_size() ) if self.firmware.efi_mode(): log.info('--> creating EFI partition') self.disk.create_efi_partition( self.firmware.get_efi_partition_size() ) if self.firmware.ofw_mode(): log.info('--> creating PReP partition') self.disk.create_prep_partition( self.firmware.get_prep_partition_size() ) if self.disk_setup.need_boot_partition(): log.info('--> creating boot partition') self.disk.create_boot_partition( self.disk_setup.boot_partition_size() ) if self.spare_part_mbsize: 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 ) squashed_root.create_on_file( filename=squashed_root_file.name, exclude=[Defaults.get_shared_cache_location()] ) squashed_rootfs_mbsize = os.path.getsize( squashed_root_file.name ) / 1048576 self.disk.create_root_readonly_partition( int(squashed_rootfs_mbsize + 50) ) if self.volume_manager_name and self.volume_manager_name == 'lvm': log.info('--> creating LVM root partition') self.disk.create_root_lvm_partition('all_free') elif self.mdraid: log.info('--> creating mdraid root partition') self.disk.create_root_raid_partition('all_free') else: log.info('--> creating root partition') self.disk.create_root_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 _create_dracut_config(self): dracut_config = [ 'hostonly="no"', 'dracut_rescue_image="no"' ] dracut_modules = [] dracut_modules_omit = ['kiwi-live', 'kiwi-dump'] if self.root_filesystem_is_multipath is False: dracut_modules_omit.append('multipath') if self.root_filesystem_is_overlay: dracut_modules.append('kiwi-overlay') else: dracut_modules_omit.append('kiwi-overlay') if self.build_type_name == 'oem': dracut_modules.append('kiwi-lib') dracut_modules.append('kiwi-repart') self._write_dracut_config( config=dracut_config, modules=dracut_modules, omit_modules=dracut_modules_omit ) def _create_system_dracut_config(self): dracut_modules = [] dracut_modules_omit = ['kiwi-live', 'kiwi-dump', 'kiwi-repart'] if self.root_filesystem_is_overlay: dracut_modules.append('kiwi-overlay') else: dracut_modules_omit.append('kiwi-overlay') self._write_dracut_config( config=[], modules=dracut_modules, omit_modules=dracut_modules_omit ) def _write_dracut_config(self, config, modules, omit_modules): dracut_config_file = ''.join( [self.root_dir, Defaults.get_dracut_conf_name()] ) if modules: config.append( 'add_dracutmodules+=" {0} "'.format(' '.join(modules)) ) if omit_modules: config.append( 'omit_dracutmodules+=" {0} "'.format(' '.join(omit_modules)) ) with open(dracut_config_file, 'w') as dracut_config: for entry in config: dracut_config.write(entry + os.linesep) 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 'boot' in device_map: 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 'efi' in device_map: self._add_generic_fstab_entry( device_map['efi'].get_device(), '/boot/efi' ) setup.create_fstab( self.generic_fstab_entries ) 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 ] ) if fstab_entry not in self.generic_fstab_entries: self.generic_fstab_entries.append( fstab_entry ) 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_config_to_system_image(self, device_map): if self.bootloader is not 'custom': log.info('Creating %s bootloader configuration', self.bootloader) boot_options = [] if self.mdraid: boot_options.append('rd.auto') boot_names = self.boot_image.get_boot_names() boot_device = device_map['root'] 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() ) self.bootloader_config.setup_disk_boot_images(boot_uuid) self.bootloader_config.setup_disk_image_config( boot_uuid=boot_uuid, root_uuid=root_uuid, kernel=boot_names.kernel_name, initrd=boot_names.initrd_name, boot_options=' '.join(boot_options) ) self.bootloader_config.write() 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()} ) if self.bootloader is not '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]) ] )
def process(self): # noqa: C901 """ Prepare and install a new system for chroot access """ self.manual = Help() if self._help(): return Privileges.check_for_root_permissions() self.load_xml_description(self.command_args['--description']) abs_root_path = os.path.abspath(self.command_args['--root']) self.runtime_checker.check_minimal_required_preferences() self.runtime_checker.check_efi_mode_for_disk_overlay_correctly_setup() self.runtime_checker.check_grub_efi_installed_for_efi_firmware() self.runtime_checker.check_boot_description_exists() self.runtime_checker.check_consistent_kernel_in_boot_and_system_image() self.runtime_checker.check_docker_tool_chain_installed() self.runtime_checker.check_volume_setup_defines_multiple_fullsize_volumes( ) self.runtime_checker.check_volume_setup_has_no_root_definition() self.runtime_checker.check_volume_label_used_with_lvm() self.runtime_checker.check_xen_uniquely_setup_as_server_or_guest() self.runtime_checker.check_target_directory_not_in_shared_cache( abs_root_path) self.runtime_checker.check_mediacheck_only_for_x86_arch() self.runtime_checker.check_dracut_module_for_live_iso_in_package_list() self.runtime_checker.check_dracut_module_for_disk_overlay_in_package_list( ) self.runtime_checker.check_dracut_module_for_disk_oem_in_package_list() self.runtime_checker.check_dracut_module_for_oem_install_in_package_list( ) if self.command_args['--ignore-repos']: self.xml_state.delete_repository_sections() elif self.command_args['--ignore-repos-used-for-build']: self.xml_state.delete_repository_sections_used_for_build() if self.command_args['--set-repo']: self.xml_state.set_repository( *self.sextuple_token(self.command_args['--set-repo'])) if self.command_args['--add-repo']: for add_repo in self.command_args['--add-repo']: self.xml_state.add_repository(*self.sextuple_token(add_repo)) if self.command_args['--set-container-tag']: self.xml_state.set_container_config_tag( self.command_args['--set-container-tag']) if self.command_args['--add-container-label']: for add_label in self.command_args['--add-container-label']: try: (name, value) = add_label.split('=', 1) self.xml_state.add_container_config_label(name, value) except Exception: log.warning('Container label {0} ignored. Invalid format: ' 'expected labelname=value'.format(add_label)) if self.command_args['--set-container-derived-from']: self.xml_state.set_derived_from_image_uri( self.command_args['--set-container-derived-from']) self.runtime_checker.check_repositories_configured() self.runtime_checker.check_image_include_repos_publicly_resolvable() log.info('Preparing system') system = SystemPrepare(self.xml_state, abs_root_path, self.command_args['--allow-existing-root']) manager = system.setup_repositories(self.command_args['--clear-cache'], self.command_args['--signing-key']) system.install_bootstrap(manager) system.install_system(manager) if self.command_args['--add-package']: system.install_packages(manager, self.command_args['--add-package']) if self.command_args['--delete-package']: system.delete_packages(manager, self.command_args['--delete-package']) profile = Profile(self.xml_state) defaults = Defaults() defaults.to_profile(profile) setup = SystemSetup(self.xml_state, abs_root_path) setup.import_shell_environment(profile) setup.import_description() setup.import_overlay_files() setup.import_image_identifier() setup.setup_groups() setup.setup_users() setup.setup_keyboard_map() setup.setup_locale() setup.setup_plymouth_splash() setup.setup_timezone() setup.setup_permissions() # make sure manager instance is cleaned up now del manager # setup permanent image repositories after cleanup setup.import_repositories_marked_as_imageinclude() setup.call_config_script() # handle uninstall package requests, gracefully uninstall # with dependency cleanup system.pinch_system(force=False) # handle delete package requests, forced uninstall without # any dependency resolution system.pinch_system(force=True) # delete any custom rpm macros created Rpm(abs_root_path, Defaults.get_custom_rpm_image_macro_name()).wipe_config() # make sure system instance is cleaned up now del system
class LiveImageBuilder(object): """ **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 defined as hash keys: * xz_options: string of XZ compression parameters """ def __init__(self, xml_state, target_dir, root_dir, custom_args=None): self.media_dir = None self.live_container_dir = None self.arch = platform.machine() if self.arch == 'i686' or self.arch == 'i586': self.arch = 'ix86' 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.filesystem_custom_parameters = { 'mount_options': xml_state.get_fs_mount_option_list() } self.publisher = xml_state.build_type.get_publisher() or \ Defaults.get_publisher() 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.isoname = ''.join( [ target_dir, '/', xml_state.xml_data.get_name(), '.' + platform.machine(), '-' + xml_state.get_image_version(), '.iso' ] ) self.result = Result(xml_state) self.runtime_config = RuntimeConfig() def create(self): """ 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 = mkdtemp( prefix='live-media.', dir=self.target_dir ) # unpack cdroot user files to media dir self.system_setup.import_cdroot_files(self.media_dir) rootsize = SystemSize(self.media_dir) # 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(Defaults.get_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() } } # 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() } filesystem_setup = FileSystemSetup( self.xml_state, self.root_dir ) root_image = NamedTemporaryFile() 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( 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() ) log.info('--> Creating squashfs container for root image') self.live_container_dir = mkdtemp( prefix='live-container.', dir=self.target_dir ) Path.create(self.live_container_dir + '/LiveOS') shutil.copy( root_image.name, self.live_container_dir + '/LiveOS/rootfs.img' ) live_container_image = FileSystem( name='squashfs', device_provider=None, root_dir=self.live_container_dir ) container_image = NamedTemporaryFile() live_container_image.create_on_file( container_image.name ) Path.create(self.media_dir + '/LiveOS') shutil.copy( container_image.name, self.media_dir + '/LiveOS/squashfs.img' ) # setup bootloader config to boot the ISO via isolinux log.info('Setting up isolinux bootloader configuration') bootloader_config_isolinux = BootLoaderConfig( 'isolinux', self.xml_state, self.media_dir ) bootloader_config_isolinux.setup_live_boot_images( mbrid=None, lookup_path=self.boot_image.boot_root_directory ) bootloader_config_isolinux.setup_live_image_config( mbrid=None ) bootloader_config_isolinux.write() # setup bootloader config to boot the ISO via EFI if self.firmware.efi_mode(): log.info('Setting up EFI grub bootloader configuration') bootloader_config_grub = BootLoaderConfig( 'grub2', self.xml_state, self.media_dir, { 'grub_directory_name': Defaults.get_grub_boot_directory_name(self.root_dir) } ) bootloader_config_grub.setup_live_boot_images( mbrid=self.mbrid, lookup_path=self.root_dir ) bootloader_config_grub.setup_live_image_config( mbrid=self.mbrid ) bootloader_config_grub.write() # call custom editbootconfig script if present self.system_setup.call_edit_boot_config_script( filesystem='iso:{0}'.format(self.media_dir), 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') self._create_dracut_live_iso_config() 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 # create iso filesystem from media_dir log.info('Creating live ISO image') iso_image = FileSystemIsoFs( device_provider=None, root_dir=self.media_dir, 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) self.result.verify_image_size( self.runtime_config.get_max_size_constraint(), self.isoname ) 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_verified', filename=self.system_setup.export_package_verification( self.target_dir ), use_for_bundle=True, compress=False, shasum=False ) return self.result def _create_dracut_live_iso_config(self): live_config_file = self.root_dir + '/etc/dracut.conf.d/02-livecd.conf' omit_modules = [ 'kiwi-dump', 'kiwi-overlay', 'kiwi-repart', 'kiwi-lib', 'multipath' ] live_config = [ 'add_dracutmodules+=" {0} pollcdrom "'.format( Defaults.get_live_dracut_module_from_flag(self.live_type) ), 'omit_dracutmodules+=" {0} "'.format(' '.join(omit_modules)), 'hostonly="no"', 'dracut_rescue_image="no"' ] with open(live_config_file, 'w') as config: for entry in live_config: config.write(entry + os.linesep) def _setup_live_iso_kernel_and_initrd(self): """ Copy kernel and initrd from the root tree into the iso boot structure """ boot_path = ''.join( [self.media_dir, '/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 ) ) def __del__(self): if self.media_dir or self.live_container_dir: log.info( 'Cleaning up {0} instance'.format(type(self).__name__) ) if self.media_dir: Path.wipe(self.media_dir) if self.live_container_dir: Path.wipe(self.live_container_dir)
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, target_dir, root_dir, custom_args=None): self.media_dir = None self.live_container_dir = 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.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): """ 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 = mkdtemp(prefix='live-media.', dir=self.target_dir) # unpack cdroot user files to media dir self.system_setup.import_cdroot_files(self.media_dir) rootsize = SystemSize(self.media_dir) # 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(Defaults.get_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( 'grub2', self.xml_state, root_dir=self.root_dir, boot_dir=self.media_dir, 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('isolinux', self.xml_state, root_dir=self.root_dir, boot_dir=self.media_dir) IsoToolsBase.setup_media_loader_directory( self.boot_image.boot_root_directory, self.media_dir, 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), 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 = NamedTemporaryFile() 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()) live_filesystem.umount() log.info('--> Creating squashfs container for root image') self.live_container_dir = mkdtemp(prefix='live-container.', dir=self.target_dir) Path.create(self.live_container_dir + '/LiveOS') shutil.copy(root_image.name, self.live_container_dir + '/LiveOS/rootfs.img') live_container_image = FileSystem.new( name='squashfs', device_provider=None, root_dir=self.live_container_dir, custom_args={ 'compression': self.xml_state.build_type.get_squashfscompression() }) container_image = NamedTemporaryFile() live_container_image.create_on_file(container_image.name) Path.create(self.media_dir + '/LiveOS') shutil.copy(container_image.name, self.media_dir + '/LiveOS/squashfs.img') # create iso filesystem from media_dir log.info('Creating live ISO image') iso_image = FileSystemIsoFs(device_provider=None, root_dir=self.media_dir, 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) self.result.verify_image_size( self.runtime_config.get_max_size_constraint(), self.isoname) 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_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): """ Copy kernel and initrd from the root tree into the iso boot structure """ boot_path = ''.join([self.media_dir, '/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)) def __del__(self): if self.media_dir or self.live_container_dir: log.info('Cleaning up {0} instance'.format(type(self).__name__)) if self.media_dir: Path.wipe(self.media_dir) if self.live_container_dir: Path.wipe(self.live_container_dir)
def process(self): # noqa: C901 """ Prepare and install a new system for chroot access """ self.manual = Help() if self._help(): return Privileges.check_for_root_permissions() self.load_xml_description( self.command_args['--description'] ) abs_root_path = os.path.abspath(self.command_args['--root']) self.runtime_checker.check_efi_mode_for_disk_overlay_correctly_setup() self.runtime_checker.check_grub_efi_installed_for_efi_firmware() self.runtime_checker.check_boot_description_exists() self.runtime_checker.check_consistent_kernel_in_boot_and_system_image() self.runtime_checker.check_docker_tool_chain_installed() self.runtime_checker.check_volume_setup_has_no_root_definition() self.runtime_checker.check_xen_uniquely_setup_as_server_or_guest() self.runtime_checker.check_target_directory_not_in_shared_cache( abs_root_path ) self.runtime_checker.check_mediacheck_only_for_x86_arch() self.runtime_checker.check_dracut_module_for_live_iso_in_package_list() self.runtime_checker.check_dracut_module_for_disk_overlay_in_package_list() self.runtime_checker.check_dracut_module_for_disk_oem_in_package_list() self.runtime_checker.check_dracut_module_for_oem_install_in_package_list() if self.command_args['--ignore-repos']: self.xml_state.delete_repository_sections() elif self.command_args['--ignore-repos-used-for-build']: self.xml_state.delete_repository_sections_used_for_build() if self.command_args['--set-repo']: self.xml_state.set_repository( *self.sextuple_token(self.command_args['--set-repo']) ) if self.command_args['--add-repo']: for add_repo in self.command_args['--add-repo']: self.xml_state.add_repository( *self.sextuple_token(add_repo) ) if self.command_args['--set-container-tag']: self.xml_state.set_container_config_tag( self.command_args['--set-container-tag'] ) if self.command_args['--set-container-derived-from']: self.xml_state.set_derived_from_image_uri( self.command_args['--set-container-derived-from'] ) self.runtime_checker.check_repositories_configured() self.runtime_checker.check_image_include_repos_publicly_resolvable() package_requests = False if self.command_args['--add-package']: package_requests = True if self.command_args['--delete-package']: package_requests = True log.info('Preparing system') system = SystemPrepare( self.xml_state, abs_root_path, self.command_args['--allow-existing-root'] ) manager = system.setup_repositories( self.command_args['--clear-cache'], self.command_args['--signing-key'] ) system.install_bootstrap(manager) system.install_system( manager ) if package_requests: if self.command_args['--add-package']: system.install_packages( manager, self.command_args['--add-package'] ) if self.command_args['--delete-package']: system.delete_packages( manager, self.command_args['--delete-package'] ) profile = Profile(self.xml_state) defaults = Defaults() defaults.to_profile(profile) setup = SystemSetup( self.xml_state, abs_root_path ) setup.import_shell_environment(profile) setup.import_description() setup.import_overlay_files() setup.import_image_identifier() setup.setup_groups() setup.setup_users() setup.setup_keyboard_map() setup.setup_locale() setup.setup_plymouth_splash() setup.setup_timezone() system.pinch_system( manager=manager, force=True ) # make sure manager instance is cleaned up now del manager # setup permanent image repositories after cleanup setup.import_repositories_marked_as_imageinclude() setup.call_config_script() # make sure system instance is cleaned up now del system
class PxeBuilder(object): """ Filesystem based PXE image builder. Attributes * :attr:`target_dir` target directory path name * :attr:`compressed` Request to XZ compress filesystem image * :attr:`machine` Configured build type machine section * :attr:`pxedeploy` Configured build type pxedeploy section * :attr:`filesystem` Instance of FileSystemBuilder * :attr:`system_setup` Instance of SystemSetup * :attr:`boot_image_task` Instance of BootImage * :attr:`image_name` File name of the filesystem image * :attr:`kernel_filename` File name of the kernel image * :attr:`hypervisor_filename` File name of the hypervisor image * :attr:`result` Instance of Result """ def __init__(self, xml_state, target_dir, root_dir, custom_args=None): self.target_dir = target_dir self.compressed = xml_state.build_type.get_compressed() self.machine = xml_state.get_build_type_machine_section() self.pxedeploy = xml_state.get_build_type_pxedeploy_section() self.filesystem = FileSystemBuilder( xml_state, target_dir, root_dir + '/' ) self.system_setup = SystemSetup( xml_state=xml_state, root_dir=root_dir ) boot_signing_keys = None if custom_args and 'signing_keys' in custom_args: boot_signing_keys = custom_args['signing_keys'] self.boot_image_task = BootImage( xml_state, target_dir, signing_keys=boot_signing_keys ) self.image_name = ''.join( [ target_dir, '/', xml_state.xml_data.get_name(), '.' + platform.machine(), '-' + xml_state.get_image_version() ] ) self.archive_name = ''.join([self.image_name, '.tar.xz']) self.kernel_filename = None self.hypervisor_filename = None self.result = Result(xml_state) def create(self): """ Build a pxe image set consisting out of a boot image(initrd) plus its appropriate kernel files and the root filesystem image with a checksum. The result can be used within the kiwi PXE boot infrastructure Image types which triggers this builder are: * image="pxe" """ log.info('Creating PXE 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.image = compress.compressed_filename log.info('Creating PXE root filesystem MD5 checksum') self.filesystem_checksum = ''.join([self.image, '.md5']) checksum = Checksum(self.image) checksum.md5(self.filesystem_checksum) # prepare boot(initrd) root system log.info('Creating PXE 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(initrd) root 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 KiwiPxeBootImageError( 'No kernel in boot image tree %s found' % self.boot_image_task.boot_root_directory ) # extract hypervisor from boot(initrd) root system if self.machine and self.machine.get_domain() == 'dom0': kernel_data = kernel.get_xen_hypervisor() if kernel_data: self.hypervisor_filename = ''.join( [os.path.basename(self.image_name), '-', kernel_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 KiwiPxeBootImageError( 'No hypervisor in boot image tree %s found' % self.boot_image_task.boot_root_directory ) # create initrd for pxe boot self.boot_image_task.create_initrd() # put results into a tarball Command.run( [ 'tar', '-C', self.target_dir, '-cJf', self.archive_name, self.kernel_filename, os.path.basename(self.boot_image_task.initrd_filename), os.path.basename(self.image), os.path.basename(self.filesystem_checksum) ] ) # store results self.result.add( key='pxe_archive', filename=self.archive_name, use_for_bundle=True, compress=False, shasum=True ) # create image root metadata self.result.add( key='image_packages', filename=self.system_setup.export_rpm_package_list( self.target_dir ), use_for_bundle=True, compress=False, shasum=False ) self.result.add( key='image_verified', filename=self.system_setup.export_rpm_package_verification( self.target_dir ), use_for_bundle=True, compress=False, shasum=False ) if self.pxedeploy: log.warning( 'Creation of client config file from pxedeploy not implemented' ) return self.result
def process(self): """ Prepare and install a new system for chroot access """ self.manual = Help() if self._help(): return Privileges.check_for_root_permissions() self.load_xml_description(self.command_args['--description']) abs_root_path = os.path.abspath(self.command_args['--root']) self.runtime_checker.check_consistent_kernel_in_boot_and_system_image() self.runtime_checker.check_boot_image_reference_correctly_setup() self.runtime_checker.check_docker_tool_chain_installed() self.runtime_checker.check_volume_setup_has_no_root_definition() self.runtime_checker.check_target_directory_not_in_shared_cache( abs_root_path) if self.command_args['--ignore-repos']: self.xml_state.delete_repository_sections() elif self.command_args['--ignore-repos-used-for-build']: self.xml_state.delete_repository_sections_used_for_build() if self.command_args['--set-repo']: self.xml_state.set_repository( *self.quintuple_token(self.command_args['--set-repo'])) if self.command_args['--add-repo']: for add_repo in self.command_args['--add-repo']: self.xml_state.add_repository(*self.quintuple_token(add_repo)) if self.command_args['--set-container-tag']: self.xml_state.set_container_config_tag( self.command_args['--set-container-tag']) if self.command_args['--set-container-derived-from']: self.xml_state.set_derived_from_image_uri( self.command_args['--set-container-derived-from']) self.runtime_checker.check_repositories_configured() self.runtime_checker.check_image_include_repos_publicly_resolvable() package_requests = False if self.command_args['--add-package']: package_requests = True if self.command_args['--delete-package']: package_requests = True log.info('Preparing system') system = SystemPrepare(self.xml_state, abs_root_path, self.command_args['--allow-existing-root']) manager = system.setup_repositories(self.command_args['--clear-cache'], self.command_args['--signing-key']) system.install_bootstrap(manager) system.install_system(manager) if package_requests: if self.command_args['--add-package']: system.install_packages(manager, self.command_args['--add-package']) if self.command_args['--delete-package']: system.delete_packages(manager, self.command_args['--delete-package']) profile = Profile(self.xml_state) defaults = Defaults() defaults.to_profile(profile) setup = SystemSetup(self.xml_state, abs_root_path) setup.import_shell_environment(profile) setup.import_description() setup.import_overlay_files() setup.import_image_identifier() setup.setup_groups() setup.setup_users() setup.setup_keyboard_map() setup.setup_locale() setup.setup_plymouth_splash() setup.setup_timezone() system.pinch_system(manager=manager, force=True) # make sure manager instance is cleaned up now del manager # setup permanent image repositories after cleanup setup.import_repositories_marked_as_imageinclude() setup.call_config_script() # make sure system instance is cleaned up now del system
class InstallImageBuilder: """ **Installation image builder** :param object xml_state: instance of :class:`XMLState` :param str root_dir: system image root directory :param str target_dir: target directory path name :param object boot_image_task: instance of :class:`BootImage` :param dict custom_args: Custom processing arguments defined as hash keys: * xz_options: string of XZ compression parameters """ def __init__(self, xml_state: XMLState, root_dir: str, target_dir: str, boot_image_task: Optional[BootImageBase], custom_args: Dict = None) -> None: self.arch = Defaults.get_platform_name() self.root_dir = root_dir self.target_dir = target_dir self.xml_state = xml_state self.root_filesystem_is_multipath = \ xml_state.get_oemconfig_oem_multipath_scan() self.initrd_system = xml_state.get_initrd_system() self.firmware = FirmWare(xml_state) self.setup = SystemSetup(self.xml_state, self.root_dir) self.iso_volume_id = self.xml_state.build_type.get_volid() or \ Defaults.get_install_volume_id() self.diskname = ''.join([ target_dir, '/', xml_state.xml_data.get_name(), '.' + self.arch, '-' + xml_state.get_image_version(), '.raw' ]) self.isoname = ''.join([ target_dir, '/', xml_state.xml_data.get_name(), '.' + self.arch, '-' + xml_state.get_image_version(), '.install.iso' ]) self.pxename = ''.join([ xml_state.xml_data.get_name(), '.' + self.arch, '-' + xml_state.get_image_version() ]) self.pxetarball = ''.join( [target_dir, '/', self.pxename, '.install.tar']) self.dracut_config_file = ''.join( [self.root_dir, Defaults.get_dracut_conf_name()]) self.squashed_diskname = ''.join( [xml_state.xml_data.get_name(), '.raw']) self.md5name = ''.join([xml_state.xml_data.get_name(), '.md5']) self.xz_options = custom_args['xz_options'] if custom_args \ and 'xz_options' in custom_args else None self.mbrid = SystemIdentifier() self.mbrid.calculate_id() self.custom_iso_args: Dict = {} if not boot_image_task: self.boot_image_task = BootImage.new(xml_state, target_dir, root_dir) self.boot_image_task.prepare() else: self.boot_image_task = boot_image_task def create_install_iso(self) -> None: """ Create an install ISO from the disk_image as hybrid ISO bootable via legacy BIOS, EFI and as disk from Stick Image types which triggers this builder are: * installiso="true|false" * installstick="true|false" """ self.media_dir = Temporary(prefix='kiwi_install_media.', path=self.target_dir).new_dir() # unpack cdroot user files to media dir self.setup.import_cdroot_files(self.media_dir.name) # custom iso metadata self.custom_iso_args = { 'meta_data': { 'volume_id': self.iso_volume_id, 'mbr_id': self.mbrid.get_id(), 'efi_mode': self.firmware.efi_mode(), 'ofw_mode': self.firmware.ofw_mode() } } # the system image transfer is checked against a checksum log.info('Creating disk image checksum') self.squashed_contents = Temporary(prefix='kiwi_install_squashfs.', path=self.target_dir).new_dir() checksum = Checksum(self.diskname) checksum.md5(self.squashed_contents.name + '/' + self.md5name) # the system image name is stored in a config file self._write_install_image_info_to_iso_image() if self.initrd_system == 'kiwi': self._write_install_image_info_to_boot_image() # the system image is stored as squashfs embedded file log.info('Creating squashfs embedded disk image') Command.run([ 'cp', '-l', self.diskname, self.squashed_contents.name + '/' + self.squashed_diskname ]) squashed_image_file = ''.join( [self.target_dir, '/', self.squashed_diskname, '.squashfs']) squashed_image = FileSystemSquashFs( device_provider=DeviceProvider(), root_dir=self.squashed_contents.name, custom_args={ 'compression': self.xml_state.build_type.get_squashfscompression() }) squashed_image.create_on_file(squashed_image_file) Command.run(['mv', squashed_image_file, self.media_dir.name]) log.info('Setting up install 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_install_boot_images( mbrid=self.mbrid, lookup_path=self.boot_image_task.boot_root_directory) 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_task.boot_root_directory, self.media_dir.name, bootloader_config.get_boot_theme()) bootloader_config.write_meta_data() bootloader_config.setup_install_image_config(mbrid=self.mbrid) bootloader_config.write() # create initrd for install image log.info('Creating install image boot image') self._create_iso_install_kernel_and_initrd() # the system image initrd is stored to allow kexec self._copy_system_image_initrd_to_iso_image() # create iso filesystem from media_dir log.info('Creating ISO filesystem') iso_image = FileSystemIsoFs(device_provider=DeviceProvider(), root_dir=self.media_dir.name, custom_args=self.custom_iso_args) iso_image.create_on_file(self.isoname) self.boot_image_task.cleanup() def create_install_pxe_archive(self) -> None: """ Create an oem install tar archive suitable for installing a disk image via the network using the PXE boot protocol. The archive contains: * The raw system image xz compressed * The raw system image checksum metadata file * The append file template for the boot server * The system image initrd for kexec * The install initrd * The kernel Image types which triggers this builder are: * installpxe="true|false" """ self.pxe_dir = Temporary(prefix='kiwi_pxe_install_media.', path=self.target_dir).new_dir() # the system image is transfered as xz compressed variant log.info('xz compressing disk image') pxe_image_filename = ''.join( [self.pxe_dir.name, '/', self.pxename, '.xz']) compress = Compress(source_filename=self.diskname, keep_source_on_compress=True) compress.xz(self.xz_options) Command.run(['mv', compress.compressed_filename, pxe_image_filename]) # the system image transfer is checked against a checksum log.info('Creating disk image checksum') pxe_md5_filename = ''.join( [self.pxe_dir.name, '/', self.pxename, '.md5']) checksum = Checksum(self.diskname) checksum.md5(pxe_md5_filename) # the install image name is stored in a config file if self.initrd_system == 'kiwi': self._write_install_image_info_to_boot_image() # the kexec required system image initrd is stored for dracut kiwi-dump if self.initrd_system == 'dracut': boot_names = self.boot_image_task.get_boot_names() system_image_initrd = os.sep.join( [self.root_dir, 'boot', boot_names.initrd_name]) target_initrd_name = '{0}/{1}.initrd'.format( self.pxe_dir.name, self.pxename) shutil.copy(system_image_initrd, target_initrd_name) os.chmod(target_initrd_name, 420) # create pxe config append information # this information helps to configure the boot server correctly append_filename = ''.join( [self.pxe_dir.name, '/', self.pxename, '.append']) if self.initrd_system == 'kiwi': cmdline = 'pxe=1' else: cmdline = ' '.join([ 'rd.kiwi.install.pxe', 'rd.kiwi.install.image=http://example.com/image.xz' ]) custom_cmdline = self.xml_state.build_type.get_kernelcmdline() if custom_cmdline: cmdline += ' ' + custom_cmdline with open(append_filename, 'w') as append: append.write('%s\n' % cmdline) # create initrd for pxe install log.info('Creating pxe install boot image') self._create_pxe_install_kernel_and_initrd() # create pxe image bound boot config file, contents can be # changed but presence is required. log.info('Creating pxe install boot options file') configname = '{0}.config.bootoptions'.format(self.pxename) shutil.copy(os.sep.join([self.root_dir, 'config.bootoptions']), os.sep.join([self.pxe_dir.name, configname])) # create pxe install tarball log.info('Creating pxe install archive') archive = ArchiveTar(self.pxetarball) archive.create(self.pxe_dir.name) self.boot_image_task.cleanup() def _create_pxe_install_kernel_and_initrd(self) -> None: kernelname = 'pxeboot.{0}.kernel'.format(self.pxename) initrdname = 'pxeboot.{0}.initrd'.format(self.pxename) kernel = Kernel(self.boot_image_task.boot_root_directory) if kernel.get_kernel(): kernel.copy_kernel(self.pxe_dir.name, kernelname) os.symlink( kernelname, ''.join([self.pxe_dir.name, '/', self.pxename, '.kernel'])) else: raise KiwiInstallBootImageError( 'No kernel in boot image tree %s found' % self.boot_image_task.boot_root_directory) if self.xml_state.is_xen_server(): if kernel.get_xen_hypervisor(): kernel.copy_xen_hypervisor( self.pxe_dir.name, '/pxeboot.{0}.xen.gz'.format(self.pxename)) else: raise KiwiInstallBootImageError( 'No hypervisor in boot image tree %s found' % self.boot_image_task.boot_root_directory) if self.initrd_system == 'dracut': self.boot_image_task.include_module('kiwi-dump') self.boot_image_task.include_module('kiwi-dump-reboot') if self.root_filesystem_is_multipath is False: self.boot_image_task.omit_module('multipath') for mod in self.xml_state.get_installmedia_initrd_modules('add'): self.boot_image_task.include_module(mod) for mod in self.xml_state.get_installmedia_initrd_modules('omit'): self.boot_image_task.omit_module(mod) self.boot_image_task.set_static_modules( self.xml_state.get_installmedia_initrd_modules('set')) self.boot_image_task.create_initrd(self.mbrid, 'initrd_kiwi_install', install_initrd=True) Command.run([ 'mv', self.boot_image_task.initrd_filename, self.pxe_dir.name + '/{0}'.format(initrdname) ]) os.chmod(self.pxe_dir.name + '/{0}'.format(initrdname), 420) def _create_iso_install_kernel_and_initrd(self) -> None: boot_path = self.media_dir.name + '/boot/' + self.arch + '/loader' Path.create(boot_path) kernel = Kernel(self.boot_image_task.boot_root_directory) if kernel.get_kernel(): kernel.copy_kernel(boot_path, '/linux') else: raise KiwiInstallBootImageError( 'No kernel in boot image tree %s found' % self.boot_image_task.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 KiwiInstallBootImageError( 'No hypervisor in boot image tree %s found' % self.boot_image_task.boot_root_directory) if self.initrd_system == 'dracut': self.boot_image_task.include_module('kiwi-dump') self.boot_image_task.include_module('kiwi-dump-reboot') if self.root_filesystem_is_multipath is False: self.boot_image_task.omit_module('multipath') for mod in self.xml_state.get_installmedia_initrd_modules('add'): self.boot_image_task.include_module(mod) for mod in self.xml_state.get_installmedia_initrd_modules('omit'): self.boot_image_task.omit_module(mod) self.boot_image_task.set_static_modules( self.xml_state.get_installmedia_initrd_modules('set')) self._add_system_image_boot_options_to_boot_image() self.boot_image_task.create_initrd(self.mbrid, 'initrd_kiwi_install', install_initrd=True) Command.run([ 'mv', self.boot_image_task.initrd_filename, boot_path + '/initrd' ]) def _add_system_image_boot_options_to_boot_image(self) -> None: filename = ''.join( [self.boot_image_task.boot_root_directory, '/config.bootoptions']) self.boot_image_task.include_file(os.sep + os.path.basename(filename)) def _copy_system_image_initrd_to_iso_image(self) -> None: boot_names = self.boot_image_task.get_boot_names() system_image_initrd = os.sep.join( [self.root_dir, 'boot', boot_names.initrd_name]) shutil.copy(system_image_initrd, self.media_dir.name + '/initrd.system_image') def _write_install_image_info_to_iso_image(self) -> None: iso_trigger = self.media_dir.name + '/config.isoclient' with open(iso_trigger, 'w') as iso_system: iso_system.write('IMAGE="%s"\n' % self.squashed_diskname) def _write_install_image_info_to_boot_image(self) -> None: initrd_trigger = \ self.boot_image_task.boot_root_directory + '/config.vmxsystem' with open(initrd_trigger, 'w') as vmx_system: vmx_system.write('IMAGE="%s"\n' % self.squashed_diskname)
class FileSystemBuilder: """ **Filesystem image builder** :param str label: filesystem label :param str root_uuid: UUID of the created filesystem (on block device only) :param str root_dir: root directory path name :param str target_dir: target directory path name :param str requested_image_type: configured image type :param str requested_filesystem: requested filesystem name :param obejct system_setup: instance of :class:`SystemSetup` :param str filename: file name of the filesystem image :param int blocksize: configured disk blocksize :param object filesystem_setup: instance of :class:`FileSystemSetup` :param object filesystems_no_device_node: List of filesystems which are created from a data tree and do not require a block device e.g loop :param dict filesystem_custom_parameters: Configured custom filesystem mount and creation arguments :param object result: instance of :class:`Result` """ def __init__(self, xml_state, target_dir, root_dir): self.label = None self.root_uuid = None self.root_dir = root_dir self.target_dir = target_dir 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): """ 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() self.result.verify_image_size( self.runtime_config.get_max_size_constraint(), self.filename ) 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_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): 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( '--> Syncing data to filesystem on %s', loop_provider.get_device() ) filesystem.sync_data( Defaults.get_exclude_list_for_root_data_sync() ) def _operate_on_file(self): 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() )
def process(self): # noqa: C901 """ Build a system image from the specified description. The build command combines the prepare and create commands """ self.manual = Help() if self._help(): return Privileges.check_for_root_permissions() abs_target_dir_path = os.path.abspath( self.command_args['--target-dir']) build_dir = os.sep.join([abs_target_dir_path, 'build']) image_root = os.sep.join([build_dir, 'image-root']) Path.create(build_dir) if not self.global_args['--logfile']: log.set_logfile( os.sep.join([abs_target_dir_path, 'build', 'image-root.log'])) self.load_xml_description(self.command_args['--description']) build_checks = self.checks_before_command_args build_checks.update({ 'check_target_directory_not_in_shared_cache': [abs_target_dir_path] }) self.run_checks(build_checks) if self.command_args['--ignore-repos']: self.xml_state.delete_repository_sections() elif self.command_args['--ignore-repos-used-for-build']: self.xml_state.delete_repository_sections_used_for_build() if self.command_args['--set-repo']: self.xml_state.set_repository( *self.sextuple_token(self.command_args['--set-repo'])) if self.command_args['--add-repo']: for add_repo in self.command_args['--add-repo']: self.xml_state.add_repository(*self.sextuple_token(add_repo)) if self.command_args['--set-container-tag']: self.xml_state.set_container_config_tag( self.command_args['--set-container-tag']) if self.command_args['--add-container-label']: for add_label in self.command_args['--add-container-label']: try: (name, value) = add_label.split('=', 1) self.xml_state.add_container_config_label(name, value) except Exception: log.warning('Container label {0} ignored. Invalid format: ' 'expected labelname=value'.format(add_label)) if self.command_args['--set-container-derived-from']: self.xml_state.set_derived_from_image_uri( self.command_args['--set-container-derived-from']) self.run_checks(self.checks_after_command_args) log.info('Preparing new root system') system = SystemPrepare(self.xml_state, image_root, self.command_args['--allow-existing-root']) manager = system.setup_repositories(self.command_args['--clear-cache'], self.command_args['--signing-key']) system.install_bootstrap(manager, self.command_args['--add-bootstrap-package']) system.install_system(manager) if self.command_args['--add-package']: system.install_packages(manager, self.command_args['--add-package']) if self.command_args['--delete-package']: system.delete_packages(manager, self.command_args['--delete-package']) profile = Profile(self.xml_state) defaults = Defaults() defaults.to_profile(profile) profile.create(Defaults.get_profile_file(image_root)) setup = SystemSetup(self.xml_state, image_root) setup.import_description() setup.import_overlay_files() setup.import_image_identifier() setup.setup_groups() setup.setup_users() setup.setup_keyboard_map() setup.setup_locale() setup.setup_plymouth_splash() setup.setup_timezone() setup.setup_permissions() # make sure manager instance is cleaned up now del manager # setup permanent image repositories after cleanup setup.import_repositories_marked_as_imageinclude() setup.call_config_script() # handle uninstall package requests, gracefully uninstall # with dependency cleanup system.pinch_system(force=False) # handle delete package requests, forced uninstall without # any dependency resolution system.pinch_system(force=True) # delete any custom rpm macros created Rpm(image_root, Defaults.get_custom_rpm_image_macro_name()).wipe_config() # make sure system instance is cleaned up now del system setup.call_image_script() # make sure setup instance is cleaned up now del setup log.info('Creating system image') image_builder = ImageBuilder(self.xml_state, abs_target_dir_path, image_root, custom_args={ 'signing_keys': self.command_args['--signing-key'], 'xz_options': self.runtime_config.get_xz_options() }) result = image_builder.create() result.print_results() result.dump(os.sep.join([abs_target_dir_path, 'kiwi.result']))
def process(self): # noqa: C901 """ Prepare and install a new system for chroot access """ self.manual = Help() if self._help(): return Privileges.check_for_root_permissions() self.load_xml_description(self.command_args['--description']) abs_root_path = os.path.abspath(self.command_args['--root']) prepare_checks = self.checks_before_command_args prepare_checks.update( {'check_target_directory_not_in_shared_cache': [abs_root_path]}) self.run_checks(prepare_checks) if self.command_args['--ignore-repos']: self.xml_state.delete_repository_sections() elif self.command_args['--ignore-repos-used-for-build']: self.xml_state.delete_repository_sections_used_for_build() if self.command_args['--set-repo']: self.xml_state.set_repository( *self.sextuple_token(self.command_args['--set-repo'])) if self.command_args['--add-repo']: for add_repo in self.command_args['--add-repo']: self.xml_state.add_repository(*self.sextuple_token(add_repo)) if self.command_args['--set-container-tag']: self.xml_state.set_container_config_tag( self.command_args['--set-container-tag']) if self.command_args['--add-container-label']: for add_label in self.command_args['--add-container-label']: try: (name, value) = add_label.split('=', 1) self.xml_state.add_container_config_label(name, value) except Exception: log.warning('Container label {0} ignored. Invalid format: ' 'expected labelname=value'.format(add_label)) if self.command_args['--set-container-derived-from']: self.xml_state.set_derived_from_image_uri( self.command_args['--set-container-derived-from']) self.run_checks(self.checks_after_command_args) log.info('Preparing system') system = SystemPrepare(self.xml_state, abs_root_path, self.command_args['--allow-existing-root']) manager = system.setup_repositories(self.command_args['--clear-cache'], self.command_args['--signing-key']) system.install_bootstrap(manager, self.command_args['--add-bootstrap-package']) system.install_system(manager) if self.command_args['--add-package']: system.install_packages(manager, self.command_args['--add-package']) if self.command_args['--delete-package']: system.delete_packages(manager, self.command_args['--delete-package']) profile = Profile(self.xml_state) defaults = Defaults() defaults.to_profile(profile) setup = SystemSetup(self.xml_state, abs_root_path) setup.import_shell_environment(profile) setup.import_description() setup.import_overlay_files() setup.import_image_identifier() setup.setup_groups() setup.setup_users() setup.setup_keyboard_map() setup.setup_locale() setup.setup_plymouth_splash() setup.setup_timezone() setup.setup_permissions() # make sure manager instance is cleaned up now del manager # setup permanent image repositories after cleanup setup.import_repositories_marked_as_imageinclude() setup.call_config_script() # handle uninstall package requests, gracefully uninstall # with dependency cleanup system.pinch_system(force=False) # handle delete package requests, forced uninstall without # any dependency resolution system.pinch_system(force=True) # delete any custom rpm macros created Rpm(abs_root_path, Defaults.get_custom_rpm_image_macro_name()).wipe_config() # make sure system instance is cleaned up now del system
class TestSystemSetup: @patch('platform.machine') def setup(self, mock_machine): mock_machine.return_value = 'x86_64' 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.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 @patch('platform.machine') def test_setup_ix86(self, mock_machine): mock_machine.return_value = '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('../data/config-cdroot.tar*') assert mock_command.call_args_list == [ call(['mkdir', '-p', 'root_dir/image']), call(['cp', '../data/config.sh', 'root_dir/image/config.sh']), call([ 'cp', '../data/my_edit_boot_script', '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', '../data/images.sh', 'root_dir/image/images.sh']), call([ 'cp', Defaults.project_file('config/functions.sh'), 'root_dir/.kconfig' ]), call(['cp', '/absolute/path/to/image.tgz', 'root_dir/image/']), call(['cp', '../data/bootstrap.tgz', 'root_dir/image/']), call(['cp', 'config-cdroot.tar.xz', 'root_dir/image/']) ] @patch('kiwi.command.Command.run') @patch('os.path.exists') def test_import_description_archive_from_derived(self, mock_path, mock_command): path_return_values = [True, False, 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(['mkdir', '-p', 'root_dir/image']), call(['cp', '../data/config.sh', 'root_dir/image/config.sh']), call([ 'cp', '../data/my_edit_boot_script', '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', '../data/images.sh', 'root_dir/image/images.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/']) ] @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] 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') @patch('os.path.exists') def test_import_description_configured_archives_not_found( self, mock_path, mock_command): path_return_values = [False, False, 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( ['rm', '-r', '-f', '/.kconfig', '/image']) def test_import_shell_environment(self): mock_profile = MagicMock() mock_profile.create = Mock(return_value=['a']) m_open = mock_open() with patch('builtins.open', m_open, create=True): self.setup.import_shell_environment(mock_profile) mock_profile.create.assert_called_once_with() m_open.assert_called_once_with('root_dir/.profile', 'w') m_open.return_value.write.assert_called_once_with('a\n') @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.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('kiwi.logger.log.warning') @patch('os.path.exists') def test_setup_keyboard_skipped(self, mock_exists, mock_log_warn): mock_exists.return_value = False self.setup.preferences['keytable'] = 'keytable' self.setup.setup_keyboard_map() assert mock_log_warn.called @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_locale(self, mock_path, mock_run, mock_shell, 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' ]) ]) mock_shell.assert_called_once_with( 'baseUpdateSysConfig', ['root_dir/etc/sysconfig/language', 'RC_LANG', 'locale1.UTF-8']) @patch('kiwi.system.setup.Shell.run_common_function') @patch('kiwi.system.setup.Command.run') @patch('os.path.exists') def test_setup_locale_POSIX(self, mock_path, mock_run, mock_shell): mock_path.return_value = True self.setup.preferences['locale'] = 'POSIX,locale2' self.setup.setup_locale() mock_shell.assert_called_once_with( 'baseUpdateSysConfig', ['root_dir/etc/sysconfig/language', 'RC_LANG', '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(['rm', '-r', '-f', 'root_dir/etc/localtime']), 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', '-d', '/home/kiwi' ]) ] 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( custom_env={'PATH': 'root_dir/usr/sbin'}, 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.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') @patch('os.stat') @patch('os.access') def test_call_non_excutable_config_script(self, mock_access, mock_stat, mock_os_path, mock_watch, mock_command): 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_command.assert_called_once_with( ['chroot', 'root_dir', 'bash', '/image/config.sh']) @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_excutable_config_script(self, mock_access, mock_stat, mock_os_path, mock_watch, mock_command): 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_command.assert_called_once_with( ['chroot', 'root_dir', '/image/config.sh']) @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(self, mock_access, mock_stat, mock_os_path, mock_watch, mock_command): 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_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.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): 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([ 'bash', '-c', 'cd root_dir && bash --norc /root_dir/image/edit_boot_install.sh ' 'my_image.raw /dev/mapper/loop0p1' ]) @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): 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): 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_entry']) assert m_open.call_args_list == [ call('root_dir/etc/fstab', 'w'), call('root_dir/etc/fstab.append', 'r') ] assert m_open.return_value.write.call_args_list == [ call('fstab_entry\n'), 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('kiwi.system.setup.NamedTemporaryFile') @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_temp, mock_command): mock_getsize.return_value = 42 compress = Mock() mock_compress.return_value = compress archive = Mock() mock_archive.return_value = archive tmpdir = Mock() tmpdir.name = 'tmpdir' mock_temp.return_value = tmpdir 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='tmpdir') archive.create.assert_called_once_with(exclude=['dev', 'proc', 'sys'], options=[ '--numeric-owner', '--hard-dereference', '--preserve-permissions' ], source_dir='root_dir') assert mock_command.call_args_list[1] == call( ['mv', 'tmpdir', 'root_dir/recovery.tar']) 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[2] == 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.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') 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') @patch('kiwi.logger.log.warning') def test_setup_permissions(self, mock_log_warn, 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 self.setup.setup_permissions() mock_log_warn.assert_called_once() @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-get') 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([ 'dpkg-query', '--admindir', 'root_dir/var/lib/dpkg', '-W', '-f', '${Package}|None|${Version}|None|${Architecture}|None|None\\n' ]) @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') 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-get') 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') 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') @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)
class ContainerBuilder(object): """ **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, target_dir, root_dir, custom_args=None): self.root_dir = root_dir self.target_dir = target_dir 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'] = custom_args['xz_options'] \ if custom_args and 'xz_options' in custom_args else None 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(), '.' + platform.machine(), '-' + xml_state.get_image_version(), '.', self.requested_container_type, '.tar' ] ) self.result = Result(xml_state) self.runtime_config = RuntimeConfig() def create(self): """ Builds a container image which is usually a tarball including container specific metadata. Image types which triggers this builder are: * image="docker" :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( 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( self.requested_container_type, self.root_dir, self.container_config ) self.filename = container_image.create( self.filename, self.base_image ) self.result.verify_image_size( self.runtime_config.get_max_size_constraint(), self.filename ) self.result.add( key='container', 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_verified', filename=self.system_setup.export_package_verification( self.target_dir ), use_for_bundle=True, compress=False, shasum=False ) return self.result
def test_setup_ix86(self, mock_machine): mock_machine.return_value = 'i686' setup = SystemSetup(MagicMock(), 'root_dir') assert setup.arch == 'ix86'
class PxeBuilder(object): """ **Filesystem based PXE 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, target_dir, root_dir, custom_args=None): self.target_dir = target_dir self.compressed = xml_state.build_type.get_compressed() self.xen_server = xml_state.is_xen_server() self.pxedeploy = xml_state.get_build_type_pxedeploy_section() self.filesystem = FileSystemBuilder( xml_state, target_dir, root_dir + '/' ) self.system_setup = SystemSetup( xml_state=xml_state, root_dir=root_dir ) 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( xml_state, target_dir, signing_keys=self.boot_signing_keys, custom_args=custom_args ) self.image_name = ''.join( [ target_dir, '/', xml_state.xml_data.get_name(), '.' + platform.machine(), '-' + xml_state.get_image_version() ] ) self.archive_name = ''.join([self.image_name, '.tar.xz']) self.kernel_filename = None self.hypervisor_filename = None self.result = Result(xml_state) self.runtime_config = RuntimeConfig() def create(self): """ Build a pxe image set consisting out of a boot image(initrd) plus its appropriate kernel files and the root filesystem image with a checksum. The result can be used within the kiwi PXE boot infrastructure Image types which triggers this builder are: * image="pxe" :raises KiwiPxeBootImageError: if no kernel or hipervisor is found in boot image tree :return: result :rtype: instance of :class:`Result` """ log.info('Creating PXE 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 PXE root filesystem MD5 checksum') self.filesystem_checksum = ''.join([self.image, '.md5']) checksum = Checksum(self.image) checksum.md5(self.filesystem_checksum) # prepare boot(initrd) root system log.info('Creating PXE 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(initrd) root 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 KiwiPxeBootImageError( '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: kernel_data = kernel.get_xen_hypervisor() if kernel_data: self.hypervisor_filename = ''.join( [os.path.basename(self.image_name), '-', kernel_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 KiwiPxeBootImageError( 'No hypervisor in boot image tree %s found' % self.boot_image_task.boot_root_directory ) # create initrd for pxe boot self.boot_image_task.create_initrd() # put results into a tarball if not self.xz_options: self.xz_options = Defaults.get_xz_compression_options() bash_command = [ 'tar', '-C', self.target_dir, '-c', '--to-stdout' ] + [ self.kernel_filename, os.path.basename(self.boot_image_task.initrd_filename), os.path.basename(self.image), os.path.basename(self.filesystem_checksum) ] + [ '|', 'xz', '-f' ] + self.xz_options + [ '>', self.archive_name ] Command.run(['bash', '-c', ' '.join(bash_command)]) self.result.verify_image_size( self.runtime_config.get_max_size_constraint(), self.archive_name ) # store results self.result.add( key='pxe_archive', filename=self.archive_name, use_for_bundle=True, compress=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_verified', filename=self.system_setup.export_package_verification( self.target_dir ), use_for_bundle=True, compress=False, shasum=False ) if self.pxedeploy: log.warning( 'Creation of client config file from pxedeploy not implemented' ) return self.result
def process(self): # noqa: C901 """ Build a system image from the specified description. The build command combines the prepare and create commands """ self.manual = Help() if self._help(): return Privileges.check_for_root_permissions() abs_target_dir_path = os.path.abspath( self.command_args['--target-dir']) build_dir = os.sep.join([abs_target_dir_path, 'build']) image_root = os.sep.join([build_dir, 'image-root']) Path.create(build_dir) if not self.global_args['--logfile']: log.set_logfile( os.sep.join([abs_target_dir_path, 'build', 'image-root.log'])) self.load_xml_description(self.command_args['--description']) self.runtime_checker.check_efi_mode_for_disk_overlay_correctly_setup() self.runtime_checker.check_boot_description_exists() self.runtime_checker.check_consistent_kernel_in_boot_and_system_image() self.runtime_checker.check_docker_tool_chain_installed() self.runtime_checker.check_volume_setup_has_no_root_definition() self.runtime_checker.check_xen_uniquely_setup_as_server_or_guest() self.runtime_checker.check_target_directory_not_in_shared_cache( abs_target_dir_path) self.runtime_checker.check_mediacheck_only_for_x86_arch() self.runtime_checker.check_dracut_module_for_live_iso_in_package_list() self.runtime_checker.check_dracut_module_for_disk_overlay_in_package_list( ) self.runtime_checker.check_dracut_module_for_disk_oem_in_package_list() self.runtime_checker.check_dracut_module_for_oem_install_in_package_list( ) if self.command_args['--ignore-repos']: self.xml_state.delete_repository_sections() elif self.command_args['--ignore-repos-used-for-build']: self.xml_state.delete_repository_sections_used_for_build() if self.command_args['--set-repo']: self.xml_state.set_repository( *self.sextuple_token(self.command_args['--set-repo'])) if self.command_args['--add-repo']: for add_repo in self.command_args['--add-repo']: self.xml_state.add_repository(*self.sextuple_token(add_repo)) if self.command_args['--set-container-tag']: self.xml_state.set_container_config_tag( self.command_args['--set-container-tag']) if self.command_args['--set-container-derived-from']: self.xml_state.set_derived_from_image_uri( self.command_args['--set-container-derived-from']) self.runtime_checker.check_repositories_configured() self.runtime_checker.check_image_include_repos_publicly_resolvable() package_requests = False if self.command_args['--add-package']: package_requests = True if self.command_args['--delete-package']: package_requests = True log.info('Preparing new root system') system = SystemPrepare(self.xml_state, image_root, self.command_args['--allow-existing-root']) manager = system.setup_repositories(self.command_args['--clear-cache'], self.command_args['--signing-key']) system.install_bootstrap(manager) system.install_system(manager) if package_requests: if self.command_args['--add-package']: system.install_packages(manager, self.command_args['--add-package']) if self.command_args['--delete-package']: system.delete_packages(manager, self.command_args['--delete-package']) profile = Profile(self.xml_state) defaults = Defaults() defaults.to_profile(profile) setup = SystemSetup(self.xml_state, image_root) setup.import_shell_environment(profile) setup.import_description() setup.import_overlay_files() setup.import_image_identifier() setup.setup_groups() setup.setup_users() setup.setup_keyboard_map() setup.setup_locale() setup.setup_plymouth_splash() setup.setup_timezone() system.pinch_system(manager=manager, force=True) # make sure manager instance is cleaned up now del manager # setup permanent image repositories after cleanup setup.import_repositories_marked_as_imageinclude() setup.call_config_script() # make sure system instance is cleaned up now del system setup.call_image_script() # make sure setup instance is cleaned up now del setup log.info('Creating system image') image_builder = ImageBuilder(self.xml_state, abs_target_dir_path, image_root, custom_args={ 'signing_keys': self.command_args['--signing-key'], 'xz_options': self.runtime_config.get_xz_options() }) result = image_builder.create() result.print_results() result.dump(os.sep.join([abs_target_dir_path, 'kiwi.result']))
class TestSystemSetup(object): @patch('platform.machine') def setup(self, mock_machine): mock_machine.return_value = 'x86_64' self.context_manager_mock = mock.Mock() self.file_mock = mock.Mock() self.enter_mock = mock.Mock() self.exit_mock = mock.Mock() self.enter_mock.return_value = self.file_mock setattr(self.context_manager_mock, '__enter__', self.enter_mock) setattr(self.context_manager_mock, '__exit__', self.exit_mock) self.xml_state = mock.MagicMock() self.xml_state.get_package_manager = mock.Mock( return_value='zypper' ) self.xml_state.build_type.get_filesystem = mock.Mock( return_value='ext3' ) self.xml_state.xml_data.get_name = mock.Mock( return_value='some-image' ) self.xml_state.get_image_version = mock.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.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 ) @patch('platform.machine') def test_setup_ix86(self, mock_machine): mock_machine.return_value = 'i686' setup = SystemSetup( mock.MagicMock(), 'root_dir' ) assert setup.arch == 'ix86' @patch('kiwi.command.Command.run') @patch_open @patch('os.path.exists') def test_import_description(self, mock_path, mock_open, mock_command): mock_path.return_value = True self.setup_with_real_xml.import_description() assert mock_command.call_args_list == [ call(['mkdir', '-p', 'root_dir/image']), call(['cp', '../data/config.sh', 'root_dir/image/config.sh']), call([ 'cp', '../data/my_edit_boot_script', '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', '../data/images.sh', 'root_dir/image/images.sh']), call([ 'cp', Defaults.project_file('config/functions.sh'), 'root_dir/.kconfig' ]), call(['cp', '/absolute/path/to/image.tgz', 'root_dir/image/']), call(['cp', '../data/bootstrap.tgz', 'root_dir/image/'])] @patch('kiwi.command.Command.run') @patch_open @patch('os.path.exists') def test_import_description_archive_from_derived( self, mock_path, mock_open, mock_command ): path_return_values = [ True, False, True, True, True, True, True ] def side_effect(arg): return path_return_values.pop() mock_path.side_effect = side_effect self.setup_with_real_xml.import_description() assert mock_command.call_args_list == [ call(['mkdir', '-p', 'root_dir/image']), call(['cp', '../data/config.sh', 'root_dir/image/config.sh']), call([ 'cp', '../data/my_edit_boot_script', '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', '../data/images.sh', 'root_dir/image/images.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/' ]) ] @patch('kiwi.command.Command.run') @patch_open @patch('os.path.exists') @raises(KiwiImportDescriptionError) def test_import_description_configured_editboot_scripts_not_found( self, mock_path, mock_open, mock_command ): path_return_values = [False, True, True] def side_effect(arg): return path_return_values.pop() mock_path.side_effect = side_effect self.setup_with_real_xml.import_description() @patch('kiwi.command.Command.run') @patch_open @patch('os.path.exists') @raises(KiwiImportDescriptionError) def test_import_description_configured_archives_not_found( self, mock_path, mock_open, mock_command ): path_return_values = [False, False, True, True, True, True] def side_effect(arg): return path_return_values.pop() mock_path.side_effect = side_effect 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( ['rm', '-r', '-f', '/.kconfig', '/image'] ) @patch_open def test_import_shell_environment(self, mock_open): mock_profile = mock.MagicMock() mock_profile.create = mock.Mock( return_value=['a'] ) mock_open.return_value = self.context_manager_mock self.setup.import_shell_environment(mock_profile) mock_profile.create.assert_called_once_with() mock_open.assert_called_once_with('root_dir/.profile', 'w') self.file_mock.write.assert_called_once_with('a\n') @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() 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() 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() 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.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('kiwi.logger.log.warning') @patch('os.path.exists') def test_setup_keyboard_skipped(self, mock_exists, mock_log_warn): mock_exists.return_value = False self.setup.preferences['keytable'] = 'keytable' self.setup.setup_keyboard_map() assert mock_log_warn.called @patch('kiwi.system.setup.Shell.run_common_function') @patch('kiwi.system.setup.Command.run') @patch('os.path.exists') def test_setup_locale(self, mock_path, mock_run, mock_shell): mock_path.return_value = True self.setup.preferences['locale'] = 'locale1,locale2' self.setup.setup_locale() mock_shell.assert_called_once_with( 'baseUpdateSysConfig', [ 'root_dir/etc/sysconfig/language', 'RC_LANG', 'locale1.UTF-8' ] ) @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_locale_with_systemd( self, mock_path, mock_run, mock_shell, 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.Shell.run_common_function') @patch('kiwi.system.setup.Command.run') @patch('os.path.exists') def test_setup_locale_POSIX(self, mock_path, mock_run, mock_shell): mock_path.return_value = True self.setup.preferences['locale'] = 'POSIX,locale2' self.setup.setup_locale() mock_shell.assert_called_once_with( 'baseUpdateSysConfig', [ 'root_dir/etc/sysconfig/language', 'RC_LANG', 'POSIX' ] ) @patch('kiwi.logger.log.warning') @patch('os.path.exists') def test_setup_locale_skipped(self, mock_exists, mock_log_warn): mock_exists.return_value = False self.setup.preferences['locale'] = 'locale1,locale2' self.setup.setup_locale() assert mock_log_warn.called @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(['rm', '-r', '-f', 'root_dir/etc/localtime']), call([ 'chroot', 'root_dir', 'systemd-firstboot', '--timezone=timezone' ]) ]) @patch('kiwi.system.setup.Users') def test_setup_groups(self, mock_users): users = mock.Mock() users.group_exists = mock.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.Mock() users.user_exists = mock.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', '-d', '/home/kiwi' ] ) ] 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.Mock() users.user_exists = mock.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.Mock() preferences.get_bootsplash_theme = mock.Mock( return_value=['some-theme'] ) self.xml_state.get_preferences_sections = mock.Mock( return_value=[preferences] ) self.setup.setup_plymouth_splash() mock_which.assert_called_once_with( custom_env={'PATH': 'root_dir/usr/sbin'}, filename='plymouth-set-default-theme' ) mock_command.assert_called_once_with( ['chroot', 'root_dir', 'plymouth-set-default-theme', 'some-theme'] ) @patch_open @patch('os.path.exists') def test_import_image_identifier(self, mock_os_path, mock_open): self.xml_state.xml_data.get_id = mock.Mock( return_value='42' ) mock_os_path.return_value = True mock_open.return_value = self.context_manager_mock self.setup.import_image_identifier() mock_open.assert_called_once_with('root_dir/etc/ImageID', 'w') self.file_mock.write.assert_called_once_with('42\n') @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') def test_call_config_script(self, mock_os_path, mock_watch, mock_command): 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 self.setup.call_config_script() mock_command.assert_called_once_with( ['chroot', 'root_dir', 'bash', '/image/config.sh'] ) @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') def test_call_image_script(self, mock_os_path, mock_watch, mock_command): 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 self.setup.call_image_script() 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.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 ): 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([ 'bash', '-c', 'cd root_dir && bash --norc /root_dir/image/edit_boot_install.sh my_image.raw /dev/mapper/loop0p1' ]) @raises(KiwiScriptFailed) @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') def test_call_image_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 self.setup.call_image_script() @raises(KiwiScriptFailed) @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 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_open def test_create_fstab(self, mock_open): mock_open.return_value = self.context_manager_mock self.setup.create_fstab(['fstab_entry']) mock_open.assert_called_once_with( 'root_dir/etc/fstab', 'w' ) self.file_mock.write.assert_called_once_with('fstab_entry\n') @patch('kiwi.command.Command.run') @patch('kiwi.system.setup.NamedTemporaryFile') @patch('kiwi.system.setup.ArchiveTar') @patch_open @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_open, mock_archive, mock_temp, mock_command ): mock_open.return_value = self.context_manager_mock mock_getsize.return_value = 42 compress = mock.Mock() mock_compress.return_value = compress archive = mock.Mock() mock_archive.return_value = archive tmpdir = mock.Mock() tmpdir.name = 'tmpdir' mock_temp.return_value = tmpdir self.setup.oemconfig['recovery'] = True self.setup.oemconfig['recovery_inplace'] = 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='tmpdir' ) archive.create.assert_called_once_with( exclude=['dev', 'proc', 'sys'], options=[ '--numeric-owner', '--hard-dereference', '--preserve-permissions' ], source_dir='root_dir' ) assert mock_command.call_args_list[1] == call( ['mv', 'tmpdir', 'root_dir/recovery.tar'] ) assert mock_open.call_args_list[0] == call( 'root_dir/recovery.tar.filesystem', 'w' ) assert self.file_mock.write.call_args_list[0] == call('ext3') assert mock_command.call_args_list[2] == call( ['bash', '-c', 'tar -tf root_dir/recovery.tar | wc -l'] ) assert mock_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 self.file_mock.write.call_args_list[1] == call('1\n') assert mock_open.call_args_list[2] == call( 'root_dir/recovery.tar.size', 'w' ) assert self.file_mock.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 mock_open.call_args_list[3] == call( 'root_dir/recovery.partition.size', 'w' ) assert self.file_mock.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() 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=['-z', '-a'] ) @patch('kiwi.system.setup.Command.run') @patch_open def test_export_package_list_rpm( self, mock_open, mock_command ): command = mock.Mock() command.output = 'packages_data' mock_command.return_value = command result = self.setup.export_package_list('target_dir') assert result == 'target_dir/some-image.x86_64-1.2.3.packages' mock_command.assert_has_calls([ call(['chroot', 'root_dir', 'rpm', '-E', '%_dbpath']), call([ 'rpm', '--root', 'root_dir', '-qa', '--qf', '%{NAME}|%{EPOCH}|%{VERSION}|%{RELEASE}|%{ARCH}|%{DISTURL}\\n', '--dbpath', 'packages_data' ]) ]) mock_open.assert_called_once_with( 'target_dir/some-image.x86_64-1.2.3.packages', 'w' ) @patch('kiwi.system.setup.Command.run') @patch_open def test_export_package_list_rpm_no_dbpath( self, mock_open, mock_command ): cmd = mock.Mock() cmd.output = 'packages_data' def dbpath_check_fails(command): if '%_dbpath' in command: raise KiwiCommandError() else: return cmd mock_command.side_effect = dbpath_check_fails result = self.setup.export_package_list('target_dir') assert result == 'target_dir/some-image.x86_64-1.2.3.packages' mock_command.assert_has_calls([ call(['chroot', 'root_dir', 'rpm', '-E', '%_dbpath']), call([ 'rpm', '--root', 'root_dir', '-qa', '--qf', '%{NAME}|%{EPOCH}|%{VERSION}|%{RELEASE}|%{ARCH}|%{DISTURL}\\n' ]) ]) mock_open.assert_called_once_with( 'target_dir/some-image.x86_64-1.2.3.packages', 'w' ) @patch('kiwi.system.setup.Command.run') @patch_open def test_export_package_list_dpkg( self, mock_open, mock_command ): command = mock.Mock() command.output = 'packages_data' mock_command.return_value = command self.xml_state.get_package_manager = mock.Mock( return_value='apt-get' ) 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([ 'dpkg-query', '--admindir', 'root_dir/var/lib/dpkg', '-W', '-f', '${Package}|None|${Version}|None|${Architecture}|None\\n' ]) mock_open.assert_called_once_with( 'target_dir/some-image.x86_64-1.2.3.packages', 'w' ) @patch('kiwi.system.setup.Command.run') @patch_open def test_export_package_verification( self, mock_open, mock_command ): command = mock.Mock() command.output = 'verification_data' mock_command.return_value = command result = self.setup.export_package_verification('target_dir') assert result == 'target_dir/some-image.x86_64-1.2.3.verified' mock_command.assert_has_calls([ call(['chroot', 'root_dir', 'rpm', '-E', '%_dbpath']), call(command=[ 'rpm', '--root', 'root_dir', '-Va', '--dbpath', 'verification_data' ], raise_on_error=False) ]) mock_open.assert_called_once_with( 'target_dir/some-image.x86_64-1.2.3.verified', 'w' ) @patch('kiwi.system.setup.Command.run') @patch_open def test_export_package_verification_no_dbpath( self, mock_open, mock_command ): cmd = mock.Mock() cmd.output = 'verification_data' def dbpath_check_fails(command, raise_on_error): if '%_dbpath' in command: raise KiwiCommandError() else: return cmd mock_command.side_effect = dbpath_check_fails result = self.setup.export_package_verification('target_dir') assert result == 'target_dir/some-image.x86_64-1.2.3.verified' mock_command.assert_has_calls([ call(['chroot', 'root_dir', 'rpm', '-E', '%_dbpath']), call( command=['rpm', '--root', 'root_dir', '-Va'], raise_on_error=False ) ]) mock_open.assert_called_once_with( 'target_dir/some-image.x86_64-1.2.3.verified', 'w' ) @patch('kiwi.system.setup.Command.run') @patch_open def test_export_package_verification_dpkg( self, mock_open, mock_command ): command = mock.Mock() command.output = 'verification_data' mock_command.return_value = command self.xml_state.get_package_manager = mock.Mock( return_value='apt-get' ) 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=[ 'dpkg', '--root', 'root_dir', '-V', '--verify-format', 'rpm' ], raise_on_error=False ) mock_open.assert_called_once_with( 'target_dir/some-image.x86_64-1.2.3.verified', 'w' ) @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') @patch('kiwi.system.setup.Uri') def test_import_repositories_marked_as_imageinclude( self, mock_uri, mock_repo ): uri = mock.Mock() mock_uri.return_value = uri uri.translate = mock.Mock( return_value="uri" ) uri.alias = mock.Mock( return_value="uri-alias" ) uri.credentials_file_name = mock.Mock( return_value='kiwiRepoCredentials' ) mock_uri.return_value = uri repo = mock.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 )
class LiveImageBuilder(object): """ Live image builder Attributes * :attr:`xml_state` Instance of XMLState * :attr:`target_dir` target directory path name * :attr:`root_dir` root directory path name * :attr:`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.media_dir = None self.live_container_dir = None self.arch = platform.machine() if self.arch == 'i686' or self.arch == 'i586': self.arch = 'ix86' 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.hybrid = xml_state.build_type.get_hybrid() self.volume_id = xml_state.build_type.get_volid() or \ Defaults.get_volume_id() self.mbrid = SystemIdentifier() self.mbrid.calculate_id() self.filesystem_custom_parameters = { 'mount_options': xml_state.get_fs_mount_option_list() } 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.isoname = ''.join([ target_dir, '/', xml_state.xml_data.get_name(), '.' + platform.machine(), '-' + xml_state.get_image_version(), '.iso' ]) self.result = Result(xml_state) def create(self): """ Build a bootable hybrid live ISO image Image types which triggers this builder are: * image="iso" """ # media dir to store CD contents self.media_dir = mkdtemp(prefix='live-media.', dir=self.target_dir) rootsize = SystemSize(self.media_dir) # 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(Defaults.get_publisher())) log.info('--> Volume id: {0}'.format(self.volume_id)) custom_iso_args = { 'create_options': [ '-A', self.mbrid.get_id(), '-p', Defaults.get_preparer(), '-publisher', Defaults.get_publisher(), '-V', self.volume_id ] } # 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() } filesystem_setup = FileSystemSetup(self.xml_state, self.root_dir) root_image = NamedTemporaryFile() 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(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()) log.info('--> Creating squashfs container for root image') self.live_container_dir = mkdtemp(prefix='live-container.', dir=self.target_dir) Path.create(self.live_container_dir + '/LiveOS') shutil.copy(root_image.name, self.live_container_dir + '/LiveOS/rootfs.img') live_container_image = FileSystem(name='squashfs', device_provider=None, root_dir=self.live_container_dir) container_image = NamedTemporaryFile() live_container_image.create_on_file(container_image.name) Path.create(self.media_dir + '/LiveOS') shutil.copy(container_image.name, self.media_dir + '/LiveOS/squashfs.img') # setup bootloader config to boot the ISO via isolinux log.info('Setting up isolinux bootloader configuration') bootloader_config_isolinux = BootLoaderConfig('isolinux', self.xml_state, self.media_dir) bootloader_config_isolinux.setup_live_boot_images( mbrid=None, lookup_path=self.boot_image.boot_root_directory) bootloader_config_isolinux.setup_live_image_config(mbrid=None) bootloader_config_isolinux.write() # setup bootloader config to boot the ISO via EFI if self.firmware.efi_mode(): log.info('Setting up EFI grub bootloader configuration') bootloader_config_grub = BootLoaderConfig( 'grub2', self.xml_state, self.media_dir, { 'grub_directory_name': Defaults.get_grub_boot_directory_name(self.root_dir) }) bootloader_config_grub.setup_live_boot_images( mbrid=self.mbrid, lookup_path=self.root_dir) bootloader_config_grub.setup_live_image_config(mbrid=self.mbrid) bootloader_config_grub.write() # call custom editbootconfig script if present self.system_setup.call_edit_boot_config_script( filesystem='iso:{0}'.format(self.media_dir), boot_part_id=1, working_directory=self.root_dir) # create dracut initrd for live image log.info('Creating live ISO boot image') self._create_dracut_live_iso_config() 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['create_options'].append('-iso-level') custom_iso_args['create_options'].append('3') custom_iso_args['create_options'].append('-udf') # create iso filesystem from media_dir log.info('Creating live ISO image') iso_image = FileSystemIsoFs(device_provider=None, root_dir=self.media_dir, custom_args=custom_iso_args) iso_header_offset = iso_image.create_on_file(self.isoname) # make it hybrid if self.hybrid: Iso.create_hybrid(iso_header_offset, self.mbrid, self.isoname, self.firmware.efi_mode()) # include metadata for checkmedia tool if self.xml_state.build_type.get_mediacheck() is True: Iso.set_media_tag(self.isoname) 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_verified', filename=self.system_setup.export_package_verification( self.target_dir), use_for_bundle=True, compress=False, shasum=False) return self.result def _create_dracut_live_iso_config(self): live_config_file = self.root_dir + '/etc/dracut.conf.d/02-livecd.conf' live_config = [ 'add_dracutmodules+=" kiwi-live pollcdrom "', 'hostonly="no"', 'dracut_rescue_image="no"' ] with open(live_config_file, 'w') as config: for entry in live_config: config.write(entry + os.linesep) def _setup_live_iso_kernel_and_initrd(self): """ Copy kernel and initrd from the root tree into the iso boot structure """ boot_path = ''.join([self.media_dir, '/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)) def __del__(self): if self.media_dir or self.live_container_dir: log.info('Cleaning up {0} instance'.format(type(self).__name__)) if self.media_dir: Path.wipe(self.media_dir) if self.live_container_dir: Path.wipe(self.live_container_dir)