mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-11-04 07:43:42 +00:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			43c553a3db
			...
			e58ccdc8ad
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e58ccdc8ad | |||
| 2a07356d24 | 
@ -13,11 +13,14 @@ ENV AHRIMAN_REPOSITORY_ROOT="/var/lib/ahriman/ahriman"
 | 
				
			|||||||
ENV AHRIMAN_USER="ahriman"
 | 
					ENV AHRIMAN_USER="ahriman"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# install environment
 | 
					# install environment
 | 
				
			||||||
 | 
					## update pacman.conf with multilib
 | 
				
			||||||
 | 
					RUN echo "[multilib]" >> "/etc/pacman.conf" && \
 | 
				
			||||||
 | 
					    echo "Include = /etc/pacman.d/mirrorlist" >> "/etc/pacman.conf"
 | 
				
			||||||
## install minimal required packages
 | 
					## install minimal required packages
 | 
				
			||||||
RUN pacman --noconfirm -Syu binutils fakeroot git make sudo
 | 
					RUN pacman --noconfirm -Syu binutils fakeroot git make sudo
 | 
				
			||||||
## create build user
 | 
					## create build user
 | 
				
			||||||
RUN useradd -m -d /home/build -s /usr/bin/nologin build && \
 | 
					RUN useradd -m -d "/home/build" -s "/usr/bin/nologin" build && \
 | 
				
			||||||
    echo "build ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/build
 | 
					    echo "build ALL=(ALL) NOPASSWD: ALL" > "/etc/sudoers.d/build"
 | 
				
			||||||
COPY "docker/install-aur-package.sh" "/usr/local/bin/install-aur-package"
 | 
					COPY "docker/install-aur-package.sh" "/usr/local/bin/install-aur-package"
 | 
				
			||||||
## install package dependencies
 | 
					## install package dependencies
 | 
				
			||||||
## darcs is not installed by reasons, because it requires a lot haskell packages which dramatically increase image size
 | 
					## darcs is not installed by reasons, because it requires a lot haskell packages which dramatically increase image size
 | 
				
			||||||
 | 
				
			|||||||
@ -26,9 +26,11 @@ Base configuration settings.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
libalpm and AUR related configuration.
 | 
					libalpm and AUR related configuration.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* ``database`` - path to pacman local database cache, string, required.
 | 
					* ``database`` - path to pacman system database cache, string, required.
 | 
				
			||||||
 | 
					* ``mirror`` - package database mirror used by pacman for syncronization, string, required. This option supports standard pacman substitutions with ``$arch`` and ``$repo``. Note that the mentioned mirror should contain all repositories which are set by ``alpm.repositories`` option.
 | 
				
			||||||
* ``repositories`` - list of pacman repositories, space separated list of strings, required.
 | 
					* ``repositories`` - list of pacman repositories, space separated list of strings, required.
 | 
				
			||||||
* ``root`` - root for alpm library, string, required.
 | 
					* ``root`` - root for alpm library, string, required.
 | 
				
			||||||
 | 
					* ``use_ahriman_cache`` - use local pacman package cache instead of system one, boolean, required. With this option enabled you might want to refresh database periodically (available as additional flag for some subcommands).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
``auth`` group
 | 
					``auth`` group
 | 
				
			||||||
--------------
 | 
					--------------
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,6 @@
 | 
				
			|||||||
Description=ArcH linux ReposItory MANager (%I architecture)
 | 
					Description=ArcH linux ReposItory MANager (%I architecture)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Service]
 | 
					[Service]
 | 
				
			||||||
ExecStart=/usr/bin/ahriman --architecture %i update
 | 
					ExecStart=/usr/bin/ahriman --architecture %i repo-update --refresh
 | 
				
			||||||
User=ahriman
 | 
					User=ahriman
 | 
				
			||||||
Group=ahriman
 | 
					Group=ahriman
 | 
				
			||||||
@ -5,8 +5,10 @@ database = /var/lib/ahriman/ahriman.db
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[alpm]
 | 
					[alpm]
 | 
				
			||||||
database = /var/lib/pacman
 | 
					database = /var/lib/pacman
 | 
				
			||||||
 | 
					mirror = https://geo.mirror.pkgbuild.com/$repo/os/$arch
 | 
				
			||||||
repositories = core extra community multilib
 | 
					repositories = core extra community multilib
 | 
				
			||||||
root = /
 | 
					root = /
 | 
				
			||||||
 | 
					use_ahriman_cache = yes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[auth]
 | 
					[auth]
 | 
				
			||||||
target = disabled
 | 
					target = disabled
 | 
				
			||||||
 | 
				
			|||||||
@ -160,6 +160,9 @@ def _set_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser:
 | 
				
			|||||||
    parser.add_argument("--no-local", help="do not check local packages for updates", action="store_true")
 | 
					    parser.add_argument("--no-local", help="do not check local packages for updates", action="store_true")
 | 
				
			||||||
    parser.add_argument("--no-manual", help="do not include manual updates", action="store_true")
 | 
					    parser.add_argument("--no-manual", help="do not include manual updates", action="store_true")
 | 
				
			||||||
    parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
 | 
					    parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
 | 
				
			||||||
 | 
					    parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
 | 
				
			||||||
 | 
					                                                "-yy to force refresh even if up to date",
 | 
				
			||||||
 | 
					                        action="count", default=0)
 | 
				
			||||||
    parser.set_defaults(handler=handlers.Daemon, dry_run=False, exit_code=False, package=[])
 | 
					    parser.set_defaults(handler=handlers.Daemon, dry_run=False, exit_code=False, package=[])
 | 
				
			||||||
    return parser
 | 
					    return parser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -251,6 +254,9 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
 | 
				
			|||||||
    parser.add_argument("package", help="package source (base name, path to local files, remote URL)", nargs="+")
 | 
					    parser.add_argument("package", help="package source (base name, path to local files, remote URL)", nargs="+")
 | 
				
			||||||
    parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
 | 
					    parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
 | 
				
			||||||
    parser.add_argument("-n", "--now", help="run update function after", action="store_true")
 | 
					    parser.add_argument("-n", "--now", help="run update function after", action="store_true")
 | 
				
			||||||
 | 
					    parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
 | 
				
			||||||
 | 
					                                                "-yy to force refresh even if up to date",
 | 
				
			||||||
 | 
					                        action="count", default=0)
 | 
				
			||||||
    parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
 | 
					    parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
 | 
				
			||||||
                        type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto)
 | 
					                        type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto)
 | 
				
			||||||
    parser.add_argument("--without-dependencies", help="do not add dependencies", action="store_true")
 | 
					    parser.add_argument("--without-dependencies", help="do not add dependencies", action="store_true")
 | 
				
			||||||
