deprecate init/repo-init command

In current workflow you need to run setup to run init (because of
repository name), but you need to run init before setup (because of
repository tree rights).

New solution just add `Repo.init()` method call to setup subcommand
after config reload to make sure that repository name has been applied.
In addition chown method as well as setuid method for check_output have
been added.
This commit is contained in:
Evgenii Alekseev 2022-03-21 00:44:44 +03:00
parent 63e79ec57a
commit 13121298f5
24 changed files with 225 additions and 147 deletions

View File

@ -25,25 +25,28 @@ make VERSION=1.0.0 archlinux # well, it does not really matter which version we
mv ahriman-*-src.tar.xz package/archlinux
chmod +777 package/archlinux # because fuck you that's why
cd package/archlinux
sudo -u nobody makepkg -cf --skipchecksums --noconfirm
sudo -u nobody -- makepkg -cf --skipchecksums --noconfirm
pacman --noconfirm -U ahriman-1.0.0-1-any.pkg.tar.zst
# create machine-id which is required by build tools
systemd-machine-id-setup
# special thing for the container, because /dev/log interface is not available there
sed -i 's/handlers = syslog_handler/handlers = console_handler/g' /etc/ahriman.ini.d/logging.ini
sed -i "s/handlers = syslog_handler/handlers = console_handler/g" /etc/ahriman.ini.d/logging.ini
# initial setup command as root
sudo -u ahriman ahriman -a x86_64 init
ahriman -a x86_64 repo-setup --packager "ahriman bot <ahriman@example.com>" --repository "github" --web-port 8080
# enable services
systemctl enable ahriman-web@x86_64
systemctl enable ahriman@x86_64.timer
# run web service (detached)
sudo -u ahriman ahriman -a x86_64 web &
sudo -u ahriman -- ahriman -a x86_64 web &
WEBPID=$!
sleep 15s # wait for the web service activation
# add the first package
# the build itself does not really work in the container because it requires procfs
sudo -u ahriman ahriman package-add yay
# the build itself does not really work in the container
sudo -u ahriman -- ahriman package-add --now yay
# check if package was actually installed
#test -n "$(find "/var/lib/ahriman/repository/x86_64" -name "yay*pkg*")"
# run package check
sudo -u ahriman ahriman repo-update
sudo -u ahriman -- ahriman repo-update
# stop web service lol
kill $WEBPID

View File

@ -20,8 +20,6 @@ fi
# create repository root inside the [[mounted]] directory and set correct ownership
[ -d "$AHRIMAN_REPOSITORY_ROOT" ] || mkdir "$AHRIMAN_REPOSITORY_ROOT"
chown "$AHRIMAN_USER":"$AHRIMAN_USER" "$AHRIMAN_REPOSITORY_ROOT"
# run initial setup
sudo -u "$AHRIMAN_USER" -- ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" repo-init
# run built-in setup command
AHRIMAN_SETUP_ARGS=("--build-as-user" "$AHRIMAN_USER")

View File

@ -12,7 +12,6 @@ TL;DR
```shell
yay -S ahriman
sudo -u ahriman ahriman -a x86_64 init
sudo ahriman -a x86_64 repo-setup --packager "ahriman bot <ahriman@example.com>" --repository "repository"
systemctl enable --now ahriman@x86_64.timer
```

View File

@ -5,11 +5,10 @@
3. TL;DR
```shell
sudo -u ahriman ahriman -a x86_64 repo-init
sudo ahriman -a x86_64 repo-setup ...
```
`repo-init` subcommand is required to create the repository tree with correct rights. `repo-setup` literally does the following steps:
`repo-setup` literally does the following steps:
1. Create `/var/lib/ahriman/.makepkg.conf` with `makepkg.conf` overrides if required (at least you might want to set `PACKAGER`):

View File

