add migration docs and ability to migrate tree automatically

This commit is contained in:
Evgenii Alekseev 2023-09-01 16:52:21 +03:00
parent 0eebcc5740
commit 1b5bfe5088
24 changed files with 469 additions and 131 deletions

View File

@ -172,6 +172,14 @@ ahriman.application.handlers.structure module
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.tree\_migrate module
-------------------------------------------------
.. automodule:: ahriman.application.handlers.tree_migrate
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.triggers module
--------------------------------------------

View File

@ -102,6 +102,53 @@ All subcommands are divided into several groups depending on the role they are d
For historical reasons and in order to keep backward compatibility some subcommands have aliases to their shorter forms or even other groups, but the service doesn't guarantee that they will remain unchanged.
Filesystem tree
---------------
The application supports two types of trees, one is for legacy configuration (when there were no repository name explicit configuration available) and another one is for new-style tree. This document describes only new-style tree in order to avoid deprecated structure.
Having default root as ``/var/lib/ahriman`` (differs from container though), the directory structure is the following:
.. code-block::
/var/lib/ahriman/
├── ahriman.db
├── cache
├── chroot
│ └── aur-clone
├── packages
│ └── aur-clone
│ └── x86_64
├── pacman
│ └── aur-clone
│ └── x86_64
│ ├── local
│ │ └── ALPM_DB_VERSION
│ └── sync
│ ├── core.db
│ ├── extra.db
│ └── multilib.db
└── repository
└── aur-clone
└── x86_64
├── aur-clone.db -> aur-clone.db.tar.gz
├── aur-clone.db.tar.gz
├── aur-clone.files -> aur-clone.files.tar.gz
└── aur-clone.files.tar.gz
There are multiple subdirectories, some of them are commons for any repository, but some of them are not.
* ``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 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 repository and architecture specific caches for pacman's databases.
* ``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 <migration>`.
Database
--------

View File

@ -71,7 +71,7 @@ states that default build command is ``extra-x86_64-build``. But if there is sec
[build:i686]
build_command = extra-i686-build
the ``extra-i686-build`` command will be used for ``i686`` architecture.
the ``extra-i686-build`` command will be used for ``i686`` architecture. You can also override settings for different repositories and architectures; in this case section names will be ``build:aur-clone`` (repository name only) and ``build:aur-clone:i686`` (both repository name and architecture).
How to generate build reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -121,7 +121,7 @@ How do I add new package
sudo -u ahriman ahriman package-add ahriman --now
``--now`` flag is totally optional and just run ``repo-update`` subcommand after the registering the new package, Thus the extended flow is the following:
``--now`` flag is totally optional and just run ``repo-update`` subcommand after the registering the new package. Thus the extended flow is the following:
.. code-block:: shell
@ -209,7 +209,7 @@ So it is the same as adding any other package, but due to restrictions you must
sudo -u ahriman ahriman package-add pacman -s repository
This feature is heavily depends on local pacman cache. In order to use this feature it is recommended to either run ``pacman -Sy`` before the interaction or configure timer for this.
This feature is heavily depends on local pacman cache. In order to use this feature it is recommended to either run ``pacman -Sy`` before the interaction or use internal application cache with ``--refresh`` flag.
Package build fails because it cannot validate PGP signature of source files
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -359,7 +359,14 @@ Example of the status page configuration is the following (status service is usi
Docker image
------------
We provide official images which can be found under ``arcan1s/ahriman`` repository. Docker image is being updated on each commit to master as well as on each version. If you would like to use last (probably unstable) build you can use ``edge`` tag or ``latest`` for any tagged versions; otherwise you can use any version tag available.
We provide official images which can be found under:
* docker registry ``arcan1s/ahriman``;
* ghcr.io registry ``ghcr.io/arcan1s/ahriman``;
These images are totally identical.
Docker image is being updated on each commit to master as well as on each version. If you would like to use last (probably unstable) build you can use ``edge`` tag or ``latest`` for any tagged versions; otherwise you can use any version tag available.
The default action (in case if no arguments provided) is ``repo-update``. Basically the idea is to run container, e.g.:
@ -456,22 +463,22 @@ Physical server setup
In this example we are going to use files and packages which are provided by official repositories of the used architecture. Note, that versions might be different, thus you need to find correct versions on the distribution web site, e.g. `archlinux32 packages <https://www.archlinux32.org/packages/>`_.
#.
First, considering having base Arch Linux system, we need to install keyring for the specified repositories:
First, considering having base Arch Linux system, we need to install keyring for the specified repositories, e.g.:
.. code-block:: shell
wget http://pool.mirror.archlinux32.org/i686/core/archlinux32-keyring-20220927-1.0-any.pkg.tar.zst
pacman -U archlinux32-keyring-20220927-1.0-any.pkg.tar.zst
wget http://pool.mirror.archlinux32.org/i686/core/archlinux32-keyring-20230705-1.0-any.pkg.tar.zst
pacman -U archlinux32-keyring-20230705-1.0-any.pkg.tar.zst
#.
In order to run ``devtools`` scripts for custom architecture they also need specific ``makepkg`` configuration, it can be retrieved by installing the ``devtools`` package of the distribution:
In order to run ``devtools`` scripts for custom architecture they also need specific ``makepkg`` configuration, it can be retrieved by installing the ``devtools`` package of the distribution, e.g.:
.. code-block:: shell
wget http://pool.mirror.archlinux32.org/i686/extra/devtools-20221208-1.0-any.pkg.tar.zst
pacman -U devtools-20221208-1.0-any.pkg.tar.zst
wget http://pool.mirror.archlinux32.org/i686/extra/devtools-20221208-1.2-any.pkg.tar.zst
pacman -U devtools-20221208-1.2-any.pkg.tar.zst
Alternatively, you can create your own ``makepkg`` configuration and save it as ``/usr/share/devtools/makepkg-i686.conf``.
Alternatively, you can create your own ``makepkg`` configuration and save it as ``/usr/share/devtools/makepkg.conf.d/i686.conf``.
#.
Setup repository as usual:
@ -485,6 +492,9 @@ In this example we are going to use files and packages which are provided by off
* ``--mirror`` - link to the mirrors which will be used instead of official repositories.
* ``--no-multilib`` - in the example we are using i686 architecture for which multilib repository doesn't exist.
#.
That's all Folks!
Docker container setup
^^^^^^^^^^^^^^^^^^^^^^
@ -510,8 +520,8 @@ There are two possible ways to achieve same setup, by using docker container. Th
.. code-block:: dockerfile
RUN pacman --noconfirm -Sy wget
RUN wget http://pool.mirror.archlinux32.org/i686/extra/devtools-20221208-1.0-any.pkg.tar.zst && pacman --noconfirm -U devtools-20221208-1.0-any.pkg.tar.zst
RUN wget http://pool.mirror.archlinux32.org/i686/core/archlinux32-keyring-20220927-1.0-any.pkg.tar.zst && pacman --noconfirm -U archlinux32-keyring-20220927-1.0-any.pkg.tar.zst
RUN wget http://pool.mirror.archlinux32.org/i686/extra/devtools-20221208-1.2-any.pkg.tar.zst && pacman --noconfirm -U devtools-20221208-1.2-any.pkg.tar.zst
RUN wget http://pool.mirror.archlinux32.org/i686/core/archlinux32-keyring-20230705-1.0-any.pkg.tar.zst && pacman --noconfirm -U archlinux32-keyring-20230705-1.0-any.pkg.tar.zst
#.
At that point you should have full ``Dockerfile`` like:
@ -523,8 +533,8 @@ There are two possible ways to achieve same setup, by using docker container. Th
RUN pacman-key --init
RUN pacman --noconfirm -Sy wget
RUN wget http://pool.mirror.archlinux32.org/i686/extra/devtools-20221208-1.0-any.pkg.tar.zst && pacman --noconfirm -U devtools-20221208-1.0-any.pkg.tar.zst
RUN wget http://pool.mirror.archlinux32.org/i686/core/archlinux32-keyring-20220927-1.0-any.pkg.tar.zst && pacman --noconfirm -U archlinux32-keyring-20220927-1.0-any.pkg.tar.zst
RUN wget http://pool.mirror.archlinux32.org/i686/extra/devtools-20221208-1.2-any.pkg.tar.zst && pacman --noconfirm -U devtools-20221208-1.2-any.pkg.tar.zst
RUN wget http://pool.mirror.archlinux32.org/i686/core/archlinux32-keyring-20230705-1.0-any.pkg.tar.zst && pacman --noconfirm -U archlinux32-keyring-20230705-1.0-any.pkg.tar.zst
#.
After that you can build you own container, e.g.:
@ -554,8 +564,8 @@ There are several choices:
.. code-block::
server {
location /aur-clone/x86_64 {
root /var/lib/ahriman/repository/aur-clone/x86_64;
location / {
root /var/lib/ahriman/repository/;
autoindex on;
}
}
@ -571,7 +581,7 @@ There are several choices:
[rsync]
remote = 192.168.0.1:/srv/repo
After that just add ``/srv/repo`` to the ``pacman.conf`` as usual. You can also upload to S3 (e.g. ``Server = https://s3.eu-central-1.amazonaws.com/repository/aur-clone/x86_64``) or to Github (e.g. ``Server = https://github.com/ahriman/repository/releases/download/aur-clone-x86_64``).
After that just add ``/srv/repo`` to the ``pacman.conf`` as usual. You can also upload to S3 (``Server = https://s3.eu-central-1.amazonaws.com/repository/aur-clone/x86_64``) or to Github (``Server = https://github.com/ahriman/repository/releases/download/aur-clone-x86_64``).
How to sync to S3
^^^^^^^^^^^^^^^^^
@ -705,7 +715,7 @@ How to generate index page for S3
path = /var/lib/ahriman/repository/aur-clone/x86_64/index.html
link_path = http://example.com/aur-clone/x86_64
After these steps ``index.html`` file will be automatically synced to S3
After these steps ``index.html`` file will be automatically synced to S3.
How to post build report to telegram
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -756,7 +766,7 @@ If you did everything fine you should receive the message with the next update.
Distributed builds
------------------
The service allows to run build on multiple machines and collect packages on main node. There are multiple ways to achieve it, this section describes officially supported methods.
The service allows to run build on multiple machines and collect packages on main node. There are several ways to achieve it, this section describes officially supported methods.
Remote synchronization and remote server call
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -933,7 +943,7 @@ Command to run worker node:
.. code-block:: shell
docker run --privileged -v worker.ini:/etc/ahriman.ini.d/overrides.ini -it arcan1s/ahriman:latest package-add arhiman --now
docker run --privileged -v worker.ini:/etc/ahriman.ini.d/overrides.ini -it arcan1s/ahriman:latest package-add ahriman --now
The command above will successfully build ``ahriman`` package, upload it on master node and, finally, will update master node repository.
@ -1059,7 +1069,7 @@ How to enable basic authorization
yay -S --asdeps python-aiohttp-security python-aiohttp-session python-cryptography
#.
Configure the service to enable authorization (``salt`` can be generated as any random string):
Configure the service to enable authorization (``salt`` can be generated as any random string and optional):
.. code-block:: ini
@ -1087,7 +1097,7 @@ How to enable basic authorization
sudo -u ahriman ahriman user-add -r full api
This command will ask for the password, just type it in stdin; *do not* leave the field blank, user will not be able to authorize, and finally configure the application:
This command will ask for the password, just type it in stdin; **do not** leave the field blank, user will not be able to authorize, and finally configure the application:
.. code-block:: ini
@ -1265,7 +1275,9 @@ You can also ask to forward logs to ``stderr``, just set ``--log-handler`` flag,
ahriman --log-handler console ...
You can even configure logging as you wish, but kindly refer to python ``logging`` module `configuration <https://docs.python.org/3/library/logging.config.html>`_. The application uses java concept to log messages, e.g. class ``Application`` imported from ``ahriman.application.application`` package will have logger called ``ahriman.application.application.Application``. In order to e.g. change logger name for whole application package it is possible to change values for ``ahriman.application`` package; thus editing ``ahriman`` logger configuration will change logging for whole application (unless there are overrides for another logger).
You can even configure logging as you wish, but kindly refer to python ``logging`` module `configuration <https://docs.python.org/3/library/logging.config.html>`_.
The application uses java concept to log messages, e.g. class ``Application`` imported from ``ahriman.application.application`` package will have logger called ``ahriman.application.application.Application``. In order to e.g. change logger name for whole application package it is possible to change values for ``ahriman.application`` package; thus editing ``ahriman`` logger configuration will change logging for whole application (unless there are overrides for another logger).
Html customization
^^^^^^^^^^^^^^^^^^

52
docs/migration.rst Normal file
View File

@ -0,0 +1,52 @@
Manual migrations
=================
Normally most of migrations are handled automatically after application start. However, some upgrades require manual interventions; this document describes them.
Upgrades from versions
----------------------
To 2.9.0
^^^^^^^^
This release includes major upgrade for the newest devtools and archlinux repository structure. In order to upgrade package need to:
#. Upgrade to the latest major release of python (3.11) (required by other changes).
#. Upgrade devtools to the latest release.
#. Backup your settings, ``/etc/ahriman.ini.d/00-setup-overrides.ini`` by default.
#. Run setup command (i.e. ``ahriman service-setup``) again with the same arguments as you used before. This step can be done manually by moving ``devtools`` configuration (something like ``/usr/share/devtools/pacman-ahriman*.conf``) to new location ``/usr/share/devtools/pacman.conf.d/`` under name ``ahriman.conf``. After that make sure to remove any ``community`` mentions from configurations (e.g. ``/usr/share/devtools/pacman.conf.d/ahriman.conf``, ``/etc/ahriman.ini``) if there were any. The only thing which will change is ``devtools`` configuration.
#. Remove build chroot as it is incompatible, e.g. ``sudo ahriman service-clean --chroot``.
#. Run ``sudo -u ahriman ahriman update --no-aur --no-local --no-manual -yy`` in order to update local databases.
To 2.12.0
^^^^^^^^^
This release includes paths migration. Unlike usual case, no automatic migration is performed because it might break user configuration. The following noticeable changes have been made:
* Path to pre-built packages now includes repository name, i.e. it has been changed from ``/var/lib/ahriman/packages/x86_64`` to ``/var/lib/ahriman/packages/aur-clone/x86_64``.
* Path to pacman databases now includes repository name too, it has been changed from ``/var/lib/ahriman/pacman/x86_64`` to ``/var/lib/ahriman/pacman/aur-clone/x86_64``.
* Path to repository itself also includes repository name, from ``/var/lib/ahriman/repository/x86_64`` to ``/var/lib/ahriman/repository/aur-clone/x86_64``.
In order to migrate to new filesystem tree the following actions are required:
#.
Stop and disable all services, e.g. timer and web service:
.. code-block:: shell
sudo systemctl disable --now ahriman@x86_64.timer
sudo systemctl disable --now ahriman-web@x86_64
#.
Create directory tree. It can be done by running ``ahriman service-tree-migrate`` subcommand. It performs copying between the old repository tree and the new one. Alternatively you can copy directories by hands.
#.
Edit configuration in case if anything is pointing to the old path, e.g. HTML report generation, in the way in which it will be pointed to directory inside repository specific one, e.g. ``/var/lib/ahriman/repository/x86_64`` to ``/var/lib/ahriman/repository/aur-clone/x86_64``.
#.
Enable and start services again. Unit template parameter should include both repository architecture and name, dash separated, e.g. ``x86_64-aur-clone``:
.. code-block:: shell
sudo systemctl enable --now ahriman@x86_64-aur-clone.timer
sudo systemctl enable --now ahriman-web@x86_64-aur-clone

View File

@ -1,6 +1,7 @@
post_upgrade() {
local breakpoints=(
2.9.0-1
2.12.0-1
)
for v in "${breakpoints[@]}"; do
@ -20,6 +21,20 @@ It was found that you were upgrading from old-devtools package to the new one, w
* remove build chroot: sudo rm -r /var/lib/ahriman/chroot/ahriman-x86_64/;
* update local databases: sudo -u ahriman ahriman update --no-aur --no-local --no-manual -yy;
For more information kindly refer to changelog https://github.com/arcan1s/ahriman/releases/tag/2.9.0
For more information kindly refer to migration notes https://ahriman.readthedocs.io/en/stable/migration.html
EOF
}
_2_12_0_1_changes() {
cat << EOF
Whereas old tree is still supported it is highly recommended to migrate to the new one:
* stop and disable all services;
* run service-tree-migrate as ahriman user;
* edit configuration to avoid pointing to the old paths;
* enable web and timer services again by using x86_64-aur-clone suffix, where x86_64 is your architecture and
aur-clone is repository name;
For more information kindly refer to migration notes https://ahriman.readthedocs.io/en/stable/migration.html
EOF
}

View File

@ -1,8 +1,8 @@
# AUTOMATICALLY GENERATED by `shtab`
_shtab_ahriman_subparsers=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'user-add' 'user-list' 'user-remove' 'web')
_shtab_ahriman_subparsers=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web')
_shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--log-handler' '--report' '--no-report' '-r' '--repository' '-q' '--quiet' '--unsafe' '--wait-timeout' '-V' '--version')
_shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--log-handler' '-q' '--quiet' '--report' '--no-report' '-r' '--repository' '--unsafe' '--wait-timeout' '-V' '--version')
_shtab_ahriman_aur_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by')
_shtab_ahriman_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by')
_shtab_ahriman_help_option_strings=('-h' '--help')
@ -65,6 +65,7 @@ _shtab_ahriman_repo_setup_option_strings=('-h' '--help' '--build-as-user' '--fro
_shtab_ahriman_setup_option_strings=('-h' '--help' '--build-as-user' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
_shtab_ahriman_service_shell_option_strings=('-h' '--help')
_shtab_ahriman_shell_option_strings=('-h' '--help')
_shtab_ahriman_service_tree_migrate_option_strings=('-h' '--help')
_shtab_ahriman_user_add_option_strings=('-h' '--help' '--key' '--packager' '-p' '--password' '-R' '--role')
_shtab_ahriman_user_list_option_strings=('-h' '--help' '-e' '--exit-code' '-R' '--role')
_shtab_ahriman_user_remove_option_strings=('-h' '--help')
@ -72,7 +73,7 @@ _shtab_ahriman_web_option_strings=('-h' '--help')
_shtab_ahriman_pos_0_choices=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'user-add' 'user-list' 'user-remove' 'web')
_shtab_ahriman_pos_0_choices=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web')
_shtab_ahriman___log_handler_choices=('console' 'syslog' 'journald')
_shtab_ahriman_aur_search___sort_by_choices=('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')
_shtab_ahriman_search___sort_by_choices=('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')
@ -110,10 +111,10 @@ _shtab_ahriman_pos_0_nargs=A...
_shtab_ahriman__h_nargs=0
_shtab_ahriman___help_nargs=0
_shtab_ahriman___force_nargs=0
_shtab_ahriman___report_nargs=0
_shtab_ahriman___no_report_nargs=0
_shtab_ahriman__q_nargs=0
_shtab_ahriman___quiet_nargs=0
_shtab_ahriman___report_nargs=0
_shtab_ahriman___no_report_nargs=0
_shtab_ahriman___unsafe_nargs=0
_shtab_ahriman__V_nargs=0
_shtab_ahriman___version_nargs=0
@ -473,6 +474,8 @@ _shtab_ahriman_shell__h_nargs=0
_shtab_ahriman_shell___help_nargs=0
_shtab_ahriman_shell__v_nargs=0
_shtab_ahriman_shell___verbose_nargs=0
_shtab_ahriman_service_tree_migrate__h_nargs=0
_shtab_ahriman_service_tree_migrate___help_nargs=0
_shtab_ahriman_user_add__h_nargs=0
_shtab_ahriman_user_add___help_nargs=0
_shtab_ahriman_user_list__h_nargs=0

View File

@ -1,9 +1,9 @@
.TH AHRIMAN "1" "2023\-09\-01" "ahriman" "Generated Python Manual"
.TH AHRIMAN "1" "2023\-09\-02" "ahriman" "Generated Python Manual"
.SH NAME
ahriman
.SH SYNOPSIS
.B ahriman
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [--report | --no-report] [-r REPOSITORY] [-q] [--unsafe] [--wait-timeout WAIT_TIMEOUT] [-V] {aur-search,search,help,help-commands-unsafe,help-updates,help-version,version,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,patch-set-add,repo-backup,repo-check,check,repo-create-keyring,repo-create-mirrorlist,repo-daemon,daemon,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-sign,sign,repo-status-update,repo-sync,sync,repo-tree,repo-triggers,repo-update,update,service-clean,clean,repo-clean,service-config,config,repo-config,service-config-validate,config-validate,repo-config-validate,service-key-import,key-import,service-setup,init,repo-init,repo-setup,setup,service-shell,shell,user-add,user-list,user-remove,web} ...
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [--wait-timeout WAIT_TIMEOUT] [-V] {aur-search,search,help,help-commands-unsafe,help-updates,help-version,version,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,patch-set-add,repo-backup,repo-check,check,repo-create-keyring,repo-create-mirrorlist,repo-daemon,daemon,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-sign,sign,repo-status-update,repo-sync,sync,repo-tree,repo-triggers,repo-update,update,service-clean,clean,repo-clean,service-config,config,repo-config,service-config-validate,config-validate,repo-config-validate,service-key-import,key-import,service-setup,init,repo-init,repo-setup,setup,service-shell,shell,service-tree-migrate,user-add,user-list,user-remove,web} ...
.SH DESCRIPTION
ArcH linux ReposItory MANager
@ -28,6 +28,10 @@ lock file
\fB\-\-log\-handler\fR \fI\,{console,syslog,journald}\/\fR
explicit log handler specification. If none set, the handler will be guessed from environment
.TP
\fB\-q\fR, \fB\-\-quiet\fR
force disable any logging
.TP
\fB\-\-report\fR, \fB\-\-no\-report\fR
force enable or disable reporting to web service
@ -36,10 +40,6 @@ force enable or disable reporting to web service
\fB\-r\fR \fI\,REPOSITORY\/\fR, \fB\-\-repository\fR \fI\,REPOSITORY\/\fR
target repository. For several subcommands it can be used multiple times
.TP
\fB\-q\fR, \fB\-\-quiet\fR
force disable any logging
.TP
\fB\-\-unsafe\fR
allow to run ahriman as non\-ahriman user. Some actions might be unavailable
@ -161,6 +161,9 @@ initial service configuration
\fBahriman\fR \fI\,service\-shell\/\fR
invoke python shell
.TP
\fBahriman\fR \fI\,service\-tree\-migrate\/\fR
migrate repository tree
.TP
\fBahriman\fR \fI\,user\-add\/\fR
create or update user
.TP
@ -761,6 +764,11 @@ drop into python shell
\fBcode\fR
instead of dropping into shell, just execute the specified code
.SH COMMAND \fI\,'ahriman service\-tree\-migrate'\/\fR
usage: ahriman service\-tree\-migrate [\-h]
migrate repository tree between versions
.SH COMMAND \fI\,'ahriman user\-add'\/\fR
usage: ahriman user\-add [\-h] [\-\-key KEY] [\-\-packager PACKAGER] [\-p PASSWORD] [\-R {unauthorized,read,reporter,full}]
username

View File

@ -59,6 +59,7 @@ _shtab_ahriman_commands() {
"service-key-import:import PGP key from public sources to the repository user"
"service-setup:create initial service configuration, requires root"
"service-shell:drop into python shell"
"service-tree-migrate:migrate repository tree between versions"
"setup:create initial service configuration, requires root"
"shell:drop into python shell"
"sign:(re-)sign packages and repository database according to current settings"
@ -82,9 +83,9 @@ _shtab_ahriman_options=(
"--force[force run, remove file lock (default\: False)]"
{-l,--lock}"[lock file (default\: \/tmp\/ahriman.lock)]:lock:"
"--log-handler[explicit log handler specification. If none set, the handler will be guessed from environment (default\: None)]:log_handler:(console syslog journald)"
{-q,--quiet}"[force disable any logging (default\: False)]"
{--report,--no-report}"[force enable or disable reporting to web service (default\: True)]:report:"
"*"{-r,--repository}"[target repository. For several subcommands it can be used multiple times (default\: None)]:repository:"
{-q,--quiet}"[force disable any logging (default\: False)]"
"--unsafe[allow to run ahriman as non-ahriman user. Some actions might be unavailable (default\: False)]"
"--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:"
"(- : *)"{-V,--version}"[show program\'s version number and exit]"
@ -491,6 +492,10 @@ _shtab_ahriman_service_shell_options=(
":instead of dropping into shell, just execute the specified code (default\: None):"
)
_shtab_ahriman_service_tree_migrate_options=(
"(- : *)"{-h,--help}"[show this help message and exit]"
)
_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:"
@ -649,6 +654,7 @@ _shtab_ahriman() {
service-key-import) _arguments -C -s $_shtab_ahriman_service_key_import_options ;;
service-setup) _arguments -C -s $_shtab_ahriman_service_setup_options ;;
service-shell) _arguments -C -s $_shtab_ahriman_service_shell_options ;;
service-tree-migrate) _arguments -C -s $_shtab_ahriman_service_tree_migrate_options ;;
setup) _arguments -C -s $_shtab_ahriman_setup_options ;;
shell) _arguments -C -s $_shtab_ahriman_shell_options ;;
sign) _arguments -C -s $_shtab_ahriman_sign_options ;;