@ -467,6 +473,9 @@ def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
 | 
				
			|||||||
    parser.add_argument("package", help="filter check by package base", nargs="*")
 | 
					    parser.add_argument("package", help="filter check by package base", nargs="*")
 | 
				
			||||||
    parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
 | 
					    parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
 | 
				
			||||||
    parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
 | 
					    parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
 | 
				
			||||||
 | 
					    parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
 | 
				
			||||||
 | 
					                                                "-yy to force refresh even if up to date",
 | 
				
			||||||
 | 
					                        action="count", default=0)
 | 
				
			||||||
    parser.set_defaults(handler=handlers.Update, dry_run=True, no_aur=False, no_local=False, no_manual=True)
 | 
					    parser.set_defaults(handler=handlers.Update, dry_run=True, no_aur=False, no_local=False, no_manual=True)
 | 
				
			||||||
    return parser
 | 
					    return parser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -717,6 +726,9 @@ def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
 | 
				
			|||||||
    parser.add_argument("--no-local", help="do not check local packages for updates", action="store_true")
 | 
					    parser.add_argument("--no-local", help="do not check local packages for updates", action="store_true")
 | 
				
			||||||
    parser.add_argument("--no-manual", help="do not include manual updates", action="store_true")
 | 
					    parser.add_argument("--no-manual", help="do not include manual updates", action="store_true")
 | 
				
			||||||
    parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
 | 
					    parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
 | 
				
			||||||
 | 
					    parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
 | 
				
			||||||
 | 
					                                                "-yy to force refresh even if up to date",
 | 
				
			||||||
 | 
					                        action="count", default=0)
 | 
				
			||||||
    parser.set_defaults(handler=handlers.Update)
 | 
					    parser.set_defaults(handler=handlers.Update)
 | 
				
			||||||
    return parser
 | 
					    return parser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -62,7 +62,7 @@ class Application(ApplicationPackages, ApplicationRepository):
 | 
				
			|||||||
            for package, properties in base.packages.items():
 | 
					            for package, properties in base.packages.items():
 | 
				
			||||||
                known_packages.add(package)
 | 
					                known_packages.add(package)
 | 
				
			||||||
                known_packages.update(properties.provides)
 | 
					                known_packages.update(properties.provides)
 | 
				
			||||||
        known_packages.update(self.repository.pacman.all_packages())
 | 
					        known_packages.update(self.repository.pacman.packages())
 | 
				
			||||||
        return known_packages
 | 
					        return known_packages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def on_result(self, result: Result) -> None:
 | 
					    def on_result(self, result: Result) -> None:
 | 
				
			||||||
 | 
				
			|||||||
@ -34,7 +34,8 @@ class ApplicationProperties(LazyLogging):
 | 
				
			|||||||
        repository(Repository): repository instance
 | 
					        repository(Repository): repository instance
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, architecture: str, configuration: Configuration, no_report: bool, unsafe: bool) -> None:
 | 
					    def __init__(self, architecture: str, configuration: Configuration,
 | 
				
			||||||
 | 
					                 no_report: bool, unsafe: bool, refresh_pacman_database: int = 0) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        default constructor
 | 
					        default constructor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -43,8 +44,10 @@ class ApplicationProperties(LazyLogging):
 | 
				
			|||||||
            configuration(Configuration): configuration instance
 | 
					            configuration(Configuration): configuration instance
 | 
				
			||||||
            no_report(bool): force disable reporting
 | 
					            no_report(bool): force disable reporting
 | 
				
			||||||
            unsafe(bool): if set no user check will be performed before path creation
 | 
					            unsafe(bool): if set no user check will be performed before path creation
 | 
				
			||||||
 | 
					            refresh_pacman_database(int): pacman database syncronization level, ``0`` is disabled
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self.configuration = configuration
 | 
					        self.configuration = configuration
 | 
				
			||||||
        self.architecture = architecture
 | 
					        self.architecture = architecture
 | 
				
			||||||
        self.database = SQLite.load(configuration)
 | 
					        self.database = SQLite.load(configuration)
 | 
				
			||||||
        self.repository = Repository(architecture, configuration, self.database, no_report, unsafe)
 | 
					        self.repository = Repository(architecture, configuration, self.database,
 | 
				
			||||||
 | 
					                                     no_report, unsafe, refresh_pacman_database)
 | 
				
			||||||
 | 
				
			|||||||
@ -44,7 +44,7 @@ class Add(Handler):
 | 
				
			|||||||
            no_report(bool): force disable reporting
 | 
					            no_report(bool): force disable reporting
 | 
				
			||||||
            unsafe(bool): if set no user check will be performed before path creation
 | 
					            unsafe(bool): if set no user check will be performed before path creation
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        application = Application(architecture, configuration, no_report, unsafe)
 | 
					        application = Application(architecture, configuration, no_report, unsafe, args.refresh)
 | 
				
			||||||
        application.on_start()
 | 
					        application.on_start()
 | 
				
			||||||
        application.add(args.package, args.source, args.without_dependencies)
 | 
					        application.add(args.package, args.source, args.without_dependencies)
 | 
				
			||||||
        if not args.now:
 | 
					        if not args.now:
 | 
				
			||||||
 | 
				
			|||||||
@ -20,6 +20,7 @@
 | 
				
			|||||||
import argparse
 | 
					import argparse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					from pwd import getpwuid
 | 
				
			||||||
from typing import Type
 | 
					from typing import Type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ahriman.application.application import Application
 | 
					from ahriman.application.application import Application
 | 
				
			||||||