@ -85,7 +85,6 @@ def _parser() -> argparse.ArgumentParser:
_set_repo_check_parser(subparsers)
_set_repo_clean_parser(subparsers)
_set_repo_config_parser(subparsers)
_set_repo_init_parser(subparsers)
_set_repo_rebuild_parser(subparsers)
_set_repo_remove_unknown_parser(subparsers)
_set_repo_report_parser(subparsers)
@ -342,19 +341,6 @@ def _set_repo_config_parser(root: SubParserAction) -> argparse.ArgumentParser:
return parser
def _set_repo_init_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository init subcommand
:param root: subparsers for the commands
:return: created argument parser
"""
parser = root.add_parser("repo-init", aliases=["init"], help="create repository tree",
description="create empty repository tree. Optional command for auto architecture support",
formatter_class=_formatter)
parser.set_defaults(handler=handlers.Init, no_report=True)
return parser
def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository rebuild subcommand
@ -406,7 +392,7 @@ def _set_repo_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
:param root: subparsers for the commands
:return: created argument parser
"""
parser = root.add_parser("repo-setup", aliases=["setup"], help="initial service configuration",
parser = root.add_parser("repo-setup", aliases=["init", "repo-init", "setup"], help="initial service configuration",
description="create initial service configuration, requires root",
epilog="Create _minimal_ configuration for the service according to provided options.",
formatter_class=_formatter)

View File

@ -22,7 +22,6 @@ from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers.add import Add
from ahriman.application.handlers.clean import Clean
from ahriman.application.handlers.dump import Dump
from ahriman.application.handlers.init import Init
from ahriman.application.handlers.key_import import KeyImport
from ahriman.application.handlers.patch import Patch
from ahriman.application.handlers.rebuild import Rebuild

View File

@ -27,7 +27,7 @@ from typing import List, Type
from ahriman.application.lock import Lock
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import MissingArchitecture, MultipleArchitecture
from ahriman.core.exceptions import MissingArchitecture, MultipleArchitectures
from ahriman.models.repository_paths import RepositoryPaths
@ -95,7 +95,7 @@ class Handler:
# actually we do not have to spawn another process if it is single-process application, do we?
if len(architectures) > 1:
if not cls.ALLOW_MULTI_ARCHITECTURE_RUN:
raise MultipleArchitecture(args.command)
raise MultipleArchitectures(args.command)
with Pool(len(architectures)) as pool:
result = pool.starmap(

View File

@ -1,47 +0,0 @@
#
# Copyright (c) 2021 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# 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 argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.core.configuration import Configuration
class Init(Handler):
"""
repository init handler
"""
ALLOW_AUTO_ARCHITECTURE_RUN = False
@classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
:param args: command line args
:param architecture: repository architecture
:param configuration: configuration instance
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
"""
Application(architecture, configuration, no_report, unsafe).repository.repo.init()

View File

@ -55,14 +55,19 @@ class Setup(Handler):
:param no_report: force disable reporting
:param unsafe: if set no user check will be performed before path creation
"""
Setup.configuration_create_ahriman(args, architecture, args.repository, configuration.include)
configuration.reload()
application = Application(architecture, configuration, no_report, unsafe)
Setup.configuration_create_makepkg(args.packager, application.repository.paths)
Setup.executable_create(args.build_command, architecture)
Setup.configuration_create_devtools(args.build_command, architecture, args.from_configuration,
args.no_multilib, args.repository, application.repository.paths)
Setup.configuration_create_ahriman(args, architecture, args.repository, configuration.include)
Setup.configuration_create_sudo(args.build_command, architecture)
application.repository.repo.init()
@staticmethod
def build_command(prefix: str, architecture: str) -> Path:
"""

View File

@ -32,6 +32,7 @@ from ahriman.core.exceptions import DuplicateRun
from ahriman.core.status.client import Client
from ahriman.core.util import check_user
from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.repository_paths import RepositoryPaths
class Lock:
@ -40,7 +41,7 @@ class Lock:
:ivar force: remove lock file on start if any
:ivar path: path to lock file if any
:ivar reporter: build status reporter instance
:ivar root: repository root (i.e. ahriman home)
:ivar paths: repository paths instance
:ivar unsafe: skip user check
"""
@ -55,7 +56,7 @@ class Lock:
self.force = args.force
self.unsafe = args.unsafe
self.root = Path(configuration.get("repository", "root"))
self.paths = RepositoryPaths(configuration.getpath("repository", "root"), architecture)
self.reporter = Client() if args.no_report else Client.load(configuration)
def __enter__(self) -> Lock:
@ -103,7 +104,7 @@ class Lock:
"""
check if current user is actually owner of ahriman root
"""
check_user(self.root, self.unsafe)
check_user(self.paths, self.unsafe)
def clear(self) -> None:
"""

View File

@ -34,6 +34,7 @@ class Repo:
:ivar name: repository name
:ivar paths: repository paths instance
:ivar sign_args: additional args which have to be used to sign repository archive
:ivar uid: uid of the repository owner user
"""
_check_output = check_output
@ -48,6 +49,7 @@ class Repo:
self.logger = logging.getLogger("build_details")
self.name = name
self.paths = paths
self.uid, _ = paths.root_owner
self.sign_args = sign_args
@property
@ -66,7 +68,8 @@ class Repo:
"repo-add", *self.sign_args, "-R", str(self.repo_path), str(path),
exception=BuildFailed(path.name),
cwd=self.paths.repository,
logger=self.logger)
logger=self.logger,
user=self.uid)
def init(self) -> None:
"""
@ -76,7 +79,8 @@ class Repo:
"repo-add", *self.sign_args, str(self.repo_path),
exception=None,
cwd=self.paths.repository,
logger=self.logger)
logger=self.logger,
user=self.uid)
def remove(self, package: str, filename: Path) -> None:
"""
@ -93,4 +97,5 @@ class Repo:
"repo-remove", *self.sign_args, str(self.repo_path), package,
exception=BuildFailed(package),
cwd=self.paths.repository,
logger=self.logger)
logger=self.logger,
user=self.uid)