View File

@ -79,6 +79,7 @@ def _parser() -> argparse.ArgumentParser:
parser.add_argument("--log-handler", help="explicit log handler specification. If none set, the handler will be "
"guessed from environment",
type=LogHandler, choices=enum_values(LogHandler))
parser.add_argument("-q", "--quiet", help="force disable any logging", action="store_true")
parser.add_argument("--report", help="force enable or disable reporting to web service",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("-r", "--repository", help="target repository. For several subcommands it can be used "
@ -86,7 +87,6 @@ def _parser() -> argparse.ArgumentParser:
# special secret argument for systemd unit. The issue is that systemd doesn't allow multiple arguments to template
# name. This parameter accepts [[arch]-repo] in order to keep backward compatibility
parser.add_argument("--repository-id", help=argparse.SUPPRESS)
parser.add_argument("-q", "--quiet", help="force disable any logging", action="store_true")
parser.add_argument("--unsafe", help="allow to run ahriman as non-ahriman user. Some actions might be unavailable",
action="store_true")
parser.add_argument("--wait-timeout", help="wait for lock to be free. Negative value will lead to "
@ -132,6 +132,7 @@ def _parser() -> argparse.ArgumentParser:
_set_service_key_import_parser(subparsers)
_set_service_setup_parser(subparsers)
_set_service_shell_parser(subparsers)
_set_service_tree_migrate_parser(subparsers)
_set_user_add_parser(subparsers)
_set_user_list_parser(subparsers)
_set_user_remove_parser(subparsers)
@ -160,8 +161,8 @@ def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("--sort-by", help="sort field by this field. In case if two packages have the same value of "
"the specified field, they will be always sorted by name",
default="name", choices=sorted(handlers.Search.SORT_FIELDS))
parser.set_defaults(handler=handlers.Search, architecture=[""], lock=None, report=False, repository=[""],
quiet=True, unsafe=True)
parser.set_defaults(handler=handlers.Search, architecture=[""], lock=None, quiet=True, report=False,
repository=[""], unsafe=True)
return parser
@ -179,7 +180,7 @@ def _set_help_parser(root: SubParserAction) -> argparse.ArgumentParser:
description="show help message for application or command and exit",
formatter_class=_formatter)
parser.add_argument("command", help="show help message for specific command", nargs="?")
parser.set_defaults(handler=handlers.Help, architecture=[""], lock=None, report=False, repository=[""], quiet=True,
parser.set_defaults(handler=handlers.Help, architecture=[""], lock=None, quiet=True, report=False, repository=[""],
unsafe=True, parser=_parser)
return parser
@ -198,8 +199,8 @@ def _set_help_commands_unsafe_parser(root: SubParserAction) -> argparse.Argument
description="list unsafe commands as defined in default args", formatter_class=_formatter)
parser.add_argument("command", help="instead of showing commands, just test command line for unsafe subcommand "
"and return 0 in case if command is safe and 1 otherwise", nargs="*")
parser.set_defaults(handler=handlers.UnsafeCommands, architecture=[""], lock=None, report=False, repository=[""],
quiet=True, unsafe=True, parser=_parser)
parser.set_defaults(handler=handlers.UnsafeCommands, architecture=[""], lock=None, quiet=True, report=False,
repository=[""], unsafe=True, parser=_parser)
return parser
@ -217,8 +218,8 @@ def _set_help_updates_parser(root: SubParserAction) -> argparse.ArgumentParser:
description="request AUR for current version and compare with current service version",
formatter_class=_formatter)
parser.add_argument("-e", "--exit-code", help="return non-zero exit code if updates available", action="store_true")
parser.set_defaults(handler=handlers.ServiceUpdates, architecture=[""], lock=None, report=False, repository=[""],
quiet=True, unsafe=True)
parser.set_defaults(handler=handlers.ServiceUpdates, architecture=[""], lock=None, quiet=True, report=False,
repository=[""], unsafe=True)
return parser
@ -234,8 +235,8 @@ def _set_help_version_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
parser = root.add_parser("help-version", aliases=["version"], help="application version",
description="print application and its dependencies versions", formatter_class=_formatter)
parser.set_defaults(handler=handlers.Versions, architecture=[""], lock=None, report=False, repository=[""],
quiet=True, unsafe=True)
parser.set_defaults(handler=handlers.Versions, architecture=[""], lock=None, quiet=True, report=False,
repository=[""], unsafe=True)
return parser
@ -317,7 +318,7 @@ def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser
action=argparse.BooleanOptionalAction, default=False)
parser.add_argument("-s", "--status", help="filter packages by status",
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum))
parser.set_defaults(handler=handlers.Status, lock=None, report=False, quiet=True, unsafe=True)
parser.set_defaults(handler=handlers.Status, lock=None, quiet=True, report=False, unsafe=True)
return parser
@ -337,7 +338,7 @@ def _set_package_status_remove_parser(root: SubParserAction) -> argparse.Argumen
"clears the status page.",
formatter_class=_formatter)
parser.add_argument("package", help="remove specified packages from status page", nargs="+")
parser.set_defaults(handler=handlers.StatusUpdate, action=Action.Remove, lock=None, report=False, quiet=True,
parser.set_defaults(handler=handlers.StatusUpdate, action=Action.Remove, lock=None, quiet=True, report=False,
unsafe=True)
return parser
@ -359,7 +360,7 @@ def _set_package_status_update_parser(root: SubParserAction) -> argparse.Argumen
nargs="*")
parser.add_argument("-s", "--status", help="new package build status",
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum), default=BuildStatusEnum.Success)
parser.set_defaults(handler=handlers.StatusUpdate, action=Action.Update, lock=None, report=False, quiet=True,
parser.set_defaults(handler=handlers.StatusUpdate, action=Action.Update, lock=None, quiet=True, report=False,
unsafe=True)
return parser
@ -690,8 +691,8 @@ def _set_repo_status_update_parser(root: SubParserAction) -> argparse.ArgumentPa
description="update repository status on the status page", formatter_class=_formatter)
parser.add_argument("-s", "--status", help="new status",
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum), default=BuildStatusEnum.Success)
parser.set_defaults(handler=handlers.StatusUpdate, action=Action.Update, lock=None, package=[], report=False,
quiet=True, unsafe=True)
parser.set_defaults(handler=handlers.StatusUpdate, action=Action.Update, lock=None, package=[], quiet=True,
report=False, unsafe=True)
return parser
@ -728,7 +729,7 @@ def _set_repo_tree_parser(root: SubParserAction) -> argparse.ArgumentParser:
formatter_class=_formatter)
parser.add_argument("-p", "--partitions", help="also divide packages by independent partitions",
type=int, default=1)
parser.set_defaults(handler=handlers.Structure, lock=None, report=False, quiet=True, unsafe=True)
parser.set_defaults(handler=handlers.Structure, lock=None, quiet=True, report=False, unsafe=True)
return parser
@ -831,7 +832,7 @@ def _set_service_config_parser(root: SubParserAction) -> argparse.ArgumentParser
formatter_class=_formatter)
parser.add_argument("--secure", help="hide passwords and secrets from output",
action=argparse.BooleanOptionalAction, default=True)
parser.set_defaults(handler=handlers.Dump, lock=None, report=False, quiet=True, unsafe=True)
parser.set_defaults(handler=handlers.Dump, lock=None, quiet=True, report=False, unsafe=True)
return parser
@ -851,7 +852,7 @@ def _set_service_config_validate_parser(root: SubParserAction) -> argparse.Argum
formatter_class=_formatter)
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if configuration is invalid",
action="store_true")
parser.set_defaults(handler=handlers.Validate, lock=None, report=False, quiet=True, unsafe=True)
parser.set_defaults(handler=handlers.Validate, lock=None, quiet=True, report=False, unsafe=True)
return parser
@ -910,7 +911,7 @@ def _set_service_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
type=SignSettings.from_option, choices=enum_values(SignSettings))
parser.add_argument("--web-port", help="port of the web service", type=int)
parser.add_argument("--web-unix-socket", help="path to unix socket used for interprocess communications", type=Path)
parser.set_defaults(handler=handlers.Setup, lock=None, report=False, quiet=True, unsafe=True)
parser.set_defaults(handler=handlers.Setup, lock=None, quiet=True, report=False, unsafe=True)
return parser
@ -932,6 +933,22 @@ def _set_service_shell_parser(root: SubParserAction) -> argparse.ArgumentParser:
return parser
def _set_service_tree_migrate_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for tree migration subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("service-tree-migrate", help="migrate repository tree",
description="migrate repository tree between versions", formatter_class=_formatter)
parser.set_defaults(handler=handlers.TreeMigrate, lock=None, quiet=True, report=False)
return parser
def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for create user subcommand
@ -954,8 +971,8 @@ def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
"which is in particular must be used for OAuth2 authorization type.")
parser.add_argument("-R", "--role", help="user access level",
type=UserAccess, choices=enum_values(UserAccess), default=UserAccess.Read)
parser.set_defaults(handler=handlers.Users, action=Action.Update, architecture=[""], lock=None, report=False,
repository=[""], quiet=True)
parser.set_defaults(handler=handlers.Users, action=Action.Update, architecture=[""], lock=None, quiet=True,
report=False, repository=[""])
return parser
@ -975,8 +992,8 @@ def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("username", help="filter users by username", nargs="?")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("-R", "--role", help="filter users by role", type=UserAccess, choices=enum_values(UserAccess))
parser.set_defaults(handler=handlers.Users, action=Action.List, architecture=[""], lock=None, report=False,
repository=[""], quiet=True, unsafe=True)
parser.set_defaults(handler=handlers.Users, action=Action.List, architecture=[""], lock=None, quiet=True,
report=False, repository=[""], unsafe=True)
return parser
@ -994,8 +1011,8 @@ def _set_user_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
description="remove user from the user mapping and update the configuration",
formatter_class=_formatter)
parser.add_argument("username", help="username for web service")
parser.set_defaults(handler=handlers.Users, action=Action.Remove, architecture=[""], lock=None, report=False,
repository=[""], quiet=True)
parser.set_defaults(handler=handlers.Users, action=Action.Remove, architecture=[""], lock=None, quiet=True,
report=False, repository=[""])
return parser