@ -173,7 +174,9 @@ class Setup(Handler):
 | 
				
			|||||||
            packager(str): packager identifier (e.g. name, email)
 | 
					            packager(str): packager identifier (e.g. name, email)
 | 
				
			||||||
            paths(RepositoryPaths): repository paths instance
 | 
					            paths(RepositoryPaths): repository paths instance
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        (paths.root / ".makepkg.conf").write_text(f"PACKAGER='{packager}'\n", encoding="utf8")
 | 
					        uid, _ = paths.root_owner
 | 
				
			||||||
 | 
					        home_dir = Path(getpwuid(uid).pw_dir)
 | 
				
			||||||
 | 
					        (home_dir / ".makepkg.conf").write_text(f"PACKAGER='{packager}'\n", encoding="utf8")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def configuration_create_sudo(paths: RepositoryPaths, prefix: str, architecture: str) -> None:
 | 
					    def configuration_create_sudo(paths: RepositoryPaths, prefix: str, architecture: str) -> None:
 | 
				
			||||||
 | 
				
			|||||||
@ -44,7 +44,7 @@ class Update(Handler):
 | 
				
			|||||||
            no_report(bool): force disable reporting
 | 
					            no_report(bool): force disable reporting
 | 
				
			||||||
            unsafe(bool): if set no user check will be performed before path creation
 | 
					            unsafe(bool): if set no user check will be performed before path creation
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        application = Application(architecture, configuration, no_report, unsafe)
 | 
					        application = Application(architecture, configuration, no_report, unsafe, args.refresh)
 | 
				
			||||||
        application.on_start()
 | 
					        application.on_start()
 | 
				
			||||||
        packages = application.updates(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs,
 | 
					        packages = application.updates(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs,
 | 
				
			||||||
                                       Update.log_fn(application, args.dry_run))
 | 
					                                       Update.log_fn(application, args.dry_run))
 | 
				
			||||||
 | 
				
			|||||||
@ -17,13 +17,18 @@
 | 
				
			|||||||
# You should have received a copy of the GNU General Public License
 | 
					# You should have received a copy of the GNU General Public License
 | 
				
			||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
					# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
from pyalpm import Handle, Package, SIG_PACKAGE  # type: ignore
 | 
					import shutil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					from pyalpm import DB, Handle, Package, SIG_PACKAGE, error as PyalpmError  # type: ignore
 | 
				
			||||||
from typing import Generator, Set
 | 
					from typing import Generator, Set
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ahriman.core.configuration import Configuration
 | 
					from ahriman.core.configuration import Configuration
 | 
				
			||||||
 | 
					from ahriman.core.lazy_logging import LazyLogging
 | 
				
			||||||
 | 
					from ahriman.models.repository_paths import RepositoryPaths
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Pacman:
 | 
					class Pacman(LazyLogging):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    alpm wrapper
 | 
					    alpm wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -31,35 +36,96 @@ class Pacman:
 | 
				
			|||||||
        handle(Handle): pyalpm root ``Handle``
 | 
					        handle(Handle): pyalpm root ``Handle``
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, configuration: Configuration) -> None:
 | 
					    def __init__(self, architecture: str, configuration: Configuration, *, refresh_database: int) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        default constructor
 | 
					        default constructor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Args:
 | 
					        Args:
 | 
				
			||||||
 | 
					            architecture(str): repository architecture
 | 
				
			||||||
            configuration(Configuration): configuration instance
 | 
					            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
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        root = configuration.get("alpm", "root")
 | 
					        root = configuration.getpath("alpm", "root")
 | 
				
			||||||
        pacman_root = configuration.getpath("alpm", "database")
 | 
					        pacman_root = configuration.getpath("alpm", "database")
 | 
				
			||||||
        self.handle = Handle(root, str(pacman_root))
 | 
					        use_ahriman_cache = configuration.getboolean("alpm", "use_ahriman_cache")
 | 
				
			||||||
        for repository in configuration.getlist("alpm", "repositories"):
 | 
					        mirror = configuration.get("alpm", "mirror")
 | 
				
			||||||
            self.handle.register_syncdb(repository, SIG_PACKAGE)
 | 
					        paths = configuration.repository_paths
 | 
				
			||||||
 | 
					        database_path = paths.pacman if use_ahriman_cache else pacman_root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def all_packages(self) -> Set[str]:
 | 
					        self.handle = Handle(str(root), str(database_path))
 | 
				
			||||||
 | 
					        for repository in configuration.getlist("alpm", "repositories"):
 | 
				
			||||||
 | 
					            database = self.database_init(repository, mirror, architecture)
 | 
				
			||||||
 | 
					            self.database_copy(database, pacman_root, paths, use_ahriman_cache=use_ahriman_cache)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if use_ahriman_cache and refresh_database:
 | 
				
			||||||
 | 
					            self.database_sync(refresh_database > 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def database_copy(self, database: DB, pacman_root: Path, paths: RepositoryPaths, *,
 | 
				
			||||||
 | 
					                      use_ahriman_cache: bool) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        get list of packages known for alpm
 | 
					        copy database from the operating system root to the ahriman local home
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            database(DB): pacman database instance to be copied
 | 
				
			||||||
 | 
					            pacman_root(Path): operating system pacman's root
 | 
				
			||||||
 | 
					            paths(RepositoryPaths): repository paths instance
 | 
				
			||||||
 | 
					            use_ahriman_cache(bool): use local ahriman cache instead of system one
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        def repository_database(root: Path) -> Path:
 | 
				
			||||||
 | 
					            return root / "sync" / f"{database.name}.db"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not use_ahriman_cache:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        # copy root database if no local copy found
 | 
				
			||||||
 | 
					        pacman_db_path = Path(self.handle.dbpath)
 | 
				
			||||||
 | 
					        if not pacman_db_path.is_dir():
 | 
				
			||||||
 | 
					            return  # root directory does not exist yet
 | 
				
			||||||
 | 
					        dst = repository_database(pacman_db_path)
 | 
				
			||||||
 | 
					        if dst.is_file():
 | 
				
			||||||
 | 
					            return  # file already exists, do not copy
 | 
				
			||||||
 | 
					        src = repository_database(pacman_root)
 | 
				
			||||||
 | 
					        if not src.is_file():
 | 
				
			||||||
 | 
					            self.logger.warning("repository %s is set to be used, however, no working copy was found", database.name)
 | 
				
			||||||
 | 
					            return  # database for some reasons deos not exist
 | 
				
			||||||
 | 
					        self.logger.info("copy pacman database from operating system root to ahriman's home")
 | 
				
			||||||
 | 
					        shutil.copy(src, dst)
 | 
				
			||||||
 | 
					        paths.chown(dst)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def database_init(self, repository: str, mirror: str, architecture: str) -> DB:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        create database instance from pacman handler and set its properties
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            repository(str): pacman repository name (e.g. core)
 | 
				
			||||||
 | 
					            mirror(str): arch linux mirror url
 | 
				
			||||||
 | 
					            architecture(str): repository architecture
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Returns:
 | 
					        Returns:
 | 
				
			||||||
            Set[str]: list of package names
 | 
					            DB: loaded pacman database instance
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        result: Set[str] = set()
 | 
					        database: DB = self.handle.register_syncdb(repository, SIG_PACKAGE)
 | 
				
			||||||
 | 
					        # replace variables in mirror address
 | 
				
			||||||
 | 
					        database.servers = [mirror.replace("$repo", repository).replace("$arch", architecture)]
 | 
				
			||||||
 | 
					        return database
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def database_sync(self, force: bool) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        sync local database
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            force(bool): force database syncronization (same as ``pacman -Syy``)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.logger.info("refresh ahriman's home pacman database (force refresh %s)", force)
 | 
				
			||||||
 | 
					        transaction = self.handle.init_transaction()
 | 
				
			||||||
        for database in self.handle.get_syncdbs():
 | 
					        for database in self.handle.get_syncdbs():
 | 
				
			||||||
            for package in database.pkgcache:
 | 
					            try:
 | 
				
			||||||
                result.add(package.name)  # package itself
 | 
					                database.update(force)
 | 
				
			||||||
                result.update(package.provides)  # provides list for meta-packages
 | 
					            except PyalpmError:
 | 
				
			||||||
 | 
					                self.logger.exception("exception during update %s", database.name)
 | 
				
			||||||
 | 
					        transaction.release()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return result
 | 
					    def package_get(self, package_name: str) -> Generator[Package, None, None]:
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self, package_name: str) -> Generator[Package, None, None]:
 | 
					 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        retrieve list of the packages from the repository by name
 | 
					        retrieve list of the packages from the repository by name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -74,3 +140,18 @@ class Pacman:
 | 
				
			|||||||
            if package is None:
 | 
					            if package is None:
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
            yield package
 | 
					            yield package
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def packages(self) -> Set[str]:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        get list of packages known for alpm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Returns:
 | 
				
			||||||
 | 
					            Set[str]: list of package names
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        result: Set[str] = set()
 | 
				
			||||||
 | 
					        for database in self.handle.get_syncdbs():
 | 
				
			||||||
 | 
					            for package in database.pkgcache:
 | 
				
			||||||
 | 
					                result.add(package.name)  # package itself
 | 
				
			||||||
 | 
					                result.update(package.provides)  # provides list for meta-packages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return result
 | 
				
			||||||
 | 
				
			|||||||