View File

@ -38,6 +38,7 @@ class Task:
:ivar logger: class logger
:ivar package: package definitions
:ivar paths: repository paths instance
:ivar uid: uid of the repository owner user
"""
_check_output = check_output
@ -53,6 +54,7 @@ class Task:
self.build_logger = logging.getLogger("build_details")
self.package = package
self.paths = paths
self.uid, _ = paths.root_owner
self.archbuild_flags = configuration.getlist("build", "archbuild_flags", fallback=[])
self.build_command = configuration.get("build", "build_command")
@ -74,7 +76,8 @@ class Task:
*command,
exception=BuildFailed(self.package.base),
cwd=self.paths.sources_for(self.package.base),
logger=self.build_logger)
logger=self.build_logger,
user=self.uid)
# well it is not actually correct, but we can deal with it
packages = Task._check_output("makepkg", "--packagelist",

View File

@ -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/>.
#
from pathlib import Path
from typing import Any
@ -85,6 +86,20 @@ class InvalidOption(ValueError):
ValueError.__init__(self, f"Invalid or unknown option value `{value}`")
class InvalidPath(ValueError):
"""
exception which will be raised on path which is not belong to root directory
"""
def __init__(self, path: Path, root: Path) -> None:
"""
default constructor
:param path: path which raised an exception
:param root: repository root (i.e. ahriman home)
"""
ValueError.__init__(self, f"Path `{path}` does not belong to repository root `{root}`")
class InvalidPackageInfo(RuntimeError):
"""
exception which will be raised on package load errors
@ -111,7 +126,7 @@ class MissingArchitecture(ValueError):
ValueError.__init__(self, f"Architecture required for subcommand {command}, but missing")
class MultipleArchitecture(ValueError):
class MultipleArchitectures(ValueError):
"""
exception which will be raised if multiple architectures are not supported by the handler
"""

View File

@ -62,7 +62,7 @@ class Properties:
self.paths = RepositoryPaths(configuration.getpath("repository", "root"), architecture)
try:
check_user(self.paths.root, unsafe)
check_user(self.paths, unsafe)
self.paths.tree_create()
except UnsafeRun:
self.logger.warning("root owner differs from the current user, skipping tree creation")

View File

