Esempio n. 1
0
class ContainerBuilder(object):
    """
    Container image builder

    Attributes

    * :attr:`root_dir`
        root directory path name

    * :attr:`target_dir`
        target directory path name

    * :attr:`container_config`
        Instance of xml_parse::containerconfig

    * :attr:`requested_container_type`
        Configured container type

    * :attr:`system_setup`
        Instance of SystemSetup

    * :attr:`filename`
        File name of the container image

    * :attr:`result`
        Instance of Result
    """
    def __init__(self, xml_state, target_dir, root_dir):
        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

        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_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
Esempio n. 2
0
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.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('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()

        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('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')
    @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)
Esempio n. 3
0
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
        )
Esempio n. 4
0
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.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,
                                    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)

    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(), 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):
        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.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 _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
Esempio n. 5
0
class FileSystemBuilder(object):
    """
    Filesystem image builder

    Attributes

    * :attr:`label`
        filesystem label

    * :attr:`root_dir`
        root directory path name

    * :attr:`target_dir`
        target directory path name

    * :attr:`requested_image_type`
        Configured image type

    * :attr:`requested_filesystem`
        Requested filesystem name

    * :attr:`system_setup`
        Instance of SystemSetup

    * :attr:`filename`
        File name of the filesystem image

    * :attr:`blocksize`
        Configured disk blocksize

    * :attr:`filesystem_setup`
        Instance of FileSystemSetup

    * :attr:`filesystems_no_device_node`
        List of filesystems which are created from a data tree
        and do not require a block device e.g loop

    * :attr:`filesystem_custom_parameters`
        Configured custom filesystem mount and creation arguments

    * :attr:`result`
        Instance of 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)

    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"
        """
        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.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_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 _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
        )
Esempio n. 6
0
class PxeBuilder(object):
    """
    Filesystem based PXE image builder.

    Attributes

    * :attr:`xml_state`
        Instance of XMLState

    * :attr:`target_dir`
        Target directory path name

    * :attr:`root_dir`
        System image root directory

    * :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.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)

    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.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
        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
Esempio n. 7
0
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

    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.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_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 _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
        ])
Esempio n. 8
0
File: live.py Progetto: pyzh/kiwi
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.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.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,
                                         custom_args=custom_args)
        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,
                exclude=Defaults.get_exclude_list_for_root_data_sync())
            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.root_dir)
            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.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 %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)