@ -48,4 +48,4 @@ class OfficialSyncdb(Official):
 | 
				
			|||||||
        Returns:
 | 
					        Returns:
 | 
				
			||||||
            AURPackage: package which match the package name
 | 
					            AURPackage: package which match the package name
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        return next(AURPackage.from_pacman(package) for package in pacman.get(package_name))
 | 
					        return next(AURPackage.from_pacman(package) for package in pacman.package_get(package_name))
 | 
				
			||||||
 | 
				
			|||||||
@ -225,14 +225,14 @@ class Configuration(configparser.RawConfigParser):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    # pylint and mypy are too stupid to find these methods
 | 
					    # pylint and mypy are too stupid to find these methods
 | 
				
			||||||
    # pylint: disable=missing-function-docstring,multiple-statements,unused-argument
 | 
					    # pylint: disable=missing-function-docstring,multiple-statements,unused-argument
 | 
				
			||||||
    def getlist(self, *args: Any, **kwargs: Any) -> List[str]: ...
 | 
					    def getlist(self, *args: Any, **kwargs: Any) -> List[str]: ...  # type: ignore
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def getpath(self, *args: Any, **kwargs: Any) -> Path: ...
 | 
					    def getpath(self, *args: Any, **kwargs: Any) -> Path: ...  # type: ignore
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def gettype(self, section: str, architecture: str) -> Tuple[str, str]:
 | 
					    def gettype(self, section: str, architecture: str) -> Tuple[str, str]:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        get type variable with fallback to old logic
 | 
					        get type variable with fallback to old logic. Despite the fact that it has same semantics as other get* methods,
 | 
				
			||||||
        Despite the fact that it has same semantics as other get* methods, but it has different argument list
 | 
					        but it has different argument list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Args:
 | 
					        Args:
 | 
				
			||||||
            section(str): section name
 | 
					            section(str): section name
 | 
				
			||||||
 | 
				
			|||||||
@ -48,7 +48,7 @@ class RepositoryProperties(LazyLogging):
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, architecture: str, configuration: Configuration, database: SQLite,
 | 
					    def __init__(self, architecture: str, configuration: Configuration, database: SQLite,
 | 
				
			||||||
                 no_report: bool, unsafe: bool) -> None:
 | 
					                 no_report: bool, unsafe: bool, refresh_pacman_database: int = 0) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        default constructor
 | 
					        default constructor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -58,6 +58,7 @@ class RepositoryProperties(LazyLogging):
 | 
				
			|||||||
            database(SQLite): database instance
 | 
					            database(SQLite): database instance
 | 
				
			||||||
            no_report(bool): force disable reporting
 | 
					            no_report(bool): force disable reporting
 | 
				
			||||||
            unsafe(bool): if set no user check will be performed before path creation
 | 
					            unsafe(bool): if set no user check will be performed before path creation
 | 
				
			||||||
 | 
					            refresh_pacman_database(int): pacman database syncronization level, ``0`` is disabled
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self.architecture = architecture
 | 
					        self.architecture = architecture
 | 
				
			||||||
        self.configuration = configuration
 | 
					        self.configuration = configuration
 | 
				
			||||||
@ -73,7 +74,7 @@ class RepositoryProperties(LazyLogging):
 | 
				
			|||||||
            self.logger.warning("root owner differs from the current user, skipping tree creation")
 | 
					            self.logger.warning("root owner differs from the current user, skipping tree creation")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.ignore_list = configuration.getlist("build", "ignore_packages", fallback=[])
 | 
					        self.ignore_list = configuration.getlist("build", "ignore_packages", fallback=[])
 | 
				
			||||||
        self.pacman = Pacman(configuration)
 | 
					        self.pacman = Pacman(architecture, configuration, refresh_database=refresh_pacman_database)
 | 
				
			||||||
        self.sign = GPG(architecture, configuration)
 | 
					        self.sign = GPG(architecture, configuration)
 | 
				
			||||||
        self.repo = Repo(self.name, self.paths, self.sign.repository_sign_args)
 | 
					        self.repo = Repo(self.name, self.paths, self.sign.repository_sign_args)
 | 
				
			||||||
        self.reporter = Client() if no_report else Client.load(configuration)
 | 
					        self.reporter = Client() if no_report else Client.load(configuration)
 | 
				
			||||||
 | 
				
			|||||||