@ -27,10 +27,11 @@ from pathlib import Path
from typing import Any, Dict, Generator, Iterable, Optional, Union
from ahriman.core.exceptions import InvalidOption, UnsafeRun
from ahriman.models.repository_paths import RepositoryPaths
def check_output(*args: str, exception: Optional[Exception], cwd: Optional[Path] = None,
input_data: Optional[str] = None, logger: Optional[Logger] = None) -> str:
input_data: Optional[str] = None, logger: Optional[Logger] = None, user: Optional[int] = None) -> str:
"""
subprocess wrapper
:param args: command line arguments
@ -38,12 +39,15 @@ def check_output(*args: str, exception: Optional[Exception], cwd: Optional[Path]
:param cwd: current working directory
:param input_data: data which will be written to command stdin
:param logger: logger to log command result if required
:param user: run process as specified user
:return: command output
"""
try:
# universal_newlines is required to read input from string
# FIXME additional workaround for linter and type check which do not know that user arg is supported
# pylint: disable=unexpected-keyword-arg
result: str = subprocess.check_output(args, cwd=cwd, input=input_data, stderr=subprocess.STDOUT,
universal_newlines=True).strip()
universal_newlines=True, user=user).strip() # type: ignore
if logger is not None:
for line in result.splitlines():
logger.debug(line)
@ -55,18 +59,18 @@ def check_output(*args: str, exception: Optional[Exception], cwd: Optional[Path]
raise exception or e
def check_user(root: Path, unsafe: bool) -> None:
def check_user(paths: RepositoryPaths, unsafe: bool) -> None:
"""
check if current user is the owner of the root
:param root: root directory (i.e. ahriman home)
:param paths: repository paths object
:param unsafe: if set no user check will be performed before path creation
"""
if not root.exists():
if not paths.root.exists():
return # no directory found, skip check
if unsafe:
return # unsafe flag is enabled, no check performed
current_uid = os.getuid()
root_uid = root.stat().st_uid
root_uid, _ = paths.root_owner
if current_uid != root_uid:
raise UnsafeRun(current_uid, root_uid)

View File

@ -19,11 +19,14 @@
#
from __future__ import annotations
import os
import shutil
from dataclasses import dataclass
from pathlib import Path
from typing import Set, Type
from typing import Set, Tuple, Type
from ahriman.core.exceptions import InvalidPath
@dataclass
@ -80,6 +83,13 @@ class RepositoryPaths:
"""
return self.root / "repository" / self.architecture
@property
def root_owner(self) -> Tuple[int, int]:
"""
:return: owner user and group of the root directory
"""
return self.owner(self.root)
@property
def sources(self) -> Path:
"""
@ -101,6 +111,16 @@ class RepositoryPaths:
if path.is_dir()
}
@staticmethod
def owner(path: Path) -> Tuple[int, int]:
"""
retrieve owner information by path
:param path: path for which extract ids
:return: owner user and group ids of the directory
"""
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
@ -109,6 +129,28 @@ class RepositoryPaths:
"""
return self.cache / package_base
def chown(self, path: Path) -> None:
"""
set owner of path recursively (from root) to root owner
:param path: path to be chown
"""
def set_owner(current: Path) -> None:
"""
set owner to the specified path
:param current: path to set
"""
uid, gid = self.owner(current)
if uid == root_uid and gid == root_gid:
return
os.chown(current, root_uid, root_gid, follow_symlinks=False)
if self.root not in path.parents:
raise InvalidPath(path, self.root)
root_uid, root_gid = self.root_owner
while path != self.root:
set_owner(path)
path = path.parent
def manual_for(self, package_base: str) -> Path:
"""
get manual path for specific package base
@ -158,3 +200,4 @@ class RepositoryPaths:
self.repository,
self.sources):
directory.mkdir(mode=0o755, parents=True, exist_ok=True)
self.chown(directory)

View File

