mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 15:27:17 +00:00
fully lazy handle load
In case of immediate handle load it would try to sync databases (or at least to create database files), which is not possible in case if command is run as non-ahriman user. This commit makes handle load lazy and allows to run some commands as non-ahriman user
This commit is contained in:
parent
541d8d9b39
commit
5bbd1ad9a9
@ -71,6 +71,8 @@ class Setup(Handler):
|
|||||||
Setup.configuration_create_sudo(application.repository.paths, args.build_command, architecture)
|
Setup.configuration_create_sudo(application.repository.paths, args.build_command, architecture)
|
||||||
|
|
||||||
application.repository.repo.init()
|
application.repository.repo.init()
|
||||||
|
# lazy database sync
|
||||||
|
application.repository.pacman.handle # pylint: disable=pointless-statement
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def build_command(root: Path, prefix: str, architecture: str) -> Path:
|
def build_command(root: Path, prefix: str, architecture: str) -> Path:
|
||||||
@ -78,7 +80,7 @@ class Setup(Handler):
|
|||||||
generate build command name
|
generate build command name
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
root(Path): root directory for the build command (must be root of the reporitory)
|
root(Path): root directory for the build command (must be root of the repository)
|
||||||
prefix(str): command prefix in {prefix}-{architecture}-build
|
prefix(str): command prefix in {prefix}-{architecture}-build
|
||||||
architecture(str): repository architecture
|
architecture(str): repository architecture
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import shutil
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pyalpm import DB, Handle, Package, SIG_PACKAGE, error as PyalpmError # type: ignore
|
from pyalpm import DB, Handle, Package, SIG_PACKAGE, error as PyalpmError # type: ignore
|
||||||
from typing import Generator, Set
|
from typing import Any, Callable, Generator, Set
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.log import LazyLogging
|
from ahriman.core.log import LazyLogging
|
||||||
@ -36,6 +36,8 @@ class Pacman(LazyLogging):
|
|||||||
handle(Handle): pyalpm root ``Handle``
|
handle(Handle): pyalpm root ``Handle``
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
handle: Handle
|
||||||
|
|
||||||
def __init__(self, architecture: str, configuration: Configuration, *, refresh_database: int) -> None:
|
def __init__(self, architecture: str, configuration: Configuration, *, refresh_database: int) -> None:
|
||||||
"""
|
"""
|
||||||
default constructor
|
default constructor
|
||||||
@ -46,6 +48,22 @@ class Pacman(LazyLogging):
|
|||||||
refresh_database(int): synchronize local cache to remote. If set to ``0``, no syncronization will be
|
refresh_database(int): synchronize local cache to remote. If set to ``0``, no syncronization will be
|
||||||
enabled, if set to ``1`` - normal syncronization, if set to ``2`` - force syncronization
|
enabled, if set to ``1`` - normal syncronization, if set to ``2`` - force syncronization
|
||||||
"""
|
"""
|
||||||
|
self.__create_handle_fn: Callable[[], Handle] = lambda: self.__create_handle(
|
||||||
|
architecture, configuration, refresh_database=refresh_database)
|
||||||
|
|
||||||
|
def __create_handle(self, architecture: str, configuration: Configuration, *, refresh_database: int) -> Handle:
|
||||||
|
"""
|
||||||
|
create lazy handle function
|
||||||
|
|
||||||
|
Args:
|
||||||
|
architecture(str): repository architecture
|
||||||
|
configuration(Configuration): configuration instance
|
||||||
|
refresh_database(int): synchronize local cache to remote. If set to ``0``, no syncronization will be
|
||||||
|
enabled, if set to ``1`` - normal syncronization, if set to ``2`` - force syncronization
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Handle: fully initialized pacman handle
|
||||||
|
"""
|
||||||
root = configuration.getpath("alpm", "root")
|
root = configuration.getpath("alpm", "root")
|
||||||
pacman_root = configuration.getpath("alpm", "database")
|
pacman_root = configuration.getpath("alpm", "database")
|
||||||
use_ahriman_cache = configuration.getboolean("alpm", "use_ahriman_cache")
|
use_ahriman_cache = configuration.getboolean("alpm", "use_ahriman_cache")
|
||||||
@ -53,20 +71,42 @@ class Pacman(LazyLogging):
|
|||||||
paths = configuration.repository_paths
|
paths = configuration.repository_paths
|
||||||
database_path = paths.pacman if use_ahriman_cache else pacman_root
|
database_path = paths.pacman if use_ahriman_cache else pacman_root
|
||||||
|
|
||||||
self.handle = Handle(str(root), str(database_path))
|
handle = Handle(str(root), str(database_path))
|
||||||
for repository in configuration.getlist("alpm", "repositories"):
|
for repository in configuration.getlist("alpm", "repositories"):
|
||||||
database = self.database_init(repository, mirror, architecture)
|
database = self.database_init(handle, repository, mirror, architecture)
|
||||||
self.database_copy(database, pacman_root, paths, use_ahriman_cache=use_ahriman_cache)
|
self.database_copy(handle, database, pacman_root, paths, use_ahriman_cache=use_ahriman_cache)
|
||||||
|
|
||||||
if use_ahriman_cache and refresh_database:
|
if use_ahriman_cache and refresh_database:
|
||||||
self.database_sync(refresh_database > 1)
|
self.database_sync(handle, force=refresh_database > 1)
|
||||||
|
|
||||||
def database_copy(self, database: DB, pacman_root: Path, paths: RepositoryPaths, *,
|
return handle
|
||||||
|
|
||||||
|
def __getattr__(self, item: str) -> Any:
|
||||||
|
"""
|
||||||
|
pacman handle extractor
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item(str): property name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: attribute by its name
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AttributeError: in case if no such attribute found
|
||||||
|
"""
|
||||||
|
if item == "handle":
|
||||||
|
handle = self.__create_handle_fn()
|
||||||
|
setattr(self, item, handle)
|
||||||
|
return handle
|
||||||
|
return super().__getattr__(item) # required for logging attribute
|
||||||
|
|
||||||
|
def database_copy(self, handle: Handle, database: DB, pacman_root: Path, paths: RepositoryPaths, *,
|
||||||
use_ahriman_cache: bool) -> None:
|
use_ahriman_cache: bool) -> None:
|
||||||
"""
|
"""
|
||||||
copy database from the operating system root to the ahriman local home
|
copy database from the operating system root to the ahriman local home
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
handle(Handle): pacman handle which will be used for database copying
|
||||||
database(DB): pacman database instance to be copied
|
database(DB): pacman database instance to be copied
|
||||||
pacman_root(Path): operating system pacman root
|
pacman_root(Path): operating system pacman root
|
||||||
paths(RepositoryPaths): repository paths instance
|
paths(RepositoryPaths): repository paths instance
|
||||||
@ -78,7 +118,7 @@ class Pacman(LazyLogging):
|
|||||||
if not use_ahriman_cache:
|
if not use_ahriman_cache:
|
||||||
return
|
return
|
||||||
# copy root database if no local copy found
|
# copy root database if no local copy found
|
||||||
pacman_db_path = Path(self.handle.dbpath)
|
pacman_db_path = Path(handle.dbpath)
|
||||||
if not pacman_db_path.is_dir():
|
if not pacman_db_path.is_dir():
|
||||||
return # root directory does not exist yet
|
return # root directory does not exist yet
|
||||||
dst = repository_database(pacman_db_path)
|
dst = repository_database(pacman_db_path)
|
||||||
@ -92,11 +132,12 @@ class Pacman(LazyLogging):
|
|||||||
shutil.copy(src, dst)
|
shutil.copy(src, dst)
|
||||||
paths.chown(dst)
|
paths.chown(dst)
|
||||||
|
|
||||||
def database_init(self, repository: str, mirror: str, architecture: str) -> DB:
|
def database_init(self, handle: Handle, repository: str, mirror: str, architecture: str) -> DB:
|
||||||
"""
|
"""
|
||||||
create database instance from pacman handler and set its properties
|
create database instance from pacman handler and set its properties
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
handle(Handle): pacman handle which will be used for database initializing
|
||||||
repository(str): pacman repository name (e.g. core)
|
repository(str): pacman repository name (e.g. core)
|
||||||
mirror(str): arch linux mirror url
|
mirror(str): arch linux mirror url
|
||||||
architecture(str): repository architecture
|
architecture(str): repository architecture
|
||||||
@ -104,21 +145,23 @@ class Pacman(LazyLogging):
|
|||||||
Returns:
|
Returns:
|
||||||
DB: loaded pacman database instance
|
DB: loaded pacman database instance
|
||||||
"""
|
"""
|
||||||
database: DB = self.handle.register_syncdb(repository, SIG_PACKAGE)
|
self.logger.info("loading pacman databases")
|
||||||
|
database: DB = handle.register_syncdb(repository, SIG_PACKAGE)
|
||||||
# replace variables in mirror address
|
# replace variables in mirror address
|
||||||
database.servers = [mirror.replace("$repo", repository).replace("$arch", architecture)]
|
database.servers = [mirror.replace("$repo", repository).replace("$arch", architecture)]
|
||||||
return database
|
return database
|
||||||
|
|
||||||
def database_sync(self, force: bool) -> None:
|
def database_sync(self, handle: Handle, *, force: bool) -> None:
|
||||||
"""
|
"""
|
||||||
sync local database
|
sync local database
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
handle(Handle): pacman handle which will be used for database sync
|
||||||
force(bool): force database syncronization (same as ``pacman -Syy``)
|
force(bool): force database syncronization (same as ``pacman -Syy``)
|
||||||
"""
|
"""
|
||||||
self.logger.info("refresh ahriman's home pacman database (force refresh %s)", force)
|
self.logger.info("refresh ahriman's home pacman database (force refresh %s)", force)
|
||||||
transaction = self.handle.init_transaction()
|
transaction = handle.init_transaction()
|
||||||
for database in self.handle.get_syncdbs():
|
for database in handle.get_syncdbs():
|
||||||
try:
|
try:
|
||||||
database.update(force)
|
database.update(force)
|
||||||
except PyalpmError:
|
except PyalpmError:
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pyalpm import error as PyalpmError
|
from pyalpm import error as PyalpmError
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
@ -21,8 +23,9 @@ def test_init_with_local_cache(configuration: Configuration, mocker: MockerFixtu
|
|||||||
with TemporaryDirectory(ignore_cleanup_errors=True) as pacman_root:
|
with TemporaryDirectory(ignore_cleanup_errors=True) as pacman_root:
|
||||||
mocker.patch.object(RepositoryPaths, "pacman", Path(pacman_root))
|
mocker.patch.object(RepositoryPaths, "pacman", Path(pacman_root))
|
||||||
# during the creation pyalpm.Handle will create also version file which we would like to remove later
|
# during the creation pyalpm.Handle will create also version file which we would like to remove later
|
||||||
Pacman("x86_64", configuration, refresh_database=1)
|
pacman = Pacman("x86_64", configuration, refresh_database=1)
|
||||||
sync_mock.assert_called_once_with(False)
|
assert pacman.handle
|
||||||
|
sync_mock.assert_called_once_with(pytest.helpers.anyvar(int), force=False)
|
||||||
|
|
||||||
|
|
||||||
def test_init_with_local_cache_forced(configuration: Configuration, mocker: MockerFixture) -> None:
|
def test_init_with_local_cache_forced(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||||
@ -37,8 +40,9 @@ def test_init_with_local_cache_forced(configuration: Configuration, mocker: Mock
|
|||||||
with TemporaryDirectory(ignore_cleanup_errors=True) as pacman_root:
|
with TemporaryDirectory(ignore_cleanup_errors=True) as pacman_root:
|
||||||
mocker.patch.object(RepositoryPaths, "pacman", Path(pacman_root))
|
mocker.patch.object(RepositoryPaths, "pacman", Path(pacman_root))
|
||||||
# during the creation pyalpm.Handle will create also version file which we would like to remove later
|
# during the creation pyalpm.Handle will create also version file which we would like to remove later
|
||||||
Pacman("x86_64", configuration, refresh_database=2)
|
pacman = Pacman("x86_64", configuration, refresh_database=2)
|
||||||
sync_mock.assert_called_once_with(True)
|
assert pacman.handle
|
||||||
|
sync_mock.assert_called_once_with(pytest.helpers.anyvar(int), force=True)
|
||||||
|
|
||||||
|
|
||||||
def test_database_copy(pacman: Pacman, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
|
def test_database_copy(pacman: Pacman, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
|
||||||
@ -54,7 +58,7 @@ def test_database_copy(pacman: Pacman, repository_paths: RepositoryPaths, mocker
|
|||||||
copy_mock = mocker.patch("shutil.copy")
|
copy_mock = mocker.patch("shutil.copy")
|
||||||
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
|
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
|
||||||
|
|
||||||
pacman.database_copy(database, path, repository_paths, use_ahriman_cache=True)
|
pacman.database_copy(pacman.handle, database, path, repository_paths, use_ahriman_cache=True)
|
||||||
copy_mock.assert_called_once_with(path / "sync" / "core.db", dst_path)
|
copy_mock.assert_called_once_with(path / "sync" / "core.db", dst_path)
|
||||||
chown_mock.assert_called_once_with(dst_path)
|
chown_mock.assert_called_once_with(dst_path)
|
||||||
|
|
||||||
@ -70,7 +74,7 @@ def test_database_copy_skip(pacman: Pacman, repository_paths: RepositoryPaths, m
|
|||||||
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: True if p.is_relative_to(path) else False)
|
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: True if p.is_relative_to(path) else False)
|
||||||
copy_mock = mocker.patch("shutil.copy")
|
copy_mock = mocker.patch("shutil.copy")
|
||||||
|
|
||||||
pacman.database_copy(database, path, repository_paths, use_ahriman_cache=False)
|
pacman.database_copy(pacman.handle, database, path, repository_paths, use_ahriman_cache=False)
|
||||||
copy_mock.assert_not_called()
|
copy_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
@ -85,7 +89,7 @@ def test_database_copy_no_directory(pacman: Pacman, repository_paths: Repository
|
|||||||
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: True if p.is_relative_to(path) else False)
|
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: True if p.is_relative_to(path) else False)
|
||||||
copy_mock = mocker.patch("shutil.copy")
|
copy_mock = mocker.patch("shutil.copy")
|
||||||
|
|
||||||
pacman.database_copy(database, path, repository_paths, use_ahriman_cache=True)
|
pacman.database_copy(pacman.handle, database, path, repository_paths, use_ahriman_cache=True)
|
||||||
copy_mock.assert_not_called()
|
copy_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
@ -100,7 +104,7 @@ def test_database_copy_no_root_file(pacman: Pacman, repository_paths: Repository
|
|||||||
mocker.patch("pathlib.Path.is_file", return_value=False)
|
mocker.patch("pathlib.Path.is_file", return_value=False)
|
||||||
copy_mock = mocker.patch("shutil.copy")
|
copy_mock = mocker.patch("shutil.copy")
|
||||||
|
|
||||||
pacman.database_copy(database, path, repository_paths, use_ahriman_cache=True)
|
pacman.database_copy(pacman.handle, database, path, repository_paths, use_ahriman_cache=True)
|
||||||
copy_mock.assert_not_called()
|
copy_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
@ -114,7 +118,7 @@ def test_database_copy_database_exist(pacman: Pacman, repository_paths: Reposito
|
|||||||
mocker.patch("pathlib.Path.is_file", return_value=True)
|
mocker.patch("pathlib.Path.is_file", return_value=True)
|
||||||
copy_mock = mocker.patch("shutil.copy")
|
copy_mock = mocker.patch("shutil.copy")
|
||||||
|
|
||||||
pacman.database_copy(database, Path("root"), repository_paths, use_ahriman_cache=True)
|
pacman.database_copy(pacman.handle, database, Path("root"), repository_paths, use_ahriman_cache=True)
|
||||||
copy_mock.assert_not_called()
|
copy_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
@ -123,7 +127,7 @@ def test_database_init(pacman: Pacman, configuration: Configuration) -> None:
|
|||||||
must init database with settings
|
must init database with settings
|
||||||
"""
|
"""
|
||||||
mirror = configuration.get("alpm", "mirror")
|
mirror = configuration.get("alpm", "mirror")
|
||||||
database = pacman.database_init("test", mirror, "x86_64")
|
database = pacman.database_init(pacman.handle, "test", mirror, "x86_64")
|
||||||
assert len(database.servers) == 1
|
assert len(database.servers) == 1
|
||||||
|
|
||||||
|
|
||||||
@ -139,7 +143,7 @@ def test_database_sync(pacman: Pacman) -> None:
|
|||||||
handle_mock.init_transaction.return_value = transaction_mock
|
handle_mock.init_transaction.return_value = transaction_mock
|
||||||
pacman.handle = handle_mock
|
pacman.handle = handle_mock
|
||||||
|
|
||||||
pacman.database_sync(False)
|
pacman.database_sync(pacman.handle, force=False)
|
||||||
handle_mock.init_transaction.assert_called_once_with()
|
handle_mock.init_transaction.assert_called_once_with()
|
||||||
core_mock.update.assert_called_once_with(False)
|
core_mock.update.assert_called_once_with(False)
|
||||||
extra_mock.update.assert_called_once_with(False)
|
extra_mock.update.assert_called_once_with(False)
|
||||||
@ -157,7 +161,7 @@ def test_database_sync_failed(pacman: Pacman) -> None:
|
|||||||
handle_mock.get_syncdbs.return_value = [core_mock, extra_mock]
|
handle_mock.get_syncdbs.return_value = [core_mock, extra_mock]
|
||||||
pacman.handle = handle_mock
|
pacman.handle = handle_mock
|
||||||
|
|
||||||
pacman.database_sync(False)
|
pacman.database_sync(pacman.handle, force=False)
|
||||||
extra_mock.update.assert_called_once_with(False)
|
extra_mock.update.assert_called_once_with(False)
|
||||||
|
|
||||||
|
|
||||||
@ -170,7 +174,7 @@ def test_database_sync_forced(pacman: Pacman) -> None:
|
|||||||
handle_mock.get_syncdbs.return_value = [core_mock]
|
handle_mock.get_syncdbs.return_value = [core_mock]
|
||||||
pacman.handle = handle_mock
|
pacman.handle = handle_mock
|
||||||
|
|
||||||
pacman.database_sync(True)
|
pacman.database_sync(pacman.handle, force=True)
|
||||||
handle_mock.init_transaction.assert_called_once_with()
|
handle_mock.init_transaction.assert_called_once_with()
|
||||||
core_mock.update.assert_called_once_with(True)
|
core_mock.update.assert_called_once_with(True)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user