The support for delta_root allows to build a delta container image from a given base container. Due to the refactoring of the kiwi code base using context managers no explicit deletion of instances happens anymore. This uncovered a weakness of the delta root code at the level of the overlay mount. At the time of the umount there are still active temporary mount handlers which keeps the mountpoint busy. In order to fix this properly also the PackageManager factory is now a context manager and the Repository factory received a cleanup method which is called when the PackageManager goes out of scope. This refactoring also fixes the busy state when building deltas
446 lines
17 KiB
Python
446 lines
17 KiB
Python
from pytest import raises
|
|
from unittest.mock import (
|
|
patch, call, MagicMock, Mock
|
|
)
|
|
import io
|
|
import os
|
|
|
|
from kiwi.defaults import Defaults
|
|
from kiwi.repository.zypper import RepositoryZypper
|
|
from kiwi.exceptions import KiwiCommandError
|
|
|
|
|
|
class TestRepositoryZypper:
|
|
@patch('kiwi.command.Command.run')
|
|
@patch('kiwi.repository.zypper.Temporary.unmanaged_file')
|
|
def setup(self, mock_temp, mock_command):
|
|
self.context_manager_mock = Mock()
|
|
self.file_mock = Mock()
|
|
self.enter_mock = Mock()
|
|
self.exit_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)
|
|
|
|
tmpfile = Mock()
|
|
tmpfile.name = 'tmpfile'
|
|
mock_temp.return_value = tmpfile
|
|
self.root_bind = Mock()
|
|
self.root_bind.root_dir = '../data'
|
|
self.root_bind.shared_location = '/shared-dir'
|
|
with patch('builtins.open', create=True):
|
|
self.repo = RepositoryZypper(
|
|
self.root_bind, ['exclude_docs', '_install_langs%en_US:de_DE']
|
|
)
|
|
|
|
@patch('kiwi.command.Command.run')
|
|
@patch('kiwi.repository.zypper.Temporary.unmanaged_file')
|
|
def setup_method(self, cls, mock_temp, mock_command):
|
|
self.setup()
|
|
|
|
@patch('kiwi.command.Command.run')
|
|
@patch('kiwi.repository.zypper.Temporary.unmanaged_file')
|
|
def test_custom_args_init_excludedocs(self, mock_temp, mock_command):
|
|
with patch('builtins.open', create=True):
|
|
repo = RepositoryZypper(self.root_bind)
|
|
assert repo.custom_args == []
|
|
|
|
@patch('kiwi.command.Command.run')
|
|
@patch('kiwi.repository.zypper.Temporary.unmanaged_file')
|
|
@patch('kiwi.repository.zypper.ConfigParser')
|
|
def test_custom_args_init_check_signatures(
|
|
self, mock_config, mock_temp, mock_command
|
|
):
|
|
Defaults.set_platform_name('x86_64')
|
|
runtime_zypp_config = Mock()
|
|
mock_config.return_value = runtime_zypp_config
|
|
with patch('builtins.open', create=True):
|
|
repo = RepositoryZypper(
|
|
self.root_bind, ['check_signatures', '_target_arch%x86_64']
|
|
)
|
|
assert repo.custom_args == []
|
|
assert runtime_zypp_config.set.call_args_list == [
|
|
call(
|
|
'main',
|
|
'credentials.global.dir',
|
|
'../data/shared-dir/zypper/credentials'
|
|
),
|
|
call('main', 'arch', 'x86_64'),
|
|
call('main', 'gpgcheck', '1'),
|
|
]
|
|
|
|
def test_use_default_location(self):
|
|
self.repo.use_default_location()
|
|
assert self.repo.zypper_args == [
|
|
'--non-interactive'
|
|
]
|
|
assert self.repo.shared_zypper_dir['reposd-dir'] == \
|
|
'../data/etc/zypp/repos.d'
|
|
assert self.repo.command_env == dict(
|
|
os.environ, LANG='C'
|
|
)
|
|
|
|
def test_runtime_config(self):
|
|
assert self.repo.runtime_config()['zypper_args'] == \
|
|
self.repo.zypper_args
|
|
assert self.repo.runtime_config()['command_env'] == \
|
|
self.repo.command_env
|
|
|
|
@patch.object(RepositoryZypper, '_backup_package_cache')
|
|
@patch.object(RepositoryZypper, '_restore_package_cache')
|
|
@patch('kiwi.command.Command.run')
|
|
@patch('kiwi.repository.zypper.Path')
|
|
@patch('kiwi.repository.zypper.Uri')
|
|
@patch('os.path.exists')
|
|
def test_add_repo_second_attempt_on_failure(
|
|
self, mock_exists, mock_uri, mock_path, mock_command,
|
|
mock_restore_package_cache, mock_backup_package_cache
|
|
):
|
|
mock_command.side_effect = KiwiCommandError('error')
|
|
with raises(KiwiCommandError):
|
|
self.repo.add_repo('foo', 'http://foo/uri', 'rpm-md', 42)
|
|
assert mock_command.call_count == 2
|
|
|
|
@patch('kiwi.repository.zypper.ConfigParser')
|
|
@patch('kiwi.command.Command.run')
|
|
@patch('kiwi.repository.zypper.Path.wipe')
|
|
@patch('os.path.exists')
|
|
@patch('kiwi.repository.zypper.Uri')
|
|
def test_add_repo(
|
|
self, mock_uri, mock_exists, mock_wipe,
|
|
mock_command, mock_config
|
|
):
|
|
repo_config = Mock()
|
|
mock_config.return_value = repo_config
|
|
mock_exists.return_value = True
|
|
with patch('builtins.open', create=True) as mock_open:
|
|
self.repo.add_repo(
|
|
'foo', 'kiwi_iso_mount/uri', 'rpm-md', 42,
|
|
customization_script='custom_script'
|
|
)
|
|
mock_wipe.assert_called_once_with(
|
|
'../data/shared-dir/zypper/repos/foo.repo'
|
|
)
|
|
uri = Mock()
|
|
uri.is_remote.return_value = True
|
|
mock_uri.return_value = uri
|
|
assert mock_command.call_args_list == [
|
|
call([
|
|
'mv', '-f',
|
|
'/shared-dir/packages', '/shared-dir/packages.moved'
|
|
]),
|
|
call(
|
|
['zypper'] + self.repo.zypper_args + [
|
|
'--root', '../data',
|
|
'addrepo', '--refresh',
|
|
'--keep-packages',
|
|
'--no-check',
|
|
'kiwi_iso_mount/uri',
|
|
'foo'
|
|
], self.repo.command_env
|
|
),
|
|
call([
|
|
'bash', '--norc', 'custom_script',
|
|
'../data/shared-dir/zypper/repos/foo.repo'
|
|
]),
|
|
call([
|
|
'mv', '-f',
|
|
'/shared-dir/packages.moved', '/shared-dir/packages'
|
|
])
|
|
]
|
|
repo_config.read.assert_called_once_with(
|
|
'../data/shared-dir/zypper/repos/foo.repo'
|
|
)
|
|
assert repo_config.set.call_args_list == [
|
|
call('foo', 'repo_gpgcheck', '0'),
|
|
call('foo', 'pkg_gpgcheck', '0'),
|
|
call('foo', 'priority', '42')
|
|
]
|
|
mock_open.assert_called_once_with(
|
|
'../data/shared-dir/zypper/repos/foo.repo', 'w'
|
|
)
|
|
|
|
mock_command.reset_mock()
|
|
uri.is_remote.return_value = False
|
|
with patch('builtins.open', create=True):
|
|
self.repo.add_repo('foo', 'http://foo/uri', 'rpm-md', 42)
|
|
assert mock_command.call_args_list == [
|
|
call([
|
|
'mv', '-f',
|
|
'/shared-dir/packages', '/shared-dir/packages.moved'
|
|
]),
|
|
call(
|
|
['zypper'] + self.repo.zypper_args + [
|
|
'--root', '../data',
|
|
'addrepo', '--refresh',
|
|
'--no-keep-packages',
|
|
'--no-check',
|
|
'http://foo/uri',
|
|
'foo'
|
|
], self.repo.command_env
|
|
),
|
|
call([
|
|
'mv', '-f',
|
|
'/shared-dir/packages.moved', '/shared-dir/packages'
|
|
])
|
|
]
|
|
|
|
@patch('kiwi.repository.zypper.ConfigParser')
|
|
@patch('kiwi.command.Command.run')
|
|
@patch('kiwi.repository.zypper.Path.wipe')
|
|
@patch('os.path.exists')
|
|
@patch('kiwi.repository.zypper.Uri')
|
|
def test_add_repo_with_credentials_in_uri(
|
|
self, mock_uri, mock_exists, mock_wipe, mock_command, mock_config
|
|
):
|
|
repo_config = Mock()
|
|
mock_config.return_value = repo_config
|
|
mock_exists.return_value = True
|
|
with patch('builtins.open', create=True) as mock_open:
|
|
self.repo.add_repo(
|
|
'foo',
|
|
'https://some_user%40domain.com:my_token123@example.com/foo',
|
|
'rpm-md'
|
|
)
|
|
assert repo_config.set.call_args_list == [
|
|
call(
|
|
'foo',
|
|
'baseurl',
|
|
'https://some_user%40domain.com:my_token123@example.com/foo'
|
|
),
|
|
call('foo', 'repo_gpgcheck', '0'),
|
|
call('foo', 'pkg_gpgcheck', '0')
|
|
]
|
|
mock_open.assert_called_once_with(
|
|
'../data/shared-dir/zypper/repos/foo.repo', 'w'
|
|
)
|
|
|
|
@patch('kiwi.repository.zypper.ConfigParser')
|
|
@patch('kiwi.command.Command.run')
|
|
@patch('kiwi.repository.zypper.Path.wipe')
|
|
@patch('os.path.exists')
|
|
@patch('kiwi.repository.zypper.Uri')
|
|
def test_add_repo_with_gpgchecks(
|
|
self, mock_uri, mock_exists, mock_wipe,
|
|
mock_command, mock_config
|
|
):
|
|
repo_config = Mock()
|
|
mock_config.return_value = repo_config
|
|
mock_exists.return_value = True
|
|
with patch('builtins.open', create=True) as mock_open:
|
|
self.repo.add_repo(
|
|
'foo', 'kiwi_iso_mount/uri', 'rpm-md', 42,
|
|
repo_gpgcheck=False, pkg_gpgcheck=True
|
|
)
|
|
mock_wipe.assert_called_once_with(
|
|
'../data/shared-dir/zypper/repos/foo.repo'
|
|
)
|
|
assert mock_command.call_args_list == [
|
|
call([
|
|
'mv', '-f',
|
|
'/shared-dir/packages', '/shared-dir/packages.moved'
|
|
]),
|
|
call(
|
|
['zypper'] + self.repo.zypper_args + [
|
|
'--root', '../data',
|
|
'addrepo', '--refresh',
|
|
'--keep-packages',
|
|
'--no-check',
|
|
'kiwi_iso_mount/uri',
|
|
'foo'
|
|
], self.repo.command_env
|
|
),
|
|
call([
|
|
'mv', '-f',
|
|
'/shared-dir/packages.moved', '/shared-dir/packages'
|
|
])
|
|
]
|
|
repo_config.read.assert_called_once_with(
|
|
'../data/shared-dir/zypper/repos/foo.repo'
|
|
)
|
|
assert repo_config.set.call_args_list == [
|
|
call('foo', 'repo_gpgcheck', '0'),
|
|
call('foo', 'pkg_gpgcheck', '1'),
|
|
call('foo', 'priority', '42')
|
|
]
|
|
mock_open.assert_called_once_with(
|
|
'../data/shared-dir/zypper/repos/foo.repo', 'w'
|
|
)
|
|
|
|
@patch('kiwi.repository.zypper.RpmDataBase')
|
|
@patch('kiwi.command.Command.run')
|
|
@patch('kiwi.repository.zypper.Path.create')
|
|
def test_setup_package_database_configuration(
|
|
self, mock_Path_create, mock_Command_run, mock_RpmDataBase
|
|
):
|
|
rpmdb = Mock()
|
|
rpmdb.has_rpm.return_value = False
|
|
rpmdb.rpmdb_host.expand_query.return_value = '/usr/lib/sysimage/rpm'
|
|
mock_RpmDataBase.return_value = rpmdb
|
|
self.repo.setup_package_database_configuration()
|
|
assert mock_RpmDataBase.call_args_list == [
|
|
call('../data', 'macros.kiwi-image-config'),
|
|
call('../data')
|
|
]
|
|
rpmdb.set_macro_from_string.assert_called_once_with(
|
|
'_install_langs%en_US:de_DE'
|
|
)
|
|
rpmdb.write_config.assert_called_once_with()
|
|
rpmdb.set_database_to_host_path.assert_called_once_with()
|
|
rpmdb.init_database.assert_called_once_with()
|
|
mock_Path_create.assert_called_once_with('../data/var/lib')
|
|
mock_Command_run.assert_called_once_with(
|
|
[
|
|
'ln', '-s', '--no-target-directory',
|
|
'../../usr/lib/sysimage/rpm', '../data/var/lib/rpm'
|
|
], raise_on_error=False
|
|
)
|
|
mock_Command_run.reset_mock()
|
|
rpmdb.rpmdb_host.expand_query.return_value = '/var/lib/rpm'
|
|
self.repo.setup_package_database_configuration()
|
|
assert not mock_Command_run.called
|
|
|
|
@patch('kiwi.repository.zypper.RpmDataBase')
|
|
@patch('kiwi.command.Command.run')
|
|
@patch('kiwi.repository.zypper.Path.create')
|
|
def test_setup_package_database_configuration_bootstrapped_system(
|
|
self, mock_Path_create, mock_Command_run, mock_RpmDataBase
|
|
):
|
|
rpmdb = Mock()
|
|
rpmdb.has_rpm.return_value = True
|
|
rpmdb.rpmdb_host.expand_query.return_value = '/usr/lib/sysimage/rpm'
|
|
mock_RpmDataBase.return_value = rpmdb
|
|
self.repo.setup_package_database_configuration()
|
|
assert mock_RpmDataBase.call_args_list == [
|
|
call('../data', 'macros.kiwi-image-config'),
|
|
call('../data')
|
|
]
|
|
rpmdb.set_macro_from_string.assert_called_once_with(
|
|
'_install_langs%en_US:de_DE'
|
|
)
|
|
rpmdb.write_config.assert_called_once_with()
|
|
rpmdb.link_database_to_host_path.assert_called_once_with()
|
|
rpmdb.init_database.assert_called_once_with()
|
|
mock_Path_create.assert_called_once_with('../data/var/lib')
|
|
mock_Command_run.assert_called_once_with(
|
|
[
|
|
'ln', '-s', '--no-target-directory',
|
|
'../../usr/lib/sysimage/rpm', '../data/var/lib/rpm'
|
|
], raise_on_error=False
|
|
)
|
|
|
|
@patch('kiwi.repository.zypper.RpmDataBase')
|
|
def test_import_trusted_keys(self, mock_RpmDataBase):
|
|
rpmdb = Mock()
|
|
mock_RpmDataBase.return_value = rpmdb
|
|
signing_keys = ['key-file-a.asc', 'key-file-b.asc']
|
|
self.repo.import_trusted_keys(signing_keys)
|
|
assert rpmdb.import_signing_key_to_image.call_args_list == [
|
|
call('key-file-a.asc'),
|
|
call('key-file-b.asc')
|
|
]
|
|
|
|
@patch('kiwi.repository.zypper.ConfigParser')
|
|
@patch('kiwi.command.Command.run')
|
|
@patch('kiwi.repository.zypper.Path.wipe')
|
|
@patch('os.path.exists')
|
|
@patch('kiwi.repository.zypper.Uri')
|
|
def test_add_repo_with_credentials(
|
|
self, mock_uri, mock_exists, mock_wipe, mock_command,
|
|
mock_config
|
|
):
|
|
exists_results = [False, False, False, True]
|
|
|
|
def side_effect(arg):
|
|
return exists_results.pop()
|
|
|
|
mock_exists.side_effect = side_effect
|
|
|
|
with patch('builtins.open', create=True) as mock_open:
|
|
mock_open.return_value = MagicMock(spec=io.IOBase)
|
|
file_handle = mock_open.return_value.__enter__.return_value
|
|
self.repo.add_repo(
|
|
name='foo', uri='http://some/repo',
|
|
user='user', secret='secret', credentials_file='credentials_file'
|
|
)
|
|
mock_wipe.assert_called_once_with(
|
|
'../data/shared-dir/zypper/credentials/credentials_file'
|
|
)
|
|
assert mock_open.call_args_list == [
|
|
call('../data/shared-dir/zypper/credentials/credentials_file', 'w'),
|
|
call('../data/shared-dir/zypper/repos/foo.repo', 'w')
|
|
]
|
|
assert file_handle.write.call_args_list == [
|
|
call('username=user\n'),
|
|
call('password=secret\n')
|
|
]
|
|
mock_command.assert_called_once_with(
|
|
['zypper'] + self.repo.zypper_args + [
|
|
'--root', '../data',
|
|
'addrepo', '--refresh',
|
|
'--keep-packages',
|
|
'--no-check',
|
|
'http://some/repo?credentials=credentials_file',
|
|
'foo'
|
|
], self.repo.command_env
|
|
)
|
|
|
|
@patch('kiwi.command.Command.run')
|
|
def test_delete_repo(self, mock_command):
|
|
self.repo.delete_repo('foo')
|
|
mock_command.assert_called_once_with(
|
|
['zypper'] + self.repo.zypper_args + [
|
|
'--root', '../data', 'removerepo', 'foo'
|
|
], self.repo.command_env
|
|
)
|
|
|
|
@patch('kiwi.path.Path.wipe')
|
|
@patch('os.walk')
|
|
def test_cleanup_unused_repos(self, mock_walk, mock_path):
|
|
mock_walk.return_value = [
|
|
('/foo', ('bar', 'baz'), ('spam', 'eggs'))
|
|
]
|
|
self.repo.repo_names = ['eggs']
|
|
self.repo.cleanup_unused_repos()
|
|
assert mock_path.call_args_list == [
|
|
call('../data/shared-dir/zypper/solv/@System'),
|
|
call('../data/shared-dir/zypper/repos/spam')
|
|
]
|
|
|
|
@patch('kiwi.path.Path.wipe')
|
|
@patch('kiwi.path.Path.create')
|
|
def test_delete_all_repos(self, mock_create, mock_wipe):
|
|
self.repo.delete_all_repos()
|
|
mock_wipe.assert_called_once_with('../data/shared-dir/zypper/repos')
|
|
mock_create.assert_called_once_with('../data/shared-dir/zypper/repos')
|
|
|
|
@patch('kiwi.path.Path.wipe')
|
|
def test_delete_repo_cache(self, mock_wipe):
|
|
self.repo.delete_repo_cache('foo')
|
|
assert mock_wipe.call_args_list == [
|
|
call('../data/shared-dir/packages/foo'),
|
|
call('../data/shared-dir/zypper/solv/foo'),
|
|
call('../data/shared-dir/zypper/raw/foo')
|
|
]
|
|
|
|
@patch('os.path.isfile')
|
|
@patch('os.unlink')
|
|
def test_cleanup(self, mock_os_unlink, mock_os_path_isfile):
|
|
self.repo.cleanup()
|
|
assert mock_os_unlink.call_args_list == [
|
|
call('tmpfile'), call('tmpfile')
|
|
]
|
|
|
|
@patch('kiwi.command.Command.run')
|
|
@patch('os.path.exists')
|
|
def test_context_manager_exit(
|
|
self, mock_os_path_exists, mock_command_run
|
|
):
|
|
mock_os_path_exists.return_value = True
|
|
with patch('builtins.open', create=True):
|
|
with RepositoryZypper(self.root_bind):
|
|
pass
|
|
mock_command_run.assert_called_once_with(
|
|
['mv', '-f', '/shared-dir/packages.moved', '/shared-dir/packages']
|
|
)
|