@ -6,7 +6,7 @@ from pytest_mock import MockerFixture
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import MissingArchitecture, MultipleArchitecture
from ahriman.core.exceptions import MissingArchitecture, MultipleArchitectures
def test_architectures_extract(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
@ -94,7 +94,7 @@ def test_execute_multiple_not_supported(args: argparse.Namespace, mocker: Mocker
args.command = "web"
mocker.patch.object(Handler, "ALLOW_MULTI_ARCHITECTURE_RUN", False)
with pytest.raises(MultipleArchitecture):
with pytest.raises(MultipleArchitectures):
Handler.execute(args)

View File

@ -1,26 +0,0 @@
import argparse
from pytest_mock import MockerFixture
from ahriman.application.handlers import Init
from ahriman.core.configuration import Configuration
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
mocker.patch("ahriman.core.repository.properties.check_user")
tree_create_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
init_mock = mocker.patch("ahriman.core.alpm.repo.Repo.init")
Init.run(args, "x86_64", configuration, True, False)
tree_create_mock.assert_called_once_with()
init_mock.assert_called_once_with()
def test_disallow_auto_architecture_run() -> None:
"""
must not allow multi architecture run
"""
assert not Init.ALLOW_AUTO_ARCHITECTURE_RUN

View File

@ -40,6 +40,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
makepkg_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_makepkg")
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")
paths = RepositoryPaths(configuration.getpath("repository", "root"), "x86_64")
Setup.run(args, "x86_64", configuration, True, False)
@ -49,6 +50,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
makepkg_configuration_mock.assert_called_once_with(args.packager, paths)
sudo_configuration_mock.assert_called_once_with(args.build_command, "x86_64")
executable_mock.assert_called_once_with(args.build_command, "x86_64")
init_mock.assert_called_once()
def test_build_command(args: argparse.Namespace) -> None:

View File

@ -288,15 +288,6 @@ def test_subparsers_repo_config(parser: argparse.ArgumentParser) -> None:
assert args.unsafe
def test_subparsers_repo_init(parser: argparse.ArgumentParser) -> None:
"""
repo-init command must imply no_report
"""
args = parser.parse_args(["-a", "x86_64", "repo-init"])
assert args.architecture == ["x86_64"]
assert args.no_report
def test_subparsers_repo_rebuild_architecture(parser: argparse.ArgumentParser) -> None:
"""
repo-rebuild command must correctly parse architecture list

View File

@ -82,7 +82,7 @@ def test_check_user(lock: Lock, mocker: MockerFixture) -> None:
"""
check_user_patch = mocker.patch("ahriman.application.lock.check_user")
lock.check_user()
check_user_patch.assert_called_once_with(lock.root, False)
check_user_patch.assert_called_once_with(lock.paths, False)
def test_check_user_exception(lock: Lock, mocker: MockerFixture) -> None:

View File

@ -9,6 +9,7 @@ from pytest_mock import MockerFixture
from ahriman.core.exceptions import InvalidOption, UnsafeRun
from ahriman.core.util import check_output, check_user, filter_json, package_like, pretty_datetime, pretty_size, walk
from ahriman.models.package import Package
from ahriman.models.repository_paths import RepositoryPaths
def test_check_output(mocker: MockerFixture) -> None:
@ -56,37 +57,37 @@ def test_check_user(mocker: MockerFixture) -> None:
"""
must check user correctly
"""
cwd = Path.cwd()
mocker.patch("os.getuid", return_value=cwd.stat().st_uid)
check_user(cwd, False)
paths = RepositoryPaths(Path.cwd(), "x86_64")
mocker.patch("os.getuid", return_value=paths.root_owner[0])
check_user(paths, False)
def test_check_user_no_directory(mocker: MockerFixture) -> None:
def test_check_user_no_directory(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must not fail in case if no directory found
"""
mocker.patch("pathlib.Path.exists", return_value=False)
check_user(Path.cwd(), False)
check_user(repository_paths, False)
def test_check_user_exception(mocker: MockerFixture) -> None:
"""
must raise exception if user differs
"""
cwd = Path.cwd()
mocker.patch("os.getuid", return_value=cwd.stat().st_uid + 1)
paths = RepositoryPaths(Path.cwd(), "x86_64")
mocker.patch("os.getuid", return_value=paths.root_owner[0] + 1)
with pytest.raises(UnsafeRun):
check_user(cwd, False)
check_user(paths, False)
def test_check_unsafe(mocker: MockerFixture) -> None:
"""
must skip check if unsafe flag is set
"""
cwd = Path.cwd()
mocker.patch("os.getuid", return_value=cwd.stat().st_uid + 1)
check_user(cwd, True)
paths = RepositoryPaths(Path.cwd(), "x86_64")
mocker.patch("os.getuid", return_value=paths.root_owner[0] + 1)
check_user(paths, True)
def test_filter_json(package_ahriman: Package) -> None:

View File

@ -1,10 +1,36 @@
from pytest_mock import MockerFixture
from unittest import mock
import pytest
from pathlib import Path
from pytest_mock import MockerFixture
from typing import Callable, Tuple
from unittest import mock
from unittest.mock import MagicMock
from ahriman.core.exceptions import InvalidPath
from ahriman.models.package import Package
from ahriman.models.repository_paths import RepositoryPaths
def _get_owner(root: Path, same: bool) -> Callable[[Path], Tuple[int, int]]:
"""
mocker function for owner definition
:param root: root directory
:param same: if True then returns the same as root directory and different otherwise
:return: function which can define ownership
"""
root_owner = (42, 42)
nonroot_owner = (42, 42) if same else (1, 1)
return lambda path: root_owner if path == root else nonroot_owner
def test_root_owner(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must correctly define root directory owner
"""
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.owner", return_value=(42, 142))
assert repository_paths.root_owner == (42, 142)
def test_known_architectures(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must list available directory paths
@ -14,6 +40,18 @@ def test_known_architectures(repository_paths: RepositoryPaths, mocker: MockerFi
iterdir_mock.assert_called_once_with()
def test_owner(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must correctly retrieve owner of the path
"""
stat_mock = MagicMock()
stat_mock.st_uid = 42
stat_mock.st_gid = 142
mocker.patch("pathlib.Path.stat", return_value=stat_mock)
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
@ -23,6 +61,56 @@ def test_cache_for(repository_paths: RepositoryPaths, package_ahriman: Package)
assert path.parent == repository_paths.cache
def test_chown(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must correctly set owner for the directory
"""
repository_paths.owner = _get_owner(repository_paths.root, same=False)
mocker.patch.object(RepositoryPaths, "root_owner", (42, 42))
chown_mock = mocker.patch("os.chown")
path = repository_paths.root / "path"
repository_paths.chown(path)
chown_mock.assert_called_once_with(path, 42, 42, follow_symlinks=False)
def test_chown_parent(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must correctly set owner for the directory including parents
"""
repository_paths.owner = _get_owner(repository_paths.root, same=False)
mocker.patch.object(RepositoryPaths, "root_owner", (42, 42))
chown_mock = mocker.patch("os.chown")
path = repository_paths.root / "parent" / "path"
repository_paths.chown(path)
chown_mock.assert_has_calls([
mock.call(path, 42, 42, follow_symlinks=False),
mock.call(path.parent, 42, 42, follow_symlinks=False)
])
def test_chown_skip(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must skip ownership set in case if it is same as root
"""
repository_paths.owner = _get_owner(repository_paths.root, same=True)
mocker.patch.object(RepositoryPaths, "root_owner", (42, 42))
chown_mock = mocker.patch("os.chown")
path = repository_paths.root / "path"
repository_paths.chown(path)
chown_mock.assert_not_called()
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(InvalidPath):
repository_paths.chown(repository_paths.root.parent)
def test_manual_for(repository_paths: RepositoryPaths, package_ahriman: Package) -> None:
"""
must return correct path for manual directory
@ -76,9 +164,17 @@ def test_tree_create(repository_paths: RepositoryPaths, mocker: MockerFixture) -
for prop in dir(repository_paths)
if not prop.startswith("_")
and not prop.endswith("_for")
and prop not in ("architecture", "known_architectures", "root", "tree_clear", "tree_create")
and prop not in ("architecture",
"chown",
"known_architectures",
"owner",
"root",
"root_owner",
"tree_clear",
"tree_create")
}
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
repository_paths.tree_create()
mkdir_mock.assert_has_calls(
@ -86,3 +182,4 @@ def test_tree_create(repository_paths: RepositoryPaths, mocker: MockerFixture) -
mock.call(mode=0o755, parents=True, exist_ok=True)
for _ in paths
], any_order=True)
chown_mock.assert_has_calls([mock.call(getattr(repository_paths, path)) for path in paths], any_order=True)

View File

@ -25,7 +25,7 @@ makepkg_flags = --skippgpcheck
[repository]
name = aur-clone
root = /var/lib/ahriman
root = ../../../
[sign]
target =