mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-04-07 11:03:37 +00:00
Compare commits
14 Commits
2.20.0rc1
...
bb01f72931
| Author | SHA1 | Date | |
|---|---|---|---|
| bb01f72931 | |||
| e2812c071b | |||
| 9ccff5657b | |||
| 4dd578ce10 | |||
| 49e558e4e6 | |||
| 80ed47b4ed | |||
| 5ee949b5ec | |||
| 61057c9f91 | |||
| 8142a3d797 | |||
| aae536204e | |||
| 27f5a5f5d1 | |||
| 918273c1bb | |||
| c651db85ee | |||
| afd62e88f6 |
2
.github/workflows/setup.sh
vendored
2
.github/workflows/setup.sh
vendored
@@ -10,7 +10,7 @@ echo -e '[arcanisrepo]\nServer = https://repo.arcanis.me/$arch\nSigLevel = Never
|
||||
# refresh the image
|
||||
pacman -Syyu --noconfirm
|
||||
# main dependencies
|
||||
pacman -S --noconfirm devtools git pyalpm python-bcrypt python-filelock python-inflection python-pyelftools python-requests python-systemd sudo
|
||||
pacman -S --noconfirm devtools git pyalpm python-bcrypt python-inflection python-pyelftools python-requests python-systemd sudo
|
||||
# make dependencies
|
||||
pacman -S --noconfirm --asdeps base-devel python-build python-flit python-installer python-tox python-wheel
|
||||
# optional dependencies
|
||||
|
||||
@@ -24,8 +24,7 @@ RUN pacman -S --noconfirm --asdeps \
|
||||
devtools \
|
||||
git \
|
||||
pyalpm \
|
||||
python-bcrypt \
|
||||
python-filelock \
|
||||
python-bcrypt \
|
||||
python-inflection \
|
||||
python-pyelftools \
|
||||
python-requests \
|
||||
|
||||
2937
docs/_static/architecture.dot
vendored
2937
docs/_static/architecture.dot
vendored
File diff suppressed because it is too large
Load Diff
@@ -12,14 +12,6 @@ ahriman.core.build\_tools.package\_archive module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.build\_tools.package\_version module
|
||||
-------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.build_tools.package_version
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.build\_tools.sources module
|
||||
----------------------------------------
|
||||
|
||||
|
||||
@@ -28,14 +28,6 @@ ahriman.core.repository.executor module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.repository.explorer module
|
||||
---------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.repository.explorer
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.repository.package\_info module
|
||||
--------------------------------------------
|
||||
|
||||
|
||||
@@ -120,20 +120,6 @@ Having default root as ``/var/lib/ahriman`` (differs from container though), the
|
||||
|
||||
/var/lib/ahriman/
|
||||
├── ahriman.db
|
||||
├── archive
|
||||
│ ├── packages
|
||||
│ │ └── a
|
||||
│ │ └── ahriman
|
||||
│ │ └── ahriman-2.0.0-1-any.pkg.tar.zst
|
||||
│ └── repos
|
||||
│ └── 2026
|
||||
│ └── 01
|
||||
│ └── 01
|
||||
│ └── aur
|
||||
│ └── x86_64
|
||||
│ ├── ahriman-2.0.0-1-any.pkg.tar.zst -> ../../../../../../packages/a/ahriman/ahriman-2.0.0-1-any.pkg.tar.zst
|
||||
│ ├── aur.db -> aur.db.tar.gz
|
||||
│ └── aur.db.tar.gz
|
||||
├── cache
|
||||
├── chroot
|
||||
│ └── aur
|
||||
@@ -153,7 +139,6 @@ Having default root as ``/var/lib/ahriman`` (differs from container though), the
|
||||
└── repository
|
||||
└── aur
|
||||
└── x86_64
|
||||
├── ahriman-2.0.0-1-any.pkg.tar.zst -> ../../../archive/packages/a/ahriman/ahriman-2.0.0-1-any.pkg.tar.zst
|
||||
├── aur.db -> aur.db.tar.gz
|
||||
├── aur.db.tar.gz
|
||||
├── aur.files -> aur.files.tar.gz
|
||||
@@ -161,18 +146,11 @@ Having default root as ``/var/lib/ahriman`` (differs from container though), the
|
||||
|
||||
There are multiple subdirectories, some of them are commons for any repository, but some of them are not.
|
||||
|
||||
* ``archive`` is the package archive directory. It is common for all repositories and architectures and contains two subdirectories:
|
||||
|
||||
* ``archive/packages/{first_letter}/{package_base}`` stores the actual built package files and their signatures.
|
||||
* ``archive/repos/{YYYY}/{MM}/{DD}/{repository}/{architecture}`` contains daily repository snapshots. Each snapshot is a repository database with symlinks pointing to the corresponding packages in the ``archive/packages`` tree.
|
||||
|
||||
The archive also allows the build process to skip rebuilding a package if a matching version already exists.
|
||||
|
||||
* ``cache`` is a directory with locally stored PKGBUILD's and VCS packages. It is common for all repositories and architectures.
|
||||
* ``chroot/{repository}`` is a chroot directory for ``devtools``. It is specific for each repository, but shared for different architectures inside (the ``devtools`` handles architectures automatically).
|
||||
* ``packages/{repository}/{architecture}`` is a directory with prebuilt packages. When a package is built, first it will be uploaded to this directory and later will be handled by update process. It is architecture and repository specific.
|
||||
* ``pacman/{repository}/{architecture}`` is the repository and architecture specific caches for pacman's databases.
|
||||
* ``repository/{repository}/{architecture}`` is a repository packages directory. Package files in this directory are symlinks to the archive.
|
||||
* ``repository/{repository}/{architecture}`` is a repository packages directory.
|
||||
|
||||
Normally you should avoid direct interaction with the application tree. For tree migration process refer to the :doc:`migration notes <migrations/index>`.
|
||||
|
||||
|
||||
@@ -97,6 +97,13 @@ libalpm and AUR related configuration. Group name can refer to architecture, e.g
|
||||
* ``sync_files_database`` - download files database from mirror, boolean, 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). If set to ``no``, databases must be synchronized manually.
|
||||
|
||||
``archive`` group
|
||||
-----------------
|
||||
|
||||
Describes settings for packages archives management extensions.
|
||||
|
||||
* ``keep_built_packages`` - keep this amount of built packages with different versions, integer, required. ``0`` (or negative number) will effectively disable archives removal.
|
||||
|
||||
``auth`` group
|
||||
--------------
|
||||
|
||||
@@ -182,13 +189,6 @@ Web server settings. This feature requires ``aiohttp`` libraries to be installed
|
||||
* ``unix_socket_unsafe`` - set unsafe (o+w) permissions to unix socket, boolean, optional, default ``yes``. This option is enabled by default, because it is supposed that unix socket is created in safe environment (only web service is supposed to be used in unsafe), but it can be disabled by configuration.
|
||||
* ``wait_timeout`` - wait timeout in seconds, maximum amount of time to be waited before lock will be free, integer, optional.
|
||||
|
||||
``archive`` group
|
||||
-----------------
|
||||
|
||||
Describes settings for packages archives management extensions.
|
||||
|
||||
* ``keep_built_packages`` - keep this amount of built packages with different versions, integer, required. ``0`` will effectively disable archives removal.
|
||||
|
||||
``keyring`` group
|
||||
-----------------
|
||||
|
||||
@@ -208,12 +208,12 @@ Keyring generator plugin
|
||||
* ``revoked`` - list of revoked packagers keys, space separated list of strings, optional.
|
||||
* ``trusted`` - list of master keys, space separated list of strings, optional, if not set, the ``key`` option from ``sign`` group will be used.
|
||||
|
||||
``logs-rotation`` group
|
||||
-----------------------
|
||||
``housekeeping`` group
|
||||
----------------------
|
||||
|
||||
This section describes settings for the ``ahriman.core.housekeeping.LogsRotationTrigger`` plugin.
|
||||
|
||||
* ``keep_last_logs`` - amount of build logs to be kept for each package, integer, required. Logs will be cleared at the end of each process.
|
||||
* ``keep_last_logs`` - amount of build logs to be kept for each package, integer, optional ,default ``0``. Logs will be cleared at the end of each process.
|
||||
|
||||
``mirrorlist`` group
|
||||
--------------------
|
||||
@@ -250,7 +250,6 @@ Available options are:
|
||||
Remote pull trigger
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
* ``type`` - type of the pull, string, optional, must be set to ``gitremote`` if exists.
|
||||
* ``pull_url`` - URL of the remote repository from which PKGBUILDs can be pulled before build process, string, required.
|
||||
* ``pull_branch`` - branch of the remote repository from which PKGBUILDs can be pulled before build process, string, optional, default is ``master``.
|
||||
|
||||
@@ -271,7 +270,6 @@ Available options are:
|
||||
Remote push trigger
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
* ``type`` - type of the push, string, optional, must be set to ``gitremote`` if exists.
|
||||
* ``commit_email`` - git commit email, string, optional, default is ``ahriman@localhost``.
|
||||
* ``commit_user`` - git commit user, string, optional, default is ``ahriman``.
|
||||
* ``push_url`` - URL of the remote repository to which PKGBUILDs should be pushed after build process, string, required.
|
||||
|
||||
@@ -40,8 +40,6 @@ docutils==0.21.2
|
||||
# sphinx
|
||||
# sphinx-argparse
|
||||
# sphinx-rtd-theme
|
||||
filelock==3.24.0
|
||||
# via ahriman (pyproject.toml)
|
||||
frozenlist==1.6.0
|
||||
# via
|
||||
# aiohttp
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Triggers
|
||||
========
|
||||
|
||||
The package provides ability to write custom extensions which will be run on (the most) actions, e.g. after updates. By default ahriman provides several types of extensions - reporting, files uploading, PKGBUILD synchronization, repository archiving, housekeeping and distributed builds support. Each extension must derive from the ``ahriman.core.triggers.Trigger`` class and should implement at least one of the abstract methods:
|
||||
The package provides ability to write custom extensions which will be run on (the most) actions, e.g. after updates. By default ahriman provides three types of extensions - reporting, files uploading and PKGBUILD synchronization. Each extension must derive from the ``ahriman.core.triggers.Trigger`` class and should implement at least one of the abstract methods:
|
||||
|
||||
* ``on_result`` - trigger action which will be called after build process, the build result and the list of repository packages will be supplied as arguments.
|
||||
* ``on_start`` - trigger action which will be called right before the start of the application process.
|
||||
@@ -14,11 +14,6 @@ Built-in triggers
|
||||
|
||||
For the configuration details and settings explanation kindly refer to the :doc:`documentation <configuration>`.
|
||||
|
||||
``ahriman.core.archive.ArchiveTrigger``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This trigger provides date-based snapshots of the repository. It organizes packages into a daily directory tree (``repos/YYYY/MM/DD``) with its own pacman database. On each run it creates symlinks from the daily snapshot to the actual package archives and maintains the database accordingly. It also takes care of cleaning up broken symlinks and empty directories for packages which have been removed.
|
||||
|
||||
``ahriman.core.distributed.WorkerLoaderTrigger``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -41,16 +36,6 @@ In order to update those packages you would need to clone your repository separa
|
||||
|
||||
This trigger will be called right after build process (``on_result``). It will pick PKGBUILDs for the updated packages, pull them (together with any other files) and commit and push changes to remote repository. No real use cases, but the most of user repositories do it.
|
||||
|
||||
``ahriman.core.housekeeping.ArchiveRotationTrigger``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This trigger removes old package versions from the archive directory. It implements ``on_result`` and, after each build, compares available versions for updated packages and removes the older ones, keeping only the last N versions as configured by ``keep_built_packages`` option.
|
||||
|
||||
``ahriman.core.housekeeping.LogsRotationTrigger``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Simple trigger to rotate build logs. It implements ``on_result`` and removes old log records after each build process, keeping only the last N records as configured by ``keep_last_logs`` option.
|
||||
|
||||
``ahriman.core.report.ReportTrigger``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
pkgbase='ahriman'
|
||||
pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web')
|
||||
pkgver=2.20.0rc1
|
||||
pkgver=2.19.0
|
||||
pkgrel=1
|
||||
pkgdesc="ArcH linux ReposItory MANager"
|
||||
arch=('any')
|
||||
url="https://ahriman.readthedocs.io/"
|
||||
license=('GPL-3.0-or-later')
|
||||
depends=('devtools>=1:1.0.0' 'git' 'pyalpm' 'python-bcrypt' 'python-filelock' 'python-inflection' 'python-pyelftools' 'python-requests')
|
||||
depends=('devtools>=1:1.0.0' 'git' 'pyalpm' 'python-bcrypt' 'python-inflection' 'python-pyelftools' 'python-requests')
|
||||
makedepends=('python-build' 'python-flit' 'python-installer' 'python-wheel')
|
||||
source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgbase-$pkgver.tar.gz"
|
||||
"$pkgbase.sysusers"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# AUTOMATICALLY GENERATED by `shtab`
|
||||
|
||||
_shtab_ahriman_subparsers=('add' 'aur-search' 'check' 'clean' 'config' 'config-validate' 'copy' 'daemon' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'init' 'key-import' 'package-add' 'package-changes' 'package-changes-remove' 'package-copy' 'package-remove' 'package-status' 'package-status-remove' 'package-status-update' 'package-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'rebuild' 'remove' 'remove-unknown' 'repo-backup' 'repo-check' 'repo-clean' 'repo-config' 'repo-config-validate' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'repo-init' 'repo-rebuild' 'repo-remove-unknown' 'repo-report' 'repo-restore' 'repo-setup' 'repo-sign' 'repo-statistics' 'repo-status-update' 'repo-sync' 'repo-tree' 'repo-triggers' 'repo-update' 'report' 'run' 'search' 'service-clean' 'service-config' 'service-config-validate' 'service-key-import' 'service-repositories' 'service-run' 'service-setup' 'service-shell' 'service-tree-migrate' 'setup' 'shell' 'sign' 'status' 'status-update' 'sync' 'update' 'user-add' 'user-list' 'user-remove' 'version' 'web' 'web-reload')
|
||||
_shtab_ahriman_subparsers=('add' 'aur-search' 'check' 'clean' 'config' 'config-validate' 'copy' 'daemon' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'init' 'key-import' 'package-add' 'package-changes' 'package-changes-remove' 'package-copy' 'package-remove' 'package-status' 'package-status-remove' 'package-status-update' 'package-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'rebuild' 'remove' 'remove-unknown' 'repo-backup' 'repo-check' 'repo-clean' 'repo-config' 'repo-config-validate' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'repo-init' 'repo-rebuild' 'repo-remove-unknown' 'repo-report' 'repo-restore' 'repo-setup' 'repo-sign' 'repo-statistics' 'repo-status-update' 'repo-sync' 'repo-tree' 'repo-triggers' 'repo-update' 'report' 'run' 'search' 'service-clean' 'service-config' 'service-config-validate' 'service-key-import' 'service-repositories' 'service-run' 'service-setup' 'service-shell' 'service-tree-migrate' 'setup' 'shell' 'sign' 'status' 'status-update' 'sync' 'update' 'user-add' 'user-list' 'user-remove' 'version' 'web')
|
||||
|
||||
_shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--log-handler' '-q' '--quiet' '--report' '--no-report' '-r' '--repository' '--unsafe' '-V' '--version' '--wait-timeout')
|
||||
_shtab_ahriman_add_option_strings=('-h' '--help' '--changes' '--no-changes' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username' '-v' '--variable')
|
||||
@@ -78,11 +78,10 @@ _shtab_ahriman_user_list_option_strings=('-h' '--help' '-e' '--exit-code' '-R' '
|
||||
_shtab_ahriman_user_remove_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_version_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_web_option_strings=('-h' '--help')
|
||||
_shtab_ahriman_web_reload_option_strings=('-h' '--help')
|
||||
|
||||
|
||||
|
||||
_shtab_ahriman_pos_0_choices=('add' 'aur-search' 'check' 'clean' 'config' 'config-validate' 'copy' 'daemon' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'init' 'key-import' 'package-add' 'package-changes' 'package-changes-remove' 'package-copy' 'package-remove' 'package-status' 'package-status-remove' 'package-status-update' 'package-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'rebuild' 'remove' 'remove-unknown' 'repo-backup' 'repo-check' 'repo-clean' 'repo-config' 'repo-config-validate' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'repo-init' 'repo-rebuild' 'repo-remove-unknown' 'repo-report' 'repo-restore' 'repo-setup' 'repo-sign' 'repo-statistics' 'repo-status-update' 'repo-sync' 'repo-tree' 'repo-triggers' 'repo-update' 'report' 'run' 'search' 'service-clean' 'service-config' 'service-config-validate' 'service-key-import' 'service-repositories' 'service-run' 'service-setup' 'service-shell' 'service-tree-migrate' 'setup' 'shell' 'sign' 'status' 'status-update' 'sync' 'update' 'user-add' 'user-list' 'user-remove' 'version' 'web' 'web-reload')
|
||||
_shtab_ahriman_pos_0_choices=('add' 'aur-search' 'check' 'clean' 'config' 'config-validate' 'copy' 'daemon' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'init' 'key-import' 'package-add' 'package-changes' 'package-changes-remove' 'package-copy' 'package-remove' 'package-status' 'package-status-remove' 'package-status-update' 'package-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'rebuild' 'remove' 'remove-unknown' 'repo-backup' 'repo-check' 'repo-clean' 'repo-config' 'repo-config-validate' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'repo-init' 'repo-rebuild' 'repo-remove-unknown' 'repo-report' 'repo-restore' 'repo-setup' 'repo-sign' 'repo-statistics' 'repo-status-update' 'repo-sync' 'repo-tree' 'repo-triggers' 'repo-update' 'report' 'run' 'search' 'service-clean' 'service-config' 'service-config-validate' 'service-key-import' 'service-repositories' 'service-run' 'service-setup' 'service-shell' 'service-tree-migrate' 'setup' 'shell' 'sign' 'status' 'status-update' 'sync' 'update' 'user-add' 'user-list' 'user-remove' 'version' 'web')
|
||||
_shtab_ahriman___log_handler_choices=('console' 'syslog' 'journald')
|
||||
_shtab_ahriman_add__s_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
|
||||
_shtab_ahriman_add___source_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
|
||||
@@ -573,8 +572,6 @@ _shtab_ahriman_version__h_nargs=0
|
||||
_shtab_ahriman_version___help_nargs=0
|
||||
_shtab_ahriman_web__h_nargs=0
|
||||
_shtab_ahriman_web___help_nargs=0
|
||||
_shtab_ahriman_web_reload__h_nargs=0
|
||||
_shtab_ahriman_web_reload___help_nargs=0
|
||||
|
||||
|
||||
# $1=COMP_WORDS[1]
|
||||
@@ -677,7 +674,6 @@ _shtab_ahriman() {
|
||||
|
||||
if [[ "$current_action_nargs" != "*" ]] && \
|
||||
[[ "$current_action_nargs" != "+" ]] && \
|
||||
[[ "$current_action_nargs" != "?" ]] && \
|
||||
[[ "$current_action_nargs" != *"..." ]] && \
|
||||
(( $word_index + 1 - $current_action_args_start_index - $pos_only >= \
|
||||
$current_action_nargs )); then
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
.TH AHRIMAN "1" "2026\-02\-18" "ahriman 2.20.0rc1" "ArcH linux ReposItory MANager"
|
||||
.TH AHRIMAN "1" "2025\-06\-29" "ahriman 2.19.0" "ArcH linux ReposItory MANager"
|
||||
.SH NAME
|
||||
ahriman \- ArcH linux ReposItory MANager
|
||||
.SH SYNOPSIS
|
||||
.B [0m[1;35mahriman[0m
|
||||
[[32m-h[0m] [[32m-a [33mARCHITECTURE[0m] [[32m-c [33mCONFIGURATION[0m] [[36m--force[0m] [[32m-l [33mLOCK[0m] [[36m--log-handler [33m{console,syslog,journald}[0m] [[32m-q[0m] [[36m--report | --no-report[0m] [[32m-r [33mREPOSITORY[0m] [[36m--unsafe[0m] [[32m-V[0m] [[36m--wait-timeout [33mWAIT_TIMEOUT[0m] [32m{add,aur-search,check,clean,config,config-validate,copy,daemon,help,help-commands-unsafe,help-updates,help-version,init,key-import,package-add,package-changes,package-changes-remove,package-copy,package-remove,package-status,package-status-remove,package-status-update,package-update,patch-add,patch-list,patch-remove,patch-set-add,rebuild,remove,remove-unknown,repo-backup,repo-check,repo-clean,repo-config,repo-config-validate,repo-create-keyring,repo-create-mirrorlist,repo-daemon,repo-init,repo-rebuild,repo-remove-unknown,repo-report,repo-restore,repo-setup,repo-sign,repo-statistics,repo-status-update,repo-sync,repo-tree,repo-triggers,repo-update,report,run,search,service-clean,service-config,service-config-validate,service-key-import,service-repositories,service-run,service-setup,service-shell,service-tree-migrate,setup,shell,sign,status,status-update,sync,update,user-add,user-list,user-remove,version,web,web-reload} ...[0m
|
||||
.B ahriman
|
||||
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [-V] [--wait-timeout WAIT_TIMEOUT] {add,aur-search,check,clean,config,config-validate,copy,daemon,help,help-commands-unsafe,help-updates,help-version,init,key-import,package-add,package-changes,package-changes-remove,package-copy,package-remove,package-status,package-status-remove,package-status-update,package-update,patch-add,patch-list,patch-remove,patch-set-add,rebuild,remove,remove-unknown,repo-backup,repo-check,repo-clean,repo-config,repo-config-validate,repo-create-keyring,repo-create-mirrorlist,repo-daemon,repo-init,repo-rebuild,repo-remove-unknown,repo-report,repo-restore,repo-setup,repo-sign,repo-statistics,repo-status-update,repo-sync,repo-tree,repo-triggers,repo-update,report,run,search,service-clean,service-config,service-config-validate,service-key-import,service-repositories,service-run,service-setup,service-shell,service-tree-migrate,setup,shell,sign,status,status-update,sync,update,user-add,user-list,user-remove,version,web} ...
|
||||
.SH DESCRIPTION
|
||||
ArcH linux ReposItory MANager
|
||||
|
||||
@@ -193,14 +193,11 @@ remove user
|
||||
.TP
|
||||
\fBahriman\fR \fI\,web\/\fR
|
||||
web server
|
||||
.TP
|
||||
\fBahriman\fR \fI\,web\-reload\/\fR
|
||||
reload configuration
|
||||
|
||||
.SH COMMAND \fI\,'ahriman aur\-search'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman aur\-search[0m [[32m\-h[0m] [[32m\-e[0m] [[36m\-\-info | \-\-no\-info[0m]
|
||||
[[36m\-\-sort\-by [33m{description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,repository,submitter,url,url_path,version}[0m]
|
||||
[32msearch [search ...][0m
|
||||
usage: ahriman aur\-search [\-h] [\-e] [\-\-info | \-\-no\-info]
|
||||
[\-\-sort\-by {description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,repository,submitter,url,url_path,version}]
|
||||
search [search ...]
|
||||
|
||||
search for package in AUR using API
|
||||
|
||||
@@ -223,7 +220,7 @@ sort field by this field. In case if two packages have the same value of the spe
|
||||
by name
|
||||
|
||||
.SH COMMAND \fI\,'ahriman help'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman help[0m [[32m\-h[0m] [32m[subcommand][0m
|
||||
usage: ahriman help [\-h] [subcommand]
|
||||
|
||||
show help message for application or command and exit
|
||||
|
||||
@@ -232,7 +229,7 @@ show help message for application or command and exit
|
||||
show help message for specific command
|
||||
|
||||
.SH COMMAND \fI\,'ahriman help\-commands\-unsafe'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman help\-commands\-unsafe[0m [[32m\-h[0m] [32m[subcommand ...][0m
|
||||
usage: ahriman help\-commands\-unsafe [\-h] [subcommand ...]
|
||||
|
||||
list unsafe commands as defined in default args
|
||||
|
||||
@@ -242,7 +239,7 @@ instead of showing commands, just test command line for unsafe subcommand and re
|
||||
otherwise
|
||||
|
||||
.SH COMMAND \fI\,'ahriman help\-updates'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman help\-updates[0m [[32m\-h[0m] [[32m\-e[0m]
|
||||
usage: ahriman help\-updates [\-h] [\-e]
|
||||
|
||||
request AUR for current version and compare with current service version
|
||||
|
||||
@@ -252,15 +249,15 @@ request AUR for current version and compare with current service version
|
||||
return non\-zero exit code if updates available
|
||||
|
||||
.SH COMMAND \fI\,'ahriman help\-version'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman help\-version[0m [[32m\-h[0m]
|
||||
usage: ahriman help\-version [\-h]
|
||||
|
||||
print application and its dependencies versions
|
||||
|
||||
.SH COMMAND \fI\,'ahriman package\-add'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman package\-add[0m [[32m\-h[0m] [[36m\-\-changes | \-\-no\-changes[0m] [[36m\-\-dependencies | \-\-no\-dependencies[0m] [[32m\-e[0m]
|
||||
[[36m\-\-increment | \-\-no\-increment[0m] [[32m\-n[0m] [[32m\-y[0m]
|
||||
[[32m\-s [33m{auto,archive,aur,directory,local,remote,repository}[0m] [[32m\-u [33mUSERNAME[0m] [[32m\-v [33mVARIABLE[0m]
|
||||
[32mpackage [package ...][0m
|
||||
usage: ahriman package\-add [\-h] [\-\-changes | \-\-no\-changes] [\-\-dependencies | \-\-no\-dependencies] [\-e]
|
||||
[\-\-increment | \-\-no\-increment] [\-n] [\-y]
|
||||
[\-s {auto,archive,aur,directory,local,remote,repository}] [\-u USERNAME] [\-v VARIABLE]
|
||||
package [package ...]
|
||||
|
||||
add existing or new package to the build queue
|
||||
|
||||
@@ -306,7 +303,7 @@ build as user
|
||||
apply specified makepkg variables to the next build
|
||||
|
||||
.SH COMMAND \fI\,'ahriman package\-changes'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman package\-changes[0m [[32m\-h[0m] [[32m\-e[0m] [32mpackage[0m
|
||||
usage: ahriman package\-changes [\-h] [\-e] package
|
||||
|
||||
retrieve package changes stored in database
|
||||
|
||||
@@ -320,7 +317,7 @@ package base
|
||||
return non\-zero exit status if result is empty
|
||||
|
||||
.SH COMMAND \fI\,'ahriman package\-changes\-remove'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman package\-changes\-remove[0m [[32m\-h[0m] [32mpackage[0m
|
||||
usage: ahriman package\-changes\-remove [\-h] package
|
||||
|
||||
remove the package changes stored remotely
|
||||
|
||||
@@ -329,7 +326,7 @@ remove the package changes stored remotely
|
||||
package base
|
||||
|
||||
.SH COMMAND \fI\,'ahriman package\-copy'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman package\-copy[0m [[32m\-h[0m] [[32m\-e[0m] [[36m\-\-remove[0m] [32msource[0m [32mpackage [package ...][0m
|
||||
usage: ahriman package\-copy [\-h] [\-e] [\-\-remove] source package [package ...]
|
||||
|
||||
copy package and its metadata from another repository
|
||||
|
||||
@@ -351,7 +348,7 @@ return non\-zero exit status if result is empty
|
||||
remove package from the source repository after
|
||||
|
||||
.SH COMMAND \fI\,'ahriman package\-remove'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman package\-remove[0m [[32m\-h[0m] [32mpackage [package ...][0m
|
||||
usage: ahriman package\-remove [\-h] package [package ...]
|
||||
|
||||
remove package from the repository
|
||||
|
||||
@@ -360,8 +357,8 @@ remove package from the repository
|
||||
package name or base
|
||||
|
||||
.SH COMMAND \fI\,'ahriman package\-status'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman package\-status[0m [[32m\-h[0m] [[36m\-\-ahriman[0m] [[32m\-e[0m] [[36m\-\-info | \-\-no\-info[0m] [[32m\-s [33m{unknown,pending,building,failed,success}[0m]
|
||||
[32m[package ...][0m
|
||||
usage: ahriman package\-status [\-h] [\-\-ahriman] [\-e] [\-\-info | \-\-no\-info] [\-s {unknown,pending,building,failed,success}]
|
||||
[package ...]
|
||||
|
||||
request status of the package
|
||||
|
||||
@@ -387,7 +384,7 @@ show additional package information
|
||||
filter packages by status
|
||||
|
||||
.SH COMMAND \fI\,'ahriman package\-status\-remove'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman package\-status\-remove[0m [[32m\-h[0m] [32mpackage [package ...][0m
|
||||
usage: ahriman package\-status\-remove [\-h] package [package ...]
|
||||
|
||||
remove the package from the status page
|
||||
|
||||
@@ -396,7 +393,7 @@ remove the package from the status page
|
||||
remove specified packages from status page
|
||||
|
||||
.SH COMMAND \fI\,'ahriman package\-status\-update'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman package\-status\-update[0m [[32m\-h[0m] [[32m\-s [33m{unknown,pending,building,failed,success}[0m] [32m[package ...][0m
|
||||
usage: ahriman package\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}] [package ...]
|
||||
|
||||
update package status on the status page
|
||||
|
||||
@@ -410,7 +407,7 @@ set status for specified packages. If no packages supplied, service status will
|
||||
new package build status
|
||||
|
||||
.SH COMMAND \fI\,'ahriman patch\-add'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman patch\-add[0m [[32m\-h[0m] [32mpackage[0m [32mvariable[0m [32m[patch][0m
|
||||
usage: ahriman patch\-add [\-h] package variable [patch]
|
||||
|
||||
create or update patched PKGBUILD function or variable
|
||||
|
||||
@@ -427,7 +424,7 @@ PKGBUILD variable or function name. If variable is a function, it must end with
|
||||
path to file which contains function or variable value. If not set, the value will be read from stdin
|
||||
|
||||
.SH COMMAND \fI\,'ahriman patch\-list'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman patch\-list[0m [[32m\-h[0m] [[32m\-e[0m] [[32m\-v [33mVARIABLE[0m] [32mpackage[0m
|
||||
usage: ahriman patch\-list [\-h] [\-e] [\-v VARIABLE] package
|
||||
|
||||
list available patches for the package
|
||||
|
||||
@@ -445,7 +442,7 @@ return non\-zero exit status if result is empty
|
||||
if set, show only patches for specified PKGBUILD variables
|
||||
|
||||
.SH COMMAND \fI\,'ahriman patch\-remove'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman patch\-remove[0m [[32m\-h[0m] [[32m\-v [33mVARIABLE[0m] [32mpackage[0m
|
||||
usage: ahriman patch\-remove [\-h] [\-v VARIABLE] package
|
||||
|
||||
remove patches for the package
|
||||
|
||||
@@ -460,7 +457,7 @@ should be used for single\-function patches in case if you wold like to remove o
|
||||
if not set, it will remove all patches related to the package
|
||||
|
||||
.SH COMMAND \fI\,'ahriman patch\-set\-add'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman patch\-set\-add[0m [[32m\-h[0m] [[32m\-t [33mTRACK[0m] [32mpackage[0m
|
||||
usage: ahriman patch\-set\-add [\-h] [\-t TRACK] package
|
||||
|
||||
create or update source patches
|
||||
|
||||
@@ -474,7 +471,7 @@ path to directory with changed files for patch addition/update
|
||||
files which has to be tracked
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-backup'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-backup[0m [[32m\-h[0m] [32mpath[0m
|
||||
usage: ahriman repo\-backup [\-h] path
|
||||
|
||||
backup repository settings and database
|
||||
|
||||
@@ -483,9 +480,9 @@ backup repository settings and database
|
||||
path of the output archive
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-check'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-check[0m [[32m\-h[0m] [[36m\-\-changes | \-\-no\-changes[0m] [[36m\-\-check\-files | \-\-no\-check\-files[0m] [[32m\-e[0m] [[36m\-\-vcs | \-\-no\-vcs[0m]
|
||||
[[32m\-y[0m]
|
||||
[32m[package ...][0m
|
||||
usage: ahriman repo\-check [\-h] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files] [\-e] [\-\-vcs | \-\-no\-vcs]
|
||||
[\-y]
|
||||
[package ...]
|
||||
|
||||
check for packages updates. Same as repo\-update \-\-dry\-run \-\-no\-manual
|
||||
|
||||
@@ -515,20 +512,20 @@ fetch actual version of VCS packages
|
||||
download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-create\-keyring'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-create\-keyring[0m [[32m\-h[0m]
|
||||
usage: ahriman repo\-create\-keyring [\-h]
|
||||
|
||||
create package which contains list of trusted keys as set by configuration. Note, that this action will only create package, the package itself has to be built manually
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-create\-mirrorlist'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-create\-mirrorlist[0m [[32m\-h[0m]
|
||||
usage: ahriman repo\-create\-mirrorlist [\-h]
|
||||
|
||||
create package which contains list of available mirrors as set by configuration. Note, that this action will only create package, the package itself has to be built manually
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-daemon'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-daemon[0m [[32m\-h[0m] [[32m\-i [33mINTERVAL[0m] [[36m\-\-aur | \-\-no\-aur[0m] [[36m\-\-changes | \-\-no\-changes[0m]
|
||||
[[36m\-\-check\-files | \-\-no\-check\-files[0m] [[36m\-\-dependencies | \-\-no\-dependencies[0m] [[36m\-\-dry\-run[0m]
|
||||
[[36m\-\-increment | \-\-no\-increment[0m] [[36m\-\-local | \-\-no\-local[0m] [[36m\-\-manual | \-\-no\-manual[0m]
|
||||
[[36m\-\-partitions | \-\-no\-partitions[0m] [[32m\-u [33mUSERNAME[0m] [[36m\-\-vcs | \-\-no\-vcs[0m] [[32m\-y[0m]
|
||||
usage: ahriman repo\-daemon [\-h] [\-i INTERVAL] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes]
|
||||
[\-\-check\-files | \-\-no\-check\-files] [\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run]
|
||||
[\-\-increment | \-\-no\-increment] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual]
|
||||
[\-\-partitions | \-\-no\-partitions] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y]
|
||||
|
||||
start process which periodically will run update process
|
||||
|
||||
@@ -586,8 +583,8 @@ fetch actual version of VCS packages
|
||||
download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-rebuild'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-rebuild[0m [[32m\-h[0m] [[36m\-\-depends\-on [33mDEPENDS_ON[0m] [[36m\-\-dry\-run[0m] [[36m\-\-from\-database[0m] [[36m\-\-increment | \-\-no\-increment[0m]
|
||||
[[32m\-e[0m] [[32m\-s [33m{unknown,pending,building,failed,success}[0m] [[32m\-u [33mUSERNAME[0m]
|
||||
usage: ahriman repo\-rebuild [\-h] [\-\-depends\-on DEPENDS_ON] [\-\-dry\-run] [\-\-from\-database] [\-\-increment | \-\-no\-increment]
|
||||
[\-e] [\-s {unknown,pending,building,failed,success}] [\-u USERNAME]
|
||||
|
||||
force rebuild whole repository
|
||||
|
||||
@@ -623,7 +620,7 @@ filter packages by status. Requires \-\-from\-database to be set
|
||||
build as user
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-remove\-unknown'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-remove\-unknown[0m [[32m\-h[0m] [[36m\-\-dry\-run[0m]
|
||||
usage: ahriman repo\-remove\-unknown [\-h] [\-\-dry\-run]
|
||||
|
||||
remove packages which are missing in AUR and do not have local PKGBUILDs
|
||||
|
||||
@@ -633,12 +630,12 @@ remove packages which are missing in AUR and do not have local PKGBUILDs
|
||||
just perform check for packages without removal
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-report'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-report[0m [[32m\-h[0m]
|
||||
usage: ahriman repo\-report [\-h]
|
||||
|
||||
generate repository report according to current settings
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-restore'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-restore[0m [[32m\-h[0m] [[32m\-o [33mOUTPUT[0m] [32mpath[0m
|
||||
usage: ahriman repo\-restore [\-h] [\-o OUTPUT] path
|
||||
|
||||
restore settings and database
|
||||
|
||||
@@ -652,7 +649,7 @@ path of the input archive
|
||||
root path of the extracted files
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-sign'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-sign[0m [[32m\-h[0m] [32m[package ...][0m
|
||||
usage: ahriman repo\-sign [\-h] [package ...]
|
||||
|
||||
(re\-)sign packages and repository database according to current settings
|
||||
|
||||
@@ -661,10 +658,10 @@ root path of the extracted files
|
||||
sign only specified packages
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-statistics'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-statistics[0m [[32m\-h[0m] [[36m\-\-chart [33mCHART[0m]
|
||||
[[32m\-e [33m{package\-outdated,package\-removed,package\-update\-failed,package\-updated}[0m]
|
||||
[[36m\-\-from\-date [33mFROM_DATE[0m] [[36m\-\-limit [33mLIMIT[0m] [[36m\-\-offset [33mOFFSET[0m] [[36m\-\-to\-date [33mTO_DATE[0m]
|
||||
[32m[package][0m
|
||||
usage: ahriman repo\-statistics [\-h] [\-\-chart CHART]
|
||||
[\-e {package\-outdated,package\-removed,package\-update\-failed,package\-updated}]
|
||||
[\-\-from\-date FROM_DATE] [\-\-limit LIMIT] [\-\-offset OFFSET] [\-\-to\-date TO_DATE]
|
||||
[package]
|
||||
|
||||
fetch repository statistics
|
||||
|
||||
@@ -698,7 +695,7 @@ skip specified amount of events
|
||||
only fetch events which are older than the date
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-status\-update'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-status\-update[0m [[32m\-h[0m] [[32m\-s [33m{unknown,pending,building,failed,success}[0m]
|
||||
usage: ahriman repo\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}]
|
||||
|
||||
update repository status on the status page
|
||||
|
||||
@@ -708,12 +705,12 @@ update repository status on the status page
|
||||
new status
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-sync'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-sync[0m [[32m\-h[0m]
|
||||
usage: ahriman repo\-sync [\-h]
|
||||
|
||||
sync repository files to remote server according to current settings
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-tree'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-tree[0m [[32m\-h[0m] [[32m\-p [33mPARTITIONS[0m]
|
||||
usage: ahriman repo\-tree [\-h] [\-p PARTITIONS]
|
||||
|
||||
dump repository tree based on packages dependencies
|
||||
|
||||
@@ -723,7 +720,7 @@ dump repository tree based on packages dependencies
|
||||
also divide packages by independent partitions
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-triggers'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-triggers[0m [[32m\-h[0m] [32m[trigger ...][0m
|
||||
usage: ahriman repo\-triggers [\-h] [trigger ...]
|
||||
|
||||
run triggers on empty build result as configured by settings
|
||||
|
||||
@@ -732,10 +729,10 @@ run triggers on empty build result as configured by settings
|
||||
instead of running all triggers as set by configuration, just process specified ones in order of mention
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo\-update'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman repo\-update[0m [[32m\-h[0m] [[36m\-\-aur | \-\-no\-aur[0m] [[36m\-\-changes | \-\-no\-changes[0m] [[36m\-\-check\-files | \-\-no\-check\-files[0m]
|
||||
[[36m\-\-dependencies | \-\-no\-dependencies[0m] [[36m\-\-dry\-run[0m] [[32m\-e[0m] [[36m\-\-increment | \-\-no\-increment[0m]
|
||||
[[36m\-\-local | \-\-no\-local[0m] [[36m\-\-manual | \-\-no\-manual[0m] [[32m\-u [33mUSERNAME[0m] [[36m\-\-vcs | \-\-no\-vcs[0m] [[32m\-y[0m]
|
||||
[32m[package ...][0m
|
||||
usage: ahriman repo\-update [\-h] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files]
|
||||
[\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run] [\-e] [\-\-increment | \-\-no\-increment]
|
||||
[\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y]
|
||||
[package ...]
|
||||
|
||||
check for packages updates and run build process if requested
|
||||
|
||||
@@ -793,8 +790,8 @@ fetch actual version of VCS packages
|
||||
download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-clean'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman service\-clean[0m [[32m\-h[0m] [[36m\-\-cache | \-\-no\-cache[0m] [[36m\-\-chroot | \-\-no\-chroot[0m] [[36m\-\-manual | \-\-no\-manual[0m]
|
||||
[[36m\-\-packages | \-\-no\-packages[0m] [[36m\-\-pacman | \-\-no\-pacman[0m]
|
||||
usage: ahriman service\-clean [\-h] [\-\-cache | \-\-no\-cache] [\-\-chroot | \-\-no\-chroot] [\-\-manual | \-\-no\-manual]
|
||||
[\-\-packages | \-\-no\-packages] [\-\-pacman | \-\-no\-pacman]
|
||||
|
||||
remove local caches
|
||||
|
||||
@@ -820,7 +817,7 @@ clear directory with built packages
|
||||
clear directory with pacman local database cache
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-config'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman service\-config[0m [[32m\-h[0m] [[36m\-\-info | \-\-no\-info[0m] [[36m\-\-secure | \-\-no\-secure[0m] [32m[section][0m [32m[key][0m
|
||||
usage: ahriman service\-config [\-h] [\-\-info | \-\-no\-info] [\-\-secure | \-\-no\-secure] [section] [key]
|
||||
|
||||
dump configuration for the specified architecture
|
||||
|
||||
@@ -842,7 +839,7 @@ show additional information, e.g. configuration files
|
||||
hide passwords and secrets from output
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-config\-validate'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman service\-config\-validate[0m [[32m\-h[0m] [[32m\-e[0m]
|
||||
usage: ahriman service\-config\-validate [\-h] [\-e]
|
||||
|
||||
validate configuration and print found errors
|
||||
|
||||
@@ -852,7 +849,7 @@ validate configuration and print found errors
|
||||
return non\-zero exit status if configuration is invalid
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-key\-import'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman service\-key\-import[0m [[32m\-h[0m] [[36m\-\-key\-server [33mKEY_SERVER[0m] [32mkey[0m
|
||||
usage: ahriman service\-key\-import [\-h] [\-\-key\-server KEY_SERVER] key
|
||||
|
||||
import PGP key from public sources to the repository user
|
||||
|
||||
@@ -866,7 +863,7 @@ PGP key to import from public server
|
||||
key server for key import
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-repositories'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman service\-repositories[0m [[32m\-h[0m] [[36m\-\-id\-only | \-\-no\-id\-only[0m]
|
||||
usage: ahriman service\-repositories [\-h] [\-\-id\-only | \-\-no\-id\-only]
|
||||
|
||||
list all available repositories
|
||||
|
||||
@@ -876,7 +873,7 @@ list all available repositories
|
||||
show machine readable identifier instead
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-run'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman service\-run[0m [[32m\-h[0m] [32mcommand [command ...][0m
|
||||
usage: ahriman service\-run [\-h] command [command ...]
|
||||
|
||||
run multiple commands on success run of the previous command
|
||||
|
||||
@@ -885,11 +882,11 @@ run multiple commands on success run of the previous command
|
||||
command to be run (quoted) without ``ahriman``
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-setup'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman service\-setup[0m [[32m\-h[0m] [[36m\-\-build\-as\-user [33mBUILD_AS_USER[0m] [[36m\-\-from\-configuration [33mFROM_CONFIGURATION[0m]
|
||||
[[36m\-\-generate\-salt | \-\-no\-generate\-salt[0m] [[36m\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs[0m]
|
||||
[[36m\-\-mirror [33mMIRROR[0m] [[36m\-\-multilib | \-\-no\-multilib[0m] [36m\-\-packager [33mPACKAGER[0m [[36m\-\-server [33mSERVER[0m]
|
||||
[[36m\-\-sign\-key [33mSIGN_KEY[0m] [[36m\-\-sign\-target [33m{disabled,packages,repository}[0m] [[36m\-\-web\-port [33mWEB_PORT[0m]
|
||||
[[36m\-\-web\-unix\-socket [33mWEB_UNIX_SOCKET[0m]
|
||||
usage: ahriman service\-setup [\-h] [\-\-build\-as\-user BUILD_AS_USER] [\-\-from\-configuration FROM_CONFIGURATION]
|
||||
[\-\-generate\-salt | \-\-no\-generate\-salt] [\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs]
|
||||
[\-\-mirror MIRROR] [\-\-multilib | \-\-no\-multilib] \-\-packager PACKAGER [\-\-server SERVER]
|
||||
[\-\-sign\-key SIGN_KEY] [\-\-sign\-target {disabled,packages,repository}] [\-\-web\-port WEB_PORT]
|
||||
[\-\-web\-unix\-socket WEB_UNIX_SOCKET]
|
||||
|
||||
create initial service configuration, requires root
|
||||
|
||||
@@ -943,7 +940,7 @@ port of the web service
|
||||
path to unix socket used for interprocess communications
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-shell'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman service\-shell[0m [[32m\-h[0m] [[32m\-o [33mOUTPUT[0m] [32m[code][0m
|
||||
usage: ahriman service\-shell [\-h] [\-o OUTPUT] [code]
|
||||
|
||||
drop into python shell
|
||||
|
||||
@@ -957,13 +954,13 @@ instead of dropping into shell, just execute the specified code
|
||||
output commands and result to the file
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-tree\-migrate'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman service\-tree\-migrate[0m [[32m\-h[0m]
|
||||
usage: ahriman service\-tree\-migrate [\-h]
|
||||
|
||||
migrate repository tree between versions
|
||||
|
||||
.SH COMMAND \fI\,'ahriman user\-add'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman user\-add[0m [[32m\-h[0m] [[36m\-\-key [33mKEY[0m] [[36m\-\-packager [33mPACKAGER[0m] [[32m\-p [33mPASSWORD[0m] [[32m\-R [33m{unauthorized,read,reporter,full}[0m]
|
||||
[32musername[0m
|
||||
usage: ahriman user\-add [\-h] [\-\-key KEY] [\-\-packager PACKAGER] [\-p PASSWORD] [\-R {unauthorized,read,reporter,full}]
|
||||
username
|
||||
|
||||
update user for web services with the given password and role. In case if password was not entered it will be asked interactively
|
||||
|
||||
@@ -990,7 +987,7 @@ authorization type.
|
||||
user access level
|
||||
|
||||
.SH COMMAND \fI\,'ahriman user\-list'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman user\-list[0m [[32m\-h[0m] [[32m\-e[0m] [[32m\-R [33m{unauthorized,read,reporter,full}[0m] [32m[username][0m
|
||||
usage: ahriman user\-list [\-h] [\-e] [\-R {unauthorized,read,reporter,full}] [username]
|
||||
|
||||
list users from the user mapping and their roles
|
||||
|
||||
@@ -1008,7 +1005,7 @@ return non\-zero exit status if result is empty
|
||||
filter users by role
|
||||
|
||||
.SH COMMAND \fI\,'ahriman user\-remove'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman user\-remove[0m [[32m\-h[0m] [32musername[0m
|
||||
usage: ahriman user\-remove [\-h] username
|
||||
|
||||
remove user from the user mapping and update the configuration
|
||||
|
||||
@@ -1017,15 +1014,10 @@ remove user from the user mapping and update the configuration
|
||||
username for web service
|
||||
|
||||
.SH COMMAND \fI\,'ahriman web'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman web[0m [[32m\-h[0m]
|
||||
usage: ahriman web [\-h]
|
||||
|
||||
start web server
|
||||
|
||||
.SH COMMAND \fI\,'ahriman web\-reload'\/\fR
|
||||
[1;34musage: [0m[1;35mahriman web\-reload[0m [[32m\-h[0m]
|
||||
|
||||
reload web server configuration
|
||||
|
||||
.SH COMMENTS
|
||||
Quick setup command (replace repository name, architecture and packager as needed):
|
||||
|
||||
|
||||
@@ -80,7 +80,6 @@ _shtab_ahriman_commands() {
|
||||
"user-remove:remove user from the user mapping and update the configuration"
|
||||
"version:print application and its dependencies versions"
|
||||
"web:start web server"
|
||||
"web-reload:reload web server configuration"
|
||||
)
|
||||
_describe 'ahriman commands' _commands
|
||||
}
|
||||
@@ -100,9 +99,6 @@ _shtab_ahriman_options=(
|
||||
"--wait-timeout[wait for lock to be free. Negative value will lead to immediate application run even if there is lock file. In case of zero value, the application will wait infinitely (default\: -1)]:wait_timeout:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_defaults_added=0
|
||||
|
||||
_shtab_ahriman_add_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:"
|
||||
@@ -117,9 +113,6 @@ _shtab_ahriman_add_options=(
|
||||
"(*):package source (base name, path to local files, remote URL):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_add_defaults_added=0
|
||||
|
||||
_shtab_ahriman_aur_search_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
|
||||
@@ -128,9 +121,6 @@ _shtab_ahriman_aur_search_options=(
|
||||
"(*):search terms, can be specified multiple times, the result will match all terms:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_aur_search_defaults_added=0
|
||||
|
||||
_shtab_ahriman_check_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:"
|
||||
@@ -141,9 +131,6 @@ _shtab_ahriman_check_options=(
|
||||
"(*)::filter check by package base (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_check_defaults_added=0
|
||||
|
||||
_shtab_ahriman_clean_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--cache,--no-cache}"[clear directory with package caches (default\: False)]:cache:"
|
||||
@@ -153,9 +140,6 @@ _shtab_ahriman_clean_options=(
|
||||
{--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: False)]:pacman:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_clean_defaults_added=0
|
||||
|
||||
_shtab_ahriman_config_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:"
|
||||
@@ -164,17 +148,11 @@ _shtab_ahriman_config_options=(
|
||||
":filter settings by key (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_config_defaults_added=0
|
||||
|
||||
_shtab_ahriman_config_validate_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if configuration is invalid (default\: False)]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_config_validate_defaults_added=0
|
||||
|
||||
_shtab_ahriman_copy_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
|
||||
@@ -183,9 +161,6 @@ _shtab_ahriman_copy_options=(
|
||||
"(*):package base:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_copy_defaults_added=0
|
||||
|
||||
_shtab_ahriman_daemon_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:"
|
||||
@@ -203,40 +178,25 @@ _shtab_ahriman_daemon_options=(
|
||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_daemon_defaults_added=0
|
||||
|
||||
_shtab_ahriman_help_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
":show help message for specific command (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_help_defaults_added=0
|
||||
|
||||
_shtab_ahriman_help_commands_unsafe_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*)::instead of showing commands, just test command line for unsafe subcommand and return 0 in case if command is safe and 1 otherwise (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_help_commands_unsafe_defaults_added=0
|
||||
|
||||
_shtab_ahriman_help_updates_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit code if updates available (default\: False)]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_help_updates_defaults_added=0
|
||||
|
||||
_shtab_ahriman_help_version_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_help_version_defaults_added=0
|
||||
|
||||
_shtab_ahriman_init_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
|
||||
@@ -253,18 +213,12 @@ _shtab_ahriman_init_options=(
|
||||
"--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_init_defaults_added=0
|
||||
|
||||
_shtab_ahriman_key_import_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--key-server[key server for key import (default\: keyserver.ubuntu.com)]:key_server:"
|
||||
":PGP key to import from public server:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_key_import_defaults_added=0
|
||||
|
||||
_shtab_ahriman_package_add_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:"
|
||||
@@ -279,26 +233,17 @@ _shtab_ahriman_package_add_options=(
|
||||
"(*):package source (base name, path to local files, remote URL):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_package_add_defaults_added=0
|
||||
|
||||
_shtab_ahriman_package_changes_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
|
||||
":package base:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_package_changes_defaults_added=0
|
||||
|
||||
_shtab_ahriman_package_changes_remove_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
":package base:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_package_changes_remove_defaults_added=0
|
||||
|
||||
_shtab_ahriman_package_copy_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
|
||||
@@ -307,17 +252,11 @@ _shtab_ahriman_package_copy_options=(
|
||||
"(*):package base:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_package_copy_defaults_added=0
|
||||
|
||||
_shtab_ahriman_package_remove_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*):package name or base:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_package_remove_defaults_added=0
|
||||
|
||||
_shtab_ahriman_package_status_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--ahriman[get service status itself (default\: False)]"
|
||||
@@ -327,26 +266,17 @@ _shtab_ahriman_package_status_options=(
|
||||
"(*)::filter status by package base (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_package_status_defaults_added=0
|
||||
|
||||
_shtab_ahriman_package_status_remove_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*):remove specified packages from status page:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_package_status_remove_defaults_added=0
|
||||
|
||||
_shtab_ahriman_package_status_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-s,--status}"[new package build status (default\: success)]:status:(unknown pending building failed success)"
|
||||
"(*)::set status for specified packages. If no packages supplied, service status will be updated (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_package_status_update_defaults_added=0
|
||||
|
||||
_shtab_ahriman_package_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:"
|
||||
@@ -361,9 +291,6 @@ _shtab_ahriman_package_update_options=(
|
||||
"(*):package source (base name, path to local files, remote URL):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_package_update_defaults_added=0
|
||||
|
||||
_shtab_ahriman_patch_add_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
":package base:"
|
||||
@@ -371,9 +298,6 @@ _shtab_ahriman_patch_add_options=(
|
||||
":path to file which contains function or variable value. If not set, the value will be read from stdin (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_patch_add_defaults_added=0
|
||||
|
||||
_shtab_ahriman_patch_list_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
|
||||
@@ -381,27 +305,18 @@ _shtab_ahriman_patch_list_options=(
|
||||
":package base:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_patch_list_defaults_added=0
|
||||
|
||||
_shtab_ahriman_patch_remove_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"*"{-v,--variable}"[should be used for single-function patches in case if you wold like to remove only specified PKGBUILD variables. In case if not set, it will remove all patches related to the package (default\: None)]:variable:"
|
||||
":package base:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_patch_remove_defaults_added=0
|
||||
|
||||
_shtab_ahriman_patch_set_add_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"*"{-t,--track}"[files which has to be tracked (default\: \[\'\*.diff\', \'\*.patch\'\])]:track:"
|
||||
":path to directory with changed files for patch addition\/update:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_patch_set_add_defaults_added=0
|
||||
|
||||
_shtab_ahriman_rebuild_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"*--depends-on[only rebuild packages that depend on specified packages (default\: None)]:depends_on:"
|
||||
@@ -413,33 +328,21 @@ _shtab_ahriman_rebuild_options=(
|
||||
{-u,--username}"[build as user (default\: None)]:username:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_rebuild_defaults_added=0
|
||||
|
||||
_shtab_ahriman_remove_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*):package name or base:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_remove_defaults_added=0
|
||||
|
||||
_shtab_ahriman_remove_unknown_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--dry-run[just perform check for packages without removal (default\: False)]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_remove_unknown_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_backup_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
":path of the output archive:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_backup_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_check_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:"
|
||||
@@ -450,9 +353,6 @@ _shtab_ahriman_repo_check_options=(
|
||||
"(*)::filter check by package base (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_check_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_clean_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--cache,--no-cache}"[clear directory with package caches (default\: False)]:cache:"
|
||||
@@ -462,9 +362,6 @@ _shtab_ahriman_repo_clean_options=(
|
||||
{--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: False)]:pacman:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_clean_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_config_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:"
|
||||
@@ -473,31 +370,19 @@ _shtab_ahriman_repo_config_options=(
|
||||
":filter settings by key (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_config_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_config_validate_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if configuration is invalid (default\: False)]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_config_validate_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_create_keyring_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_create_keyring_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_create_mirrorlist_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_create_mirrorlist_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_daemon_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:"
|
||||
@@ -515,9 +400,6 @@ _shtab_ahriman_repo_daemon_options=(
|
||||
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_daemon_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_init_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
|
||||
@@ -534,9 +416,6 @@ _shtab_ahriman_repo_init_options=(
|
||||
"--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_init_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_rebuild_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"*--depends-on[only rebuild packages that depend on specified packages (default\: None)]:depends_on:"
|
||||
@@ -548,33 +427,21 @@ _shtab_ahriman_repo_rebuild_options=(
|
||||
{-u,--username}"[build as user (default\: None)]:username:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_rebuild_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_remove_unknown_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--dry-run[just perform check for packages without removal (default\: False)]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_remove_unknown_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_report_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_report_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_restore_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-o,--output}"[root path of the extracted files (default\: \/)]:output:"
|
||||
":path of the input archive:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_restore_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_setup_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
|
||||
@@ -591,17 +458,11 @@ _shtab_ahriman_repo_setup_options=(
|
||||
"--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_setup_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_sign_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*)::sign only specified packages (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_sign_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_statistics_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--chart[create updates chart and save it to the specified path (default\: None)]:chart:"
|
||||
@@ -613,40 +474,25 @@ _shtab_ahriman_repo_statistics_options=(
|
||||
":fetch only events for the specified package (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_statistics_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_status_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-s,--status}"[new status (default\: success)]:status:(unknown pending building failed success)"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_status_update_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_sync_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_sync_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_tree_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-p,--partitions}"[also divide packages by independent partitions (default\: 1)]:partitions:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_tree_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_triggers_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*)::instead of running all triggers as set by configuration, just process specified ones in order of mention (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_triggers_defaults_added=0
|
||||
|
||||
_shtab_ahriman_repo_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:"
|
||||
@@ -664,24 +510,15 @@ _shtab_ahriman_repo_update_options=(
|
||||
"(*)::filter check by package base (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_repo_update_defaults_added=0
|
||||
|
||||
_shtab_ahriman_report_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_report_defaults_added=0
|
||||
|
||||
_shtab_ahriman_run_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*):command to be run (quoted) without \`\`ahriman\`\`:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_run_defaults_added=0
|
||||
|
||||
_shtab_ahriman_search_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
|
||||
@@ -690,9 +527,6 @@ _shtab_ahriman_search_options=(
|
||||
"(*):search terms, can be specified multiple times, the result will match all terms:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_search_defaults_added=0
|
||||
|
||||
_shtab_ahriman_service_clean_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--cache,--no-cache}"[clear directory with package caches (default\: False)]:cache:"
|
||||
@@ -702,9 +536,6 @@ _shtab_ahriman_service_clean_options=(
|
||||
{--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: False)]:pacman:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_service_clean_defaults_added=0
|
||||
|
||||
_shtab_ahriman_service_config_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:"
|
||||
@@ -713,42 +544,27 @@ _shtab_ahriman_service_config_options=(
|
||||
":filter settings by key (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_service_config_defaults_added=0
|
||||
|
||||
_shtab_ahriman_service_config_validate_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if configuration is invalid (default\: False)]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_service_config_validate_defaults_added=0
|
||||
|
||||
_shtab_ahriman_service_key_import_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--key-server[key server for key import (default\: keyserver.ubuntu.com)]:key_server:"
|
||||
":PGP key to import from public server:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_service_key_import_defaults_added=0
|
||||
|
||||
_shtab_ahriman_service_repositories_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--id-only,--no-id-only}"[show machine readable identifier instead (default\: False)]:id_only:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_service_repositories_defaults_added=0
|
||||
|
||||
_shtab_ahriman_service_run_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*):command to be run (quoted) without \`\`ahriman\`\`:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_service_run_defaults_added=0
|
||||
|
||||
_shtab_ahriman_service_setup_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
|
||||
@@ -765,25 +581,16 @@ _shtab_ahriman_service_setup_options=(
|
||||
"--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_service_setup_defaults_added=0
|
||||
|
||||
_shtab_ahriman_service_shell_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-o,--output}"[output commands and result to the file (default\: None)]:output:"
|
||||
":instead of dropping into shell, just execute the specified code (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_service_shell_defaults_added=0
|
||||
|
||||
_shtab_ahriman_service_tree_migrate_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_service_tree_migrate_defaults_added=0
|
||||
|
||||
_shtab_ahriman_setup_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
|
||||
@@ -800,26 +607,17 @@ _shtab_ahriman_setup_options=(
|
||||
"--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_setup_defaults_added=0
|
||||
|
||||
_shtab_ahriman_shell_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-o,--output}"[output commands and result to the file (default\: None)]:output:"
|
||||
":instead of dropping into shell, just execute the specified code (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_shell_defaults_added=0
|
||||
|
||||
_shtab_ahriman_sign_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"(*)::sign only specified packages (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_sign_defaults_added=0
|
||||
|
||||
_shtab_ahriman_status_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--ahriman[get service status itself (default\: False)]"
|
||||
@@ -829,25 +627,16 @@ _shtab_ahriman_status_options=(
|
||||
"(*)::filter status by package base (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_status_defaults_added=0
|
||||
|
||||
_shtab_ahriman_status_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-s,--status}"[new package build status (default\: success)]:status:(unknown pending building failed success)"
|
||||
"(*)::set status for specified packages. If no packages supplied, service status will be updated (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_status_update_defaults_added=0
|
||||
|
||||
_shtab_ahriman_sync_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_sync_defaults_added=0
|
||||
|
||||
_shtab_ahriman_update_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:"
|
||||
@@ -865,9 +654,6 @@ _shtab_ahriman_update_options=(
|
||||
"(*)::filter check by package base (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_update_defaults_added=0
|
||||
|
||||
_shtab_ahriman_user_add_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--key[optional PGP key used by this user. The private key must be imported (default\: None)]:key:"
|
||||
@@ -877,9 +663,6 @@ _shtab_ahriman_user_add_options=(
|
||||
":username for web service:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_user_add_defaults_added=0
|
||||
|
||||
_shtab_ahriman_user_list_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
|
||||
@@ -887,48 +670,25 @@ _shtab_ahriman_user_list_options=(
|
||||
":filter users by username (default\: None):"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_user_list_defaults_added=0
|
||||
|
||||
_shtab_ahriman_user_remove_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
":username for web service:"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_user_remove_defaults_added=0
|
||||
|
||||
_shtab_ahriman_version_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_version_defaults_added=0
|
||||
|
||||
_shtab_ahriman_web_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_web_defaults_added=0
|
||||
|
||||
_shtab_ahriman_web_reload_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
)
|
||||
|
||||
# guard to ensure default positional specs are added only once per session
|
||||
_shtab_ahriman_web_reload_defaults_added=0
|
||||
|
||||
|
||||
_shtab_ahriman() {
|
||||
local context state line curcontext="$curcontext" one_or_more='(*)' remainder='(-)*' default='*::: :->ahriman'
|
||||
local context state line curcontext="$curcontext" one_or_more='(-)*' remainder='(*)'
|
||||
|
||||
# Add default positional/remainder specs only if none exist, and only once per session
|
||||
if (( ! _shtab_ahriman_defaults_added )); then
|
||||
if (( ${_shtab_ahriman_options[(I)${(q)one_or_more}*]} + ${_shtab_ahriman_options[(I)${(q)remainder}*]} + ${_shtab_ahriman_options[(I)${(q)default}]} == 0 )); then
|
||||
_shtab_ahriman_options+=(': :_shtab_ahriman_commands' '*::: :->ahriman')
|
||||
fi
|
||||
_shtab_ahriman_defaults_added=1
|
||||
if ((${_shtab_ahriman_options[(I)${(q)one_or_more}*]} + ${_shtab_ahriman_options[(I)${(q)remainder}*]} == 0)); then # noqa: E501
|
||||
_shtab_ahriman_options+=(': :_shtab_ahriman_commands' '*::: :->ahriman')
|
||||
fi
|
||||
_arguments -C -s $_shtab_ahriman_options
|
||||
|
||||
@@ -1013,7 +773,6 @@ _shtab_ahriman() {
|
||||
user-remove) _arguments -C -s $_shtab_ahriman_user_remove_options ;;
|
||||
version) _arguments -C -s $_shtab_ahriman_version_options ;;
|
||||
web) _arguments -C -s $_shtab_ahriman_web_options ;;
|
||||
web-reload) _arguments -C -s $_shtab_ahriman_web_reload_options ;;
|
||||
esac
|
||||
esac
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ authors = [
|
||||
|
||||
dependencies = [
|
||||
"bcrypt",
|
||||
"filelock",
|
||||
"inflection",
|
||||
"pyelftools",
|
||||
"requests",
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
__version__ = "2.20.0rc1"
|
||||
__version__ = "2.19.0"
|
||||
|
||||
@@ -22,7 +22,6 @@ from collections.abc import Iterable
|
||||
from ahriman.application.application.application_properties import ApplicationProperties
|
||||
from ahriman.application.application.workers import Updater
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.packagers import Packagers
|
||||
from ahriman.models.result import Result
|
||||
@@ -117,7 +116,7 @@ class ApplicationRepository(ApplicationProperties):
|
||||
for single in probe.packages:
|
||||
try:
|
||||
_ = Package.from_aur(single, None)
|
||||
except UnknownPackageError:
|
||||
except Exception:
|
||||
packages.append(single)
|
||||
return packages
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Callable, Iterable
|
||||
from multiprocessing import Pool
|
||||
from typing import ClassVar, TypeVar
|
||||
|
||||
@@ -28,9 +28,9 @@ from ahriman.application.lock import Lock
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import ExitCode, MissingArchitectureError, MultipleArchitecturesError
|
||||
from ahriman.core.log.log_loader import LogLoader
|
||||
from ahriman.core.repository import Explorer
|
||||
from ahriman.core.types import ExplicitBool
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
# this workaround is for several things
|
||||
@@ -169,6 +169,11 @@ class Handler:
|
||||
Raises:
|
||||
MissingArchitectureError: if no architecture set and automatic detection is not allowed or failed
|
||||
"""
|
||||
configuration = Configuration()
|
||||
configuration.load(args.configuration)
|
||||
# pylint, wtf???
|
||||
root = configuration.getpath("repository", "root") # pylint: disable=assignment-from-no-return
|
||||
|
||||
# preparse systemd repository-id argument
|
||||
# we are using unescaped values, so / is not allowed here, because it is impossible to separate if from dashes
|
||||
if args.repository_id is not None:
|
||||
@@ -179,10 +184,27 @@ class Handler:
|
||||
if repository_parts:
|
||||
args.repository = "-".join(repository_parts) # replace slash with dash
|
||||
|
||||
configuration = Configuration()
|
||||
configuration.load(args.configuration)
|
||||
repositories = Explorer.repositories_extract(configuration, args.repository, args.architecture)
|
||||
# extract repository names first
|
||||
if (from_args := args.repository) is not None:
|
||||
repositories: Iterable[str] = [from_args]
|
||||
elif from_filesystem := RepositoryPaths.known_repositories(root):
|
||||
repositories = from_filesystem
|
||||
else: # try to read configuration now
|
||||
repositories = [configuration.get("repository", "name")]
|
||||
|
||||
if not repositories:
|
||||
# extract architecture names
|
||||
if (architecture := args.architecture) is not None:
|
||||
parsed = set(
|
||||
RepositoryId(architecture, repository)
|
||||
for repository in repositories
|
||||
)
|
||||
else: # try to read from file system
|
||||
parsed = set(
|
||||
RepositoryId(architecture, repository)
|
||||
for repository in repositories
|
||||
for architecture in RepositoryPaths.known_architectures(root, repository)
|
||||
)
|
||||
|
||||
if not parsed:
|
||||
raise MissingArchitectureError(args.command)
|
||||
return sorted(repositories)
|
||||
return sorted(parsed)
|
||||
|
||||
@@ -72,16 +72,14 @@ class Setup(Handler):
|
||||
|
||||
application = Application(repository_id, configuration, report=report)
|
||||
|
||||
# basically we create configuration here as root, but it is ok, because those files are only used for reading
|
||||
Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths)
|
||||
Setup.executable_create(application.repository.paths, repository_id)
|
||||
repository_server = f"file://{application.repository.paths.repository}" if args.server is None else args.server
|
||||
Setup.configuration_create_devtools(
|
||||
repository_id, args.from_configuration, args.mirror, args.multilib, repository_server)
|
||||
Setup.configuration_create_sudo(application.repository.paths, repository_id)
|
||||
|
||||
# finish initialization
|
||||
with application.repository.paths.preserve_owner():
|
||||
Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths)
|
||||
Setup.executable_create(application.repository.paths, repository_id)
|
||||
repository_server = f"file://{application.repository.paths.repository}" if args.server is None else args.server
|
||||
Setup.configuration_create_devtools(
|
||||
repository_id, args.from_configuration, args.mirror, args.multilib, repository_server)
|
||||
Setup.configuration_create_sudo(application.repository.paths, repository_id)
|
||||
|
||||
application.repository.repo.init()
|
||||
# lazy database sync
|
||||
application.repository.pacman.handle # pylint: disable=pointless-statement
|
||||
|
||||
@@ -21,7 +21,7 @@ import argparse
|
||||
|
||||
from ahriman.application.handlers.handler import Handler, SubParserAction
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.utils import symlink_relative, walk
|
||||
from ahriman.core.utils import walk
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
@@ -50,7 +50,7 @@ class TreeMigrate(Handler):
|
||||
target_tree.tree_create()
|
||||
# perform migration
|
||||
TreeMigrate.tree_move(current_tree, target_tree)
|
||||
TreeMigrate.symlinks_fix(target_tree)
|
||||
TreeMigrate.fix_symlinks(target_tree)
|
||||
|
||||
@staticmethod
|
||||
def _set_service_tree_migrate_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
@@ -69,9 +69,9 @@ class TreeMigrate(Handler):
|
||||
return parser
|
||||
|
||||
@staticmethod
|
||||
def symlinks_fix(paths: RepositoryPaths) -> None:
|
||||
def fix_symlinks(paths: RepositoryPaths) -> None:
|
||||
"""
|
||||
fix package archive symlinks
|
||||
fix packages archives symlinks
|
||||
|
||||
Args:
|
||||
paths(RepositoryPaths): new repository paths
|
||||
@@ -82,7 +82,7 @@ class TreeMigrate(Handler):
|
||||
continue
|
||||
if (source_archive := archives.get(symlink.name)) is not None:
|
||||
symlink.unlink()
|
||||
symlink_relative(symlink, source_archive)
|
||||
symlink.symlink_to(source_archive.relative_to(symlink.parent, walk_up=True))
|
||||
|
||||
@staticmethod
|
||||
def tree_move(from_tree: RepositoryPaths, to_tree: RepositoryPaths) -> None:
|
||||
|
||||
@@ -158,7 +158,7 @@ class Lock(LazyLogging):
|
||||
"""
|
||||
check if current user is actually owner of ahriman root
|
||||
"""
|
||||
check_user(self.paths.root, unsafe=self.unsafe)
|
||||
check_user(self.paths, unsafe=self.unsafe)
|
||||
self.paths.tree_create()
|
||||
|
||||
def check_version(self) -> None:
|
||||
|
||||
@@ -130,7 +130,7 @@ class Pacman(LazyLogging):
|
||||
return # database for some reason deos not exist
|
||||
|
||||
self.logger.info("copy pacman database %s from operating system root to ahriman's home %s", src, dst)
|
||||
with self.repository_paths.preserve_owner():
|
||||
with self.repository_paths.preserve_owner(dst.parent):
|
||||
shutil.copy(src, dst)
|
||||
|
||||
def database_init(self, handle: Handle, repository: str, architecture: str) -> DB:
|
||||
|
||||
@@ -59,15 +59,22 @@ class Repo(LazyLogging):
|
||||
"""
|
||||
return self.root / f"{self.name}.db.tar.gz"
|
||||
|
||||
def add(self, path: Path) -> None:
|
||||
def add(self, path: Path, *, remove: bool = True) -> None:
|
||||
"""
|
||||
add new package to repository
|
||||
|
||||
Args:
|
||||
path(Path): path to archive to add
|
||||
remove(bool, optional): whether to remove old packages or not (Default value = True)
|
||||
"""
|
||||
command = ["repo-add", *self.sign_args]
|
||||
if remove:
|
||||
command.extend(["--remove"])
|
||||
command.extend([str(self.repo_path), str(path)])
|
||||
|
||||
# add to repository
|
||||
check_output(
|
||||
"repo-add", *self.sign_args, "--remove", str(self.repo_path), str(path),
|
||||
*command,
|
||||
exception=BuildError.from_process(path.name),
|
||||
cwd=self.root,
|
||||
logger=self.logger,
|
||||
@@ -81,16 +88,18 @@ class Repo(LazyLogging):
|
||||
check_output("repo-add", *self.sign_args, str(self.repo_path),
|
||||
cwd=self.root, logger=self.logger, user=self.uid)
|
||||
|
||||
def remove(self, package_name: str, filename: Path) -> None:
|
||||
def remove(self, package_name: str | None, filename: Path) -> None:
|
||||
"""
|
||||
remove package from repository
|
||||
|
||||
Args:
|
||||
package_name(str): package name to remove
|
||||
package_name(str | None): package name to remove. If none set, it will be guessed from filename
|
||||
filename(Path): package filename to remove
|
||||
"""
|
||||
package_name = package_name or filename.name.rsplit("-", maxsplit=3)[0]
|
||||
|
||||
# remove package and signature (if any) from filesystem
|
||||
for full_path in self.root.glob(f"{filename.name}*"):
|
||||
for full_path in self.root.glob(f"**/{filename.name}*"):
|
||||
full_path.unlink()
|
||||
|
||||
# remove package from registry
|
||||
|
||||
@@ -19,14 +19,12 @@
|
||||
#
|
||||
import datetime
|
||||
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
|
||||
from ahriman.core.alpm.repo import Repo
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.utils import package_like, symlink_relative, utcnow, walk
|
||||
from ahriman.core.utils import utcnow, walk
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
@@ -50,58 +48,6 @@ class ArchiveTree(LazyLogging):
|
||||
self.repository_id = repository_path.repository_id
|
||||
self.sign_args = sign_args
|
||||
|
||||
@staticmethod
|
||||
def _package_symlinks_create(package_description: PackageDescription, root: Path, archive: Path) -> bool:
|
||||
"""
|
||||
process symlinks creation for single package
|
||||
|
||||
Args:
|
||||
package_description(PackageDescription): archive descriptor
|
||||
root(Path): path to the archive repository root
|
||||
archive(Path): path to directory with archives
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if symlinks were created and ``False`` otherwise
|
||||
"""
|
||||
symlinks_created = False
|
||||
# here we glob for archive itself and signature if any
|
||||
for file in archive.glob(f"{package_description.filename}*"):
|
||||
try:
|
||||
symlink_relative(root / file.name, file)
|
||||
symlinks_created = True
|
||||
except FileExistsError:
|
||||
continue # symlink is already created, skip processing
|
||||
|
||||
return symlinks_created
|
||||
|
||||
def _repo(self, root: Path) -> Repo:
|
||||
"""
|
||||
constructs :class:`ahriman.core.alpm.repo.Repo` object for given path
|
||||
|
||||
Args:
|
||||
root(Path): root of the repository
|
||||
|
||||
Returns:
|
||||
Repo: constructed object with correct properties
|
||||
"""
|
||||
return Repo(self.repository_id.name, self.paths, self.sign_args, root)
|
||||
|
||||
def directories_fix(self, paths: set[Path]) -> None:
|
||||
"""
|
||||
remove empty repository directories recursively
|
||||
|
||||
Args:
|
||||
paths(set[Path]): repositories to check
|
||||
"""
|
||||
root = self.paths.archive / "repos"
|
||||
for repository in paths:
|
||||
parents = [repository] + list(repository.parents[:-1])
|
||||
for parent in parents:
|
||||
path = root / parent
|
||||
if list(path.iterdir()):
|
||||
continue # directory is not empty
|
||||
path.rmdir()
|
||||
|
||||
def repository_for(self, date: datetime.date | None = None) -> Path:
|
||||
"""
|
||||
get full path to repository at the specified date
|
||||
@@ -132,7 +78,7 @@ class ArchiveTree(LazyLogging):
|
||||
packages(list[Package]): list of packages to be updated
|
||||
"""
|
||||
root = self.repository_for()
|
||||
repo = self._repo(root)
|
||||
repo = Repo(self.repository_id.name, self.paths, self.sign_args, root)
|
||||
|
||||
for package in packages:
|
||||
archive = self.paths.archive_for(package.base)
|
||||
@@ -142,15 +88,21 @@ class ArchiveTree(LazyLogging):
|
||||
self.logger.warning("received empty package filename for %s", package_name)
|
||||
continue
|
||||
|
||||
if self._package_symlinks_create(single, root, archive):
|
||||
has_file = False
|
||||
for file in archive.glob(f"{single.filename}*"):
|
||||
symlink = root / file.name
|
||||
try:
|
||||
symlink.symlink_to(file.relative_to(symlink.parent, walk_up=True))
|
||||
has_file = True
|
||||
except FileExistsError:
|
||||
continue # symlink is already created, skip processing
|
||||
|
||||
if has_file:
|
||||
repo.add(root / single.filename)
|
||||
|
||||
def symlinks_fix(self) -> Iterator[Path]:
|
||||
def symlinks_fix(self) -> None:
|
||||
"""
|
||||
remove broken symlinks across repositories for all dates
|
||||
|
||||
Yields:
|
||||
Path: path of the sub-repository with removed symlinks
|
||||
"""
|
||||
for path in walk(self.paths.archive / "repos"):
|
||||
root = path.parent
|
||||
@@ -158,18 +110,12 @@ class ArchiveTree(LazyLogging):
|
||||
if self.repository_id.name != name or self.repository_id.architecture != architecture:
|
||||
continue # we only process same name repositories
|
||||
|
||||
if not package_like(path):
|
||||
continue
|
||||
if not path.is_symlink():
|
||||
continue # find symlinks only
|
||||
if path.exists():
|
||||
continue # filter out not broken symlinks
|
||||
|
||||
# here we don't have access to original archive, so we have to guess name based on archive name
|
||||
# normally it should be fine to do so
|
||||
package_name = path.name.rsplit("-", maxsplit=3)[0]
|
||||
self._repo(root).remove(package_name, path)
|
||||
yield path.parent.relative_to(self.paths.archive / "repos")
|
||||
Repo(self.repository_id.name, self.paths, self.sign_args, root).remove(None, path)
|
||||
|
||||
def tree_create(self) -> None:
|
||||
"""
|
||||
@@ -179,7 +125,7 @@ class ArchiveTree(LazyLogging):
|
||||
if root.exists():
|
||||
return
|
||||
|
||||
with self.paths.preserve_owner():
|
||||
with self.paths.preserve_owner(self.paths.archive):
|
||||
root.mkdir(0o755, parents=True)
|
||||
# init empty repository here
|
||||
self._repo(root).init()
|
||||
Repo(self.repository_id.name, self.paths, self.sign_args, root).init()
|
||||
|
||||
@@ -66,5 +66,4 @@ class ArchiveTrigger(Trigger):
|
||||
"""
|
||||
trigger action which will be called before the stop of the application
|
||||
"""
|
||||
repositories = set(self.tree.symlinks_fix())
|
||||
self.tree.directories_fix(repositories)
|
||||
self.tree.symlinks_fix()
|
||||
|
||||
@@ -22,11 +22,6 @@ try:
|
||||
except ImportError:
|
||||
aiohttp_security = None # type: ignore[assignment]
|
||||
|
||||
try:
|
||||
import aiohttp_session
|
||||
except ImportError:
|
||||
aiohttp_session = None # type: ignore[assignment]
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
@@ -55,7 +50,7 @@ async def check_authorized(*args: Any, **kwargs: Any) -> Any:
|
||||
|
||||
Args:
|
||||
*args(Any): argument list as provided by check_authorized function
|
||||
**kwargs(Any): named argument list as provided by check_authorized function
|
||||
**kwargs(Any): named argument list as provided by authorized_userid function
|
||||
|
||||
Returns:
|
||||
Any: ``None`` in case if no aiohttp_security module found and function call otherwise
|
||||
@@ -71,7 +66,7 @@ async def forget(*args: Any, **kwargs: Any) -> Any:
|
||||
|
||||
Args:
|
||||
*args(Any): argument list as provided by forget function
|
||||
**kwargs(Any): named argument list as provided by forget function
|
||||
**kwargs(Any): named argument list as provided by authorized_userid function
|
||||
|
||||
Returns:
|
||||
Any: ``None`` in case if no aiohttp_security module found and function call otherwise
|
||||
@@ -81,29 +76,13 @@ async def forget(*args: Any, **kwargs: Any) -> Any:
|
||||
return None
|
||||
|
||||
|
||||
async def get_session(*args: Any, **kwargs: Any) -> Any:
|
||||
"""
|
||||
handle aiohttp session methods
|
||||
|
||||
Args:
|
||||
*args(Any): argument list as provided by get_session function
|
||||
**kwargs(Any): named argument list as provided by get_session function
|
||||
|
||||
Returns:
|
||||
Any: empty dictionary in case if no aiohttp_session module found and function call otherwise
|
||||
"""
|
||||
if aiohttp_session is not None:
|
||||
return await aiohttp_session.get_session(*args, **kwargs)
|
||||
return {}
|
||||
|
||||
|
||||
async def remember(*args: Any, **kwargs: Any) -> Any:
|
||||
"""
|
||||
handle disabled auth
|
||||
|
||||
Args:
|
||||
*args(Any): argument list as provided by remember function
|
||||
**kwargs(Any): named argument list as provided by remember function
|
||||
**kwargs(Any): named argument list as provided by authorized_userid function
|
||||
|
||||
Returns:
|
||||
Any: ``None`` in case if no aiohttp_security module found and function call otherwise
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
#
|
||||
import aioauth_client
|
||||
|
||||
from typing import Any
|
||||
|
||||
from ahriman.core.auth.mapping import Mapping
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
@@ -55,7 +53,7 @@ class OAuth(Mapping):
|
||||
self.client_secret = configuration.get("auth", "client_secret")
|
||||
# in order to use OAuth feature the service must be publicity available
|
||||
# thus we expect that address is set
|
||||
self.redirect_uri = f"{configuration.get("web", "address")}/api/v1/login"
|
||||
self.redirect_uri = f"""{configuration.get("web", "address")}/api/v1/login"""
|
||||
self.provider = self.get_provider(configuration.get("auth", "oauth_provider"))
|
||||
# it is list, but we will have to convert to string it anyway
|
||||
self.scopes = configuration.get("auth", "oauth_scopes")
|
||||
@@ -104,35 +102,27 @@ class OAuth(Mapping):
|
||||
"""
|
||||
return self.provider(client_id=self.client_id, client_secret=self.client_secret)
|
||||
|
||||
def get_oauth_url(self, state: str) -> str:
|
||||
def get_oauth_url(self) -> str:
|
||||
"""
|
||||
get authorization URI for the specified settings
|
||||
|
||||
Args:
|
||||
state(str): CSRF token to pass to OAuth2 provider
|
||||
|
||||
Returns:
|
||||
str: authorization URI as a string
|
||||
"""
|
||||
client = self.get_client()
|
||||
uri: str = client.get_authorize_url(scope=self.scopes, redirect_uri=self.redirect_uri, state=state)
|
||||
uri: str = client.get_authorize_url(scope=self.scopes, redirect_uri=self.redirect_uri)
|
||||
return uri
|
||||
|
||||
async def get_oauth_username(self, code: str, state: str | None, session: dict[str, Any]) -> str | None:
|
||||
async def get_oauth_username(self, code: str) -> str | None:
|
||||
"""
|
||||
extract OAuth username from remote
|
||||
|
||||
Args:
|
||||
code(str): authorization code provided by external service
|
||||
state(str | None): CSRF token returned by external service
|
||||
session(dict[str, Any]): current session instance
|
||||
|
||||
Returns:
|
||||
str | None: username as is in OAuth provider
|
||||
"""
|
||||
if state is None or state != session.get("state"):
|
||||
return None
|
||||
|
||||
try:
|
||||
client = self.get_client()
|
||||
access_token, _ = await client.get_access_token(code, redirect_uri=self.redirect_uri)
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2021-2026 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/>.
|
||||
#
|
||||
from ahriman.core.build_tools.task import Task
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.utils import full_version, utcnow
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild import Pkgbuild
|
||||
|
||||
|
||||
class PackageVersion(LazyLogging):
|
||||
"""
|
||||
package version extractor and helper
|
||||
|
||||
Attributes:
|
||||
package(Package): package definitions
|
||||
"""
|
||||
|
||||
def __init__(self, package: Package) -> None:
|
||||
"""
|
||||
Args:
|
||||
package(Package): package definitions
|
||||
"""
|
||||
self.package = package
|
||||
|
||||
def actual_version(self, configuration: Configuration) -> str:
|
||||
"""
|
||||
additional method to handle VCS package versions
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
|
||||
Returns:
|
||||
str: package version if package is not VCS and current version according to VCS otherwise
|
||||
"""
|
||||
if not self.package.is_vcs:
|
||||
return self.package.version
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
paths = configuration.repository_paths
|
||||
task = Task(self.package, configuration, repository_id.architecture, paths)
|
||||
|
||||
try:
|
||||
# create fresh chroot environment, fetch sources and - automagically - update PKGBUILD
|
||||
task.init(paths.cache_for(self.package.base), [], None)
|
||||
pkgbuild = Pkgbuild.from_file(paths.cache_for(self.package.base) / "PKGBUILD")
|
||||
|
||||
return full_version(pkgbuild.get("epoch"), pkgbuild["pkgver"], pkgbuild["pkgrel"])
|
||||
except Exception:
|
||||
self.logger.exception("cannot determine version of VCS package")
|
||||
finally:
|
||||
# clear log files generated by devtools
|
||||
for log_file in paths.cache_for(self.package.base).glob("*.log"):
|
||||
log_file.unlink()
|
||||
|
||||
return self.package.version
|
||||
|
||||
def is_newer_than(self, timestamp: float | int) -> bool:
|
||||
"""
|
||||
check if package was built after the specified timestamp
|
||||
|
||||
Args:
|
||||
timestamp(float | int): timestamp to check build date against
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if package was built after the specified date and ``False`` otherwise.
|
||||
In case if build date is not set by any of packages, it returns False
|
||||
"""
|
||||
return any(
|
||||
package.build_date > timestamp
|
||||
for package in self.package.packages.values()
|
||||
if package.build_date is not None
|
||||
)
|
||||
|
||||
def is_outdated(self, remote: Package, configuration: Configuration, *,
|
||||
calculate_version: bool = True) -> bool:
|
||||
"""
|
||||
check if package is out-of-dated
|
||||
|
||||
Args:
|
||||
remote(Package): package properties from remote source
|
||||
configuration(Configuration): configuration instance
|
||||
calculate_version(bool, optional): expand version to actual value (by calculating git versions)
|
||||
(Default value = True)
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if the package is out-of-dated and ``False`` otherwise
|
||||
"""
|
||||
vcs_allowed_age = configuration.getint("build", "vcs_allowed_age", fallback=0)
|
||||
min_vcs_build_date = utcnow().timestamp() - vcs_allowed_age
|
||||
|
||||
if calculate_version and not self.is_newer_than(min_vcs_build_date):
|
||||
remote_version = PackageVersion(remote).actual_version(configuration)
|
||||
else:
|
||||
remote_version = remote.version
|
||||
|
||||
return self.package.vercmp(remote_version) < 0
|
||||
@@ -27,7 +27,6 @@ from ahriman.core.exceptions import CalledProcessError
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.utils import check_output, utcnow, walk
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild import Pkgbuild
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
@@ -82,7 +81,7 @@ class Sources(LazyLogging):
|
||||
Returns:
|
||||
list[PkgbuildPatch]: generated patch for PKGBUILD architectures if required
|
||||
"""
|
||||
architectures = Pkgbuild.supported_architectures(sources_dir)
|
||||
architectures = Package.supported_architectures(sources_dir)
|
||||
if "any" in architectures: # makepkg does not like when there is any other arch except for any
|
||||
return []
|
||||
architectures.add(architecture)
|
||||
@@ -162,7 +161,7 @@ class Sources(LazyLogging):
|
||||
cwd=sources_dir, logger=instance.logger)
|
||||
|
||||
# extract local files...
|
||||
files = ["PKGBUILD", ".SRCINFO"] + [str(path) for path in Pkgbuild.local_files(sources_dir)]
|
||||
files = ["PKGBUILD", ".SRCINFO"] + [str(path) for path in Package.local_files(sources_dir)]
|
||||
instance.add(sources_dir, *files)
|
||||
# ...and commit them
|
||||
instance.commit(sources_dir)
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
# 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 dataclasses import replace
|
||||
from sqlite3 import Connection
|
||||
|
||||
from ahriman.application.handlers.handler import Handler
|
||||
from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.repository import Explorer
|
||||
from ahriman.core.sign.gpg import GPG
|
||||
from ahriman.core.utils import atomic_move, package_like, symlink_relative
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pacman_synchronization import PacmanSynchronization
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
@@ -43,13 +43,16 @@ def migrate_data(connection: Connection, configuration: Configuration) -> None:
|
||||
"""
|
||||
del connection
|
||||
|
||||
for repository_id in Explorer.repositories_extract(configuration):
|
||||
config_path, _ = configuration.check_loaded()
|
||||
args = argparse.Namespace(configuration=config_path, architecture=None, repository=None, repository_id=None)
|
||||
|
||||
for repository_id in Handler.repositories_extract(args):
|
||||
paths = replace(configuration.repository_paths, repository_id=repository_id)
|
||||
pacman = Pacman(repository_id, configuration, refresh_database=PacmanSynchronization.Disabled)
|
||||
|
||||
# create archive directory if required
|
||||
if not paths.archive.is_dir():
|
||||
with paths.preserve_owner():
|
||||
with paths.preserve_owner(paths.archive):
|
||||
paths.archive.mkdir(mode=0o755, parents=True)
|
||||
|
||||
move_packages(paths, pacman)
|
||||
@@ -63,19 +66,19 @@ def move_packages(repository_paths: RepositoryPaths, pacman: Pacman) -> None:
|
||||
repository_paths(RepositoryPaths): repository paths instance
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
"""
|
||||
for archive in filter(package_like, repository_paths.repository.iterdir()):
|
||||
if not archive.is_file(follow_symlinks=False):
|
||||
for source in repository_paths.repository.iterdir():
|
||||
if not source.is_file(follow_symlinks=False):
|
||||
continue # skip symbolic links if any
|
||||
|
||||
package = Package.from_archive(archive, pacman)
|
||||
artifacts = [archive]
|
||||
# check if there are signatures for this package and append it here too
|
||||
if (signature := GPG.signature(archive)).exists():
|
||||
artifacts.append(signature)
|
||||
filename = source.name
|
||||
if filename.startswith(".") or ".pkg." not in filename:
|
||||
# we don't use package_like method here, because it also filters out signatures
|
||||
continue
|
||||
package = Package.from_archive(source, pacman)
|
||||
|
||||
for source in artifacts:
|
||||
target = repository_paths.ensure_exists(repository_paths.archive_for(package.base)) / source.name
|
||||
# move package to the archive directory
|
||||
atomic_move(source, target)
|
||||
# create symlink to the archive
|
||||
symlink_relative(source, target)
|
||||
# move package to the archive directory
|
||||
target = repository_paths.archive_for(package.base) / filename
|
||||
source.rename(target)
|
||||
|
||||
# create symlink to the archive
|
||||
source.symlink_to(target.relative_to(source.parent, walk_up=True))
|
||||
|
||||
@@ -141,15 +141,14 @@ class LogsOperations(Operations):
|
||||
connection.execute(
|
||||
"""
|
||||
delete from logs
|
||||
where repository = :repository
|
||||
and (package_base, version, repository, process_id) not in (
|
||||
select package_base, version, repository, process_id from logs
|
||||
where (package_base, version, repository, created) in (
|
||||
select package_base, version, repository, max(created) from logs
|
||||
where repository = :repository
|
||||
group by package_base, version, repository
|
||||
)
|
||||
where (package_base, version, repository, process_id) not in (
|
||||
select package_base, version, repository, process_id from logs
|
||||
where (package_base, version, repository, created) in (
|
||||
select package_base, version, repository, max(created) from logs
|
||||
where repository = :repository
|
||||
group by package_base, version, repository
|
||||
)
|
||||
)
|
||||
""",
|
||||
{
|
||||
"repository": repository_id.id,
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import subprocess
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any, Self
|
||||
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
@@ -228,6 +229,20 @@ class PkgbuildParserError(ValueError):
|
||||
ValueError.__init__(self, message)
|
||||
|
||||
|
||||
class PathError(ValueError):
|
||||
"""
|
||||
exception which will be raised on path which is not belong to root directory
|
||||
"""
|
||||
|
||||
def __init__(self, path: Path, root: Path) -> None:
|
||||
"""
|
||||
Args:
|
||||
path(Path): path which raised an exception
|
||||
root(Path): repository root (i.e. ahriman home)
|
||||
"""
|
||||
ValueError.__init__(self, f"Path `{path}` does not belong to repository root `{root}`")
|
||||
|
||||
|
||||
class PasswordError(ValueError):
|
||||
"""
|
||||
exception which will be raised in case of password related errors
|
||||
|
||||
@@ -48,10 +48,6 @@ class RemotePullTrigger(Trigger):
|
||||
"gitremote": {
|
||||
"type": "dict",
|
||||
"schema": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"allowed": ["gitremote"],
|
||||
},
|
||||
"pull_url": {
|
||||
"type": "string",
|
||||
"required": True,
|
||||
@@ -64,6 +60,7 @@ class RemotePullTrigger(Trigger):
|
||||
},
|
||||
},
|
||||
}
|
||||
CONFIGURATION_SCHEMA_FALLBACK = "gitremote"
|
||||
|
||||
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
|
||||
"""
|
||||
@@ -92,6 +89,7 @@ class RemotePullTrigger(Trigger):
|
||||
trigger action which will be called at the start of the application
|
||||
"""
|
||||
for target in self.targets:
|
||||
section, _ = self.configuration.gettype(target, self.repository_id, fallback="gitremote")
|
||||
section, _ = self.configuration.gettype(
|
||||
target, self.repository_id, fallback=self.CONFIGURATION_SCHEMA_FALLBACK)
|
||||
runner = RemotePull(self.repository_id, self.configuration, section)
|
||||
runner.run()
|
||||
|
||||
@@ -52,10 +52,6 @@ class RemotePushTrigger(Trigger):
|
||||
"gitremote": {
|
||||
"type": "dict",
|
||||
"schema": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"allowed": ["gitremote"],
|
||||
},
|
||||
"commit_email": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
@@ -76,6 +72,7 @@ class RemotePushTrigger(Trigger):
|
||||
},
|
||||
},
|
||||
}
|
||||
CONFIGURATION_SCHEMA_FALLBACK = "gitremote"
|
||||
|
||||
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
|
||||
"""
|
||||
@@ -114,6 +111,7 @@ class RemotePushTrigger(Trigger):
|
||||
reporter = ctx.get(Client)
|
||||
|
||||
for target in self.targets:
|
||||
section, _ = self.configuration.gettype(target, self.repository_id, fallback="gitremote")
|
||||
section, _ = self.configuration.gettype(
|
||||
target, self.repository_id, fallback=self.CONFIGURATION_SCHEMA_FALLBACK)
|
||||
runner = RemotePush(reporter, self.configuration, section)
|
||||
runner.run(result)
|
||||
|
||||
@@ -94,7 +94,6 @@ class ArchiveRotationTrigger(Trigger):
|
||||
|
||||
comparator: Callable[[Package, Package], int] = lambda left, right: left.vercmp(right.version)
|
||||
to_remove = sorted(packages.values(), key=cmp_to_key(comparator))
|
||||
# 0 will implicitly be translated into [:0], meaning we keep all packages
|
||||
for single in to_remove[:-self.keep_built_packages]:
|
||||
self.logger.info("removing version %s of package %s", single.version, single.base)
|
||||
for archive in single.packages.values():
|
||||
|
||||
@@ -17,5 +17,4 @@
|
||||
# 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 ahriman.core.repository.explorer import Explorer
|
||||
from ahriman.core.repository.repository import Repository
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#
|
||||
import shutil
|
||||
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import Iterable, Iterator
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
@@ -27,7 +27,7 @@ from ahriman.core.build_tools.package_archive import PackageArchive
|
||||
from ahriman.core.build_tools.task import Task
|
||||
from ahriman.core.repository.cleaner import Cleaner
|
||||
from ahriman.core.repository.package_info import PackageInfo
|
||||
from ahriman.core.utils import atomic_move, filelock, list_flatmap, package_like, safe_filename, symlink_relative
|
||||
from ahriman.core.utils import atomic_move, filelock, package_like, safe_filename
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.event import EventType
|
||||
from ahriman.models.package import Package
|
||||
@@ -41,34 +41,35 @@ class Executor(PackageInfo, Cleaner):
|
||||
trait for common repository update processes
|
||||
"""
|
||||
|
||||
def _archive_lookup(self, package: Package) -> list[Path]:
|
||||
def _archive_lookup(self, package: Package) -> Iterator[Path]:
|
||||
"""
|
||||
check if there is a rebuilt package already
|
||||
|
||||
Args:
|
||||
package(Package): package to check
|
||||
|
||||
Returns:
|
||||
list[Path]: list of built packages and signatures if available, empty list otherwise
|
||||
Yields:
|
||||
Path: list of built packages and signatures if available, empty list otherwise
|
||||
"""
|
||||
archive = self.paths.archive_for(package.base)
|
||||
if not archive.is_dir():
|
||||
return []
|
||||
|
||||
for path in filter(package_like, archive.iterdir()):
|
||||
# check if package version is the same
|
||||
built = Package.from_archive(path, self.pacman)
|
||||
if built.version != package.version:
|
||||
continue
|
||||
# find all packages which have same version
|
||||
same_version = [
|
||||
built
|
||||
for path in filter(package_like, archive.iterdir())
|
||||
if (built := Package.from_archive(path, self.pacman)).version == package.version
|
||||
]
|
||||
# no packages of the same version found
|
||||
if not same_version:
|
||||
return
|
||||
|
||||
packages = built.packages.values()
|
||||
# all packages must be either any or same architecture
|
||||
if not all(single.architecture in ("any", self.architecture) for single in packages):
|
||||
continue
|
||||
packages = [single for built in same_version for single in built.packages.values()]
|
||||
# all packages must be either any or same architecture
|
||||
if not all(single.architecture in ("any", self.architecture) for single in packages):
|
||||
return
|
||||
|
||||
return list_flatmap(packages, lambda single: archive.glob(f"{single.filename}*"))
|
||||
|
||||
return []
|
||||
for single in packages:
|
||||
yield from archive.glob(f"{single.filename}*")
|
||||
|
||||
def _archive_rename(self, description: PackageDescription, package_base: str) -> None:
|
||||
"""
|
||||
@@ -110,10 +111,10 @@ class Executor(PackageInfo, Cleaner):
|
||||
if prebuilt := list(self._archive_lookup(loaded_package)):
|
||||
self.logger.info("using prebuilt packages for %s-%s", loaded_package.base, loaded_package.version)
|
||||
built = []
|
||||
for artifact in prebuilt:
|
||||
with filelock(artifact):
|
||||
shutil.copy(artifact, path)
|
||||
built.append(path / artifact.name)
|
||||
for artefact in prebuilt:
|
||||
with filelock(artefact):
|
||||
shutil.copy(artefact, path)
|
||||
built.append(path / artefact.name)
|
||||
else:
|
||||
built = task.build(path, PACKAGER=packager)
|
||||
|
||||
@@ -142,7 +143,7 @@ class Executor(PackageInfo, Cleaner):
|
||||
remove package base from repository
|
||||
|
||||
Args:
|
||||
package_base(str): package base name
|
||||
package_base(str): package base name:
|
||||
"""
|
||||
try:
|
||||
with self.in_event(package_base, EventType.PackageRemoved):
|
||||
@@ -168,10 +169,10 @@ class Executor(PackageInfo, Cleaner):
|
||||
files = self.sign.process_sign_package(full_path, packager_key)
|
||||
|
||||
for src in files:
|
||||
dst = self.paths.ensure_exists(self.paths.archive_for(package_base)) / src.name
|
||||
dst = self.paths.archive_for(package_base) / src.name
|
||||
atomic_move(src, dst) # move package to archive directory
|
||||
if not (symlink := self.paths.repository / dst.name).exists():
|
||||
symlink_relative(symlink, dst) # create link to archive
|
||||
symlink.symlink_to(dst.relative_to(symlink.parent, walk_up=True)) # create link to archive
|
||||
|
||||
self.repo.add(self.paths.repository / filename)
|
||||
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2021-2026 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/>.
|
||||
#
|
||||
from collections.abc import Iterable
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
class Explorer:
|
||||
"""
|
||||
helper to read filesystem and find created repositories
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def repositories_extract(configuration: Configuration, repository: str | None = None,
|
||||
architecture: str | None = None) -> list[RepositoryId]:
|
||||
"""
|
||||
get known architectures
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
repository(str | None, optional): predefined repository name if available (Default value = None)
|
||||
architecture(str | None, optional): predefined repository architecture if available (Default value = None)
|
||||
|
||||
Returns:
|
||||
list[RepositoryId]: list of repository names and architectures for which tree is created
|
||||
"""
|
||||
# pylint, wtf???
|
||||
root = configuration.getpath("repository", "root") # pylint: disable=assignment-from-no-return
|
||||
|
||||
# extract repository names first
|
||||
if repository is not None:
|
||||
repositories: Iterable[str] = [repository]
|
||||
elif from_filesystem := RepositoryPaths.known_repositories(root):
|
||||
repositories = from_filesystem
|
||||
else: # try to read configuration now
|
||||
repositories = [configuration.get("repository", "name")]
|
||||
|
||||
# extract architecture names
|
||||
if architecture is not None:
|
||||
parsed = set(
|
||||
RepositoryId(architecture, repository)
|
||||
for repository in repositories
|
||||
)
|
||||
else: # try to read from file system
|
||||
parsed = set(
|
||||
RepositoryId(architecture, repository)
|
||||
for repository in repositories
|
||||
for architecture in RepositoryPaths.known_architectures(root, repository)
|
||||
)
|
||||
|
||||
return sorted(parsed)
|
||||
@@ -17,13 +17,10 @@
|
||||
# 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 copy
|
||||
|
||||
from collections.abc import Iterable
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from ahriman.core.build_tools.package_version import PackageVersion
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.repository.repository_properties import RepositoryProperties
|
||||
from ahriman.core.utils import package_like
|
||||
@@ -36,40 +33,6 @@ class PackageInfo(RepositoryProperties):
|
||||
handler for the package information
|
||||
"""
|
||||
|
||||
def full_depends(self, package: Package, packages: Iterable[Package]) -> list[str]:
|
||||
"""
|
||||
generate full dependencies list including transitive dependencies
|
||||
|
||||
Args:
|
||||
package(Package): package to check dependencies for
|
||||
packages(Iterable[Package]): repository package list
|
||||
|
||||
Returns:
|
||||
list[str]: all dependencies of the package
|
||||
"""
|
||||
dependencies = {}
|
||||
# load own package dependencies
|
||||
for package_base in packages:
|
||||
for name, repo_package in package_base.packages.items():
|
||||
dependencies[name] = repo_package.depends
|
||||
for provides in repo_package.provides:
|
||||
dependencies[provides] = repo_package.depends
|
||||
# load repository dependencies
|
||||
for database in self.pacman.handle.get_syncdbs():
|
||||
for pacman_package in database.pkgcache:
|
||||
dependencies[pacman_package.name] = pacman_package.depends
|
||||
for provides in pacman_package.provides:
|
||||
dependencies[provides] = pacman_package.depends
|
||||
|
||||
result = set(package.depends)
|
||||
current_depends: set[str] = set()
|
||||
while result != current_depends:
|
||||
current_depends = copy.deepcopy(result)
|
||||
for package_name in current_depends:
|
||||
result.update(dependencies.get(package_name, []))
|
||||
|
||||
return sorted(result)
|
||||
|
||||
def load_archives(self, packages: Iterable[Path]) -> list[Package]:
|
||||
"""
|
||||
load packages from list of archives
|
||||
@@ -95,7 +58,7 @@ class PackageInfo(RepositoryProperties):
|
||||
# force version to max of them
|
||||
self.logger.warning("version of %s differs, found %s and %s",
|
||||
current.base, current.version, local.version)
|
||||
if PackageVersion(current).is_outdated(local, self.configuration, calculate_version=False):
|
||||
if current.is_outdated(local, self.configuration, calculate_version=False):
|
||||
current.version = local.version
|
||||
current.packages.update(local.packages)
|
||||
except Exception:
|
||||
@@ -167,5 +130,5 @@ class PackageInfo(RepositoryProperties):
|
||||
return [
|
||||
package
|
||||
for package in packages
|
||||
if depends_on.intersection(self.full_depends(package, packages))
|
||||
if depends_on.intersection(package.full_depends(self.pacman, packages))
|
||||
]
|
||||
|
||||
@@ -19,12 +19,10 @@
|
||||
#
|
||||
from collections.abc import Iterable
|
||||
|
||||
from ahriman.core.build_tools.package_version import PackageVersion
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.core.repository.cleaner import Cleaner
|
||||
from ahriman.core.repository.package_info import PackageInfo
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.event import EventType
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
@@ -69,12 +67,10 @@ class UpdateHandler(PackageInfo, Cleaner):
|
||||
try:
|
||||
remote = load_remote(local)
|
||||
|
||||
if PackageVersion(local).is_outdated(remote, self.configuration, calculate_version=vcs):
|
||||
if local.is_outdated(remote, self.configuration, calculate_version=vcs):
|
||||
self.reporter.set_pending(local.base)
|
||||
self.event(local.base, EventType.PackageOutdated, "Remote version is newer than local")
|
||||
result.append(remote)
|
||||
else:
|
||||
self.reporter.package_status_update(local.base, BuildStatusEnum.Success)
|
||||
except Exception:
|
||||
self.reporter.set_failed(local.base)
|
||||
self.logger.exception("could not load remote package %s", local.base)
|
||||
@@ -83,8 +79,7 @@ class UpdateHandler(PackageInfo, Cleaner):
|
||||
|
||||
def updates_dependencies(self, filter_packages: Iterable[str]) -> list[Package]:
|
||||
"""
|
||||
check packages which are required to be rebuilt based on dynamic dependencies
|
||||
(e.g. linking, modules paths, etc.)
|
||||
check packages which ae required to be rebuilt based on dynamic dependencies (e.g. linking, modules paths, etc.)
|
||||
|
||||
Args:
|
||||
filter_packages(Iterable[str]): do not check every package just specified in the list
|
||||
@@ -158,7 +153,7 @@ class UpdateHandler(PackageInfo, Cleaner):
|
||||
if local.remote.is_remote:
|
||||
continue # avoid checking AUR packages
|
||||
|
||||
if PackageVersion(local).is_outdated(remote, self.configuration, calculate_version=vcs):
|
||||
if local.is_outdated(remote, self.configuration, calculate_version=vcs):
|
||||
self.reporter.set_pending(local.base)
|
||||
self.event(local.base, EventType.PackageOutdated, "Locally pulled sources are outdated")
|
||||
result.append(remote)
|
||||
@@ -185,9 +180,8 @@ class UpdateHandler(PackageInfo, Cleaner):
|
||||
else:
|
||||
self.reporter.set_pending(local.base)
|
||||
self.event(local.base, EventType.PackageOutdated, "Manual update is requested")
|
||||
|
||||
self.clear_queue()
|
||||
except Exception:
|
||||
self.logger.exception("could not load packages from database")
|
||||
self.clear_queue()
|
||||
|
||||
return result
|
||||
|
||||
@@ -34,6 +34,8 @@ class Trigger(LazyLogging):
|
||||
|
||||
Attributes:
|
||||
CONFIGURATION_SCHEMA(ConfigurationSchema): (class attribute) configuration schema template
|
||||
CONFIGURATION_SCHEMA_FALLBACK(str | None): (class attribute) optional fallback option for defining
|
||||
configuration schema type used
|
||||
REQUIRES_REPOSITORY(bool): (class attribute) either trigger requires loaded repository or not
|
||||
configuration(Configuration): configuration instance
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
@@ -57,6 +59,7 @@ class Trigger(LazyLogging):
|
||||
"""
|
||||
|
||||
CONFIGURATION_SCHEMA: ClassVar[ConfigurationSchema] = {}
|
||||
CONFIGURATION_SCHEMA_FALLBACK: ClassVar[str | None] = None
|
||||
REQUIRES_REPOSITORY: ClassVar[bool] = True
|
||||
|
||||
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# pylint: disable=too-many-lines
|
||||
import contextlib
|
||||
import datetime
|
||||
import fcntl
|
||||
import io
|
||||
import itertools
|
||||
import logging
|
||||
@@ -32,13 +33,12 @@ import subprocess
|
||||
from collections.abc import Callable, Iterable, Iterator, Mapping
|
||||
from dataclasses import asdict
|
||||
from enum import Enum
|
||||
from filelock import FileLock
|
||||
from pathlib import Path
|
||||
from pwd import getpwuid
|
||||
from typing import Any, IO, TypeVar
|
||||
|
||||
from ahriman.core.exceptions import CalledProcessError, OptionError, UnsafeRunError
|
||||
from ahriman.core.types import Comparable
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
__all__ = [
|
||||
@@ -51,9 +51,7 @@ __all__ = [
|
||||
"filelock",
|
||||
"filter_json",
|
||||
"full_version",
|
||||
"list_flatmap",
|
||||
"minmax",
|
||||
"owner",
|
||||
"package_like",
|
||||
"parse_version",
|
||||
"partition",
|
||||
@@ -63,14 +61,12 @@ __all__ = [
|
||||
"safe_filename",
|
||||
"srcinfo_property",
|
||||
"srcinfo_property_list",
|
||||
"symlink_relative",
|
||||
"trim_package",
|
||||
"utcnow",
|
||||
"walk",
|
||||
]
|
||||
|
||||
|
||||
R = TypeVar("R", bound=Comparable)
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@@ -198,13 +194,12 @@ def check_output(*args: str, exception: Exception | Callable[[int, list[str], st
|
||||
return stdout
|
||||
|
||||
|
||||
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
|
||||
|
||||
Args:
|
||||
root(Path): path to root directory (e.g. repository root
|
||||
:attr:`ahriman.models.repository_paths.RepositoryPaths.root`)
|
||||
paths(RepositoryPaths): repository paths object
|
||||
unsafe(bool): if set no user check will be performed before path creation
|
||||
|
||||
Raises:
|
||||
@@ -213,16 +208,14 @@ def check_user(root: Path, *, unsafe: bool) -> None:
|
||||
Examples:
|
||||
Simply run function with arguments::
|
||||
|
||||
>>> check_user(root, unsafe=False)
|
||||
>>> check_user(paths, unsafe=False)
|
||||
"""
|
||||
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.geteuid()
|
||||
root_uid, _ = owner(root)
|
||||
|
||||
current_uid = os.getuid()
|
||||
root_uid, _ = paths.root_owner
|
||||
if current_uid != root_uid:
|
||||
raise UnsafeRunError(current_uid, root_uid)
|
||||
|
||||
@@ -265,22 +258,24 @@ def extract_user() -> str | None:
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def filelock(path: Path) -> Iterator[FileLock]:
|
||||
def filelock(path: Path) -> Iterator[None]:
|
||||
"""
|
||||
wrapper around :class:`filelock.FileLock`, which also removes locks afterward
|
||||
lock on file passed as argument
|
||||
|
||||
Args:
|
||||
path(Path): path to lock on. The lock file will be created as ``.{path.name}.lock``
|
||||
|
||||
Yields:
|
||||
FileLock: acquired file lock instance
|
||||
path(Path): path object on which lock must be performed
|
||||
"""
|
||||
lock_path = path.with_name(f".{path.name}.lock")
|
||||
lock_path = path.with_name(f".{path.name}")
|
||||
try:
|
||||
with FileLock(lock_path) as lock:
|
||||
yield lock
|
||||
with lock_path.open("ab") as lock_file:
|
||||
fd = lock_file.fileno()
|
||||
try:
|
||||
fcntl.flock(fd, fcntl.LOCK_EX) # lock file and wait lock is until available
|
||||
yield
|
||||
finally:
|
||||
fcntl.flock(fd, fcntl.LOCK_UN) # unlock file first
|
||||
finally:
|
||||
lock_path.unlink(missing_ok=True)
|
||||
lock_path.unlink(missing_ok=True) # remove lock file at the end
|
||||
|
||||
|
||||
def filter_json(source: dict[str, Any], known_fields: Iterable[str]) -> dict[str, Any]:
|
||||
@@ -323,24 +318,6 @@ def full_version(epoch: str | int | None, pkgver: str, pkgrel: str) -> str:
|
||||
return f"{prefix}{pkgver}-{pkgrel}"
|
||||
|
||||
|
||||
def list_flatmap(source: Iterable[T], extractor: Callable[[T], Iterable[R]]) -> list[R]:
|
||||
"""
|
||||
extract elements from list of lists, flatten them and apply ``extractor``
|
||||
|
||||
Args:
|
||||
source(Iterable[T]): source list
|
||||
extractor(Callable[[T], Iterable[R]]): property extractor
|
||||
|
||||
Returns:
|
||||
list[R]: combined list of unique entries in properties list
|
||||
"""
|
||||
def generator() -> Iterator[R]:
|
||||
for inner in source:
|
||||
yield from extractor(inner)
|
||||
|
||||
return sorted(set(generator()))
|
||||
|
||||
|
||||
def minmax(source: Iterable[T], *, key: Callable[[T], Any] | None = None) -> tuple[T, T]:
|
||||
"""
|
||||
get min and max value from iterable
|
||||
@@ -357,20 +334,6 @@ def minmax(source: Iterable[T], *, key: Callable[[T], Any] | None = None) -> tup
|
||||
return min(first_iter, key=key), max(second_iter, key=key) # type: ignore
|
||||
|
||||
|
||||
def owner(path: Path) -> tuple[int, int]:
|
||||
"""
|
||||
retrieve owner information by path
|
||||
|
||||
Args:
|
||||
path(Path): path for which extract ids
|
||||
|
||||
Returns:
|
||||
tuple[int, int]: owner user and group ids of the directory
|
||||
"""
|
||||
stat = path.stat()
|
||||
return stat.st_uid, stat.st_gid
|
||||
|
||||
|
||||
def package_like(filename: Path) -> bool:
|
||||
"""
|
||||
check if file looks like package
|
||||
@@ -554,17 +517,6 @@ def srcinfo_property_list(key: str, srcinfo: Mapping[str, Any], package_srcinfo:
|
||||
return values
|
||||
|
||||
|
||||
def symlink_relative(symlink: Path, source: Path) -> None:
|
||||
"""
|
||||
create symlink with relative path to the target directory
|
||||
|
||||
Args:
|
||||
symlink(Path): path to symlink to create
|
||||
source(Path): source file to be symlinked
|
||||
"""
|
||||
symlink.symlink_to(source.relative_to(symlink.parent, walk_up=True))
|
||||
|
||||
|
||||
def trim_package(package_name: str) -> str:
|
||||
"""
|
||||
remove version bound and description from package name. Pacman allows to specify version bound (=, <=, >= etc.) for
|
||||
|
||||
@@ -17,18 +17,23 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# pylint: disable=too-many-lines,too-many-public-methods
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
import copy
|
||||
|
||||
from collections.abc import Callable, Iterable, Iterator
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from pyalpm import vercmp # type: ignore[import-not-found]
|
||||
from typing import Any, Self
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.alpm.remote import AUR, Official, OfficialSyncdb
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.utils import dataclass_view, full_version, list_flatmap, parse_version, srcinfo_property_list
|
||||
from ahriman.core.utils import dataclass_view, full_version, parse_version, srcinfo_property_list, utcnow
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.pkgbuild import Pkgbuild
|
||||
@@ -84,7 +89,7 @@ class Package(LazyLogging):
|
||||
Returns:
|
||||
list[str]: sum of dependencies per each package
|
||||
"""
|
||||
return list_flatmap(self.packages.values(), lambda package: package.depends)
|
||||
return self._package_list_property(lambda package: package.depends)
|
||||
|
||||
@property
|
||||
def depends_build(self) -> set[str]:
|
||||
@@ -104,7 +109,7 @@ class Package(LazyLogging):
|
||||
Returns:
|
||||
list[str]: sum of test dependencies per each package
|
||||
"""
|
||||
return list_flatmap(self.packages.values(), lambda package: package.check_depends)
|
||||
return self._package_list_property(lambda package: package.check_depends)
|
||||
|
||||
@property
|
||||
def depends_make(self) -> list[str]:
|
||||
@@ -114,7 +119,7 @@ class Package(LazyLogging):
|
||||
Returns:
|
||||
list[str]: sum of make dependencies per each package
|
||||
"""
|
||||
return list_flatmap(self.packages.values(), lambda package: package.make_depends)
|
||||
return self._package_list_property(lambda package: package.make_depends)
|
||||
|
||||
@property
|
||||
def depends_opt(self) -> list[str]:
|
||||
@@ -124,7 +129,7 @@ class Package(LazyLogging):
|
||||
Returns:
|
||||
list[str]: sum of optional dependencies per each package
|
||||
"""
|
||||
return list_flatmap(self.packages.values(), lambda package: package.opt_depends)
|
||||
return self._package_list_property(lambda package: package.opt_depends)
|
||||
|
||||
@property
|
||||
def groups(self) -> list[str]:
|
||||
@@ -134,7 +139,7 @@ class Package(LazyLogging):
|
||||
Returns:
|
||||
list[str]: sum of groups per each package
|
||||
"""
|
||||
return list_flatmap(self.packages.values(), lambda package: package.groups)
|
||||
return self._package_list_property(lambda package: package.groups)
|
||||
|
||||
@property
|
||||
def is_single_package(self) -> bool:
|
||||
@@ -155,7 +160,7 @@ class Package(LazyLogging):
|
||||
bool: ``True`` in case if package base looks like VCS package and ``False`` otherwise
|
||||
"""
|
||||
return self.base.endswith("-bzr") \
|
||||
or self.base.endswith("-cvs") \
|
||||
or self.base.endswith("-csv") \
|
||||
or self.base.endswith("-darcs") \
|
||||
or self.base.endswith("-git") \
|
||||
or self.base.endswith("-hg") \
|
||||
@@ -169,7 +174,7 @@ class Package(LazyLogging):
|
||||
Returns:
|
||||
list[str]: sum of licenses per each package
|
||||
"""
|
||||
return list_flatmap(self.packages.values(), lambda package: package.licenses)
|
||||
return self._package_list_property(lambda package: package.licenses)
|
||||
|
||||
@property
|
||||
def packages_full(self) -> list[str]:
|
||||
@@ -200,7 +205,7 @@ class Package(LazyLogging):
|
||||
package = pacman.handle.load_pkg(str(path))
|
||||
description = PackageDescription.from_package(package, path)
|
||||
return cls(
|
||||
base=package.base or package.name,
|
||||
base=package.base,
|
||||
version=package.version,
|
||||
remote=RemoteSource(source=PackageSource.Archive),
|
||||
packages={package.name: description},
|
||||
@@ -340,6 +345,183 @@ class Package(LazyLogging):
|
||||
packager=packager,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def local_files(path: Path) -> Iterator[Path]:
|
||||
"""
|
||||
extract list of local files
|
||||
|
||||
Args:
|
||||
path(Path): path to package sources directory
|
||||
|
||||
Yields:
|
||||
Path: list of paths of files which belong to the package and distributed together with this tarball.
|
||||
All paths are relative to the ``path``
|
||||
|
||||
Raises:
|
||||
PackageInfoError: if there are parsing errors
|
||||
"""
|
||||
pkgbuild = Pkgbuild.from_file(path / "PKGBUILD")
|
||||
# we could use arch property, but for consistency it is better to call special method
|
||||
architectures = Package.supported_architectures(path)
|
||||
|
||||
for architecture in architectures:
|
||||
for source in srcinfo_property_list("source", pkgbuild, {}, architecture=architecture):
|
||||
if "::" in source:
|
||||
_, source = source.split("::", maxsplit=1) # in case if filename is specified, remove it
|
||||
|
||||
if urlparse(source).scheme:
|
||||
# basically file schema should use absolute path which is impossible if we are distributing
|
||||
# files together with PKGBUILD. In this case we are going to skip it also
|
||||
continue
|
||||
|
||||
yield Path(source)
|
||||
|
||||
if (install := pkgbuild.get("install")) is not None:
|
||||
yield Path(install)
|
||||
|
||||
@staticmethod
|
||||
def supported_architectures(path: Path) -> set[str]:
|
||||
"""
|
||||
load supported architectures from package sources
|
||||
|
||||
Args:
|
||||
path(Path): path to package sources directory
|
||||
|
||||
Returns:
|
||||
set[str]: list of package supported architectures
|
||||
"""
|
||||
pkgbuild = Pkgbuild.from_file(path / "PKGBUILD")
|
||||
return set(pkgbuild.get("arch", []))
|
||||
|
||||
def _package_list_property(self, extractor: Callable[[PackageDescription], list[str]]) -> list[str]:
|
||||
"""
|
||||
extract list property from single packages and combine them into one list
|
||||
|
||||
Notes:
|
||||
Basically this method is generic for type of ``list[T]``, but there is no trait ``Comparable`` in default
|
||||
packages, thus we limit this method only to new types
|
||||
|
||||
Args:
|
||||
extractor(Callable[[PackageDescription], list[str]): package property extractor
|
||||
|
||||
Returns:
|
||||
list[str]: combined list of unique entries in properties list
|
||||
"""
|
||||
def generator() -> Iterator[str]:
|
||||
for package in self.packages.values():
|
||||
yield from extractor(package)
|
||||
|
||||
return sorted(set(generator()))
|
||||
|
||||
def actual_version(self, configuration: Configuration) -> str:
|
||||
"""
|
||||
additional method to handle VCS package versions
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
|
||||
Returns:
|
||||
str: package version if package is not VCS and current version according to VCS otherwise
|
||||
"""
|
||||
if not self.is_vcs:
|
||||
return self.version
|
||||
|
||||
from ahriman.core.build_tools.task import Task
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
paths = configuration.repository_paths
|
||||
task = Task(self, configuration, repository_id.architecture, paths)
|
||||
|
||||
try:
|
||||
# create fresh chroot environment, fetch sources and - automagically - update PKGBUILD
|
||||
task.init(paths.cache_for(self.base), [], None)
|
||||
pkgbuild = Pkgbuild.from_file(paths.cache_for(self.base) / "PKGBUILD")
|
||||
|
||||
return full_version(pkgbuild.get("epoch"), pkgbuild["pkgver"], pkgbuild["pkgrel"])
|
||||
except Exception:
|
||||
self.logger.exception("cannot determine version of VCS package")
|
||||
finally:
|
||||
# clear log files generated by devtools
|
||||
for log_file in paths.cache_for(self.base).glob("*.log"):
|
||||
log_file.unlink()
|
||||
|
||||
return self.version
|
||||
|
||||
def full_depends(self, pacman: Pacman, packages: Iterable[Package]) -> list[str]:
|
||||
"""
|
||||
generate full dependencies list including transitive dependencies
|
||||
|
||||
Args:
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
packages(Iterable[Package]): repository package list
|
||||
|
||||
Returns:
|
||||
list[str]: all dependencies of the package
|
||||
"""
|
||||
dependencies = {}
|
||||
# load own package dependencies
|
||||
for package_base in packages:
|
||||
for name, repo_package in package_base.packages.items():
|
||||
dependencies[name] = repo_package.depends
|
||||
for provides in repo_package.provides:
|
||||
dependencies[provides] = repo_package.depends
|
||||
# load repository dependencies
|
||||
for database in pacman.handle.get_syncdbs():
|
||||
for pacman_package in database.pkgcache:
|
||||
dependencies[pacman_package.name] = pacman_package.depends
|
||||
for provides in pacman_package.provides:
|
||||
dependencies[provides] = pacman_package.depends
|
||||
|
||||
result = set(self.depends)
|
||||
current_depends: set[str] = set()
|
||||
while result != current_depends:
|
||||
current_depends = copy.deepcopy(result)
|
||||
for package in current_depends:
|
||||
result.update(dependencies.get(package, []))
|
||||
|
||||
return sorted(result)
|
||||
|
||||
def is_newer_than(self, timestamp: float | int) -> bool:
|
||||
"""
|
||||
check if package was built after the specified timestamp
|
||||
|
||||
Args:
|
||||
timestamp(float | int): timestamp to check build date against
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if package was built after the specified date and ``False`` otherwise.
|
||||
In case if build date is not set by any of packages, it returns False
|
||||
"""
|
||||
return any(
|
||||
package.build_date > timestamp
|
||||
for package in self.packages.values()
|
||||
if package.build_date is not None
|
||||
)
|
||||
|
||||
def is_outdated(self, remote: Package, configuration: Configuration, *,
|
||||
calculate_version: bool = True) -> bool:
|
||||
"""
|
||||
check if package is out-of-dated
|
||||
|
||||
Args:
|
||||
remote(Package): package properties from remote source
|
||||
configuration(Configuration): configuration instance
|
||||
calculate_version(bool, optional): expand version to actual value (by calculating git versions)
|
||||
(Default value = True)
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if the package is out-of-dated and ``False`` otherwise
|
||||
"""
|
||||
vcs_allowed_age = configuration.getint("build", "vcs_allowed_age", fallback=0)
|
||||
min_vcs_build_date = utcnow().timestamp() - vcs_allowed_age
|
||||
|
||||
if calculate_version and not self.is_newer_than(min_vcs_build_date):
|
||||
remote_version = remote.actual_version(configuration)
|
||||
else:
|
||||
remote_version = remote.version
|
||||
|
||||
return self.vercmp(remote_version) < 0
|
||||
|
||||
def next_pkgrel(self, local_version: str | None) -> str | None:
|
||||
"""
|
||||
generate next pkgrel variable. The package release will be incremented if ``local_version`` is more or equal to
|
||||
|
||||
@@ -22,10 +22,8 @@ from dataclasses import dataclass
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from typing import Any, ClassVar, IO, Self
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from ahriman.core.alpm.pkgbuild_parser import PkgbuildParser, PkgbuildToken
|
||||
from ahriman.core.utils import srcinfo_property_list
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
|
||||
|
||||
@@ -105,54 +103,6 @@ class Pkgbuild(Mapping[str, Any]):
|
||||
|
||||
return cls({key: value for key, value in fields.items() if key})
|
||||
|
||||
@staticmethod
|
||||
def local_files(path: Path) -> Iterator[Path]:
|
||||
"""
|
||||
extract list of local files
|
||||
|
||||
Args:
|
||||
path(Path): path to package sources directory
|
||||
|
||||
Yields:
|
||||
Path: list of paths of files which belong to the package and distributed together with this tarball.
|
||||
All paths are relative to the ``path``
|
||||
|
||||
Raises:
|
||||
PackageInfoError: if there are parsing errors
|
||||
"""
|
||||
pkgbuild = Pkgbuild.from_file(path / "PKGBUILD")
|
||||
# we could use arch property, but for consistency it is better to call special method
|
||||
architectures = Pkgbuild.supported_architectures(path)
|
||||
|
||||
for architecture in architectures:
|
||||
for source in srcinfo_property_list("source", pkgbuild, {}, architecture=architecture):
|
||||
if "::" in source:
|
||||
_, source = source.split("::", maxsplit=1) # in case if filename is specified, remove it
|
||||
|
||||
if urlparse(source).scheme:
|
||||
# basically file schema should use absolute path which is impossible if we are distributing
|
||||
# files together with PKGBUILD. In this case we are going to skip it also
|
||||
continue
|
||||
|
||||
yield Path(source)
|
||||
|
||||
if (install := pkgbuild.get("install")) is not None:
|
||||
yield Path(install)
|
||||
|
||||
@staticmethod
|
||||
def supported_architectures(path: Path) -> set[str]:
|
||||
"""
|
||||
load supported architectures from package sources
|
||||
|
||||
Args:
|
||||
path(Path): path to package sources directory
|
||||
|
||||
Returns:
|
||||
set[str]: list of package supported architectures
|
||||
"""
|
||||
pkgbuild = Pkgbuild.from_file(path / "PKGBUILD")
|
||||
return set(pkgbuild.get("arch", []))
|
||||
|
||||
def packages(self) -> dict[str, Self]:
|
||||
"""
|
||||
extract properties from internal package functions
|
||||
|
||||
@@ -27,8 +27,8 @@ from functools import cached_property
|
||||
from pathlib import Path
|
||||
from pwd import getpwuid
|
||||
|
||||
from ahriman.core.exceptions import PathError
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.utils import owner
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ class RepositoryPaths(LazyLogging):
|
||||
Returns:
|
||||
Path: path to directory in which build process is run
|
||||
"""
|
||||
uid, _ = owner(self.root)
|
||||
uid, _ = self.owner(self.root)
|
||||
return self.chroot / f"{self.repository_id.name}-{self.repository_id.architecture}" / getpwuid(uid).pw_name
|
||||
|
||||
@property
|
||||
@@ -165,7 +165,7 @@ class RepositoryPaths(LazyLogging):
|
||||
Returns:
|
||||
tuple[int, int]: owner user and group of the root directory
|
||||
"""
|
||||
return owner(self.root)
|
||||
return self.owner(self.root)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
@classmethod
|
||||
@@ -218,6 +218,47 @@ class RepositoryPaths(LazyLogging):
|
||||
|
||||
return set(walk(instance))
|
||||
|
||||
@staticmethod
|
||||
def owner(path: Path) -> tuple[int, int]:
|
||||
"""
|
||||
retrieve owner information by path
|
||||
|
||||
Args:
|
||||
path(Path): path for which extract ids
|
||||
|
||||
Returns:
|
||||
tuple[int, int]: owner user and group ids of the directory
|
||||
"""
|
||||
stat = path.stat()
|
||||
return stat.st_uid, stat.st_gid
|
||||
|
||||
def _chown(self, path: Path) -> None:
|
||||
"""
|
||||
set owner of path recursively (from root) to root owner
|
||||
|
||||
Notes:
|
||||
More likely you don't want to call this method explicitly, consider using :func:`preserve_owner()`
|
||||
as context manager instead
|
||||
|
||||
Args:
|
||||
path(Path): path to be chown
|
||||
|
||||
Raises:
|
||||
PathError: if path does not belong to root
|
||||
"""
|
||||
def set_owner(current: Path) -> None:
|
||||
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 PathError(path, self.root)
|
||||
root_uid, root_gid = self.root_owner
|
||||
while path != self.root:
|
||||
set_owner(path)
|
||||
path = path.parent
|
||||
|
||||
def archive_for(self, package_base: str) -> Path:
|
||||
"""
|
||||
get path to archive specified search criteria
|
||||
@@ -228,7 +269,12 @@ class RepositoryPaths(LazyLogging):
|
||||
Returns:
|
||||
Path: path to archive directory for package base
|
||||
"""
|
||||
return self.archive / "packages" / package_base[0] / package_base
|
||||
directory = self.archive / "packages" / package_base[0] / package_base
|
||||
if not directory.is_dir(): # create if not exists
|
||||
with self.preserve_owner(self.archive):
|
||||
directory.mkdir(mode=0o755, parents=True)
|
||||
|
||||
return directory
|
||||
|
||||
def cache_for(self, package_base: str) -> Path:
|
||||
"""
|
||||
@@ -242,32 +288,14 @@ class RepositoryPaths(LazyLogging):
|
||||
"""
|
||||
return self.cache / package_base
|
||||
|
||||
def ensure_exists(self, directory: Path) -> Path:
|
||||
"""
|
||||
get path based on ``directory`` callable provided and ensure it exists
|
||||
|
||||
Args:
|
||||
directory(Path): path to directory to check
|
||||
|
||||
Returns:
|
||||
Path: original path based on extractor provided. Directory will always exist
|
||||
|
||||
Examples:
|
||||
This method calls directory accessor and then checks if there is a directory and - otherwise - creates it::
|
||||
|
||||
>>> paths.ensure_exists(paths.archive_for(package_base))
|
||||
"""
|
||||
if not directory.is_dir():
|
||||
with self.preserve_owner():
|
||||
directory.mkdir(mode=0o755, parents=True)
|
||||
|
||||
return directory
|
||||
|
||||
@contextlib.contextmanager
|
||||
def preserve_owner(self) -> Iterator[None]:
|
||||
def preserve_owner(self, path: Path | None = None) -> Iterator[None]:
|
||||
"""
|
||||
perform any action preserving owner for any newly created file or directory
|
||||
|
||||
Args:
|
||||
path(Path | None, optional): use this path as root instead of repository root (Default value = None)
|
||||
|
||||
Examples:
|
||||
This method is designed to use as context manager when you are going to perform operations which might
|
||||
change filesystem, especially if you are doing it under unsafe flag, e.g.::
|
||||
@@ -278,26 +306,29 @@ class RepositoryPaths(LazyLogging):
|
||||
Note, however, that this method doesn't handle any exceptions and will eventually interrupt
|
||||
if there will be any.
|
||||
"""
|
||||
# guard non-root
|
||||
# the reason we do this is that it only works if permissions can be actually changed. Hence,
|
||||
# non-privileged user (e.g. personal user or ahriman user) can't change permissions.
|
||||
# The only one who can do so is root, so if user is not root we just terminate function
|
||||
current_uid, current_gid = os.geteuid(), os.getegid()
|
||||
if current_uid != 0:
|
||||
yield
|
||||
return
|
||||
path = path or self.root
|
||||
|
||||
# set uid and gid to root owner
|
||||
target_uid, target_gid = self.root_owner
|
||||
os.setegid(target_gid)
|
||||
os.seteuid(target_uid)
|
||||
def walk(root: Path) -> Iterator[Path]:
|
||||
yield root
|
||||
if not root.exists():
|
||||
return
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
# reset uid and gid
|
||||
os.seteuid(current_uid)
|
||||
os.setegid(current_gid)
|
||||
# basically walk, but skipping some content
|
||||
for child in root.iterdir():
|
||||
yield child
|
||||
if child in (self.chroot.parent,):
|
||||
yield from child.iterdir() # we only yield top-level in chroot directory
|
||||
elif child.is_dir():
|
||||
yield from walk(child)
|
||||
|
||||
# get current filesystem and run action
|
||||
previous_snapshot = set(walk(path))
|
||||
yield
|
||||
|
||||
# get newly created files and directories and chown them
|
||||
new_entries = set(walk(path)).difference(previous_snapshot)
|
||||
for entry in new_entries:
|
||||
self._chown(entry)
|
||||
|
||||
def tree_clear(self, package_base: str) -> None:
|
||||
"""
|
||||
@@ -308,7 +339,6 @@ class RepositoryPaths(LazyLogging):
|
||||
"""
|
||||
for directory in (
|
||||
self.cache_for(package_base),
|
||||
self.archive_for(package_base),
|
||||
):
|
||||
shutil.rmtree(directory, ignore_errors=True)
|
||||
|
||||
@@ -319,12 +349,13 @@ class RepositoryPaths(LazyLogging):
|
||||
if self.repository_id.is_empty:
|
||||
return # do not even try to create tree in case if no repository id set
|
||||
|
||||
for directory in (
|
||||
self.archive,
|
||||
self.cache,
|
||||
self.chroot,
|
||||
self.packages,
|
||||
self.pacman,
|
||||
self.repository,
|
||||
):
|
||||
self.ensure_exists(directory)
|
||||
with self.preserve_owner():
|
||||
for directory in (
|
||||
self.archive,
|
||||
self.cache,
|
||||
self.chroot,
|
||||
self.packages,
|
||||
self.pacman,
|
||||
self.repository,
|
||||
):
|
||||
directory.mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||
|
||||
@@ -28,6 +28,3 @@ class OAuth2Schema(Schema):
|
||||
code = fields.String(metadata={
|
||||
"description": "OAuth2 authorization code. In case if not set, the redirect to provider will be initiated",
|
||||
})
|
||||
state = fields.String(metadata={
|
||||
"description": "CSRF token returned by OAuth2 provider",
|
||||
})
|
||||
|
||||
@@ -18,10 +18,9 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from aiohttp.web import HTTPBadRequest, HTTPFound, HTTPMethodNotAllowed, HTTPUnauthorized
|
||||
from secrets import token_urlsafe
|
||||
from typing import ClassVar
|
||||
|
||||
from ahriman.core.auth.helpers import get_session, remember
|
||||
from ahriman.core.auth.helpers import remember
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.apispec.decorators import apidocs
|
||||
from ahriman.web.schemas import LoginSchema, OAuth2Schema
|
||||
@@ -69,18 +68,15 @@ class LoginView(BaseView):
|
||||
raise HTTPMethodNotAllowed(self.request.method, ["POST"])
|
||||
|
||||
oauth_provider = self.validator
|
||||
if not isinstance(oauth_provider, OAuth):
|
||||
if not isinstance(oauth_provider, OAuth): # there is actually property, but mypy does not like it anyway
|
||||
raise HTTPMethodNotAllowed(self.request.method, ["POST"])
|
||||
|
||||
session = await get_session(self.request)
|
||||
|
||||
code = self.request.query.get("code")
|
||||
if not code:
|
||||
state = session["state"] = token_urlsafe()
|
||||
raise HTTPFound(oauth_provider.get_oauth_url(state))
|
||||
raise HTTPFound(oauth_provider.get_oauth_url())
|
||||
|
||||
response = HTTPFound("/")
|
||||
identity = await oauth_provider.get_oauth_username(code, self.request.query.get("state"), session)
|
||||
identity = await oauth_provider.get_oauth_username(code)
|
||||
if identity is not None and await self.validator.known_username(identity):
|
||||
await remember(self.request, response, identity)
|
||||
raise response
|
||||
|
||||
@@ -5,7 +5,6 @@ from pytest_mock import MockerFixture
|
||||
from unittest.mock import call as MockCall
|
||||
|
||||
from ahriman.application.application.application_repository import ApplicationRepository
|
||||
from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.core.tree import Leaf, Tree
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.package import Package
|
||||
@@ -136,7 +135,7 @@ def test_unknown_no_aur(application_repository: ApplicationRepository, package_a
|
||||
must return empty list in case if there is locally stored PKGBUILD
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=UnknownPackageError(package_ahriman.base))
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception)
|
||||
mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=False)
|
||||
@@ -150,7 +149,7 @@ def test_unknown_no_aur_no_local(application_repository: ApplicationRepository,
|
||||
must return list of packages missing in aur and in local storage
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=UnknownPackageError(package_ahriman.base))
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception)
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
|
||||
packages = application_repository.unknown()
|
||||
|
||||
@@ -145,11 +145,63 @@ def test_repositories_extract(args: argparse.Namespace, configuration: Configura
|
||||
args.configuration = configuration.path
|
||||
args.repository = "repo"
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
extract_mock = mocker.patch("ahriman.core.repository.Explorer.repositories_extract",
|
||||
return_value=[RepositoryId("arch", "repo")])
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
|
||||
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("arch", "repo")]
|
||||
extract_mock.assert_called_once_with(pytest.helpers.anyvar(Configuration, True), args.repository, args.architecture)
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_repositories_extract_repository(args: argparse.Namespace, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate list of available repositories based on flags and tree
|
||||
"""
|
||||
args.architecture = "arch"
|
||||
args.configuration = configuration.path
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories",
|
||||
return_value={"repo"})
|
||||
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("arch", "repo")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_called_once_with(configuration.repository_paths.root)
|
||||
|
||||
|
||||
def test_repositories_extract_repository_legacy(args: argparse.Namespace, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate list of available repositories based on flags and tree (legacy mode)
|
||||
"""
|
||||
args.architecture = "arch"
|
||||
args.configuration = configuration.path
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories",
|
||||
return_value=set())
|
||||
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("arch", "aur")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_called_once_with(configuration.repository_paths.root)
|
||||
|
||||
|
||||
def test_repositories_extract_architecture(args: argparse.Namespace, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must read repository name from config
|
||||
"""
|
||||
args.configuration = configuration.path
|
||||
args.repository = "repo"
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures",
|
||||
return_value={"arch"})
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
|
||||
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("arch", "repo")]
|
||||
known_architectures_mock.assert_called_once_with(configuration.repository_paths.root, "repo")
|
||||
known_repositories_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_repositories_extract_empty(args: argparse.Namespace, configuration: Configuration,
|
||||
@@ -160,7 +212,8 @@ def test_repositories_extract_empty(args: argparse.Namespace, configuration: Con
|
||||
args.command = "config"
|
||||
args.configuration = configuration.path
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
mocker.patch("ahriman.core.repository.Explorer.repositories_extract", return_value=[])
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures", return_value=set())
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories", return_value=set())
|
||||
|
||||
with pytest.raises(MissingArchitectureError):
|
||||
Handler.repositories_extract(args)
|
||||
@@ -174,11 +227,12 @@ def test_repositories_extract_systemd(args: argparse.Namespace, configuration: C
|
||||
args.configuration = configuration.path
|
||||
args.repository_id = "i686/some/repo/name"
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
extract_mock = mocker.patch("ahriman.core.repository.Explorer.repositories_extract",
|
||||
return_value=[RepositoryId("i686", "some-repo-name")])
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
|
||||
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("i686", "some-repo-name")]
|
||||
extract_mock.assert_called_once_with(pytest.helpers.anyvar(Configuration, True), "some-repo-name", "i686")
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_repositories_extract_systemd_with_dash(args: argparse.Namespace, configuration: Configuration,
|
||||
@@ -189,11 +243,12 @@ def test_repositories_extract_systemd_with_dash(args: argparse.Namespace, config
|
||||
args.configuration = configuration.path
|
||||
args.repository_id = "i686-some-repo-name"
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
extract_mock = mocker.patch("ahriman.core.repository.Explorer.repositories_extract",
|
||||
return_value=[RepositoryId("i686", "some-repo-name")])
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
|
||||
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("i686", "some-repo-name")]
|
||||
extract_mock.assert_called_once_with(pytest.helpers.anyvar(Configuration, True), "some-repo-name", "i686")
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_repositories_extract_systemd_legacy(args: argparse.Namespace, configuration: Configuration,
|
||||
@@ -204,8 +259,10 @@ def test_repositories_extract_systemd_legacy(args: argparse.Namespace, configura
|
||||
args.configuration = configuration.path
|
||||
args.repository_id = "i686"
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
extract_mock = mocker.patch("ahriman.core.repository.Explorer.repositories_extract",
|
||||
return_value=[RepositoryId("i686", "aur")])
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories",
|
||||
return_value=set())
|
||||
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("i686", "aur")]
|
||||
extract_mock.assert_called_once_with(pytest.helpers.anyvar(Configuration, True), None, "i686")
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_called_once_with(configuration.repository_paths.root)
|
||||
|
||||
@@ -17,7 +17,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
||||
"""
|
||||
tree_create_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
application_mock = mocker.patch("ahriman.application.handlers.tree_migrate.TreeMigrate.tree_move")
|
||||
symlinks_mock = mocker.patch("ahriman.application.handlers.tree_migrate.TreeMigrate.symlinks_fix")
|
||||
symlinks_mock = mocker.patch("ahriman.application.handlers.tree_migrate.TreeMigrate.fix_symlinks")
|
||||
_, repository_id = configuration.check_loaded()
|
||||
old_paths = configuration.repository_paths
|
||||
new_paths = RepositoryPaths(old_paths.root, old_paths.repository_id, _force_current_tree=True)
|
||||
@@ -28,10 +28,11 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
||||
symlinks_mock.assert_called_once_with(new_paths)
|
||||
|
||||
|
||||
def test_symlinks_fix(repository_paths: RepositoryPaths, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_fix_symlinks(repository_paths: RepositoryPaths, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must replace symlinks during migration
|
||||
"""
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
||||
mocker.patch("ahriman.application.handlers.tree_migrate.walk", side_effect=[
|
||||
[
|
||||
repository_paths.archive_for(package_ahriman.base) / "file",
|
||||
@@ -46,7 +47,7 @@ def test_symlinks_fix(repository_paths: RepositoryPaths, package_ahriman: Packag
|
||||
unlink_mock = mocker.patch("pathlib.Path.unlink")
|
||||
symlink_mock = mocker.patch("pathlib.Path.symlink_to")
|
||||
|
||||
TreeMigrate.symlinks_fix(repository_paths)
|
||||
TreeMigrate.fix_symlinks(repository_paths)
|
||||
unlink_mock.assert_called_once_with()
|
||||
symlink_mock.assert_called_once_with(
|
||||
Path("..") /
|
||||
|
||||
@@ -142,7 +142,7 @@ def test_check_user(lock: Lock, mocker: MockerFixture) -> None:
|
||||
tree_create = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
|
||||
lock.check_user()
|
||||
check_user_patch.assert_called_once_with(lock.paths.root, unsafe=False)
|
||||
check_user_patch.assert_called_once_with(lock.paths, unsafe=False)
|
||||
tree_create.assert_called_once_with()
|
||||
|
||||
|
||||
|
||||
@@ -365,27 +365,6 @@ def package_python_schedule(
|
||||
packages=packages)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def package_tpacpi_bat_git() -> Package:
|
||||
"""
|
||||
git package fixture
|
||||
|
||||
Returns:
|
||||
Package: git package test instance
|
||||
"""
|
||||
return Package(
|
||||
base="tpacpi-bat-git",
|
||||
version="3.1.r12.g4959b52-1",
|
||||
remote=RemoteSource(
|
||||
source=PackageSource.AUR,
|
||||
git_url=AUR.remote_git_url("tpacpi-bat-git", "aur"),
|
||||
web_url=AUR.remote_web_url("tpacpi-bat-git"),
|
||||
path=".",
|
||||
branch="master",
|
||||
),
|
||||
packages={"tpacpi-bat-git": PackageDescription()})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def package_description_ahriman() -> PackageDescription:
|
||||
"""
|
||||
|
||||
@@ -67,7 +67,7 @@ def test_database_copy(pacman: Pacman, mocker: MockerFixture) -> None:
|
||||
pacman.database_copy(pacman.handle, database, path, use_ahriman_cache=True)
|
||||
mkdir_mock.assert_called_once_with(mode=0o755, exist_ok=True)
|
||||
copy_mock.assert_called_once_with(path / "sync" / "core.db", dst_path)
|
||||
owner_guard_mock.assert_called_once_with()
|
||||
owner_guard_mock.assert_called_once_with(dst_path.parent)
|
||||
|
||||
|
||||
def test_database_copy_skip(pacman: Pacman, mocker: MockerFixture) -> None:
|
||||
|
||||
@@ -35,6 +35,17 @@ def test_repo_add(repo: Repo, mocker: MockerFixture) -> None:
|
||||
assert "--remove" in check_output_mock.call_args[0]
|
||||
|
||||
|
||||
def test_repo_add_no_remove(repo: Repo, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call repo-add without remove flag
|
||||
"""
|
||||
check_output_mock = mocker.patch("ahriman.core.alpm.repo.check_output")
|
||||
|
||||
repo.add(Path("path"), remove=False)
|
||||
check_output_mock.assert_called_once() # it will be checked later
|
||||
assert "--remove" not in check_output_mock.call_args[0]
|
||||
|
||||
|
||||
def test_repo_init(repo: Repo, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call repo-add with empty package list on repo initializing
|
||||
@@ -60,6 +71,20 @@ def test_repo_remove(repo: Repo, package_ahriman: Package, mocker: MockerFixture
|
||||
assert package_ahriman.base in check_output_mock.call_args[0]
|
||||
|
||||
|
||||
def test_repo_remove_guess_package(repo: Repo, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call repo-remove on package removal if no package name set
|
||||
"""
|
||||
filepath = package_ahriman.packages[package_ahriman.base].filepath
|
||||
mocker.patch("pathlib.Path.glob", return_value=[])
|
||||
check_output_mock = mocker.patch("ahriman.core.alpm.repo.check_output")
|
||||
|
||||
repo.remove(None, filepath)
|
||||
check_output_mock.assert_called_once() # it will be checked later
|
||||
assert check_output_mock.call_args[0][0] == "repo-remove"
|
||||
assert package_ahriman.base in check_output_mock.call_args[0]
|
||||
|
||||
|
||||
def test_repo_remove_fail_no_file(repo: Repo, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must fail removal on missing file
|
||||
|
||||
@@ -7,18 +7,6 @@ from ahriman.core.utils import utcnow
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
def test_repo(archive_tree: ArchiveTree) -> None:
|
||||
"""
|
||||
must return correct repository object
|
||||
"""
|
||||
local = Path("local")
|
||||
repo = archive_tree._repo(local)
|
||||
|
||||
assert repo.sign_args == archive_tree.sign_args
|
||||
assert repo.name == archive_tree.repository_id.name
|
||||
assert repo.root == local
|
||||
|
||||
|
||||
def test_repository_for(archive_tree: ArchiveTree) -> None:
|
||||
"""
|
||||
must correctly generate path to repository
|
||||
@@ -29,25 +17,6 @@ def test_repository_for(archive_tree: ArchiveTree) -> None:
|
||||
assert set(map("{:02d}".format, utcnow().timetuple()[:3])).issubset(path.parts)
|
||||
|
||||
|
||||
def test_directories_fix(archive_tree: ArchiveTree, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must remove empty directories recursively
|
||||
"""
|
||||
root = archive_tree.paths.archive / "repos"
|
||||
(root / "a" / "b").mkdir(parents=True, exist_ok=True)
|
||||
(root / "a" / "b" / "file").touch()
|
||||
(root / "a" / "b" / "c" / "d").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
_original_rmdir = Path.rmdir
|
||||
rmdir_mock = mocker.patch("pathlib.Path.rmdir", autospec=True, side_effect=_original_rmdir)
|
||||
|
||||
archive_tree.directories_fix({Path("a") / "b" / "c" / "d"})
|
||||
rmdir_mock.assert_has_calls([
|
||||
MockCall(root / "a" / "b" / "c" / "d"),
|
||||
MockCall(root / "a" / "b" / "c"),
|
||||
])
|
||||
|
||||
|
||||
def test_symlinks_create(archive_tree: ArchiveTree, package_ahriman: Package, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
@@ -99,7 +68,7 @@ def test_symlinks_fix(archive_tree: ArchiveTree, mocker: MockerFixture) -> None:
|
||||
_original_exists = Path.exists
|
||||
|
||||
def exists_mock(path: Path) -> bool:
|
||||
if path.name.startswith("symlink"):
|
||||
if path.name == "symlink":
|
||||
return True
|
||||
return _original_exists(path)
|
||||
|
||||
@@ -107,21 +76,13 @@ def test_symlinks_fix(archive_tree: ArchiveTree, mocker: MockerFixture) -> None:
|
||||
mocker.patch("pathlib.Path.exists", autospec=True, side_effect=exists_mock)
|
||||
walk_mock = mocker.patch("ahriman.core.archive.archive_tree.walk", return_value=[
|
||||
archive_tree.repository_for() / filename
|
||||
for filename in (
|
||||
"symlink-1.0.0-1-x86_64.pkg.tar.zst",
|
||||
"symlink-1.0.0-1-x86_64.pkg.tar.zst.sig",
|
||||
"broken_symlink-1.0.0-1-x86_64.pkg.tar.zst",
|
||||
"file-1.0.0-1-x86_64.pkg.tar.zst",
|
||||
)
|
||||
for filename in ("symlink", "broken_symlink", "file")
|
||||
])
|
||||
remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
|
||||
|
||||
assert list(archive_tree.symlinks_fix()) == [
|
||||
archive_tree.repository_for().relative_to(archive_tree.paths.archive / "repos"),
|
||||
]
|
||||
archive_tree.symlinks_fix()
|
||||
walk_mock.assert_called_once_with(archive_tree.paths.archive / "repos")
|
||||
remove_mock.assert_called_once_with(
|
||||
"broken_symlink", archive_tree.repository_for() / "broken_symlink-1.0.0-1-x86_64.pkg.tar.zst")
|
||||
remove_mock.assert_called_once_with(None, archive_tree.repository_for() / "broken_symlink")
|
||||
|
||||
|
||||
def test_symlinks_fix_foreign_repository(archive_tree: ArchiveTree, mocker: MockerFixture) -> None:
|
||||
@@ -131,7 +92,7 @@ def test_symlinks_fix_foreign_repository(archive_tree: ArchiveTree, mocker: Mock
|
||||
_original_exists = Path.exists
|
||||
|
||||
def exists_mock(path: Path) -> bool:
|
||||
if path.name.startswith("symlink"):
|
||||
if path.name == "symlink":
|
||||
return True
|
||||
return _original_exists(path)
|
||||
|
||||
@@ -139,15 +100,11 @@ def test_symlinks_fix_foreign_repository(archive_tree: ArchiveTree, mocker: Mock
|
||||
mocker.patch("pathlib.Path.exists", autospec=True, side_effect=exists_mock)
|
||||
mocker.patch("ahriman.core.archive.archive_tree.walk", return_value=[
|
||||
archive_tree.repository_for().with_name("i686") / filename
|
||||
for filename in (
|
||||
"symlink-1.0.0-1-x86_64.pkg.tar.zst",
|
||||
"broken_symlink-1.0.0-1-x86_64.pkg.tar.zst",
|
||||
"file-1.0.0-1-x86_64.pkg.tar.zst",
|
||||
)
|
||||
for filename in ("symlink", "broken_symlink", "file")
|
||||
])
|
||||
remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
|
||||
|
||||
assert list(archive_tree.symlinks_fix()) == []
|
||||
archive_tree.symlinks_fix()
|
||||
remove_mock.assert_not_called()
|
||||
|
||||
|
||||
@@ -160,7 +117,7 @@ def test_tree_create(archive_tree: ArchiveTree, mocker: MockerFixture) -> None:
|
||||
init_mock = mocker.patch("ahriman.core.alpm.repo.Repo.init")
|
||||
|
||||
archive_tree.tree_create()
|
||||
owner_guard_mock.assert_called_once_with()
|
||||
owner_guard_mock.assert_called_once_with(archive_tree.paths.archive)
|
||||
mkdir_mock.assert_called_once_with(0o755, parents=True)
|
||||
init_mock.assert_called_once_with()
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.archive import ArchiveTrigger
|
||||
@@ -28,10 +27,6 @@ def test_on_stop(archive_trigger: ArchiveTrigger, mocker: MockerFixture) -> None
|
||||
"""
|
||||
must fix broken symlinks on stop
|
||||
"""
|
||||
local = Path("local")
|
||||
symlinks_mock = mocker.patch("ahriman.core.archive.archive_tree.ArchiveTree.symlinks_fix", return_value=[local])
|
||||
directories_mock = mocker.patch("ahriman.core.archive.archive_tree.ArchiveTree.directories_fix")
|
||||
|
||||
symlinks_mock = mocker.patch("ahriman.core.archive.archive_tree.ArchiveTree.symlinks_fix")
|
||||
archive_trigger.on_stop()
|
||||
symlinks_mock.assert_called_once_with()
|
||||
directories_mock.assert_called_once_with({local})
|
||||
|
||||
@@ -13,13 +13,6 @@ def test_import_aiohttp_security() -> None:
|
||||
assert helpers.aiohttp_security
|
||||
|
||||
|
||||
def test_import_aiohttp_session() -> None:
|
||||
"""
|
||||
must import aiohttp_session correctly
|
||||
"""
|
||||
assert helpers.aiohttp_session
|
||||
|
||||
|
||||
async def test_authorized_userid_dummy(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must not call authorized_userid from library if not enabled
|
||||
@@ -62,23 +55,6 @@ async def test_forget_dummy(mocker: MockerFixture) -> None:
|
||||
await helpers.forget()
|
||||
|
||||
|
||||
async def test_get_session_dummy(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return empty dict if no aiohttp_session module found
|
||||
"""
|
||||
mocker.patch.object(helpers, "aiohttp_session", None)
|
||||
assert await helpers.get_session() == {}
|
||||
|
||||
|
||||
async def test_get_session_library(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call get_session from library if enabled
|
||||
"""
|
||||
get_session_mock = mocker.patch("aiohttp_session.get_session")
|
||||
await helpers.get_session()
|
||||
get_session_mock.assert_called_once_with()
|
||||
|
||||
|
||||
async def test_forget_library(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call forget from library if enabled
|
||||
@@ -112,12 +88,3 @@ def test_import_aiohttp_security_missing(mocker: MockerFixture) -> None:
|
||||
mocker.patch.dict(sys.modules, {"aiohttp_security": None})
|
||||
importlib.reload(helpers)
|
||||
assert helpers.aiohttp_security is None
|
||||
|
||||
|
||||
def test_import_aiohttp_session_missing(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must set missing flag if no aiohttp_session module found
|
||||
"""
|
||||
mocker.patch.dict(sys.modules, {"aiohttp_session": None})
|
||||
importlib.reload(helpers)
|
||||
assert helpers.aiohttp_session is None
|
||||
|
||||
@@ -57,8 +57,8 @@ def test_get_oauth_url(oauth: OAuth, mocker: MockerFixture) -> None:
|
||||
must generate valid OAuth authorization URL
|
||||
"""
|
||||
authorize_url_mock = mocker.patch("aioauth_client.GoogleClient.get_authorize_url")
|
||||
oauth.get_oauth_url(state="state")
|
||||
authorize_url_mock.assert_called_once_with(scope=oauth.scopes, redirect_uri=oauth.redirect_uri, state="state")
|
||||
oauth.get_oauth_url()
|
||||
authorize_url_mock.assert_called_once_with(scope=oauth.scopes, redirect_uri=oauth.redirect_uri)
|
||||
|
||||
|
||||
async def test_get_oauth_username(oauth: OAuth, mocker: MockerFixture) -> None:
|
||||
@@ -69,9 +69,10 @@ async def test_get_oauth_username(oauth: OAuth, mocker: MockerFixture) -> None:
|
||||
user_info_mock = mocker.patch("aioauth_client.GoogleClient.user_info",
|
||||
return_value=(aioauth_client.User(email="email"), ""))
|
||||
|
||||
assert await oauth.get_oauth_username("code", state="state", session={"state": "state"}) == "email"
|
||||
email = await oauth.get_oauth_username("code")
|
||||
access_token_mock.assert_called_once_with("code", redirect_uri=oauth.redirect_uri)
|
||||
user_info_mock.assert_called_once_with()
|
||||
assert email == "email"
|
||||
|
||||
|
||||
async def test_get_oauth_username_empty_email(oauth: OAuth, mocker: MockerFixture) -> None:
|
||||
@@ -81,7 +82,8 @@ async def test_get_oauth_username_empty_email(oauth: OAuth, mocker: MockerFixtur
|
||||
mocker.patch("aioauth_client.GoogleClient.get_access_token", return_value=("token", ""))
|
||||
mocker.patch("aioauth_client.GoogleClient.user_info", return_value=(aioauth_client.User(username="username"), ""))
|
||||
|
||||
assert await oauth.get_oauth_username("code", state="state", session={"state": "state"}) == "username"
|
||||
username = await oauth.get_oauth_username("code")
|
||||
assert username == "username"
|
||||
|
||||
|
||||
async def test_get_oauth_username_exception_1(oauth: OAuth, mocker: MockerFixture) -> None:
|
||||
@@ -91,7 +93,8 @@ async def test_get_oauth_username_exception_1(oauth: OAuth, mocker: MockerFixtur
|
||||
mocker.patch("aioauth_client.GoogleClient.get_access_token", side_effect=Exception)
|
||||
user_info_mock = mocker.patch("aioauth_client.GoogleClient.user_info")
|
||||
|
||||
assert await oauth.get_oauth_username("code", state="state", session={"state": "state"}) is None
|
||||
email = await oauth.get_oauth_username("code")
|
||||
assert email is None
|
||||
user_info_mock.assert_not_called()
|
||||
|
||||
|
||||
@@ -102,19 +105,5 @@ async def test_get_oauth_username_exception_2(oauth: OAuth, mocker: MockerFixtur
|
||||
mocker.patch("aioauth_client.GoogleClient.get_access_token", return_value=("token", ""))
|
||||
mocker.patch("aioauth_client.GoogleClient.user_info", side_effect=Exception)
|
||||
|
||||
username = await oauth.get_oauth_username("code", state="state", session={"state": "state"})
|
||||
assert username is None
|
||||
|
||||
|
||||
async def test_get_oauth_username_csrf_missing(oauth: OAuth) -> None:
|
||||
"""
|
||||
must return None if CSRF state is missing
|
||||
"""
|
||||
assert await oauth.get_oauth_username("code", state=None, session={"state": "state"}) is None
|
||||
|
||||
|
||||
async def test_get_oauth_username_csrf_mismatch(oauth: OAuth) -> None:
|
||||
"""
|
||||
must return None if CSRF state does not match session
|
||||
"""
|
||||
assert await oauth.get_oauth_username("code", state="wrong", session={"state": "state"}) is None
|
||||
email = await oauth.get_oauth_username("code")
|
||||
assert email is None
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.build_tools.package_version import PackageVersion
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.utils import utcnow
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild import Pkgbuild
|
||||
|
||||
|
||||
def test_actual_version(package_ahriman: Package, configuration: Configuration) -> None:
|
||||
"""
|
||||
must return same actual_version as version is
|
||||
"""
|
||||
assert PackageVersion(package_ahriman).actual_version(configuration) == package_ahriman.version
|
||||
|
||||
|
||||
def test_actual_version_vcs(package_tpacpi_bat_git: Package, configuration: Configuration,
|
||||
mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must return valid actual_version for VCS package
|
||||
"""
|
||||
pkgbuild = resource_path_root / "models" / "package_tpacpi-bat-git_pkgbuild"
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.from_file", return_value=Pkgbuild.from_file(pkgbuild))
|
||||
mocker.patch("pathlib.Path.glob", return_value=[Path("local")])
|
||||
init_mock = mocker.patch("ahriman.core.build_tools.task.Task.init")
|
||||
unlink_mock = mocker.patch("pathlib.Path.unlink")
|
||||
|
||||
assert PackageVersion(package_tpacpi_bat_git).actual_version(configuration) == "3.1.r13.g4959b52-1"
|
||||
init_mock.assert_called_once_with(configuration.repository_paths.cache_for(package_tpacpi_bat_git.base), [], None)
|
||||
unlink_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_actual_version_failed(package_tpacpi_bat_git: Package, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return same version in case if exception occurred
|
||||
"""
|
||||
mocker.patch("ahriman.core.build_tools.task.Task.init", side_effect=Exception)
|
||||
mocker.patch("pathlib.Path.glob", return_value=[Path("local")])
|
||||
unlink_mock = mocker.patch("pathlib.Path.unlink")
|
||||
|
||||
assert PackageVersion(package_tpacpi_bat_git).actual_version(configuration) == package_tpacpi_bat_git.version
|
||||
unlink_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_is_newer_than(package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||
"""
|
||||
must correctly check if package is newer than specified timestamp
|
||||
"""
|
||||
# base checks, true/false
|
||||
older = package_ahriman.packages[package_ahriman.base].build_date - 1
|
||||
assert PackageVersion(package_ahriman).is_newer_than(older)
|
||||
|
||||
newer = package_ahriman.packages[package_ahriman.base].build_date + 1
|
||||
assert not PackageVersion(package_ahriman).is_newer_than(newer)
|
||||
|
||||
# list check
|
||||
min_date = min(package.build_date for package in package_python_schedule.packages.values())
|
||||
assert PackageVersion(package_python_schedule).is_newer_than(min_date)
|
||||
|
||||
# null list check
|
||||
package_python_schedule.packages["python-schedule"].build_date = None
|
||||
assert PackageVersion(package_python_schedule).is_newer_than(min_date)
|
||||
|
||||
package_python_schedule.packages["python2-schedule"].build_date = None
|
||||
assert not PackageVersion(package_python_schedule).is_newer_than(min_date)
|
||||
|
||||
|
||||
def test_is_outdated_false(package_ahriman: Package, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must be not outdated for the same package
|
||||
"""
|
||||
actual_version_mock = mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.actual_version",
|
||||
return_value=package_ahriman.version)
|
||||
assert not PackageVersion(package_ahriman).is_outdated(package_ahriman, configuration)
|
||||
actual_version_mock.assert_called_once_with(configuration)
|
||||
|
||||
|
||||
def test_is_outdated_true(package_ahriman: Package, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must be outdated for the new version
|
||||
"""
|
||||
other = Package.from_json(package_ahriman.view())
|
||||
other.version = other.version.replace("-1", "-2")
|
||||
actual_version_mock = mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.actual_version",
|
||||
return_value=other.version)
|
||||
|
||||
assert PackageVersion(package_ahriman).is_outdated(other, configuration)
|
||||
actual_version_mock.assert_called_once_with(configuration)
|
||||
|
||||
|
||||
def test_is_outdated_no_version_calculation(package_ahriman: Package, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must not call actual version if calculation is disabled
|
||||
"""
|
||||
actual_version_mock = mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.actual_version")
|
||||
assert not PackageVersion(package_ahriman).is_outdated(package_ahriman, configuration, calculate_version=False)
|
||||
actual_version_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_is_outdated_fresh_package(package_ahriman: Package, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must not call actual version if package is never than specified time
|
||||
"""
|
||||
configuration.set_option("build", "vcs_allowed_age", str(int(utcnow().timestamp())))
|
||||
actual_version_mock = mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.actual_version")
|
||||
assert not PackageVersion(package_ahriman).is_outdated(package_ahriman, configuration)
|
||||
actual_version_mock.assert_not_called()
|
||||
@@ -55,8 +55,7 @@ def test_extend_architectures(mocker: MockerFixture) -> None:
|
||||
must update available architecture list
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_file", return_value=True)
|
||||
architectures_mock = mocker.patch("ahriman.models.pkgbuild.Pkgbuild.supported_architectures",
|
||||
return_value={"x86_64"})
|
||||
architectures_mock = mocker.patch("ahriman.models.package.Package.supported_architectures", return_value={"x86_64"})
|
||||
|
||||
assert Sources.extend_architectures(Path("local"), "i686") == [PkgbuildPatch("arch", list({"x86_64", "i686"}))]
|
||||
architectures_mock.assert_called_once_with(Path("local"))
|
||||
@@ -67,7 +66,7 @@ def test_extend_architectures_any(mocker: MockerFixture) -> None:
|
||||
must skip architecture patching in case if there is any architecture
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_file", return_value=True)
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.supported_architectures", return_value={"any"})
|
||||
mocker.patch("ahriman.models.package.Package.supported_architectures", return_value={"any"})
|
||||
assert Sources.extend_architectures(Path("local"), "i686") == []
|
||||
|
||||
|
||||
@@ -192,7 +191,7 @@ def test_init(sources: Sources, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create empty repository at the specified path
|
||||
"""
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.local_files", return_value=[Path("local")])
|
||||
mocker.patch("ahriman.models.package.Package.local_files", return_value=[Path("local")])
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
add_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.add")
|
||||
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output")
|
||||
@@ -210,7 +209,7 @@ def test_init_skip(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip git init if it was already
|
||||
"""
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.local_files", return_value=[Path("local")])
|
||||
mocker.patch("ahriman.models.package.Package.local_files", return_value=[Path("local")])
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.add")
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.commit")
|
||||
|
||||
@@ -23,7 +23,7 @@ def test_migrate_data(connection: Connection, configuration: Configuration, mock
|
||||
repository_id,
|
||||
replace(repository_id, architecture="i686"),
|
||||
]
|
||||
mocker.patch("ahriman.core.repository.Explorer.repositories_extract", return_value=repositories)
|
||||
mocker.patch("ahriman.application.handlers.handler.Handler.repositories_extract", return_value=repositories)
|
||||
migration_mock = mocker.patch("ahriman.core.database.migrations.m016_archive.move_packages")
|
||||
|
||||
migrate_data(connection, configuration)
|
||||
@@ -47,24 +47,22 @@ def test_move_packages(repository_paths: RepositoryPaths, pacman: Pacman, packag
|
||||
repository_paths.repository / "directory",
|
||||
repository_paths.repository / "file.pkg.tar.xz",
|
||||
repository_paths.repository / "file.pkg.tar.xz.sig",
|
||||
repository_paths.repository / "file2.pkg.tar.xz",
|
||||
repository_paths.repository / "symlink.pkg.tar.xz",
|
||||
])
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=is_file)
|
||||
mocker.patch("pathlib.Path.exists", return_value=True)
|
||||
archive_mock = mocker.patch("ahriman.models.package.Package.from_archive", return_value=package_ahriman)
|
||||
move_mock = mocker.patch("ahriman.core.database.migrations.m016_archive.atomic_move")
|
||||
rename_mock = mocker.patch("pathlib.Path.rename")
|
||||
symlink_mock = mocker.patch("pathlib.Path.symlink_to")
|
||||
|
||||
move_packages(repository_paths, pacman)
|
||||
archive_mock.assert_has_calls([
|
||||
MockCall(repository_paths.repository / filename, pacman)
|
||||
for filename in ("file.pkg.tar.xz", "file2.pkg.tar.xz")
|
||||
MockCall(repository_paths.repository / "file.pkg.tar.xz", pacman),
|
||||
MockCall(repository_paths.repository / "file.pkg.tar.xz.sig", pacman),
|
||||
])
|
||||
move_mock.assert_has_calls([
|
||||
MockCall(repository_paths.repository / filename, repository_paths.archive_for(package_ahriman.base) / filename)
|
||||
for filename in ("file.pkg.tar.xz", "file.pkg.tar.xz.sig", "file2.pkg.tar.xz")
|
||||
rename_mock.assert_has_calls([
|
||||
MockCall(repository_paths.archive_for(package_ahriman.base) / "file.pkg.tar.xz"),
|
||||
MockCall(repository_paths.archive_for(package_ahriman.base) / "file.pkg.tar.xz.sig"),
|
||||
])
|
||||
symlink_mock.assert_has_calls([
|
||||
MockCall(
|
||||
@@ -72,7 +70,13 @@ def test_move_packages(repository_paths: RepositoryPaths, pacman: Pacman, packag
|
||||
".." /
|
||||
".." /
|
||||
repository_paths.archive_for(package_ahriman.base).relative_to(repository_paths.root) /
|
||||
filename
|
||||
)
|
||||
for filename in ("file.pkg.tar.xz", "file.pkg.tar.xz.sig", "file2.pkg.tar.xz")
|
||||
"file.pkg.tar.xz"
|
||||
),
|
||||
MockCall(
|
||||
Path("..") /
|
||||
".." /
|
||||
".." /
|
||||
repository_paths.archive_for(package_ahriman.base).relative_to(repository_paths.root) /
|
||||
"file.pkg.tar.xz.sig"
|
||||
),
|
||||
])
|
||||
|
||||
@@ -16,7 +16,7 @@ def test_load(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
init_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_init(database: SQLite, mocker: MockerFixture) -> None:
|
||||
def test_init(database: SQLite, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run migrations on init
|
||||
"""
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import call as MockCall
|
||||
|
||||
from ahriman.core.alpm.repo import Repo
|
||||
from ahriman.core.build_tools.task import Task
|
||||
|
||||
@@ -19,7 +19,7 @@ def test_archive_lookup(executor: Executor, package_ahriman: Package, package_py
|
||||
"""
|
||||
must existing packages which match the version
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[
|
||||
Path("1.pkg.tar.zst"),
|
||||
Path("2.pkg.tar.zst"),
|
||||
@@ -40,7 +40,7 @@ def test_archive_lookup_version_mismatch(executor: Executor, package_ahriman: Pa
|
||||
"""
|
||||
must return nothing if no packages found with the same version
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[
|
||||
Path("1.pkg.tar.zst"),
|
||||
])
|
||||
@@ -55,8 +55,8 @@ def test_archive_lookup_architecture_mismatch(executor: Executor, package_ahrima
|
||||
must return nothing if architecture doesn't match
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].architecture = "x86_64"
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.architecture", return_value="i686")
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[
|
||||
Path("1.pkg.tar.zst"),
|
||||
])
|
||||
@@ -65,17 +65,6 @@ def test_archive_lookup_architecture_mismatch(executor: Executor, package_ahrima
|
||||
assert list(executor._archive_lookup(package_ahriman)) == []
|
||||
|
||||
|
||||
def test_archive_lookup_no_archive_directory(
|
||||
executor: Executor,
|
||||
package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return nothing if no archive directory found
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
assert list(executor._archive_lookup(package_ahriman)) == []
|
||||
|
||||
|
||||
def test_archive_rename(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must correctly remove package archive
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.repository import Explorer
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
def test_repositories_extract(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate list of available repositories based on arguments
|
||||
"""
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
|
||||
|
||||
assert Explorer.repositories_extract(configuration, "repo", "arch") == [RepositoryId("arch", "repo")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_repositories_extract_repository(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate list of available repositories based on arguments and tree
|
||||
"""
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories",
|
||||
return_value={"repo"})
|
||||
|
||||
assert Explorer.repositories_extract(configuration, architecture="arch") == [RepositoryId("arch", "repo")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_called_once_with(configuration.repository_paths.root)
|
||||
|
||||
|
||||
def test_repositories_extract_repository_legacy(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate list of available repositories based on arguments and tree (legacy mode)
|
||||
"""
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories",
|
||||
return_value=set())
|
||||
|
||||
assert Explorer.repositories_extract(configuration, architecture="arch") == [RepositoryId("arch", "aur")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_called_once_with(configuration.repository_paths.root)
|
||||
|
||||
|
||||
def test_repositories_extract_architecture(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must read repository name from config
|
||||
"""
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures",
|
||||
return_value={"arch"})
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
|
||||
|
||||
assert Explorer.repositories_extract(configuration, repository="repo") == [RepositoryId("arch", "repo")]
|
||||
known_architectures_mock.assert_called_once_with(configuration.repository_paths.root, "repo")
|
||||
known_repositories_mock.assert_not_called()
|
||||
@@ -2,32 +2,12 @@ import pytest
|
||||
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from ahriman.core.repository.package_info import PackageInfo
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
def test_full_depends(package_info: PackageInfo, package_ahriman: Package, package_python_schedule: Package,
|
||||
pyalpm_package_ahriman: MagicMock) -> None:
|
||||
"""
|
||||
must extract all dependencies from the package
|
||||
"""
|
||||
package_python_schedule.packages[package_python_schedule.base].provides = ["python3-schedule"]
|
||||
|
||||
database_mock = MagicMock()
|
||||
database_mock.pkgcache = [pyalpm_package_ahriman]
|
||||
package_info.pacman = MagicMock()
|
||||
package_info.pacman.handle.get_syncdbs.return_value = [database_mock]
|
||||
|
||||
assert package_info.full_depends(package_ahriman, [package_python_schedule]) == package_ahriman.depends
|
||||
|
||||
package_python_schedule.packages[package_python_schedule.base].depends = [package_ahriman.base]
|
||||
expected = sorted(set(package_python_schedule.depends + package_ahriman.depends))
|
||||
assert package_info.full_depends(package_python_schedule, [package_python_schedule]) == expected
|
||||
|
||||
|
||||
def test_load_archives(package_ahriman: Package, package_python_schedule: Package,
|
||||
package_info: PackageInfo, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
|
||||
@@ -6,7 +6,6 @@ from typing import Any
|
||||
|
||||
from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.core.repository.update_handler import UpdateHandler
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.dependencies import Dependencies
|
||||
from ahriman.models.event import EventType
|
||||
from ahriman.models.package import Package
|
||||
@@ -24,8 +23,7 @@ def test_updates_aur(update_handler: UpdateHandler, package_ahriman: Package,
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman)
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_pending")
|
||||
event_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.event")
|
||||
package_is_outdated_mock = mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.is_outdated",
|
||||
return_value=True)
|
||||
package_is_outdated_mock = mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
|
||||
|
||||
assert update_handler.updates_aur([], vcs=True) == [package_ahriman]
|
||||
packages_mock.assert_called_once_with([])
|
||||
@@ -44,7 +42,7 @@ def test_updates_aur_official(update_handler: UpdateHandler, package_ahriman: Pa
|
||||
"""
|
||||
package_ahriman.remote = RemoteSource(source=PackageSource.Repository)
|
||||
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.is_outdated", return_value=True)
|
||||
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
|
||||
mocker.patch("ahriman.models.package.Package.from_official", return_value=package_ahriman)
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_pending")
|
||||
event_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.event")
|
||||
@@ -68,20 +66,6 @@ def test_updates_aur_failed(update_handler: UpdateHandler, package_ahriman: Pack
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||
|
||||
|
||||
def test_updates_aur_up_to_date(update_handler: UpdateHandler, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must set success status for packages which are not out-of-dated
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman)
|
||||
mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.is_outdated", return_value=False)
|
||||
status_client_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_status_update")
|
||||
|
||||
assert update_handler.updates_aur([], vcs=True) == []
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base, BuildStatusEnum.Success)
|
||||
|
||||
|
||||
def test_updates_aur_local(update_handler: UpdateHandler, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
@@ -101,7 +85,7 @@ def test_updates_aur_filter(update_handler: UpdateHandler, package_ahriman: Pack
|
||||
"""
|
||||
packages_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages",
|
||||
return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.is_outdated", return_value=True)
|
||||
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
|
||||
package_load_mock = mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman)
|
||||
|
||||
assert update_handler.updates_aur([package_ahriman.base], vcs=True) == [package_ahriman]
|
||||
@@ -130,8 +114,7 @@ def test_updates_aur_ignore_vcs(update_handler: UpdateHandler, package_ahriman:
|
||||
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman)
|
||||
mocker.patch("ahriman.models.package.Package.is_vcs", return_value=True)
|
||||
package_is_outdated_mock = mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.is_outdated",
|
||||
return_value=False)
|
||||
package_is_outdated_mock = mocker.patch("ahriman.models.package.Package.is_outdated", return_value=False)
|
||||
|
||||
assert not update_handler.updates_aur([], vcs=False)
|
||||
package_is_outdated_mock.assert_called_once_with(
|
||||
@@ -152,7 +135,7 @@ def test_updates_aur_load_by_package(update_handler: UpdateHandler, package_pyth
|
||||
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages",
|
||||
return_value=[package_python_schedule])
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=package_selector)
|
||||
mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.is_outdated", return_value=True)
|
||||
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
|
||||
assert update_handler.updates_aur([], vcs=True) == [package_python_schedule]
|
||||
|
||||
|
||||
@@ -234,8 +217,7 @@ def test_updates_local(update_handler: UpdateHandler, package_ahriman: Package,
|
||||
package_load_mock = mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_pending")
|
||||
event_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.event")
|
||||
package_is_outdated_mock = mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.is_outdated",
|
||||
return_value=True)
|
||||
package_is_outdated_mock = mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
|
||||
|
||||
assert update_handler.updates_local(vcs=True) == [package_ahriman]
|
||||
fetch_mock.assert_called_once_with(Path(package_ahriman.base), pytest.helpers.anyvar(int))
|
||||
@@ -258,8 +240,7 @@ def test_updates_local_ignore_vcs(update_handler: UpdateHandler, package_ahriman
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[Path(package_ahriman.base)])
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
|
||||
mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
|
||||
package_is_outdated_mock = mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.is_outdated",
|
||||
return_value=False)
|
||||
package_is_outdated_mock = mocker.patch("ahriman.models.package.Package.is_outdated", return_value=False)
|
||||
|
||||
assert not update_handler.updates_local(vcs=False)
|
||||
package_is_outdated_mock.assert_called_once_with(
|
||||
@@ -273,7 +254,7 @@ def test_updates_local_unknown(update_handler: UpdateHandler, package_ahriman: P
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[])
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[Path(package_ahriman.base)])
|
||||
mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.is_outdated", return_value=True)
|
||||
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
|
||||
mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
|
||||
|
||||
@@ -286,7 +267,7 @@ def test_updates_local_remote(update_handler: UpdateHandler, package_ahriman: Pa
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[Path(package_ahriman.base)])
|
||||
mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.is_outdated", return_value=True)
|
||||
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
|
||||
mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
|
||||
|
||||
@@ -357,8 +338,4 @@ def test_updates_manual_with_failures(update_handler: UpdateHandler, package_ahr
|
||||
"""
|
||||
mocker.patch("ahriman.core.database.SQLite.build_queue_get", side_effect=Exception)
|
||||
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
|
||||
|
||||
assert update_handler.updates_manual() == []
|
||||
|
||||
from ahriman.core.repository.cleaner import Cleaner
|
||||
Cleaner.clear_queue.assert_not_called()
|
||||
|
||||
@@ -35,7 +35,7 @@ def test_event_get(local_client: LocalClient, package_ahriman: Package, mocker:
|
||||
local_client.repository_id)
|
||||
|
||||
|
||||
def test_logs_rotate(local_client: LocalClient, mocker: MockerFixture) -> None:
|
||||
def test_logs_rotate(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must rotate logs
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import datetime
|
||||
import fcntl
|
||||
import logging
|
||||
import os
|
||||
import pytest
|
||||
@@ -6,10 +7,33 @@ import pytest
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, call as MockCall
|
||||
from unittest.mock import call as MockCall
|
||||
|
||||
from ahriman.core.exceptions import BuildError, CalledProcessError, OptionError, UnsafeRunError
|
||||
from ahriman.core.utils import *
|
||||
from ahriman.core.utils import (
|
||||
atomic_move,
|
||||
check_output,
|
||||
check_user,
|
||||
dataclass_view,
|
||||
enum_values,
|
||||
extract_user,
|
||||
filelock,
|
||||
filter_json,
|
||||
full_version,
|
||||
minmax,
|
||||
package_like,
|
||||
parse_version,
|
||||
partition,
|
||||
pretty_datetime,
|
||||
pretty_interval,
|
||||
pretty_size,
|
||||
safe_filename,
|
||||
srcinfo_property,
|
||||
srcinfo_property_list,
|
||||
trim_package,
|
||||
utcnow,
|
||||
walk,
|
||||
)
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
@@ -172,8 +196,8 @@ def test_check_user(repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
must check user correctly
|
||||
"""
|
||||
paths = RepositoryPaths(Path.cwd(), repository_id)
|
||||
mocker.patch("os.geteuid", return_value=paths.root_owner[0])
|
||||
check_user(paths.root, unsafe=False)
|
||||
mocker.patch("os.getuid", return_value=paths.root_owner[0])
|
||||
check_user(paths, unsafe=False)
|
||||
|
||||
|
||||
def test_check_user_no_directory(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
|
||||
@@ -181,7 +205,7 @@ def test_check_user_no_directory(repository_paths: RepositoryPaths, mocker: Mock
|
||||
must not fail in case if no directory found
|
||||
"""
|
||||
mocker.patch("pathlib.Path.exists", return_value=False)
|
||||
check_user(repository_paths.root, unsafe=False)
|
||||
check_user(repository_paths, unsafe=False)
|
||||
|
||||
|
||||
def test_check_user_exception(repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
@@ -189,10 +213,10 @@ def test_check_user_exception(repository_id: RepositoryId, mocker: MockerFixture
|
||||
must raise exception if user differs
|
||||
"""
|
||||
paths = RepositoryPaths(Path.cwd(), repository_id)
|
||||
mocker.patch("os.geteuid", return_value=paths.root_owner[0] + 1)
|
||||
mocker.patch("os.getuid", return_value=paths.root_owner[0] + 1)
|
||||
|
||||
with pytest.raises(UnsafeRunError):
|
||||
check_user(paths.root, unsafe=False)
|
||||
check_user(paths, unsafe=False)
|
||||
|
||||
|
||||
def test_check_user_unsafe(repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
@@ -200,8 +224,8 @@ def test_check_user_unsafe(repository_id: RepositoryId, mocker: MockerFixture) -
|
||||
must skip check if unsafe flag is set
|
||||
"""
|
||||
paths = RepositoryPaths(Path.cwd(), repository_id)
|
||||
mocker.patch("os.geteuid", return_value=paths.root_owner[0] + 1)
|
||||
check_user(paths.root, unsafe=True)
|
||||
mocker.patch("os.getuid", return_value=paths.root_owner[0] + 1)
|
||||
check_user(paths, unsafe=True)
|
||||
|
||||
|
||||
def test_dataclass_view(package_ahriman: Package) -> None:
|
||||
@@ -247,28 +271,51 @@ def test_extract_user() -> None:
|
||||
assert extract_user() == "doas"
|
||||
|
||||
|
||||
def test_filelock(tmp_path: Path) -> None:
|
||||
def test_filelock(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must acquire lock and remove lock file after
|
||||
must perform file locking
|
||||
"""
|
||||
local = tmp_path / "local"
|
||||
lock = local.with_name(f".{local.name}.lock")
|
||||
lock_mock = mocker.patch("fcntl.flock")
|
||||
open_mock = mocker.patch("pathlib.Path.open", autospec=True)
|
||||
unlink_mock = mocker.patch("pathlib.Path.unlink")
|
||||
|
||||
with filelock(local):
|
||||
assert lock.exists()
|
||||
assert not lock.exists()
|
||||
with filelock(Path("local")):
|
||||
pass
|
||||
open_mock.assert_called_once_with(Path(".local"), "ab")
|
||||
lock_mock.assert_has_calls([
|
||||
MockCall(pytest.helpers.anyvar(int), fcntl.LOCK_EX),
|
||||
MockCall(pytest.helpers.anyvar(int), fcntl.LOCK_UN),
|
||||
])
|
||||
unlink_mock.assert_called_once_with(missing_ok=True)
|
||||
|
||||
|
||||
def test_filelock_cleanup_on_missing(tmp_path: Path) -> None:
|
||||
def test_filelock_remove_lock(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must not fail if lock file is already removed
|
||||
must remove lock file in case of exception
|
||||
"""
|
||||
local = tmp_path / "local"
|
||||
lock = local.with_name(f".{local.name}.lock")
|
||||
mocker.patch("pathlib.Path.open", side_effect=Exception)
|
||||
unlink_mock = mocker.patch("pathlib.Path.unlink")
|
||||
|
||||
with filelock(local):
|
||||
lock.unlink(missing_ok=True)
|
||||
assert not lock.exists()
|
||||
with pytest.raises(Exception):
|
||||
with filelock(Path("local")):
|
||||
pass
|
||||
unlink_mock.assert_called_once_with(missing_ok=True)
|
||||
|
||||
|
||||
def test_filelock_unlock(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must unlock file in case of exception
|
||||
"""
|
||||
mocker.patch("pathlib.Path.open")
|
||||
lock_mock = mocker.patch("fcntl.flock")
|
||||
|
||||
with pytest.raises(Exception):
|
||||
with filelock(Path("local")):
|
||||
raise Exception
|
||||
lock_mock.assert_has_calls([
|
||||
MockCall(pytest.helpers.anyvar(int), fcntl.LOCK_EX),
|
||||
MockCall(pytest.helpers.anyvar(int), fcntl.LOCK_UN),
|
||||
])
|
||||
|
||||
|
||||
def test_filter_json(package_ahriman: Package) -> None:
|
||||
@@ -301,15 +348,6 @@ def test_full_version() -> None:
|
||||
assert full_version(1, "0.12.1", "1") == "1:0.12.1-1"
|
||||
|
||||
|
||||
def test_list_flatmap() -> None:
|
||||
"""
|
||||
must flat map iterable correctly
|
||||
"""
|
||||
assert list_flatmap([], lambda e: [e * 2]) == []
|
||||
assert list_flatmap([3, 1, 2], lambda e: [e * 2]) == [2, 4, 6]
|
||||
assert list_flatmap([1, 2, 1], lambda e: [e * 2]) == [2, 4]
|
||||
|
||||
|
||||
def test_minmax() -> None:
|
||||
"""
|
||||
must correctly define minimal and maximal value
|
||||
@@ -318,18 +356,6 @@ def test_minmax() -> None:
|
||||
assert minmax([[1, 2, 3], [4, 5], [6, 7, 8, 9]], key=len) == ([4, 5], [6, 7, 8, 9])
|
||||
|
||||
|
||||
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 owner(repository_paths.root) == (42, 142)
|
||||
|
||||
|
||||
def test_package_like(package_ahriman: Package) -> None:
|
||||
"""
|
||||
package_like must return true for archives
|
||||
@@ -506,23 +532,6 @@ def test_srcinfo_property_list() -> None:
|
||||
assert srcinfo_property_list("key", {"key_x86_64": ["overrides"]}, {}, architecture="x86_64") == ["overrides"]
|
||||
|
||||
|
||||
def test_symlink_relative(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create symlinks with relative paths
|
||||
"""
|
||||
symlink_mock = mocker.patch("pathlib.Path.symlink_to")
|
||||
|
||||
symlink_relative(Path("a"), Path("b"))
|
||||
symlink_relative(Path("root/a"), Path("root/c"))
|
||||
symlink_relative(Path("root/sub/a"), Path("root/c"))
|
||||
|
||||
symlink_mock.assert_has_calls([
|
||||
MockCall(Path("b")),
|
||||
MockCall(Path("c")),
|
||||
MockCall(Path("../c")),
|
||||
])
|
||||
|
||||
|
||||
def test_trim_package() -> None:
|
||||
"""
|
||||
must trim package version
|
||||
|
||||
@@ -58,7 +58,7 @@ def test_configuration_schema_no_schema(configuration: Configuration) -> None:
|
||||
assert ReportTrigger.configuration_schema(configuration) == {}
|
||||
|
||||
|
||||
def test_configuration_schema_empty() -> None:
|
||||
def test_configuration_schema_empty(configuration: Configuration) -> None:
|
||||
"""
|
||||
must return default schema if no configuration set
|
||||
"""
|
||||
@@ -70,6 +70,7 @@ def test_configuration_schema_variables() -> None:
|
||||
must return empty schema
|
||||
"""
|
||||
assert Trigger.CONFIGURATION_SCHEMA == {}
|
||||
assert Trigger.CONFIGURATION_SCHEMA_FALLBACK is None
|
||||
|
||||
|
||||
def test_configuration_sections(configuration: Configuration) -> None:
|
||||
|
||||
@@ -4,12 +4,16 @@ from pathlib import Path
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
from ahriman import __version__
|
||||
from ahriman.core.alpm.remote import AUR
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.counters import Counters
|
||||
from ahriman.models.filesystem_package import FilesystemPackage
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.pkgbuild import Pkgbuild
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
from ahriman.models.repository_stats import RepositoryStats
|
||||
|
||||
|
||||
@@ -74,6 +78,27 @@ def internal_status(counters: Counters) -> InternalStatus:
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def package_tpacpi_bat_git() -> Package:
|
||||
"""
|
||||
git package fixture
|
||||
|
||||
Returns:
|
||||
Package: git package test instance
|
||||
"""
|
||||
return Package(
|
||||
base="tpacpi-bat-git",
|
||||
version="3.1.r12.g4959b52-1",
|
||||
remote=RemoteSource(
|
||||
source=PackageSource.AUR,
|
||||
git_url=AUR.remote_git_url("tpacpi-bat-git", "aur"),
|
||||
web_url=AUR.remote_web_url("tpacpi-bat-git"),
|
||||
path=".",
|
||||
branch="master",
|
||||
),
|
||||
packages={"tpacpi-bat-git": PackageDescription()})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pkgbuild_ahriman(resource_path_root: Path) -> Pkgbuild:
|
||||
"""
|
||||
|
||||
@@ -2,13 +2,16 @@ import copy
|
||||
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import MagicMock, PropertyMock, call as MockCall
|
||||
from unittest.mock import MagicMock, call as MockCall
|
||||
|
||||
from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.utils import utcnow
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
from ahriman.models.pkgbuild import Pkgbuild
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
|
||||
|
||||
def test_depends(package_python_schedule: Package) -> None:
|
||||
@@ -160,23 +163,6 @@ def test_from_archive(package_ahriman: Package, pyalpm_handle: MagicMock, mocker
|
||||
assert generated == package_ahriman
|
||||
|
||||
|
||||
def test_from_archive_empty_base(package_ahriman: Package, pyalpm_package_ahriman: MagicMock,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must construct package with empty base from alpm library
|
||||
"""
|
||||
pyalpm_handle = MagicMock()
|
||||
type(pyalpm_package_ahriman).base = PropertyMock(return_value=None)
|
||||
pyalpm_handle.handle.load_pkg.return_value = pyalpm_package_ahriman
|
||||
|
||||
mocker.patch("ahriman.models.package_description.PackageDescription.from_package",
|
||||
return_value=package_ahriman.packages[package_ahriman.base])
|
||||
generated = Package.from_archive(Path("path"), pyalpm_handle)
|
||||
generated.remote = package_ahriman.remote
|
||||
|
||||
assert generated == package_ahriman
|
||||
|
||||
|
||||
def test_from_aur(package_ahriman: Package, aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must construct package from aur
|
||||
@@ -319,6 +305,183 @@ def test_from_official(package_ahriman: Package, aur_package_ahriman: AURPackage
|
||||
assert package_ahriman.packager == package.packager
|
||||
|
||||
|
||||
def test_local_files(mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must extract local file sources
|
||||
"""
|
||||
pkgbuild = resource_path_root / "models" / "package_yay_pkgbuild"
|
||||
parsed_pkgbuild = Pkgbuild.from_file(pkgbuild)
|
||||
parsed_pkgbuild.fields["source"] = PkgbuildPatch("source", ["local-file.tar.gz"])
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.from_file", return_value=parsed_pkgbuild)
|
||||
mocker.patch("ahriman.models.package.Package.supported_architectures", return_value=["any"])
|
||||
|
||||
assert list(Package.local_files(Path("path"))) == [Path("local-file.tar.gz")]
|
||||
|
||||
|
||||
def test_local_files_empty(mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must extract empty local files list when there are no local files
|
||||
"""
|
||||
pkgbuild = resource_path_root / "models" / "package_yay_pkgbuild"
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.from_file", return_value=Pkgbuild.from_file(pkgbuild))
|
||||
mocker.patch("ahriman.models.package.Package.supported_architectures", return_value=["any"])
|
||||
|
||||
assert not list(Package.local_files(Path("path")))
|
||||
|
||||
|
||||
def test_local_files_schema(mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must skip local file source when file schema is used
|
||||
"""
|
||||
pkgbuild = resource_path_root / "models" / "package_yay_pkgbuild"
|
||||
parsed_pkgbuild = Pkgbuild.from_file(pkgbuild)
|
||||
parsed_pkgbuild.fields["source"] = PkgbuildPatch("source", ["file:///local-file.tar.gz"])
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.from_file", return_value=parsed_pkgbuild)
|
||||
mocker.patch("ahriman.models.package.Package.supported_architectures", return_value=["any"])
|
||||
|
||||
assert not list(Package.local_files(Path("path")))
|
||||
|
||||
|
||||
def test_local_files_with_install(mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must extract local file sources with install file
|
||||
"""
|
||||
pkgbuild = resource_path_root / "models" / "package_yay_pkgbuild"
|
||||
parsed_pkgbuild = Pkgbuild.from_file(pkgbuild)
|
||||
parsed_pkgbuild.fields["install"] = PkgbuildPatch("install", "install")
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.from_file", return_value=parsed_pkgbuild)
|
||||
mocker.patch("ahriman.models.package.Package.supported_architectures", return_value=["any"])
|
||||
|
||||
assert list(Package.local_files(Path("path"))) == [Path("install")]
|
||||
|
||||
|
||||
def test_supported_architectures(mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must generate list of available architectures
|
||||
"""
|
||||
pkgbuild = resource_path_root / "models" / "package_yay_pkgbuild"
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.from_file", return_value=Pkgbuild.from_file(pkgbuild))
|
||||
assert Package.supported_architectures(Path("path")) == \
|
||||
{"i686", "pentium4", "x86_64", "arm", "armv7h", "armv6h", "aarch64", "riscv64"}
|
||||
|
||||
|
||||
def test_actual_version(package_ahriman: Package, configuration: Configuration) -> None:
|
||||
"""
|
||||
must return same actual_version as version is
|
||||
"""
|
||||
assert package_ahriman.actual_version(configuration) == package_ahriman.version
|
||||
|
||||
|
||||
def test_actual_version_vcs(package_tpacpi_bat_git: Package, configuration: Configuration,
|
||||
mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must return valid actual_version for VCS package
|
||||
"""
|
||||
pkgbuild = resource_path_root / "models" / "package_tpacpi-bat-git_pkgbuild"
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.from_file", return_value=Pkgbuild.from_file(pkgbuild))
|
||||
mocker.patch("pathlib.Path.glob", return_value=[Path("local")])
|
||||
init_mock = mocker.patch("ahriman.core.build_tools.task.Task.init")
|
||||
unlink_mock = mocker.patch("pathlib.Path.unlink")
|
||||
|
||||
assert package_tpacpi_bat_git.actual_version(configuration) == "3.1.r13.g4959b52-1"
|
||||
init_mock.assert_called_once_with(configuration.repository_paths.cache_for(package_tpacpi_bat_git.base), [], None)
|
||||
unlink_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_actual_version_failed(package_tpacpi_bat_git: Package, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return same version in case if exception occurred
|
||||
"""
|
||||
mocker.patch("ahriman.core.build_tools.task.Task.init", side_effect=Exception)
|
||||
mocker.patch("pathlib.Path.glob", return_value=[Path("local")])
|
||||
unlink_mock = mocker.patch("pathlib.Path.unlink")
|
||||
|
||||
assert package_tpacpi_bat_git.actual_version(configuration) == package_tpacpi_bat_git.version
|
||||
unlink_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_full_depends(package_ahriman: Package, package_python_schedule: Package, pyalpm_package_ahriman: MagicMock,
|
||||
pyalpm_handle: MagicMock) -> None:
|
||||
"""
|
||||
must extract all dependencies from the package
|
||||
"""
|
||||
package_python_schedule.packages[package_python_schedule.base].provides = ["python3-schedule"]
|
||||
|
||||
database_mock = MagicMock()
|
||||
database_mock.pkgcache = [pyalpm_package_ahriman]
|
||||
pyalpm_handle.handle.get_syncdbs.return_value = [database_mock]
|
||||
|
||||
assert package_ahriman.full_depends(pyalpm_handle, [package_python_schedule]) == package_ahriman.depends
|
||||
|
||||
package_python_schedule.packages[package_python_schedule.base].depends = [package_ahriman.base]
|
||||
expected = sorted(set(package_python_schedule.depends + package_ahriman.depends))
|
||||
assert package_python_schedule.full_depends(pyalpm_handle, [package_python_schedule]) == expected
|
||||
|
||||
|
||||
def test_is_newer_than(package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||
"""
|
||||
must correctly check if package is newer than specified timestamp
|
||||
"""
|
||||
# base checks, true/false
|
||||
assert package_ahriman.is_newer_than(package_ahriman.packages[package_ahriman.base].build_date - 1)
|
||||
assert not package_ahriman.is_newer_than(package_ahriman.packages[package_ahriman.base].build_date + 1)
|
||||
|
||||
# list check
|
||||
min_date = min(package.build_date for package in package_python_schedule.packages.values())
|
||||
assert package_python_schedule.is_newer_than(min_date)
|
||||
|
||||
# null list check
|
||||
package_python_schedule.packages["python-schedule"].build_date = None
|
||||
assert package_python_schedule.is_newer_than(min_date)
|
||||
|
||||
package_python_schedule.packages["python2-schedule"].build_date = None
|
||||
assert not package_python_schedule.is_newer_than(min_date)
|
||||
|
||||
|
||||
def test_is_outdated_false(package_ahriman: Package, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must be not outdated for the same package
|
||||
"""
|
||||
actual_version_mock = mocker.patch("ahriman.models.package.Package.actual_version",
|
||||
return_value=package_ahriman.version)
|
||||
assert not package_ahriman.is_outdated(package_ahriman, configuration)
|
||||
actual_version_mock.assert_called_once_with(configuration)
|
||||
|
||||
|
||||
def test_is_outdated_true(package_ahriman: Package, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must be outdated for the new version
|
||||
"""
|
||||
other = Package.from_json(package_ahriman.view())
|
||||
other.version = other.version.replace("-1", "-2")
|
||||
actual_version_mock = mocker.patch("ahriman.models.package.Package.actual_version", return_value=other.version)
|
||||
|
||||
assert package_ahriman.is_outdated(other, configuration)
|
||||
actual_version_mock.assert_called_once_with(configuration)
|
||||
|
||||
|
||||
def test_is_outdated_no_version_calculation(package_ahriman: Package, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must not call actual version if calculation is disabled
|
||||
"""
|
||||
actual_version_mock = mocker.patch("ahriman.models.package.Package.actual_version")
|
||||
assert not package_ahriman.is_outdated(package_ahriman, configuration, calculate_version=False)
|
||||
actual_version_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_is_outdated_fresh_package(package_ahriman: Package, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must not call actual version if package is never than specified time
|
||||
"""
|
||||
configuration.set_option("build", "vcs_allowed_age", str(int(utcnow().timestamp())))
|
||||
actual_version_mock = mocker.patch("ahriman.models.package.Package.actual_version")
|
||||
assert not package_ahriman.is_outdated(package_ahriman, configuration)
|
||||
actual_version_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_next_pkgrel(package_ahriman: Package) -> None:
|
||||
"""
|
||||
must correctly bump pkgrel
|
||||
|
||||
@@ -78,66 +78,6 @@ def test_from_io_empty(pkgbuild_ahriman: Pkgbuild, mocker: MockerFixture) -> Non
|
||||
assert Pkgbuild.from_io(StringIO("mock")) == pkgbuild_ahriman
|
||||
|
||||
|
||||
def test_local_files(mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must extract local file sources
|
||||
"""
|
||||
pkgbuild = resource_path_root / "models" / "package_yay_pkgbuild"
|
||||
parsed_pkgbuild = Pkgbuild.from_file(pkgbuild)
|
||||
parsed_pkgbuild.fields["source"] = PkgbuildPatch("source", ["local-file.tar.gz"])
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.from_file", return_value=parsed_pkgbuild)
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.supported_architectures", return_value=["any"])
|
||||
|
||||
assert list(Pkgbuild.local_files(Path("path"))) == [Path("local-file.tar.gz")]
|
||||
|
||||
|
||||
def test_local_files_empty(mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must extract empty local files list when there are no local files
|
||||
"""
|
||||
pkgbuild = resource_path_root / "models" / "package_yay_pkgbuild"
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.from_file", return_value=Pkgbuild.from_file(pkgbuild))
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.supported_architectures", return_value=["any"])
|
||||
|
||||
assert not list(Pkgbuild.local_files(Path("path")))
|
||||
|
||||
|
||||
def test_local_files_schema(mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must skip local file source when file schema is used
|
||||
"""
|
||||
pkgbuild = resource_path_root / "models" / "package_yay_pkgbuild"
|
||||
parsed_pkgbuild = Pkgbuild.from_file(pkgbuild)
|
||||
parsed_pkgbuild.fields["source"] = PkgbuildPatch("source", ["file:///local-file.tar.gz"])
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.from_file", return_value=parsed_pkgbuild)
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.supported_architectures", return_value=["any"])
|
||||
|
||||
assert not list(Pkgbuild.local_files(Path("path")))
|
||||
|
||||
|
||||
def test_local_files_with_install(mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must extract local file sources with install file
|
||||
"""
|
||||
pkgbuild = resource_path_root / "models" / "package_yay_pkgbuild"
|
||||
parsed_pkgbuild = Pkgbuild.from_file(pkgbuild)
|
||||
parsed_pkgbuild.fields["install"] = PkgbuildPatch("install", "install")
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.from_file", return_value=parsed_pkgbuild)
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.supported_architectures", return_value=["any"])
|
||||
|
||||
assert list(Pkgbuild.local_files(Path("path"))) == [Path("install")]
|
||||
|
||||
|
||||
def test_supported_architectures(mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must generate list of available architectures
|
||||
"""
|
||||
pkgbuild = resource_path_root / "models" / "package_yay_pkgbuild"
|
||||
mocker.patch("ahriman.models.pkgbuild.Pkgbuild.from_file", return_value=Pkgbuild.from_file(pkgbuild))
|
||||
assert Pkgbuild.supported_architectures(Path("path")) == \
|
||||
{"i686", "pentium4", "x86_64", "arm", "armv7h", "armv6h", "aarch64", "riscv64"}
|
||||
|
||||
|
||||
def test_packages(pkgbuild_ahriman: Pkgbuild) -> None:
|
||||
"""
|
||||
must correctly load package function
|
||||
|
||||
@@ -3,8 +3,9 @@ import pytest
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import call as MockCall
|
||||
from unittest.mock import MagicMock, call as MockCall
|
||||
|
||||
from ahriman.core.exceptions import PathError
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
@@ -54,7 +55,7 @@ def test_root_owner(repository_paths: RepositoryPaths, mocker: MockerFixture) ->
|
||||
"""
|
||||
must correctly define root directory owner
|
||||
"""
|
||||
mocker.patch("ahriman.models.repository_paths.owner", return_value=(42, 142))
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.owner", return_value=(42, 142))
|
||||
assert repository_paths.root_owner == (42, 142)
|
||||
|
||||
|
||||
@@ -185,14 +186,90 @@ def test_known_repositories_empty(repository_paths: RepositoryPaths, mocker: Moc
|
||||
iterdir_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_archive_for(repository_paths: RepositoryPaths, package_ahriman: Package) -> None:
|
||||
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_chown(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must correctly set owner for the directory
|
||||
"""
|
||||
object.__setattr__(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
|
||||
"""
|
||||
object.__setattr__(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([
|
||||
MockCall(path, 42, 42, follow_symlinks=False),
|
||||
MockCall(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
|
||||
"""
|
||||
object.__setattr__(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(PathError):
|
||||
repository_paths._chown(repository_paths.root.parent)
|
||||
|
||||
|
||||
def test_archive_for(repository_paths: RepositoryPaths, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must correctly define archive path
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
path = repository_paths.archive_for(package_ahriman.base)
|
||||
assert path == repository_paths.archive / "packages" / "a" / package_ahriman.base
|
||||
|
||||
|
||||
def test_archive_for_create_tree(repository_paths: RepositoryPaths, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create archive directory if it doesn't exist
|
||||
"""
|
||||
owner_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
||||
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
|
||||
|
||||
repository_paths.archive_for(package_ahriman.base)
|
||||
owner_mock.assert_called_once_with(repository_paths.archive)
|
||||
mkdir_mock.assert_called_once_with(mode=0o755, parents=True)
|
||||
|
||||
|
||||
def test_cache_for(repository_paths: RepositoryPaths, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must return correct path for cache directory
|
||||
@@ -202,76 +279,46 @@ def test_cache_for(repository_paths: RepositoryPaths, package_ahriman: Package)
|
||||
assert path.parent == repository_paths.cache
|
||||
|
||||
|
||||
def test_ensure_exists(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create directory if it doesn't exist
|
||||
"""
|
||||
owner_guard_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
||||
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
|
||||
|
||||
repository_paths.ensure_exists(repository_paths.archive)
|
||||
owner_guard_mock.assert_called_once_with()
|
||||
mkdir_mock.assert_called_once_with(mode=0o755, parents=True)
|
||||
|
||||
|
||||
def test_ensure_exists_skip(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must do not create directory if it already exists
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
|
||||
|
||||
repository_paths.ensure_exists(repository_paths.archive)
|
||||
mkdir_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_preserve_owner(tmp_path: Path, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must preserve file owner during operations
|
||||
"""
|
||||
mocker.patch("os.geteuid", return_value=0)
|
||||
mocker.patch("os.getegid", return_value=0)
|
||||
seteuid_mock = mocker.patch("os.seteuid")
|
||||
setegid_mock = mocker.patch("os.setegid")
|
||||
|
||||
repository_paths = RepositoryPaths(tmp_path, repository_id)
|
||||
target_uid, target_gid = repository_paths.root_owner
|
||||
repository_paths.tree_create()
|
||||
seteuid_mock.assert_has_calls([MockCall(target_uid), MockCall(0)])
|
||||
setegid_mock.assert_has_calls([MockCall(target_gid), MockCall(0)])
|
||||
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths._chown")
|
||||
|
||||
with repository_paths.preserve_owner():
|
||||
(repository_paths.root / "created1").touch()
|
||||
(repository_paths.chroot / "created2").touch()
|
||||
chown_mock.assert_has_calls([MockCall(repository_paths.root / "created1")])
|
||||
|
||||
|
||||
def test_preserve_owner_exception(tmp_path: Path, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
def test_preserve_owner_specific(tmp_path: Path, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return to original uid and gid even during exception
|
||||
must preserve file owner during operations only in specific directory
|
||||
"""
|
||||
mocker.patch("os.geteuid", return_value=0)
|
||||
mocker.patch("os.getegid", return_value=0)
|
||||
mocker.patch("pathlib.Path.mkdir", side_effect=Exception)
|
||||
seteuid_mock = mocker.patch("os.seteuid")
|
||||
setegid_mock = mocker.patch("os.setegid")
|
||||
|
||||
repository_paths = RepositoryPaths(tmp_path, repository_id)
|
||||
target_uid, target_gid = repository_paths.root_owner
|
||||
with pytest.raises(Exception):
|
||||
repository_paths.tree_create()
|
||||
seteuid_mock.assert_has_calls([MockCall(target_uid), MockCall(0)])
|
||||
setegid_mock.assert_has_calls([MockCall(target_gid), MockCall(0)])
|
||||
|
||||
|
||||
def test_preserve_owner_non_root(tmp_path: Path, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip processing if user is not root
|
||||
"""
|
||||
mocker.patch("os.geteuid", return_value=42)
|
||||
mocker.patch("os.getegid", return_value=42)
|
||||
repository_paths = RepositoryPaths(tmp_path, repository_id)
|
||||
seteuid_mock = mocker.patch("os.seteuid")
|
||||
setegid_mock = mocker.patch("os.setegid")
|
||||
|
||||
repository_paths.tree_create()
|
||||
seteuid_mock.assert_not_called()
|
||||
setegid_mock.assert_not_called()
|
||||
(repository_paths.root / "content").mkdir()
|
||||
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths._chown")
|
||||
|
||||
with repository_paths.preserve_owner(repository_paths.root / "content"):
|
||||
(repository_paths.root / "created1").touch()
|
||||
(repository_paths.root / "content" / "created2").touch()
|
||||
(repository_paths.chroot / "created3").touch()
|
||||
chown_mock.assert_has_calls([MockCall(repository_paths.root / "content" / "created2")])
|
||||
|
||||
|
||||
def test_preserve_owner_no_directory(tmp_path: Path, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip directory scan if it does not exist
|
||||
"""
|
||||
repository_paths = RepositoryPaths(tmp_path, repository_id)
|
||||
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths._chown")
|
||||
|
||||
with repository_paths.preserve_owner(Path("empty")):
|
||||
(repository_paths.root / "created1").touch()
|
||||
chown_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_tree_clear(repository_paths: RepositoryPaths, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
@@ -280,7 +327,6 @@ def test_tree_clear(repository_paths: RepositoryPaths, package_ahriman: Package,
|
||||
"""
|
||||
paths = {
|
||||
repository_paths.cache_for(package_ahriman.base),
|
||||
repository_paths.archive_for(package_ahriman.base),
|
||||
}
|
||||
rmtree_mock = mocker.patch("shutil.rmtree")
|
||||
|
||||
@@ -314,12 +360,8 @@ def test_tree_create(repository_paths: RepositoryPaths, mocker: MockerFixture) -
|
||||
owner_guard_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
||||
|
||||
repository_paths.tree_create()
|
||||
mkdir_mock.assert_has_calls([MockCall(mode=0o755, parents=True) for _ in paths], any_order=True)
|
||||
owner_guard_mock.assert_has_calls([
|
||||
MockCall(),
|
||||
MockCall().__enter__(),
|
||||
MockCall().__exit__(None, None, None)
|
||||
] * len(paths))
|
||||
mkdir_mock.assert_has_calls([MockCall(mode=0o755, parents=True, exist_ok=True) for _ in paths], any_order=True)
|
||||
owner_guard_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_tree_create_skip(mocker: MockerFixture) -> None:
|
||||
|
||||
@@ -66,7 +66,7 @@ async def test_delete_partially(client: TestClient, package_ahriman: Package) ->
|
||||
assert json
|
||||
|
||||
|
||||
async def test_delete_exception(client: TestClient) -> None:
|
||||
async def test_delete_exception(client: TestClient, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must raise exception on invalid payload
|
||||
"""
|
||||
|
||||
@@ -54,7 +54,7 @@ async def test_get_redirect_to_oauth(client_with_oauth_auth: TestClient) -> None
|
||||
assert not request_schema.validate(payload)
|
||||
response = await client_with_oauth_auth.get("/api/v1/login", params=payload, allow_redirects=False)
|
||||
assert response.ok
|
||||
oauth.get_oauth_url.assert_called_once_with(pytest.helpers.anyvar(str))
|
||||
oauth.get_oauth_url.assert_called_once_with()
|
||||
|
||||
|
||||
async def test_get_redirect_to_oauth_empty_code(client_with_oauth_auth: TestClient) -> None:
|
||||
@@ -69,15 +69,13 @@ async def test_get_redirect_to_oauth_empty_code(client_with_oauth_auth: TestClie
|
||||
assert not request_schema.validate(payload)
|
||||
response = await client_with_oauth_auth.get("/api/v1/login", params=payload, allow_redirects=False)
|
||||
assert response.ok
|
||||
oauth.get_oauth_url.assert_called_once_with(pytest.helpers.anyvar(str))
|
||||
oauth.get_oauth_url.assert_called_once_with()
|
||||
|
||||
|
||||
async def test_get(client_with_oauth_auth: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must log in user correctly from OAuth
|
||||
"""
|
||||
session = {"state": "state"}
|
||||
mocker.patch("ahriman.web.views.v1.user.login.get_session", return_value=session)
|
||||
oauth = client_with_oauth_auth.app[AuthKey]
|
||||
oauth.get_oauth_username.return_value = "user"
|
||||
oauth.known_username.return_value = True
|
||||
@@ -86,12 +84,12 @@ async def test_get(client_with_oauth_auth: TestClient, mocker: MockerFixture) ->
|
||||
remember_mock = mocker.patch("ahriman.web.views.v1.user.login.remember")
|
||||
request_schema = pytest.helpers.schema_request(LoginView.get, location="querystring")
|
||||
|
||||
payload = {"code": "code", "state": "state"}
|
||||
payload = {"code": "code"}
|
||||
assert not request_schema.validate(payload)
|
||||
response = await client_with_oauth_auth.get("/api/v1/login", params=payload)
|
||||
|
||||
assert response.ok
|
||||
oauth.get_oauth_username.assert_called_once_with("code", "state", session)
|
||||
oauth.get_oauth_username.assert_called_once_with("code")
|
||||
oauth.known_username.assert_called_once_with("user")
|
||||
remember_mock.assert_called_once_with(
|
||||
pytest.helpers.anyvar(int), pytest.helpers.anyvar(int), pytest.helpers.anyvar(int))
|
||||
|
||||
Reference in New Issue
Block a user