@ -67,8 +67,10 @@ class AURPackage:
 | 
				
			|||||||
            >>>
 | 
					            >>>
 | 
				
			||||||
            >>>
 | 
					            >>>
 | 
				
			||||||
            >>> from ahriman.core.alpm.pacman import Pacman
 | 
					            >>> from ahriman.core.alpm.pacman import Pacman
 | 
				
			||||||
 | 
					            >>> from ahriman.core.configuration import Configuration
 | 
				
			||||||
            >>>
 | 
					            >>>
 | 
				
			||||||
            >>> pacman = Pacman(configuration)
 | 
					            >>> configuration = Configuration()
 | 
				
			||||||
 | 
					            >>> pacman = Pacman("x86_64", configuration)
 | 
				
			||||||
            >>> metadata = pacman.get("pacman")
 | 
					            >>> metadata = pacman.get("pacman")
 | 
				
			||||||
            >>> package = AURPackage.from_pacman(next(metadata))  # load package from pyalpm wrapper
 | 
					            >>> package = AURPackage.from_pacman(next(metadata))  # load package from pyalpm wrapper
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
				
			|||||||
@ -57,7 +57,7 @@ class PackageDescription:
 | 
				
			|||||||
            >>> from ahriman.core.configuration import Configuration
 | 
					            >>> from ahriman.core.configuration import Configuration
 | 
				
			||||||
            >>>
 | 
					            >>>
 | 
				
			||||||
            >>> configuration = Configuration()
 | 
					            >>> configuration = Configuration()
 | 
				
			||||||
            >>> pacman = Pacman(configuration)
 | 
					            >>> pacman = Pacman("x86_64", configuration)
 | 
				
			||||||
            >>> pyalpm_description = next(package for package in pacman.get("pacman"))
 | 
					            >>> pyalpm_description = next(package for package in pacman.get("pacman"))
 | 
				
			||||||
            >>> description = PackageDescription.from_package(
 | 
					            >>> description = PackageDescription.from_package(
 | 
				
			||||||
            >>>     pyalpm_description, Path("/var/cache/pacman/pkg/pacman-6.0.1-4-x86_64.pkg.tar.zst"))
 | 
					            >>>     pyalpm_description, Path("/var/cache/pacman/pkg/pacman-6.0.1-4-x86_64.pkg.tar.zst"))
 | 
				
			||||||
 | 
				
			|||||||
@ -87,6 +87,16 @@ class RepositoryPaths:
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        return self.root / "packages" / self.architecture
 | 
					        return self.root / "packages" / self.architecture
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def pacman(self) -> Path:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        get directory for pacman local package cache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Returns:
 | 
				
			||||||
 | 
					            Path: full path to pacman local database cache
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return self.root / "pacman" / self.architecture
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def repository(self) -> Path:
 | 
					    def repository(self) -> Path:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@ -194,6 +204,7 @@ class RepositoryPaths:
 | 
				
			|||||||
                self.cache,
 | 
					                self.cache,
 | 
				
			||||||
                self.chroot,
 | 
					                self.chroot,
 | 
				
			||||||
                self.packages,
 | 
					                self.packages,
 | 
				
			||||||
 | 
					                self.pacman / "sync",  # we need sync directory in order to be able to copy databases
 | 
				
			||||||
                self.repository,
 | 
					                self.repository,
 | 
				
			||||||
        ):
 | 
					        ):
 | 
				
			||||||
            directory.mkdir(mode=0o755, parents=True, exist_ok=True)
 | 
					            directory.mkdir(mode=0o755, parents=True, exist_ok=True)
 | 
				
			||||||
 | 
				
			|||||||
@ -21,8 +21,8 @@ from __future__ import annotations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from dataclasses import dataclass, replace
 | 
					from dataclasses import dataclass, replace
 | 
				
			||||||
from typing import Optional, Type
 | 
					from typing import Optional, Type
 | 
				
			||||||
from passlib.pwd import genword as generate_password  # type: ignore
 | 
					from passlib.pwd import genword as generate_password
 | 
				
			||||||
