def process_install_requests_bootstrap(self, root_bind: RootBind = None ) -> command_call_type: """ Process package install requests for bootstrap phase (no chroot) The debootstrap program is used to bootstrap a new system with a collection of predefined packages. The kiwi bootstrap section information is not used in this case :param object root_bind: instance of RootBind to manage kernel file systems before debootstrap call :raises KiwiDebootstrapError: if no main distribution repository is configured, if the debootstrap script is not found or if the debootstrap script execution fails :return: process results in command type :rtype: namedtuple """ if not self.distribution: raise KiwiDebootstrapError( 'No main distribution repository is configured') bootstrap_script = '/usr/share/debootstrap/scripts/' + \ self.distribution if not os.path.exists(bootstrap_script): raise KiwiDebootstrapError( 'debootstrap script for %s distribution not found' % self.distribution) # APT package manager does not support bootstrapping. To circumvent # this limitation there is the debootstrap tool for APT based distros. # Because of that there is a little overlap between KIWI and # debootstrap. Debootstrap manages itself the kernel file systems for # chroot environment, thus we need to umount the kernel file systems # before calling debootstrap and remount them afterwards. if root_bind: root_bind.umount_kernel_file_systems() # debootsrap will create its own dev/fd devices debootstrap_device_node_conflicts = ['dev/fd', 'dev/pts'] for node in debootstrap_device_node_conflicts: Path.wipe(os.path.normpath(os.sep.join([self.root_dir, node]))) if 'apt' in self.package_requests: # debootstrap takes care to install apt self.package_requests.remove('apt') try: cmd = ['debootstrap'] if self.repository.unauthenticated == 'false' and \ os.path.exists(self.repository.keyring): cmd.append('--keyring={}'.format(self.repository.keyring)) else: cmd.append('--no-check-gpg') if self.deboostrap_minbase: cmd.append('--variant=minbase') if self.package_requests: cmd.append('--include={}'.format(','.join( self.package_requests))) if self.repository.components: cmd.append('--components={0}'.format(','.join( self.repository.components))) self.cleanup_requests() cmd.extend( [self.distribution, self.root_dir, self.distribution_path]) return Command.call(cmd, self.command_env) except Exception as e: raise KiwiDebootstrapError('%s: %s' % (type(e).__name__, format(e)))
class TestRootBind: @fixture(autouse=True) def inject_fixtures(self, caplog): self._caplog = caplog def setup(self): root = Mock() root.root_dir = 'root-dir' self.bind_root = RootBind(root) # stub config files and bind locations self.bind_root.config_files = ['/etc/sysconfig/proxy'] self.bind_root.bind_locations = ['/proc'] # stub files/dirs and mountpoints to cleanup self.mount_manager = Mock() self.bind_root.cleanup_files = ['/etc/sysconfig/proxy.kiwi'] self.bind_root.mount_stack = [self.mount_manager] self.bind_root.dir_stack = ['/mountpoint'] def teardown(self): sys.argv = argv_kiwi_tests @patch('kiwi.system.root_bind.MountManager.bind_mount') @patch('kiwi.system.root_bind.RootBind.cleanup') @patch('os.path.exists') def test_kernel_file_systems_raises_error(self, mock_exists, mock_cleanup, mock_mount): mock_exists.return_value = True mock_mount.side_effect = KiwiMountKernelFileSystemsError('mount-error') with raises(KiwiMountKernelFileSystemsError): self.bind_root.mount_kernel_file_systems() mock_cleanup.assert_called_once_with() @patch('kiwi.system.root_bind.MountManager.bind_mount') @patch('kiwi.system.root_bind.Path.create') @patch('kiwi.system.root_bind.RootBind.cleanup') def test_shared_directory_raises_error(self, mock_cleanup, mock_path, mock_mount): mock_mount.side_effect = KiwiMountSharedDirectoryError('mount-error') with raises(KiwiMountSharedDirectoryError): self.bind_root.mount_shared_directory() mock_cleanup.assert_called_once_with() @patch('kiwi.command.Command.run') @patch('kiwi.system.root_bind.RootBind.cleanup') @patch('os.path.exists') def test_intermediate_config_raises_error(self, mock_exists, mock_cleanup, mock_command): mock_exists.return_value = True mock_command.side_effect = KiwiSetupIntermediateConfigError( 'config-error') with raises(KiwiSetupIntermediateConfigError): self.bind_root.setup_intermediate_config() mock_cleanup.assert_called_once_with() @patch('kiwi.system.root_bind.os.path.exists') @patch('kiwi.system.root_bind.MountManager') def test_mount_kernel_file_systems(self, mock_mount, mock_exists): mock_exists.return_value = True shared_mount = Mock() mock_mount.return_value = shared_mount self.bind_root.mount_kernel_file_systems() mock_mount.assert_called_once_with(device='/proc', mountpoint='root-dir/proc') shared_mount.bind_mount.assert_called_once_with() @patch('kiwi.system.root_bind.MountManager') def test_umount_kernel_file_systems(self, mock_mount): self.mount_manager.device = '/proc' self.mount_manager.is_mounted = Mock(return_value=True) self.bind_root.umount_kernel_file_systems() self.mount_manager.umount_lazy.assert_called_once_with() assert self.bind_root.mount_stack == [] @patch('kiwi.system.root_bind.MountManager') def test_umount_kernel_file_systems_raises_error(self, mock_mount): self.mount_manager.device = '/proc' self.mount_manager.is_mounted = Mock(return_value=True) self.mount_manager.umount_lazy = Mock(side_effect=Exception) self.bind_root.umount_kernel_file_systems() self.mount_manager.umount_lazy.assert_called_once_with() assert self.bind_root.mount_stack == [self.mount_manager] @patch('kiwi.system.root_bind.MountManager') @patch('kiwi.system.root_bind.Path.create') def test_mount_shared_directory(self, mock_path, mock_mount): shared_mount = Mock() mock_mount.return_value = shared_mount self.bind_root.mount_shared_directory() mock_path.call_args_list = [ call('root-dir/var/cache/kiwi'), call('/var/cache/kiwi') ] mock_mount.assert_called_once_with( device='/var/cache/kiwi', mountpoint='root-dir/var/cache/kiwi') shared_mount.bind_mount.assert_called_once_with() @patch('kiwi.command.Command.run') @patch('kiwi.system.root_bind.Checksum') @patch('os.path.exists') def test_intermediate_config(self, mock_exists, mock_Checksum, mock_command): checksum = Mock() mock_Checksum.return_value = checksum mock_exists.return_value = True with patch('builtins.open') as m_open: self.bind_root.setup_intermediate_config() m_open.assert_called_once_with('root-dir/etc/sysconfig/proxy.sha', 'w') assert mock_command.call_args_list == [ call([ 'cp', '/etc/sysconfig/proxy', 'root-dir/etc/sysconfig/proxy.kiwi' ]), call([ 'ln', '-s', '-f', 'proxy.kiwi', 'root-dir/etc/sysconfig/proxy' ]) ] checksum.sha256.assert_called_once_with() @patch('kiwi.system.root_bind.Checksum') @patch('kiwi.system.root_bind.MountManager.is_mounted') @patch('kiwi.system.root_bind.Command.run') @patch('kiwi.system.root_bind.Path.remove_hierarchy') @patch('os.path.islink') @patch('os.path.exists') @patch('shutil.move') def test_cleanup(self, mock_move, mock_exists, mock_islink, mock_remove_hierarchy, mock_command, mock_is_mounted, mock_Checksum): checksum = Mock() checksum.matches.return_value = False mock_Checksum.return_value = checksum os_exists_return_values = [False, True, True, False] def exists_side_effect(*args): return os_exists_return_values.pop() mock_is_mounted.return_value = False mock_exists.side_effect = exists_side_effect mock_islink.return_value = True with self._caplog.at_level(logging.WARNING): self.bind_root.cleanup() self.mount_manager.umount_lazy.assert_called_once_with() mock_remove_hierarchy.assert_called_once_with(root='root-dir', path='/mountpoint') assert mock_command.call_args_list == [ call(['rm', '-f', 'root-dir/etc/sysconfig/proxy']), call([ 'cp', 'root-dir/usr/share/fillup-templates/sysconfig.proxy', 'root-dir/etc/sysconfig/proxy' ]), call([ 'rm', '-f', 'root-dir/etc/sysconfig/proxy.kiwi', 'root-dir/etc/sysconfig/proxy.sha' ]) ] mock_move.assert_called_once_with( 'root-dir/etc/sysconfig/proxy.rpmnew', 'root-dir/etc/sysconfig/proxy') @patch('os.path.islink') @patch('kiwi.command.Command.run') @patch('kiwi.system.root_bind.Path.remove_hierarchy') @patch('kiwi.system.root_bind.Checksum') def test_cleanup_continue_on_error(self, mock_Checksum, mock_remove_hierarchy, mock_command, mock_islink): mock_islink.return_value = True mock_remove_hierarchy.side_effect = Exception('rm') mock_command.side_effect = Exception self.mount_manager.umount_lazy.side_effect = Exception with self._caplog.at_level(logging.WARNING): self.bind_root.cleanup() assert 'Image root directory root-dir not cleanly umounted:' in \ self._caplog.text assert 'Failed to remove directory hierarchy ' 'root-dir/mountpoint: rm' in self._caplog.text assert 'Failed to cleanup intermediate config files' in \ self._caplog.text @patch('kiwi.command.Command.run') @patch('kiwi.system.root_bind.Path.remove_hierarchy') @patch('kiwi.system.root_bind.Checksum') def test_cleanup_nothing_mounted(self, mock_Checksum, mock_remove_hierarchy, mock_command): self.mount_manager.is_mounted.return_value = False self.mount_manager.mountpoint = '/mountpoint' with self._caplog.at_level(logging.WARNING): self.bind_root.cleanup() assert 'Path /mountpoint not a mountpoint' in self._caplog.text