View File

@ -39,6 +39,7 @@ from ahriman.application.handlers.sign import Sign
from ahriman.application.handlers.status import Status
from ahriman.application.handlers.status_update import StatusUpdate
from ahriman.application.handlers.structure import Structure
from ahriman.application.handlers.tree_migrate import TreeMigrate
from ahriman.application.handlers.triggers import Triggers
from ahriman.application.handlers.unsafe_commands import UnsafeCommands
from ahriman.application.handlers.update import Update

View File

@ -25,7 +25,7 @@ from multiprocessing import Pool
from ahriman.application.lock import Lock
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import ExitCode, MissingArchitectureError, MultipleArchitecturesError
from ahriman.core.log import Log
from ahriman.core.log.log_loader import LogLoader
from ahriman.models.repository_id import RepositoryId
from ahriman.models.repository_paths import RepositoryPaths
@ -63,8 +63,8 @@ class Handler:
try:
configuration = Configuration.from_path(args.configuration, repository_id)
log_handler = Log.handler(args.log_handler)
Log.load(configuration, log_handler, quiet=args.quiet, report=args.report)
log_handler = LogLoader.handler(args.log_handler)
LogLoader.load(configuration, log_handler, quiet=args.quiet, report=args.report)
with Lock(args, repository_id, configuration):
cls.run(args, repository_id, configuration, report=args.report)

