mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-16 07:19:57 +00:00
fix: careful handling of file permissions during initialization
It has been found that during cold start (e.g. in docker container),
some permissions are invalid. In order to handle that, some operations
are not guarded with RepositoryPaths.preserve_root guard
In addition, it has been also found that in some cases (e.g. web server
start) migrations are performed on empty repository identifier which may
lead to wrong data (see also 435375721d
),
as well as some unexpected results during database operations. In order
to handle that, now all watcher instances have their own databases (and
configurations)
This commit is contained in:
@ -58,9 +58,11 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
|
||||
sudo_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_sudo")
|
||||
executable_mock = mocker.patch("ahriman.application.handlers.setup.Setup.executable_create")
|
||||
init_mock = mocker.patch("ahriman.core.alpm.repo.Repo.init")
|
||||
owner_guard_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Setup.run(args, repository_id, configuration, report=False)
|
||||
owner_guard_mock.assert_called_once_with()
|
||||
ahriman_configuration_mock.assert_called_once_with(args, repository_id, configuration)
|
||||
devtools_configuration_mock.assert_called_once_with(
|
||||
repository_id, args.from_configuration, args.mirror, args.multilib, f"file://{repository_paths.repository}")
|
||||
@ -268,13 +270,11 @@ def test_executable_create(configuration: Configuration, repository_paths: Repos
|
||||
"""
|
||||
must create executable
|
||||
"""
|
||||
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
|
||||
symlink_mock = mocker.patch("pathlib.Path.symlink_to")
|
||||
unlink_mock = mocker.patch("pathlib.Path.unlink")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Setup.executable_create(repository_paths, repository_id)
|
||||
chown_mock.assert_called_once_with(Setup.build_command(repository_paths.root, repository_id))
|
||||
symlink_mock.assert_called_once_with(Setup.ARCHBUILD_COMMAND_PATH)
|
||||
unlink_mock.assert_called_once_with(missing_ok=True)
|
||||
|
||||
|
@ -62,12 +62,12 @@ def test_database_copy(pacman: Pacman, mocker: MockerFixture) -> None:
|
||||
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: p.is_relative_to(path))
|
||||
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
|
||||
copy_mock = mocker.patch("shutil.copy")
|
||||
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
|
||||
owner_guard_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
||||
|
||||
pacman.database_copy(pacman.handle, database, path, use_ahriman_cache=True)
|
||||
mkdir_mock.assert_called_once_with(mode=0o755, exist_ok=True)
|
||||
copy_mock.assert_called_once_with(path / "sync" / "core.db", dst_path)
|
||||
chown_mock.assert_called_once_with(dst_path)
|
||||
owner_guard_mock.assert_called_once_with(dst_path.parent)
|
||||
|
||||
|
||||
def test_database_copy_skip(pacman: Pacman, mocker: MockerFixture) -> None:
|
||||
|
@ -6,7 +6,6 @@ from unittest.mock import call as MockCall
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database.migrations.m011_repository_name import migrate_data, migrate_package_repository, steps
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
def test_migration_repository_name() -> None:
|
||||
@ -38,13 +37,3 @@ def test_migrate_package_repository(connection: Connection, configuration: Confi
|
||||
MockCall(pytest.helpers.anyvar(str, strict=True), {"repository": configuration.repository_id.id}),
|
||||
MockCall(pytest.helpers.anyvar(str, strict=True), {"repository": configuration.repository_id.id}),
|
||||
])
|
||||
|
||||
|
||||
def test_migrate_package_repository_empty_id(connection: Connection, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must do nothing on empty repository id
|
||||
"""
|
||||
mocker.patch("ahriman.core.configuration.Configuration.check_loaded", return_value=("", RepositoryId("", "")))
|
||||
migrate_package_repository(connection, configuration)
|
||||
connection.execute.assert_not_called()
|
||||
|
@ -36,6 +36,17 @@ def test_init_skip_migration(database: SQLite, mocker: MockerFixture) -> None:
|
||||
migrate_schema_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_init_skip_empty_repository(database: SQLite, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip migrations if repository identifier is not set
|
||||
"""
|
||||
database._repository_id = RepositoryId("", "")
|
||||
migrate_schema_mock = mocker.patch("ahriman.core.database.migrations.Migrations.migrate")
|
||||
|
||||
database.init()
|
||||
migrate_schema_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_package_clear(database: SQLite, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clear package data
|
||||
|
@ -198,15 +198,6 @@ def test_owner(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None
|
||||
assert RepositoryPaths.owner(repository_paths.root) == (42, 142)
|
||||
|
||||
|
||||
def test_cache_for(repository_paths: RepositoryPaths, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must return correct path for cache directory
|
||||
"""
|
||||
path = repository_paths.cache_for(package_ahriman.base)
|
||||
assert path.name == package_ahriman.base
|
||||
assert path.parent == repository_paths.cache
|
||||
|
||||
|
||||
def test_chown(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must correctly set owner for the directory
|
||||
@ -216,7 +207,7 @@ def test_chown(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None
|
||||
chown_mock = mocker.patch("os.chown")
|
||||
|
||||
path = repository_paths.root / "path"
|
||||
repository_paths.chown(path)
|
||||
repository_paths._chown(path)
|
||||
chown_mock.assert_called_once_with(path, 42, 42, follow_symlinks=False)
|
||||
|
||||
|
||||
@ -229,7 +220,7 @@ def test_chown_parent(repository_paths: RepositoryPaths, mocker: MockerFixture)
|
||||
chown_mock = mocker.patch("os.chown")
|
||||
|
||||
path = repository_paths.root / "parent" / "path"
|
||||
repository_paths.chown(path)
|
||||
repository_paths._chown(path)
|
||||
chown_mock.assert_has_calls([
|
||||
MockCall(path, 42, 42, follow_symlinks=False),
|
||||
MockCall(path.parent, 42, 42, follow_symlinks=False)
|
||||
@ -245,7 +236,7 @@ def test_chown_skip(repository_paths: RepositoryPaths, mocker: MockerFixture) ->
|
||||
chown_mock = mocker.patch("os.chown")
|
||||
|
||||
path = repository_paths.root / "path"
|
||||
repository_paths.chown(path)
|
||||
repository_paths._chown(path)
|
||||
chown_mock.assert_not_called()
|
||||
|
||||
|
||||
@ -254,7 +245,46 @@ def test_chown_invalid_path(repository_paths: RepositoryPaths) -> None:
|
||||
must raise invalid path exception in case if directory outside the root supplied
|
||||
"""
|
||||
with pytest.raises(PathError):
|
||||
repository_paths.chown(repository_paths.root.parent)
|
||||
repository_paths._chown(repository_paths.root.parent)
|
||||
|
||||
|
||||
def test_cache_for(repository_paths: RepositoryPaths, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must return correct path for cache directory
|
||||
"""
|
||||
path = repository_paths.cache_for(package_ahriman.base)
|
||||
assert path.name == package_ahriman.base
|
||||
assert path.parent == repository_paths.cache
|
||||
|
||||
|
||||
def test_preserve_owner(tmp_path: Path, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must preserve file owner during operations
|
||||
"""
|
||||
repository_paths = RepositoryPaths(tmp_path, repository_id)
|
||||
repository_paths.tree_create()
|
||||
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths._chown")
|
||||
|
||||
with repository_paths.preserve_owner():
|
||||
(repository_paths.root / "created1").touch()
|
||||
(repository_paths.chroot / "created2").touch()
|
||||
chown_mock.assert_has_calls([MockCall(repository_paths.root / "created1")])
|
||||
|
||||
|
||||
def test_preserve_owner_specific(tmp_path: Path, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must preserve file owner during operations only in specific directory
|
||||
"""
|
||||
repository_paths = RepositoryPaths(tmp_path, repository_id)
|
||||
repository_paths.tree_create()
|
||||
(repository_paths.root / "content").mkdir()
|
||||
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths._chown")
|
||||
|
||||
with repository_paths.preserve_owner(repository_paths.root / "content"):
|
||||
(repository_paths.root / "created1").touch()
|
||||
(repository_paths.root / "content" / "created2").touch()
|
||||
(repository_paths.chroot / "created3").touch()
|
||||
chown_mock.assert_has_calls([MockCall(repository_paths.root / "content" / "created2")])
|
||||
|
||||
|
||||
def test_tree_clear(repository_paths: RepositoryPaths, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
@ -293,11 +323,11 @@ def test_tree_create(repository_paths: RepositoryPaths, mocker: MockerFixture) -
|
||||
and not callable(getattr(repository_paths, prop))
|
||||
}
|
||||
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
|
||||
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
|
||||
owner_guard_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
||||
|
||||
repository_paths.tree_create()
|
||||
mkdir_mock.assert_has_calls([MockCall(mode=0o755, parents=True, exist_ok=True) for _ in paths], any_order=True)
|
||||
chown_mock.assert_has_calls([MockCall(pytest.helpers.anyvar(int)) for _ in paths], any_order=True)
|
||||
owner_guard_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_tree_create_skip(mocker: MockerFixture) -> None:
|
||||
|
Reference in New Issue
Block a user