mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-11 21:15:46 +00:00
Compare commits
4 Commits
feature/we
...
master
Author | SHA1 | Date | |
---|---|---|---|
c6306631e6 | |||
97b906c536 | |||
435375721d | |||
4c5caba6b7 |
@ -8,7 +8,7 @@ services:
|
||||
AHRIMAN_OUTPUT: console
|
||||
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
||||
AHRIMAN_PORT: 8080
|
||||
AHRIMAN_PRESETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||
AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||
AHRIMAN_REPOSITORY: ahriman-demo
|
||||
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
||||
|
||||
|
@ -8,7 +8,7 @@ services:
|
||||
AHRIMAN_OUTPUT: console
|
||||
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
||||
AHRIMAN_PORT: 8080
|
||||
AHRIMAN_PRESETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||
AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||
AHRIMAN_REPOSITORY: ahriman-demo
|
||||
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
||||
|
||||
|
@ -8,7 +8,7 @@ services:
|
||||
AHRIMAN_OUTPUT: console
|
||||
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
||||
AHRIMAN_PORT: 8080
|
||||
AHRIMAN_PRESETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||
AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||
AHRIMAN_REPOSITORY: ahriman-demo
|
||||
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
||||
|
||||
@ -62,7 +62,7 @@ services:
|
||||
AHRIMAN_OUTPUT: console
|
||||
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
||||
AHRIMAN_PORT: 8080
|
||||
AHRIMAN_PRESETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||
AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||
AHRIMAN_REPOSITORY: ahriman-demo
|
||||
AHRIMAN_REPOSITORY_SERVER: http://frontend/repo/$$repo/$$arch
|
||||
|
||||
|
@ -12,7 +12,7 @@ services:
|
||||
AHRIMAN_PACMAN_MIRROR: https://de.mirror.archlinux32.org/$$arch/$$repo
|
||||
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
||||
AHRIMAN_PORT: 8080
|
||||
AHRIMAN_PRESETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||
AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||
AHRIMAN_REPOSITORY: ahriman-demo
|
||||
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
||||
|
||||
|
@ -8,8 +8,8 @@ services:
|
||||
AHRIMAN_OUTPUT: console
|
||||
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
||||
AHRIMAN_PORT: 8080
|
||||
AHRIMAN_POSTSETUP_COMMAND: ahriman --architecture x86_64 --repository another-demo service-setup --build-as-user ahriman --packager 'ahriman bot <ahriman@example.com>'
|
||||
AHRIMAN_PRESETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||
AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||
AHRIMAN_PRESETUP_COMMAND: ahriman --architecture x86_64 --repository another-demo service-setup --build-as-user ahriman --packager 'ahriman bot <ahriman@example.com>'
|
||||
AHRIMAN_REPOSITORY: ahriman-demo
|
||||
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
||||
|
||||
|
@ -9,7 +9,7 @@ services:
|
||||
AHRIMAN_OAUTH_CLIENT_SECRET: ${AHRIMAN_OAUTH_CLIENT_SECRET}
|
||||
AHRIMAN_OUTPUT: console
|
||||
AHRIMAN_PORT: 8080
|
||||
AHRIMAN_PRESETUP_COMMAND: sudo -u ahriman ahriman user-add ${AHRIMAN_OAUTH_USER} -R full -p ""
|
||||
AHRIMAN_POSTSETUP_COMMAND: sudo -u ahriman ahriman user-add ${AHRIMAN_OAUTH_USER} -R full -p ""
|
||||
AHRIMAN_REPOSITORY: ahriman-demo
|
||||
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
||||
|
||||
|
@ -6,7 +6,7 @@ services:
|
||||
environment:
|
||||
AHRIMAN_DEBUG: yes
|
||||
AHRIMAN_OUTPUT: console
|
||||
AHRIMAN_PRESETUP_COMMAND: sudo -u ahriman gpg --import /run/secrets/key
|
||||
AHRIMAN_POSTSETUP_COMMAND: sudo -u ahriman gpg --import /run/secrets/key
|
||||
AHRIMAN_REPOSITORY: ahriman-demo
|
||||
|
||||
configs:
|
||||
|
@ -8,7 +8,7 @@ services:
|
||||
AHRIMAN_OUTPUT: console
|
||||
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
||||
AHRIMAN_PORT: 8080
|
||||
AHRIMAN_PRESETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||
AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||
AHRIMAN_REPOSITORY: ahriman-demo
|
||||
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
||||
|
||||
|
@ -72,6 +72,7 @@ class Setup(Handler):
|
||||
|
||||
application = Application(repository_id, configuration, report=report)
|
||||
|
||||
with application.repository.paths.preserve_owner():
|
||||
Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths)
|
||||
Setup.executable_create(application.repository.paths, repository_id)
|
||||
repository_server = f"file://{application.repository.paths.repository}" if args.server is None else args.server
|
||||
@ -280,6 +281,5 @@ class Setup(Handler):
|
||||
command = Setup.build_command(paths.root, repository_id)
|
||||
command.unlink(missing_ok=True)
|
||||
command.symlink_to(Setup.ARCHBUILD_COMMAND_PATH)
|
||||
paths.chown(command) # we would like to keep owner inside ahriman's home
|
||||
|
||||
arguments = [_set_service_setup_parser]
|
||||
|
@ -130,8 +130,8 @@ class Pacman(LazyLogging):
|
||||
return # database for some reason deos not exist
|
||||
|
||||
self.logger.info("copy pacman database %s from operating system root to ahriman's home %s", src, dst)
|
||||
with self.repository_paths.preserve_owner(dst.parent):
|
||||
shutil.copy(src, dst)
|
||||
self.repository_paths.chown(dst)
|
||||
|
||||
def database_init(self, handle: Handle, repository: str, architecture: str) -> DB:
|
||||
"""
|
||||
@ -267,7 +267,8 @@ class Pacman(LazyLogging):
|
||||
Package: list of packages which were returned by the query
|
||||
"""
|
||||
def is_package_provided(package: Package) -> bool:
|
||||
return package_name in package.provides
|
||||
provides = [trim_package(name) for name in package.provides]
|
||||
return package_name in provides
|
||||
|
||||
for database in self.handle.get_syncdbs():
|
||||
yield from filter(is_package_provided, database.search(package_name))
|
||||
|
@ -146,7 +146,7 @@ class AUR(Remote):
|
||||
# search api provides reduced models
|
||||
for stub in self.package_search(package_name, pacman=pacman, search_by="provides")
|
||||
# verity that found package actually provides it
|
||||
if package_name in (package := self.package_info(stub.package_base, pacman=pacman)).provides
|
||||
if package_name in (package := self.package_info(stub.name, pacman=pacman)).provides
|
||||
]
|
||||
|
||||
def package_search(self, *keywords: str, pacman: Pacman | None, search_by: str | None) -> list[AURPackage]:
|
||||
|
@ -94,9 +94,13 @@ class SQLite(
|
||||
sqlite3.register_adapter(list, json.dumps)
|
||||
sqlite3.register_converter("json", json.loads)
|
||||
|
||||
if self._configuration.getboolean("settings", "apply_migrations", fallback=True):
|
||||
if not self._configuration.getboolean("settings", "apply_migrations", fallback=True):
|
||||
return
|
||||
if self._repository_id.is_empty:
|
||||
return # do not perform migration on empty repository identifier (e.g. multirepo command)
|
||||
|
||||
with self._repository_paths.preserve_owner():
|
||||
self.with_connection(lambda connection: Migrations.migrate(connection, self._configuration))
|
||||
self._repository_paths.chown(self.path)
|
||||
|
||||
def package_clear(self, package_base: str, repository_id: RepositoryId | None = None) -> None:
|
||||
"""
|
||||
|
@ -25,7 +25,7 @@ from dataclasses import dataclass, field, fields
|
||||
from pyalpm import Package # type: ignore[import-not-found]
|
||||
from typing import Any, Self
|
||||
|
||||
from ahriman.core.utils import filter_json, full_version
|
||||
from ahriman.core.utils import filter_json, full_version, trim_package
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
@ -103,6 +103,17 @@ class AURPackage:
|
||||
keywords: list[str] = field(default_factory=list)
|
||||
groups: list[str] = field(default_factory=list)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""
|
||||
update packages lists accordingly
|
||||
"""
|
||||
object.__setattr__(self, "depends", [trim_package(package) for package in self.depends])
|
||||
object.__setattr__(self, "make_depends", [trim_package(package) for package in self.make_depends])
|
||||
object.__setattr__(self, "opt_depends", [trim_package(package) for package in self.opt_depends])
|
||||
object.__setattr__(self, "check_depends", [trim_package(package) for package in self.check_depends])
|
||||
object.__setattr__(self, "conflicts", [trim_package(package) for package in self.conflicts])
|
||||
object.__setattr__(self, "provides", [trim_package(package) for package in self.provides])
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, dump: dict[str, Any]) -> Self:
|
||||
"""
|
||||
|
@ -83,12 +83,13 @@ class PackageDescription:
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""
|
||||
update dependencies list accordingly
|
||||
update packages lists accordingly
|
||||
"""
|
||||
self.depends = [trim_package(package) for package in self.depends]
|
||||
self.opt_depends = [trim_package(package) for package in self.opt_depends]
|
||||
self.make_depends = [trim_package(package) for package in self.make_depends]
|
||||
self.opt_depends = [trim_package(package) for package in self.opt_depends]
|
||||
self.check_depends = [trim_package(package) for package in self.check_depends]
|
||||
self.provides = [trim_package(package) for package in self.provides]
|
||||
|
||||
@property
|
||||
def filepath(self) -> Path | None:
|
||||
|
@ -17,6 +17,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import contextlib
|
||||
import os
|
||||
import shutil
|
||||
|
||||
@ -221,22 +222,14 @@ class RepositoryPaths(LazyLogging):
|
||||
stat = path.stat()
|
||||
return stat.st_uid, stat.st_gid
|
||||
|
||||
def cache_for(self, package_base: str) -> Path:
|
||||
"""
|
||||
get path to cached PKGBUILD and package sources for the package base
|
||||
|
||||
Args:
|
||||
package_base(str): package base name
|
||||
|
||||
Returns:
|
||||
Path: full path to directory for specified package base cache
|
||||
"""
|
||||
return self.cache / package_base
|
||||
|
||||
def chown(self, path: Path) -> None:
|
||||
def _chown(self, path: Path) -> None:
|
||||
"""
|
||||
set owner of path recursively (from root) to root owner
|
||||
|
||||
Notes:
|
||||
More likely you don't want to call this method explicitly, consider using :func:`preserve_owner`
|
||||
as context manager instead
|
||||
|
||||
Args:
|
||||
path(Path): path to be chown
|
||||
|
||||
@ -256,6 +249,56 @@ class RepositoryPaths(LazyLogging):
|
||||
set_owner(path)
|
||||
path = path.parent
|
||||
|
||||
def cache_for(self, package_base: str) -> Path:
|
||||
"""
|
||||
get path to cached PKGBUILD and package sources for the package base
|
||||
|
||||
Args:
|
||||
package_base(str): package base name
|
||||
|
||||
Returns:
|
||||
Path: full path to directory for specified package base cache
|
||||
"""
|
||||
return self.cache / package_base
|
||||
|
||||
@contextlib.contextmanager
|
||||
def preserve_owner(self, path: Path | None = None) -> Generator[None, None, None]:
|
||||
"""
|
||||
perform any action preserving owner for any newly created file or directory
|
||||
|
||||
Args:
|
||||
path(Path | None, optional): use this path as root instead of repository root (Default value = None)
|
||||
|
||||
Examples:
|
||||
This method is designed to use as context manager when you are going to perform operations which might
|
||||
change filesystem, especially if you are doing it under unsafe flag, e.g.::
|
||||
|
||||
>>> with paths.preserve_owner():
|
||||
>>> paths.tree_create()
|
||||
|
||||
Note, however, that this method doesn't handle any exceptions and will eventually interrupt
|
||||
if there will be any.
|
||||
"""
|
||||
path = path or self.root
|
||||
|
||||
def walk(root: Path) -> Generator[Path, None, None]:
|
||||
# basically walk, but skipping some content
|
||||
for child in root.iterdir():
|
||||
yield child
|
||||
if child in (self.chroot.parent,):
|
||||
yield from child.iterdir() # we only yield top-level in chroot directory
|
||||
elif child.is_dir():
|
||||
yield from walk(child)
|
||||
|
||||
# get current filesystem and run action
|
||||
previous_snapshot = set(walk(path))
|
||||
yield
|
||||
|
||||
# get newly created files and directories and chown them
|
||||
new_entries = set(walk(path)).difference(previous_snapshot)
|
||||
for entry in new_entries:
|
||||
self._chown(entry)
|
||||
|
||||
def tree_clear(self, package_base: str) -> None:
|
||||
"""
|
||||
clear package specific files
|
||||
@ -274,6 +317,8 @@ class RepositoryPaths(LazyLogging):
|
||||
"""
|
||||
if self.repository_id.is_empty:
|
||||
return # do not even try to create tree in case if no repository id set
|
||||
|
||||
with self.preserve_owner():
|
||||
for directory in (
|
||||
self.cache,
|
||||
self.chroot,
|
||||
@ -282,4 +327,3 @@ class RepositoryPaths(LazyLogging):
|
||||
self.repository,
|
||||
):
|
||||
directory.mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||
self.chown(directory)
|
||||
|
@ -72,7 +72,7 @@ def _create_socket(configuration: Configuration, application: Application) -> so
|
||||
async def remove_socket(_: Application) -> None:
|
||||
unix_socket.unlink(missing_ok=True)
|
||||
|
||||
application.on_shutdown.append(remove_socket) # type: ignore[arg-type]
|
||||
application.on_shutdown.append(remove_socket)
|
||||
|
||||
return sock
|
||||
|
||||
@ -142,8 +142,8 @@ def setup_server(configuration: Configuration, spawner: Spawn, repositories: lis
|
||||
InitializeError: if no repositories set
|
||||
"""
|
||||
application = Application(logger=logging.getLogger(__name__))
|
||||
application.on_shutdown.append(_on_shutdown) # type: ignore[arg-type]
|
||||
application.on_startup.append(_on_startup) # type: ignore[arg-type]
|
||||
application.on_shutdown.append(_on_shutdown)
|
||||
application.on_startup.append(_on_startup)
|
||||
|
||||
application.middlewares.append(normalize_path_middleware(append_slash=False, remove_slash=True))
|
||||
application.middlewares.append(exception_handler(application.logger))
|
||||
@ -166,11 +166,16 @@ def setup_server(configuration: Configuration, spawner: Spawn, repositories: lis
|
||||
# package cache
|
||||
if not repositories:
|
||||
raise InitializeError("No repositories configured, exiting")
|
||||
database = SQLite.load(configuration)
|
||||
watchers: dict[RepositoryId, Watcher] = {}
|
||||
configuration_path, _ = configuration.check_loaded()
|
||||
for repository_id in repositories:
|
||||
application.logger.info("load repository %s", repository_id)
|
||||
client = Client.load(repository_id, configuration, database, report=False) # explicitly load local client
|
||||
# load settings explicitly for architecture if any
|
||||
repository_configuration = Configuration.from_path(configuration_path, repository_id)
|
||||
# load database instance, because it holds identifier
|
||||
database = SQLite.load(repository_configuration)
|
||||
# explicitly load local client
|
||||
client = Client.load(repository_id, repository_configuration, database, report=False)
|
||||
watchers[repository_id] = Watcher(client)
|
||||
application[WatcherKey] = watchers
|
||||
# workers cache
|
||||
@ -179,6 +184,7 @@ def setup_server(configuration: Configuration, spawner: Spawn, repositories: lis
|
||||
application[SpawnKey] = spawner
|
||||
|
||||
application.logger.info("setup authorization")
|
||||
database = SQLite.load(configuration)
|
||||
validator = application[AuthKey] = Auth.load(configuration, database)
|
||||
if validator.enabled:
|
||||
from ahriman.web.middlewares.auth_handler import setup_auth
|
||||
|
@ -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:
|
||||
@ -289,3 +289,4 @@ def test_package_provided_by(pacman: Pacman) -> None:
|
||||
must search through the provides lists
|
||||
"""
|
||||
assert list(pacman.provided_by("sh"))
|
||||
assert list(pacman.provided_by("libacl.so")) # case with exact version
|
||||
|
@ -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
|
||||
|
@ -2,7 +2,7 @@ import datetime
|
||||
import json
|
||||
import pyalpm # typing: ignore
|
||||
|
||||
from dataclasses import asdict, fields
|
||||
from dataclasses import asdict, fields, replace
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from typing import Any
|
||||
@ -38,6 +38,25 @@ def _get_official_data(resource_path_root: Path) -> dict[str, Any]:
|
||||
return json.loads(response)["results"][0]
|
||||
|
||||
|
||||
def test_post_init(aur_package_ahriman: AURPackage) -> None:
|
||||
"""
|
||||
must trim versions and descriptions from packages list
|
||||
"""
|
||||
package = replace(
|
||||
aur_package_ahriman,
|
||||
depends=["a=1"],
|
||||
make_depends=["b>=3"],
|
||||
opt_depends=["c: a description"],
|
||||
check_depends=["d=4"],
|
||||
provides=["e=5"],
|
||||
)
|
||||
assert package.depends == ["a"]
|
||||
assert package.make_depends == ["b"]
|
||||
assert package.opt_depends == ["c"]
|
||||
assert package.check_depends == ["d"]
|
||||
assert package.provides == ["e"]
|
||||
|
||||
|
||||
def test_from_json(aur_package_ahriman: AURPackage, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must load package from json
|
||||
|
@ -6,10 +6,15 @@ from ahriman.models.package_description import PackageDescription
|
||||
|
||||
def test_post_init() -> None:
|
||||
"""
|
||||
must trim versions and descriptions from dependencies list
|
||||
must trim versions and descriptions from packages list
|
||||
"""
|
||||
assert PackageDescription(depends=["a=1"], make_depends=["b>=3"], opt_depends=["c: a description"]) == \
|
||||
PackageDescription(depends=["a"], make_depends=["b"], opt_depends=["c"])
|
||||
assert PackageDescription(
|
||||
depends=["a=1"],
|
||||
make_depends=["b>=3"],
|
||||
opt_depends=["c: a description"],
|
||||
check_depends=["d=4"],
|
||||
provides=["e=5"]
|
||||
) == PackageDescription(depends=["a"], make_depends=["b"], opt_depends=["c"], check_depends=["d"], provides=["e"])
|
||||
|
||||
|
||||
def test_filepath(package_description_ahriman: PackageDescription) -> None:
|
||||
|
@ -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