View File

@ -0,0 +1,68 @@
#
# Copyright (c) 2021-2023 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import argparse
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.models.repository_id import RepositoryId
from ahriman.models.repository_paths import RepositoryPaths
class TreeMigrate(Handler):
"""
tree migration handler
"""
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
"""
current_tree = configuration.repository_paths
target_tree = RepositoryPaths(current_tree.root, current_tree.repository_id, _force_current_tree=True)
# create repository tree
target_tree.tree_create()
# perform migration
TreeMigrate.tree_move(current_tree, target_tree)
@staticmethod
def tree_move(from_tree: RepositoryPaths, to_tree: RepositoryPaths) -> None:
"""
move files between trees. Trees must be created in advance
Args:
from_tree(RepositoryPaths): old repository tree
to_tree(RepositoryPaths): new repository tree
"""
# we don't care about devtools chroot
for attribute in (
RepositoryPaths.packages,
RepositoryPaths.pacman,
RepositoryPaths.repository,
):
attribute.fget(from_tree).rename(attribute.fget(to_tree)) # type: ignore[attr-defined]

View File

@ -115,6 +115,7 @@ class Pacman(LazyLogging):
dst = repository_database(pacman_db_path)
if dst.is_file():
return # file already exists, do not copy
dst.parent.mkdir(mode=0o755, exist_ok=True) # create sync directory if it doesn't exist
src = repository_database(pacman_root)
if not src.is_file():
self.logger.warning("repository %s is set to be used, however, no working copy was found", database.name)

View File

@ -18,4 +18,3 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.log.lazy_logging import LazyLogging
from ahriman.core.log.log import Log

View File

@ -27,7 +27,7 @@ from ahriman.core.log.http_log_handler import HttpLogHandler
from ahriman.models.log_handler import LogHandler
class Log:
class LogLoader:
"""
simple static method class which setups application loggers
@ -63,7 +63,7 @@ class Log:
del JournalHandler
return LogHandler.Journald # journald import was found
except ImportError:
if Log.DEFAULT_SYSLOG_DEVICE.exists():
if LogLoader.DEFAULT_SYSLOG_DEVICE.exists():
return LogHandler.Syslog
return LogHandler.Console
@ -94,7 +94,7 @@ class Log:
fileConfig(log_configuration, disable_existing_loggers=True)
logging.debug("using %s logger", default_handler)
except Exception:
logging.basicConfig(filename=None, format=Log.DEFAULT_LOG_FORMAT, level=Log.DEFAULT_LOG_LEVEL)
logging.basicConfig(filename=None, format=LogLoader.DEFAULT_LOG_FORMAT, level=LogLoader.DEFAULT_LOG_LEVEL)
logging.exception("could not load logging from configuration, fallback to stderr")
HttpLogHandler.load(configuration, report=report)