from passlib.handlers.sha2_crypt import sha512_crypt  # type: ignore
 | 
					from passlib.handlers.sha2_crypt import sha512_crypt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ahriman.models.user_access import UserAccess
 | 
					from ahriman.models.user_access import UserAccess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										17
									
								
								tests/ahriman/application/handlers/conftest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								tests/ahriman/application/handlers/conftest.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from collections import namedtuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_passwd = namedtuple("passwd", ["pw_dir"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
					def passwd() -> _passwd:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    get passwd structure for the user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Returns:
 | 
				
			||||||
 | 
					        _passwd: passwd structure test instance
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    return _passwd("home")
 | 
				
			||||||
@ -23,6 +23,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
 | 
				
			|||||||
    args.package = []
 | 
					    args.package = []
 | 
				
			||||||
    args.exit_code = False
 | 
					    args.exit_code = False
 | 
				
			||||||
    args.now = False
 | 
					    args.now = False
 | 
				
			||||||
 | 
					    args.refresh = 0
 | 
				
			||||||
    args.source = PackageSource.Auto
 | 
					    args.source = PackageSource.Auto
 | 
				
			||||||
    args.without_dependencies = False
 | 
					    args.without_dependencies = False
 | 
				
			||||||
    return args
 | 
					    return args
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import pytest
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
from pytest_mock import MockerFixture
 | 
					from pytest_mock import MockerFixture
 | 
				
			||||||
 | 
					from typing import Any
 | 
				
			||||||
from unittest import mock
 | 
					from unittest import mock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ahriman.application.handlers import Setup
 | 
					from ahriman.application.handlers import Setup
 | 
				
			||||||
@ -130,15 +131,17 @@ def test_configuration_create_devtools_no_multilib(args: argparse.Namespace, rep
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_configuration_create_makepkg(args: argparse.Namespace, repository_paths: RepositoryPaths,
 | 
					def test_configuration_create_makepkg(args: argparse.Namespace, repository_paths: RepositoryPaths,
 | 
				
			||||||
                                      mocker: MockerFixture) -> None:
 | 
					                                      passwd: Any, mocker: MockerFixture) -> None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    must create makepkg configuration
 | 
					    must create makepkg configuration
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    args = _default_args(args)
 | 
					    args = _default_args(args)
 | 
				
			||||||
    write_text_mock = mocker.patch("pathlib.Path.write_text")
 | 
					    mocker.patch("ahriman.application.handlers.setup.getpwuid", return_value=passwd)
 | 
				
			||||||
 | 
					    write_text_mock = mocker.patch("pathlib.Path.write_text", autospec=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Setup.configuration_create_makepkg(args.packager, repository_paths)
 | 
					    Setup.configuration_create_makepkg(args.packager, repository_paths)
 | 
				
			||||||
    write_text_mock.assert_called_once_with(pytest.helpers.anyvar(str, True), encoding="utf8")
 | 
					    write_text_mock.assert_called_once_with(
 | 
				
			||||||
 | 
					        Path("home") / ".makepkg.conf", pytest.helpers.anyvar(str, True), encoding="utf8")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_configuration_create_sudo(args: argparse.Namespace, repository_paths: RepositoryPaths,
 | 
					def test_configuration_create_sudo(args: argparse.Namespace, repository_paths: RepositoryPaths,
 | 
				
			||||||
 | 
				
			|||||||
@ -28,6 +28,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
 | 
				
			|||||||
    args.no_local = False
 | 
					    args.no_local = False
 | 
				
			||||||
    args.no_manual = False
 | 
					    args.no_manual = False
 | 
				
			||||||
    args.no_vcs = False
 | 
					    args.no_vcs = False
 | 
				
			||||||
 | 
					    args.refresh = 0
 | 
				
			||||||
    return args
 | 
					    return args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -75,6 +75,18 @@ def test_subparsers_daemon(parser: argparse.ArgumentParser) -> None:
 | 
				
			|||||||
    assert args.package == []
 | 
					    assert args.package == []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_subparsers_daemon_option_refresh(parser: argparse.ArgumentParser) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    daemon command must count refresh options
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    args = parser.parse_args(["daemon"])
 | 
				
			||||||
 | 
					    assert args.refresh == 0
 | 
				
			||||||
 | 
					    args = parser.parse_args(["daemon", "-y"])
 | 
				
			||||||
 | 
					    assert args.refresh == 1
 | 
				
			||||||
 | 
					    args = parser.parse_args(["daemon", "-yy"])
 | 
				
			||||||
 | 
					    assert args.refresh == 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_subparsers_daemon_option_interval(parser: argparse.ArgumentParser) -> None:
 | 
					def test_subparsers_daemon_option_interval(parser: argparse.ArgumentParser) -> None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    daemon command must convert interval option to int instance
 | 
					    daemon command must convert interval option to int instance
 | 
				
			||||||
@ -155,6 +167,18 @@ def test_subparsers_package_add_architecture(parser: argparse.ArgumentParser) ->
 | 
				
			|||||||
    assert args.architecture == ["x86_64"]
 | 
					    assert args.architecture == ["x86_64"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_subparsers_package_add_option_refresh(parser: argparse.ArgumentParser) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    package-add command must count refresh options
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    args = parser.parse_args(["package-add", "ahriman"])
 | 
				
			||||||
 | 
					    assert args.refresh == 0
 | 
				
			||||||
 | 
					    args = parser.parse_args(["package-add", "ahriman", "-y"])
 | 
				
			||||||
 | 
					    assert args.refresh == 1
 | 
				
			||||||
 | 
					    args = parser.parse_args(["package-add", "ahriman", "-yy"])
 | 
				
			||||||
 | 
					    assert args.refresh == 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_subparsers_package_remove_architecture(parser: argparse.ArgumentParser) -> None:
 | 
					def test_subparsers_package_remove_architecture(parser: argparse.ArgumentParser) -> None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    package-remove command must correctly parse architecture list
 | 
					    package-remove command must correctly parse architecture list
 | 
				
			||||||
@ -345,6 +369,18 @@ def test_subparsers_repo_check_architecture(parser: argparse.ArgumentParser) ->
 | 
				
			|||||||
    assert args.architecture == ["x86_64"]
 | 
					    assert args.architecture == ["x86_64"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_subparsers_repo_check_option_refresh(parser: argparse.ArgumentParser) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    repo-check command must count refresh options
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    args = parser.parse_args(["repo-check"])
 | 
				
			||||||
 | 
					    assert args.refresh == 0
 | 
				
			||||||
 | 
					    args = parser.parse_args(["repo-check", "-y"])
 | 
				
			||||||
 | 
					    assert args.refresh == 1
 | 
				
			||||||
 | 
					    args = parser.parse_args(["repo-check", "-yy"])
 | 
				
			||||||
 | 
					    assert args.refresh == 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_subparsers_repo_clean(parser: argparse.ArgumentParser) -> None:
 | 
					def test_subparsers_repo_clean(parser: argparse.ArgumentParser) -> None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    repo-clean command must imply quiet and unsafe
 | 
					    repo-clean command must imply quiet and unsafe
 | 
				
			||||||
@ -540,6 +576,18 @@ def test_subparsers_repo_update_architecture(parser: argparse.ArgumentParser) ->
 | 
				
			|||||||
    assert args.architecture == ["x86_64"]
 | 
					    assert args.architecture == ["x86_64"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_subparsers_repo_update_option_refresh(parser: argparse.ArgumentParser) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    repo-update command must count refresh options
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    args = parser.parse_args(["repo-update"])
 | 
				
			||||||
 | 
					    assert args.refresh == 0
 | 
				
			||||||
 | 
					    args = parser.parse_args(["repo-update", "-y"])
 | 
				
			||||||
 | 
					    assert args.refresh == 1
 | 
				
			||||||
 | 
					    args = parser.parse_args(["repo-update", "-yy"])
 | 
				
			||||||
 | 
					    assert args.refresh == 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_subparsers_shell(parser: argparse.ArgumentParser) -> None:
 | 
					def test_subparsers_shell(parser: argparse.ArgumentParser) -> None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    shell command must imply lock and no-report
 | 
					    shell command must imply lock and no-report
 | 
				
			||||||
 | 
				
			|||||||
@ -360,7 +360,7 @@ def pacman(configuration: Configuration) -> Pacman:
 | 
				
			|||||||
    Returns:
 | 
					    Returns:
 | 
				
			||||||
        Pacman: pacman wrapper test instance
 | 
					        Pacman: pacman wrapper test instance
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    return Pacman(configuration)
 | 
					    return Pacman("x86_64", configuration, refresh_database=0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.fixture
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,7 @@ def test_package_info(official_syncdb: OfficialSyncdb, aur_package_akonadi: AURP
 | 
				
			|||||||
    must return package info from the database
 | 
					    must return package info from the database
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    mocker.patch("ahriman.models.aur_package.AURPackage.from_pacman", return_value=aur_package_akonadi)
 | 
					    mocker.patch("ahriman.models.aur_package.AURPackage.from_pacman", return_value=aur_package_akonadi)
 | 
				
			||||||
    get_mock = mocker.patch("ahriman.core.alpm.pacman.Pacman.get", return_value=[aur_package_akonadi])
 | 
					    get_mock = mocker.patch("ahriman.core.alpm.pacman.Pacman.package_get", return_value=[aur_package_akonadi])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    package = official_syncdb.package_info(aur_package_akonadi.name, pacman=pacman)
 | 
					    package = official_syncdb.package_info(aur_package_akonadi.name, pacman=pacman)
 | 
				
			||||||
    get_mock.assert_called_once_with(aur_package_akonadi.name)
 | 
					    get_mock.assert_called_once_with(aur_package_akonadi.name)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,31 +1,205 @@
 | 
				
			|||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					from pyalpm import error as PyalpmError
 | 
				
			||||||
 | 
					from pytest_mock import MockerFixture
 | 
				
			||||||
 | 
					from tempfile import TemporaryDirectory
 | 
				
			||||||
 | 
					from unittest.mock import MagicMock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ahriman.core.alpm.pacman import Pacman
 | 
					from ahriman.core.alpm.pacman import Pacman
 | 
				
			||||||
 | 
					from ahriman.core.configuration import Configuration
 | 
				
			||||||
 | 
					from ahriman.models.repository_paths import RepositoryPaths
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_all_packages(pacman: Pacman) -> None:
 | 
					def test_init_with_local_cache(configuration: Configuration, mocker: MockerFixture) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must sync repositories at the start if set
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    mocker.patch("ahriman.core.alpm.pacman.Pacman.database_copy")
 | 
				
			||||||
 | 
					    sync_mock = mocker.patch("ahriman.core.alpm.pacman.Pacman.database_sync")
 | 
				
			||||||
 | 
					    configuration.set_option("alpm", "use_ahriman_cache", "yes")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # pyalpm.Handle is trying to reach the directory we've asked, thus we need to patch it a bit
 | 
				
			||||||
 | 
					    with TemporaryDirectory(ignore_cleanup_errors=True) as 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
 | 
				
			||||||
 | 
					        Pacman("x86_64", configuration, refresh_database=1)
 | 
				
			||||||
 | 
					        sync_mock.assert_called_once_with(False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_init_with_local_cache_forced(configuration: Configuration, mocker: MockerFixture) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must sync repositories at the start if set with force flag
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    mocker.patch("ahriman.core.alpm.pacman.Pacman.database_copy")
 | 
				
			||||||
 | 
					    sync_mock = mocker.patch("ahriman.core.alpm.pacman.Pacman.database_sync")
 | 
				
			||||||
 | 
					    configuration.set_option("alpm", "use_ahriman_cache", "yes")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # pyalpm.Handle is trying to reach the directory we've asked, thus we need to patch it a bit
 | 
				
			||||||
 | 
					    with TemporaryDirectory(ignore_cleanup_errors=True) as 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
 | 
				
			||||||
 | 
					        Pacman("x86_64", configuration, refresh_database=2)
 | 
				
			||||||
 | 
					        sync_mock.assert_called_once_with(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_database_copy(pacman: Pacman, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must copy database from root
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    database = next(db for db in pacman.handle.get_syncdbs() if db.name == "core")
 | 
				
			||||||
 | 
					    path = Path("randomname")
 | 
				
			||||||
 | 
					    dst_path = Path("/var/lib/pacman/sync/core.db")
 | 
				
			||||||
 | 
					    mocker.patch("pathlib.Path.is_dir", return_value=True)
 | 
				
			||||||
 | 
					    # root database exists, local database does not
 | 
				
			||||||
 | 
					    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")
 | 
				
			||||||
 | 
					    chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pacman.database_copy(database, path, repository_paths, use_ahriman_cache=True)
 | 
				
			||||||
 | 
					    copy_mock.assert_called_once_with(path / "sync" / "core.db", dst_path)
 | 
				
			||||||
 | 
					    chown_mock.assert_called_once_with(dst_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_database_copy_skip(pacman: Pacman, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must do not copy database from root if local cache is disabled
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    database = next(db for db in pacman.handle.get_syncdbs() if db.name == "core")
 | 
				
			||||||
 | 
					    path = Path("randomname")
 | 
				
			||||||
 | 
					    mocker.patch("pathlib.Path.is_dir", return_value=True)
 | 
				
			||||||
 | 
					    # root database exists, local database does not
 | 
				
			||||||
 | 
					    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")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pacman.database_copy(database, path, repository_paths, use_ahriman_cache=False)
 | 
				
			||||||
 | 
					    copy_mock.assert_not_called()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_database_copy_no_directory(pacman: Pacman, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must do not copy database if local cache already exists
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    database = next(db for db in pacman.handle.get_syncdbs() if db.name == "core")
 | 
				
			||||||
 | 
					    path = Path("randomname")
 | 
				
			||||||
 | 
					    mocker.patch("pathlib.Path.is_dir", return_value=False)
 | 
				
			||||||
 | 
					    # root database exists, local database does not
 | 
				
			||||||
 | 
					    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")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pacman.database_copy(database, path, repository_paths, use_ahriman_cache=True)
 | 
				
			||||||
 | 
					    copy_mock.assert_not_called()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_database_copy_no_root_file(pacman: Pacman, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must do not copy database if no repository file exists in filesystem
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    database = next(db for db in pacman.handle.get_syncdbs() if db.name == "core")
 | 
				
			||||||
 | 
					    path = Path("randomname")
 | 
				
			||||||
 | 
					    mocker.patch("pathlib.Path.is_dir", return_value=True)
 | 
				
			||||||
 | 
					    # root database does not exist, local database does not either
 | 
				
			||||||
 | 
					    mocker.patch("pathlib.Path.is_file", return_value=False)
 | 
				
			||||||
 | 
					    copy_mock = mocker.patch("shutil.copy")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pacman.database_copy(database, path, repository_paths, use_ahriman_cache=True)
 | 
				
			||||||
 | 
					    copy_mock.assert_not_called()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_database_copy_database_exist(pacman: Pacman, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must do not copy database if local cache already exists
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    database = next(db for db in pacman.handle.get_syncdbs() if db.name == "core")
 | 
				
			||||||
 | 
					    mocker.patch("pathlib.Path.is_dir", return_value=True)
 | 
				
			||||||
 | 
					    # root database exists, local database does either
 | 
				
			||||||
 | 
					    mocker.patch("pathlib.Path.is_file", return_value=True)
 | 
				
			||||||
 | 
					    copy_mock = mocker.patch("shutil.copy")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pacman.database_copy(database, Path("root"), repository_paths, use_ahriman_cache=True)
 | 
				
			||||||
 | 
					    copy_mock.assert_not_called()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_database_init(pacman: Pacman, configuration: Configuration) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must init database with settings
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    mirror = configuration.get("alpm", "mirror")
 | 
				
			||||||
 | 
					    database = pacman.database_init("test", mirror, "x86_64")
 | 
				
			||||||
 | 
					    assert len(database.servers) == 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_database_sync(pacman: Pacman) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must sync databases
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    handle_mock = MagicMock()
 | 
				
			||||||
 | 
					    core_mock = MagicMock()
 | 
				
			||||||
 | 
					    extra_mock = MagicMock()
 | 
				
			||||||
 | 
					    transaction_mock = MagicMock()
 | 
				
			||||||
 | 
					    handle_mock.get_syncdbs.return_value = [core_mock, extra_mock]
 | 
				
			||||||
 | 
					    handle_mock.init_transaction.return_value = transaction_mock
 | 
				
			||||||
 | 
					    pacman.handle = handle_mock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pacman.database_sync(False)
 | 
				
			||||||
 | 
					    handle_mock.init_transaction.assert_called_once_with()
 | 
				
			||||||
 | 
					    core_mock.update.assert_called_once_with(False)
 | 
				
			||||||
 | 
					    extra_mock.update.assert_called_once_with(False)
 | 
				
			||||||
 | 
					    transaction_mock.release.assert_called_once_with()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_database_sync_failed(pacman: Pacman) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must sync databases even if there was exception
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    handle_mock = MagicMock()
 | 
				
			||||||
 | 
					    core_mock = MagicMock()
 | 
				
			||||||
 | 
					    core_mock.update.side_effect = PyalpmError()
 | 
				
			||||||
 | 
					    extra_mock = MagicMock()
 | 
				
			||||||
 | 
					    handle_mock.get_syncdbs.return_value = [core_mock, extra_mock]
 | 
				
			||||||
 | 
					    pacman.handle = handle_mock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pacman.database_sync(False)
 | 
				
			||||||
 | 
					    extra_mock.update.assert_called_once_with(False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_database_sync_forced(pacman: Pacman) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must sync databases with force flag
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    handle_mock = MagicMock()
 | 
				
			||||||
 | 
					    core_mock = MagicMock()
 | 
				
			||||||
 | 
					    handle_mock.get_syncdbs.return_value = [core_mock]
 | 
				
			||||||
 | 
					    pacman.handle = handle_mock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pacman.database_sync(True)
 | 
				
			||||||
 | 
					    handle_mock.init_transaction.assert_called_once_with()
 | 
				
			||||||
 | 
					    core_mock.update.assert_called_once_with(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_package_get(pacman: Pacman) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must retrieve package
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    assert list(pacman.package_get("pacman"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_package_get_empty(pacman: Pacman) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must return empty packages list without exception
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    assert not list(pacman.package_get("some-random-name"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_packages(pacman: Pacman) -> None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    package list must not be empty
 | 
					    package list must not be empty
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    packages = pacman.all_packages()
 | 
					    packages = pacman.packages()
 | 
				
			||||||
    assert packages
 | 
					    assert packages
 | 
				
			||||||
    assert "pacman" in packages
 | 
					    assert "pacman" in packages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_all_packages_with_provides(pacman: Pacman) -> None:
 | 
					def test_packages_with_provides(pacman: Pacman) -> None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    package list must contain provides packages
 | 
					    package list must contain provides packages
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    assert "sh" in pacman.all_packages()
 | 
					    assert "sh" in pacman.packages()
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def test_get(pacman: Pacman) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    must retrieve package
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    assert list(pacman.get("pacman"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def test_get_empty(pacman: Pacman) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    must return empty packages list without exception
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    assert not list(pacman.get("some-random-name"))
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -154,9 +154,5 @@ def test_tree_create(repository_paths: RepositoryPaths, mocker: MockerFixture) -
 | 
				
			|||||||
    chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
 | 
					    chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    repository_paths.tree_create()
 | 
					    repository_paths.tree_create()
 | 
				
			||||||
    mkdir_mock.assert_has_calls(
 | 
					    mkdir_mock.assert_has_calls([mock.call(mode=0o755, parents=True, exist_ok=True) for _ in paths], any_order=True)
 | 
				
			||||||
        [
 | 
					    chown_mock.assert_has_calls([mock.call(pytest.helpers.anyvar(int)) for _ in paths], any_order=True)
 | 
				
			||||||
            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)
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -5,8 +5,10 @@ database = ../../../ahriman-test.db
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[alpm]
 | 
					[alpm]
 | 
				
			||||||
database = /var/lib/pacman
 | 
					database = /var/lib/pacman
 | 
				
			||||||
 | 
					mirror = https://geo.mirror.pkgbuild.com/$repo/os/$arch
 | 
				
			||||||
repositories = core extra community multilib
 | 
					repositories = core extra community multilib
 | 
				
			||||||
root = /
 | 
					root = /
 | 
				
			||||||
 | 
					use_ahriman_cache = no
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[auth]
 | 
					[auth]
 | 
				
			||||||
client_id = client_id
 | 
					client_id = client_id
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user