View File

@ -34,6 +34,16 @@ class RepositoryId:
architecture: str
name: str
@property
def is_empty(self) -> bool:
"""
check if all data is supplied for the loading
Returns:
bool: True in case if architecture or name are not set and False otherwise
"""
return not self.architecture or not self.name
def __lt__(self, other: Any) -> bool:
"""
comparison operator for sorting

View File

@ -21,16 +21,17 @@ import os
import shutil
from collections.abc import Generator
from dataclasses import dataclass
from dataclasses import dataclass, field
from functools import cached_property
from pathlib import Path
from ahriman.core.exceptions import PathError
from ahriman.core.log import LazyLogging
from ahriman.models.repository_id import RepositoryId
@dataclass(frozen=True)
class RepositoryPaths:
class RepositoryPaths(LazyLogging):
"""
repository paths holder. For the most operations with paths you want to use this object
@ -55,6 +56,7 @@ class RepositoryPaths:
root: Path
repository_id: RepositoryId
_force_current_tree: bool = field(default=False, kw_only=True)
@property
def _repository_root(self) -> Path:
@ -75,8 +77,10 @@ class RepositoryPaths:
Path: relative path which contains only architecture segment in case if legacy tree is used and repository
name and architecture otherwise
"""
if (self._repository_root / self.repository_id.architecture).is_dir():
return Path(self.repository_id.architecture)
if not self._force_current_tree:
if (self._repository_root / self.repository_id.architecture).is_dir():
self.logger.warning("using legacy per architecture tree")
return Path(self.repository_id.architecture) # legacy tree suffix
return Path(self.repository_id.name) / self.repository_id.architecture
@property
@ -257,11 +261,13 @@ class RepositoryPaths:
"""
create ahriman working tree
"""
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.cache,
self.chroot,
self.packages,
self.pacman / "sync", # we need sync directory in order to be able to copy databases
self.pacman,
self.repository,
):
directory.mkdir(mode=0o755, parents=True, exist_ok=True)

View File

@ -21,8 +21,8 @@ def test_call(args: argparse.Namespace, configuration: Configuration, mocker: Mo
args.report = False
mocker.patch("ahriman.application.handlers.Handler.run")
configuration_mock = mocker.patch("ahriman.core.configuration.Configuration.from_path", return_value=configuration)
log_handler_mock = mocker.patch("ahriman.core.log.Log.handler", return_value=args.log_handler)
log_load_mock = mocker.patch("ahriman.core.log.Log.load")
log_handler_mock = mocker.patch("ahriman.core.log.log_loader.LogLoader.handler", return_value=args.log_handler)
log_load_mock = mocker.patch("ahriman.core.log.log_loader.LogLoader.load")
enter_mock = mocker.patch("ahriman.application.lock.Lock.__enter__")
exit_mock = mocker.patch("ahriman.application.lock.Lock.__exit__")

View File

@ -0,0 +1,42 @@
import argparse
from pathlib import Path
from pytest_mock import MockerFixture
from unittest.mock import call as MockCall
from ahriman.application.handlers import TreeMigrate
from ahriman.core.configuration import Configuration
from ahriman.models.repository_id import RepositoryId
from ahriman.models.repository_paths import RepositoryPaths
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
tree_create_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
application_mock = mocker.patch("ahriman.application.handlers.TreeMigrate.tree_move")
_, repository_id = configuration.check_loaded()
old_paths = configuration.repository_paths
new_paths = RepositoryPaths(old_paths.root, old_paths.repository_id, _force_current_tree=True)
TreeMigrate.run(args, repository_id, configuration, report=False)
tree_create_mock.assert_called_once_with()
application_mock.assert_called_once_with(old_paths, new_paths)
def test_move_tree(mocker: MockerFixture) -> None:
"""
must move tree
"""
rename_mock = mocker.patch("pathlib.Path.rename", autospec=True)
root = Path("local")
from_paths = RepositoryPaths(root, RepositoryId("arch", ""))
to_paths = RepositoryPaths(root, RepositoryId("arch", "repo"))
TreeMigrate.tree_move(from_paths, to_paths)
rename_mock.assert_has_calls([
MockCall(from_paths.packages, to_paths.packages),
MockCall(from_paths.pacman, to_paths.pacman),
MockCall(from_paths.repository, to_paths.repository),
])

View File

@ -93,14 +93,14 @@ def test_parser_option_repository_multiple(parser: argparse.ArgumentParser) -> N
def test_subparsers_aur_search(parser: argparse.ArgumentParser) -> None:
"""
aur-search command must imply architecture list, lock, report, repository, quiet and unsafe
aur-search command must imply architecture list, lock, quiet, report, repository and unsafe
"""
args = parser.parse_args(["aur-search", "ahriman"])
assert args.architecture == [""]
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == [""]
assert args.quiet
assert args.unsafe
@ -122,14 +122,14 @@ def test_subparsers_aur_search_option_repository(parser: argparse.ArgumentParser
def test_subparsers_help(parser: argparse.ArgumentParser) -> None:
"""
help command must imply architecture list, lock, report, repository, quiet, unsafe and parser
help command must imply architecture list, lock, quiet, report, repository, unsafe and parser
"""
args = parser.parse_args(["help"])
assert args.architecture == [""]
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == [""]
assert args.quiet
assert args.unsafe
assert args.parser is not None and args.parser()
@ -152,14 +152,14 @@ def test_subparsers_help_option_repository(parser: argparse.ArgumentParser) -> N
def test_subparsers_help_commands_unsafe(parser: argparse.ArgumentParser) -> None:
"""
help-commands-unsafe command must imply architecture list, lock, report, repository, quiet, unsafe and parser
help-commands-unsafe command must imply architecture list, lock, quiet, report, repository, unsafe and parser
"""
args = parser.parse_args(["help-commands-unsafe"])
assert args.architecture == [""]
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == [""]
assert args.quiet
assert args.unsafe
assert args.parser is not None and args.parser()
@ -182,14 +182,14 @@ def test_subparsers_help_commands_unsafe_option_repository(parser: argparse.Argu
def test_subparsers_help_updates(parser: argparse.ArgumentParser) -> None:
"""
help-updates command must imply architecture list, lock, report, repository, quiet and unsafe
help-updates command must imply architecture list, lock, quiet, report, repository, and unsafe
"""
args = parser.parse_args(["help-updates"])
assert args.architecture == [""]
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == [""]
assert args.quiet
assert args.unsafe
@ -211,14 +211,14 @@ def test_subparsers_help_updates_option_repository(parser: argparse.ArgumentPars
def test_subparsers_help_version(parser: argparse.ArgumentParser) -> None:
"""
help-version command must imply architecture, lock, report, repository, quiet and unsafe
help-version command must imply architecture, lock, quiet, report, repository and unsafe
"""
args = parser.parse_args(["help-version"])
assert args.architecture == [""]
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == [""]
assert args.quiet
assert args.unsafe
@ -292,42 +292,42 @@ def test_subparsers_package_remove_option_repository(parser: argparse.ArgumentPa
def test_subparsers_package_status(parser: argparse.ArgumentParser) -> None:
"""
package-status command must imply lock, report, quiet and unsafe
package-status command must imply lock, quiet, report and unsafe
"""
args = parser.parse_args(["-a", "x86_64", "-r", "repo", "package-status"])
assert args.architecture == ["x86_64"]
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == ["repo"]
assert args.quiet
assert args.unsafe
def test_subparsers_package_status_remove(parser: argparse.ArgumentParser) -> None:
"""
package-status-remove command must imply action, lock, report, quiet and unsafe
package-status-remove command must imply action, lock, quiet, report and unsafe
"""
args = parser.parse_args(["-a", "x86_64", "-r", "repo", "package-status-remove", "ahriman"])
assert args.architecture == ["x86_64"]
assert args.action == Action.Remove
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == ["repo"]
assert args.quiet
assert args.unsafe
def test_subparsers_package_status_update(parser: argparse.ArgumentParser) -> None:
"""
package-status-update command must imply action, lock, report, quiet and unsafe
package-status-update command must imply action, lock, quiet, report and unsafe
"""
args = parser.parse_args(["-a", "x86_64", "-r", "repo", "package-status-update"])
assert args.architecture == ["x86_64"]
assert args.action == Action.Update
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == ["repo"]
assert args.quiet
assert args.unsafe
@ -805,16 +805,16 @@ def test_subparsers_repo_sign_option_repository(parser: argparse.ArgumentParser)
def test_subparsers_repo_status_update(parser: argparse.ArgumentParser) -> None:
"""
re[p-status-update command must imply action, lock, report, package, quiet and unsafe
re[p-status-update command must imply action, lock, quiet, report, package and unsafe
"""
args = parser.parse_args(["-a", "x86_64", "-r", "repo", "package-status-update"])
assert args.architecture == ["x86_64"]
assert args.action == Action.Update
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == ["repo"]
assert not args.package
assert args.quiet
assert args.unsafe
@ -858,12 +858,12 @@ def test_subparsers_repo_sync_option_repository(parser: argparse.ArgumentParser)
def test_subparsers_repo_tree(parser: argparse.ArgumentParser) -> None:
"""
repo-tree command must imply lock, report, quiet and unsafe
repo-tree command must imply lock, quiet, report and unsafe
"""
args = parser.parse_args(["repo-tree"])
assert args.lock is None
assert not args.report
assert args.quiet
assert not args.report
assert args.unsafe
@ -980,27 +980,27 @@ def test_subparsers_service_clean_option_repository(parser: argparse.ArgumentPar
def test_subparsers_service_config(parser: argparse.ArgumentParser) -> None:
"""
service-config command must imply lock, report, quiet and unsafe
service-config command must imply lock, quiet, report and unsafe
"""
args = parser.parse_args(["-a", "x86_64", "-r", "repo", "service-config"])
assert args.architecture == ["x86_64"]
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == ["repo"]
assert args.quiet
assert args.unsafe
def test_subparsers_service_config_validate(parser: argparse.ArgumentParser) -> None:
"""
service-config-validate command must imply lock, report, quiet and unsafe
service-config-validate command must imply lock, quiet, report and unsafe
"""
args = parser.parse_args(["-a", "x86_64", "-r", "repo", "service-config-validate"])
assert args.architecture == ["x86_64"]
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == ["repo"]
assert args.quiet
assert args.unsafe
@ -1033,14 +1033,14 @@ def test_subparsers_service_key_import_option_repository(parser: argparse.Argume
def test_subparsers_service_setup(parser: argparse.ArgumentParser) -> None:
"""
service-setup command must imply lock, report, quiet and unsafe
service-setup command must imply lock, quiet, report and unsafe
"""
args = parser.parse_args(["-a", "x86_64", "-r", "repo", "service-setup", "--packager", "John Doe <john@doe.com>"])
assert args.architecture == ["x86_64"]
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == ["repo"]
assert args.quiet
assert args.unsafe
@ -1091,17 +1091,27 @@ def test_subparsers_service_shell(parser: argparse.ArgumentParser) -> None:
assert not args.report
def test_subparsers_service_tree_migrate(parser: argparse.ArgumentParser) -> None:
"""
service-tree-migrate command must imply lock, quiet and report
"""
args = parser.parse_args(["service-tree-migrate"])
assert args.lock is None
assert args.quiet
assert not args.report
def test_subparsers_user_add(parser: argparse.ArgumentParser) -> None:
"""
user-add command must imply action, architecture, lock, report, repository and quiet
user-add command must imply action, architecture, lock, quiet, report and repository
"""
args = parser.parse_args(["user-add", "username"])
assert args.action == Action.Update
assert args.architecture == [""]
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == [""]
assert args.quiet
def test_subparsers_user_add_option_architecture(parser: argparse.ArgumentParser) -> None:
@ -1132,15 +1142,15 @@ def test_subparsers_user_add_option_role(parser: argparse.ArgumentParser) -> Non
def test_subparsers_user_list(parser: argparse.ArgumentParser) -> None:
"""
user-list command must imply action, architecture, lock, report, repository, quiet and unsafe
user-list command must imply action, architecture, lock, quiet, report, repository and unsafe
"""
args = parser.parse_args(["user-list"])
assert args.action == Action.List
assert args.architecture == [""]
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == [""]
assert args.quiet
assert args.unsafe
@ -1170,15 +1180,15 @@ def test_subparsers_user_list_option_role(parser: argparse.ArgumentParser) -> No
def test_subparsers_user_remove(parser: argparse.ArgumentParser) -> None:
"""
user-remove command must imply action, architecture, lock, report, repository and quiet
user-remove command must imply action, architecture, lock, quiet, report and repository
"""
args = parser.parse_args(["user-remove", "username"])
assert args.action == Action.Remove
assert args.architecture == [""]
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == [""]
assert args.quiet
def test_subparsers_user_remove_option_architecture(parser: argparse.ArgumentParser) -> None:

View File

@ -58,10 +58,12 @@ def test_database_copy(pacman: Pacman, repository_paths: RepositoryPaths, mocker
mocker.patch("pathlib.Path.is_dir", return_value=True)
# root database exists, local database does not
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: p.is_relative_to(path))
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
copy_mock = mocker.patch("shutil.copy")
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
pacman.database_copy(pacman.handle, database, path, repository_paths, use_ahriman_cache=True)
mkdir_mock.assert_called_once_with(mode=0o755, exist_ok=True)
copy_mock.assert_called_once_with(path / "sync" / "core.db", dst_path)
chown_mock.assert_called_once_with(dst_path)

View File

@ -7,7 +7,7 @@ from pytest_mock import MockerFixture
from systemd.journal import JournalHandler
from ahriman.core.configuration import Configuration
from ahriman.core.log import Log
from ahriman.core.log.log_loader import LogLoader
from ahriman.models.log_handler import LogHandler
@ -15,14 +15,14 @@ def test_handler() -> None:
"""
must extract journald handler if available
"""
assert Log.handler(None) == LogHandler.Journald
assert LogLoader.handler(None) == LogHandler.Journald
def test_handler_selected() -> None:
"""
must return selected log handler
"""
assert Log.handler(LogHandler.Console) == LogHandler.Console
assert LogLoader.handler(LogHandler.Console) == LogHandler.Console
def test_handler_syslog(mocker: MockerFixture) -> None:
@ -31,7 +31,7 @@ def test_handler_syslog(mocker: MockerFixture) -> None:
"""
mocker.patch("pathlib.Path.exists", return_value=True)
mocker.patch.dict(sys.modules, {"systemd.journal": None})
assert Log.handler(None) == LogHandler.Syslog
assert LogLoader.handler(None) == LogHandler.Syslog
def test_handler_console(mocker: MockerFixture) -> None:
@ -40,17 +40,17 @@ def test_handler_console(mocker: MockerFixture) -> None:
"""
mocker.patch("pathlib.Path.exists", return_value=False)
mocker.patch.dict(sys.modules, {"systemd.journal": None})
assert Log.handler(None) == LogHandler.Console
assert LogLoader.handler(None) == LogHandler.Console
def test_load(configuration: Configuration, mocker: MockerFixture) -> None:
"""
must load logging
"""
logging_mock = mocker.patch("ahriman.core.log.log.fileConfig", side_effect=fileConfig)
logging_mock = mocker.patch("ahriman.core.log.log_loader.fileConfig", side_effect=fileConfig)
http_log_mock = mocker.patch("ahriman.core.log.http_log_handler.HttpLogHandler.load")
Log.load(configuration, LogHandler.Journald, quiet=False, report=False)
LogLoader.load(configuration, LogHandler.Journald, quiet=False, report=False)
logging_mock.assert_called_once_with(pytest.helpers.anyvar(int), disable_existing_loggers=True)
http_log_mock.assert_called_once_with(configuration, report=False)
assert all(isinstance(handler, JournalHandler) for handler in logging.getLogger().handlers)
@ -60,8 +60,8 @@ def test_load_fallback(configuration: Configuration, mocker: MockerFixture) -> N
"""
must fall back to stderr without errors
"""
mocker.patch("ahriman.core.log.log.fileConfig", side_effect=PermissionError())
Log.load(configuration, LogHandler.Journald, quiet=False, report=False)
mocker.patch("ahriman.core.log.log_loader.fileConfig", side_effect=PermissionError())
LogLoader.load(configuration, LogHandler.Journald, quiet=False, report=False)
def test_load_quiet(configuration: Configuration, mocker: MockerFixture) -> None:
@ -69,5 +69,5 @@ def test_load_quiet(configuration: Configuration, mocker: MockerFixture) -> None
must disable logging in case if quiet flag set
"""
disable_mock = mocker.patch("logging.disable")
Log.load(configuration, LogHandler.Journald, quiet=True, report=False)
LogLoader.load(configuration, LogHandler.Journald, quiet=True, report=False)
disable_mock.assert_called_once_with(logging.WARNING)

View File

@ -3,6 +3,16 @@ import pytest
from ahriman.models.repository_id import RepositoryId
def test_is_empty() -> None:
"""
must check if repository id is empty or not
"""
assert RepositoryId("", "").is_empty
assert RepositoryId("arch", "").is_empty
assert RepositoryId("", "repo").is_empty
assert not RepositoryId("arch", "repo").is_empty
def test_lt() -> None:
"""
must correctly compare instances

View File

@ -31,12 +31,24 @@ def test_suffix(repository_id: RepositoryId, mocker: MockerFixture) -> None:
"""
must correctly define suffix
"""
is_dir_mock = mocker.patch("pathlib.Path.is_dir", autospec=True, return_value=True)
assert RepositoryPaths(Path("root"), repository_id)._suffix == Path(repository_id.architecture)
is_dir_mock.assert_called_once_with(Path("root") / "repository" / repository_id.architecture)
is_dir_mock = mocker.patch("pathlib.Path.is_dir", autospec=True)
mocker.patch("pathlib.Path.is_dir", return_value=False)
assert RepositoryPaths(Path("root"), repository_id)._suffix == Path(repository_id.name) / repository_id.architecture
is_dir_mock.return_value = True
instance = RepositoryPaths(Path("root"), repository_id)
assert instance._suffix == Path(repository_id.architecture)
is_dir_mock.return_value = False
instance = RepositoryPaths(Path("root"), repository_id)
assert instance._suffix == Path(repository_id.name) / repository_id.architecture
is_dir_mock.return_value = True
instance = RepositoryPaths(Path("root"), repository_id, _force_current_tree=True)
assert instance._suffix == Path(repository_id.name) / repository_id.architecture
is_dir_mock.assert_has_calls([
MockCall(Path("root") / "repository" / repository_id.architecture),
MockCall(Path("root") / "repository" / repository_id.architecture),
])
def test_root_owner(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
@ -270,20 +282,29 @@ def test_tree_create(repository_paths: RepositoryPaths, mocker: MockerFixture) -
prop
for prop in dir(repository_paths)
if not prop.startswith("_")
and not prop.endswith("_for")
and prop not in ("chown",
"known_architectures",
"known_repositories",
"owner",
and not callable(getattr(repository_paths, prop))
and prop not in ("logger_name",
"logger",
"repository_id",
"root",
"root_owner",
"tree_clear",
"tree_create")
"root_owner")
}
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
print(paths)
repository_paths.tree_create()
mkdir_mock.assert_has_calls([MockCall(mode=0o755, parents=True, exist_ok=True) for _ in paths], any_order=True)
chown_mock.assert_has_calls([MockCall(pytest.helpers.anyvar(int)) for _ in paths], any_order=True)
def test_tree_create_skip(mocker: MockerFixture) -> None:
"""
must skip tree creation if repository id is not set
"""
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
repository_paths = RepositoryPaths(Path("local"), RepositoryId("", ""))
repository_paths.tree_create()
mkdir_mock.assert_not_called()