diff --git a/.github/workflows/setup.sh b/.github/workflows/setup.sh index 0d3cc807..1d16d87f 100755 --- a/.github/workflows/setup.sh +++ b/.github/workflows/setup.sh @@ -26,11 +26,16 @@ cp "docker/systemd-nspawn.sh" "/usr/local/bin/systemd-nspawn" # create fresh tarball tox -e archive # run makepkg -mv dist/ahriman-*.tar.gz package/archlinux +PKGVER=$(python -c "from src.ahriman import __version__; print(__version__)") +mv "dist/ahriman-$PKGVER.tar.gz" package/archlinux chmod +777 package/archlinux # because fuck you that's why cd package/archlinux sudo -u nobody -- makepkg -cf --skipchecksums --noconfirm -sudo -u nobody -- makepkg --packagelist | grep -v -- -debug- | pacman -U --noconfirm - +sudo -u nobody -- makepkg --packagelist | grep "ahriman-core-$PKGVER" | pacman -U --noconfirm --nodeps - +if [[ -z $MINIMAL_INSTALL ]]; then + sudo -u nobody -- makepkg --packagelist | grep "ahriman-triggers-$PKGVER" | pacman -U --noconfirm --nodeps - + sudo -u nobody -- makepkg --packagelist | grep "ahriman-web-$PKGVER" | pacman -U --noconfirm --nodeps - +fi # create machine-id which is required by build tools systemd-machine-id-setup @@ -41,12 +46,12 @@ pacman -Qdtq | pacman -Rscn --noconfirm - [[ -z $MINIMAL_INSTALL ]] && WEB_ARGS=("--web-port" "8080") ahriman -a x86_64 -r "github" service-setup --packager "ahriman bot " "${WEB_ARGS[@]}" # enable services -systemctl enable ahriman-web systemctl enable ahriman@x86_64-github.timer if [[ -z $MINIMAL_INSTALL ]]; then # validate configuration ahriman service-config-validate --exit-code # run web service (detached) + systemctl enable ahriman-web sudo -u ahriman -- ahriman web & WEB_PID=$! fi diff --git a/Dockerfile b/Dockerfile index 61fdc8ec..312eaddc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -79,7 +79,8 @@ RUN cd "/home/build/ahriman" && \ tox -e archive && \ cp ./dist/*.tar.gz "package/archlinux" && \ cd "package/archlinux" && \ - runuser -u build -- makepkg --noconfirm --install --skipchecksums && \ + runuser -u build -- makepkg --noconfirm --skipchecksums && \ + runuser -u build -- makepkg --packagelist | grep -v -- -debug- | pacman -U --noconfirm --nodeps - && \ cd / && rm -r "/home/build/ahriman" # cleanup unused diff --git a/docs/advanced-usage/handlers.rst b/docs/advanced-usage/handlers.rst new file mode 100644 index 00000000..17a53f84 --- /dev/null +++ b/docs/advanced-usage/handlers.rst @@ -0,0 +1,51 @@ +Writing own handler +=================== + +It is possible to extend the application by adding own custom commands. To do so it is required to implement class, which derives from ``ahriman.application.handlers.handler.Handler`` and put it to the ``ahriman.application.handlers`` package. The class later will be loaded automatically and included to each command run. + +Let's imagine, that the new class implements ``help-web``, which prints server information to the stdout. To do so, we need to implement base ``ahriman.application.handlers.handler.Handler.run`` method which is entry point for all subcommands: + +.. code-block:: python + + from ahriman.application.application import Application + from ahriman.application.handlers.handler import Handler + + + class HelpWeb(Handler): + + @classmethod + def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *, + report: bool) -> None: + # load application instance + # report is set to True to make sure that web client is loaded + application = Application(repository_id, configuration, report=True) + # extract web client + client = application.repository.reporter + + # send request to the server + response = client.make_request("GET", f"{client.address}/api/v1/info") + result = response.json() + print(result) + +The main functionality of the class is already described, but command is still not available yet. To do so, it is required to set ``arguments`` property, which is list of functions, which takes argument parser object, creates new subcommand and returns the modified parser, e.g.: + +.. code-block:: python + + import argparse + + from ahriman.application.handlers.handler import SubParserAction + + ... + + @staticmethod + def set_parser(root: SubParserAction) -> argparse.ArgumentParser: + parser = root.add_parser("help-web", help="get web server status", + description="request server info and print it to stdout") + + arguments = set_parser + +In addition, ``ahriman.application.handlers.handler.Handler.ALLOW_MULTI_ARCHITECTURE_RUN`` can be set to ``False`` in order to disable multiprocess run (e.g. in case if there are conflicting operations, like writting to stdout). + +Save the file above as ``/usr/lib/python3.12/site-packages/ahriman/application/handlers/help_web.py`` (replace ``python3.12`` with actual python version) and you are set. + +For more examples and details, please check builtin handlers and classes documentations. diff --git a/docs/advanced-usage.rst b/docs/advanced-usage/index.rst similarity index 96% rename from docs/advanced-usage.rst rename to docs/advanced-usage/index.rst index 3956c8c5..161c050f 100644 --- a/docs/advanced-usage.rst +++ b/docs/advanced-usage/index.rst @@ -1,6 +1,12 @@ Advanced usage ============== +.. toctree:: + :maxdepth: 2 + + handlers + views + Depending on the goal the package can be used in different ways. Nevertheless, in the most cases you will need some basic classes .. code-block:: python diff --git a/docs/advanced-usage/views.rst b/docs/advanced-usage/views.rst new file mode 100644 index 00000000..2334f5bc --- /dev/null +++ b/docs/advanced-usage/views.rst @@ -0,0 +1,41 @@ +Writing own API endpoint +======================== + +The web service loads views dynamically, thus it is possible to add custom API endpoint or even web page. The views must be derived from ``ahriman.web.views.base.BaseView`` and implements HTTP methods. The API specification will be also loaded (if available), but optional. The implementation must be saved into the ``ahriman.web.views`` package + +Let's consider example for API endpoint which always returns 204 with no response: + +.. code-block:: python + + from aiohttp.web import Response, HTTPNoContent + + from ahriman.web.views.base import BaseView + + class PingView(BaseView): + + async def get(self) -> Response: + # do nothing, just raise 204 response + # check public methods of the BaseView class for all available controls + raise HTTPNoContent + +The ``get()`` method can be decorated by ``aiohttp_apispec`` methods, but we will leave it for self-study. Consider checking examples of usages in the main package. + +In order to view to be set correctly to routes list, few more options are required to be set. First of all, it is required to specify ``ROUTES`` (list of strings), which contains list of all available routes, e.g.: + +.. code-block:: python + + ... + + ROUTES = ["/api/v1/ping"] + +In addition, it is also recommended to specify permission level for using this endpoint. Since this endpoint neither does anything nor returns sensitive information, it can be set to ``UserAccess.Unauthorized``: + +.. code-block:: python + + ... + + GET_PERMISSION = UserAccess.Unauthorized + +That's all. Just save the file as ``/usr/lib/python3.12/site-packages/ahriman/web/views/ping.py`` (replace ``python3.12`` with actual python version) and restart web server. + +For more examples and details, please check builtin handlers and classes documentations. diff --git a/docs/ahriman.application.handlers.rst b/docs/ahriman.application.handlers.rst index c754110c..8debf454 100644 --- a/docs/ahriman.application.handlers.rst +++ b/docs/ahriman.application.handlers.rst @@ -228,6 +228,14 @@ ahriman.application.handlers.triggers module :no-undoc-members: :show-inheritance: +ahriman.application.handlers.triggers\_support module +----------------------------------------------------- + +.. automodule:: ahriman.application.handlers.triggers_support + :members: + :no-undoc-members: + :show-inheritance: + ahriman.application.handlers.unsafe\_commands module ---------------------------------------------------- diff --git a/docs/ahriman.core.rst b/docs/ahriman.core.rst index 36551728..b69f9d62 100644 --- a/docs/ahriman.core.rst +++ b/docs/ahriman.core.rst @@ -36,6 +36,14 @@ ahriman.core.exceptions module :no-undoc-members: :show-inheritance: +ahriman.core.module\_loader module +---------------------------------- + +.. automodule:: ahriman.core.module_loader + :members: + :no-undoc-members: + :show-inheritance: + ahriman.core.spawn module ------------------------- diff --git a/docs/architecture.rst b/docs/architecture.rst index 780d10ef..bb2762c5 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -18,7 +18,7 @@ Full dependency diagram: ``ahriman.application`` package ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This package contains application (aka executable) related classes and everything for it. It also contains package called ``ahriman.application.handlers`` in which all available subcommands are described as separated classes derived from the base ``ahriman.application.handlers.Handler`` class. +This package contains application (aka executable) related classes and everything for it. It also contains package called ``ahriman.application.handlers`` in which all available subcommands are described as separated classes derived from the base ``ahriman.application.handlers.handler.Handler`` class. ``ahriman.application.application.Application`` (god class) is used for any interaction from parsers with repository. It is divided into multiple traits by functions (package related and repository related) in the same package. @@ -52,8 +52,10 @@ This package contains everything required for the most of application actions an This package also provides some generic functions and classes which may be used by other packages: * ``ahriman.core.exceptions`` provides custom exceptions. +* ``ahriman.core.module_loader`` provides ``implementations`` method which can be used for dynamic classes load. In particular, this method is used for web views and application handlers loading. * ``ahriman.core.spawn.Spawn`` is a tool which can spawn another ``ahriman`` process. This feature is used by web application. * ``ahriman.core.tree`` is a dependency tree implementation. +* ``ahriman.core.types`` are an additional global types for mypy checks. ``ahriman.models`` package ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -436,6 +438,9 @@ REST API supports only JSON data. Different APIs are separated into different packages: * ``ahriman.web.views.api`` not a real API, but some views which provide OpenAPI support. +* ``ahriman.web.views.*.auditlog`` provides event log API. +* ``ahriman.web.views.*.distributed`` is an API for builders interaction for multi-node setup. +* ``ahriman.web.views.*.pacakges`` contains views which provide information about existing packages. * ``ahriman.web.views.*.service`` provides views for application controls. * ``ahriman.web.views.*.status`` package provides REST API for application reporting. * ``ahriman.web.views.*.user`` package provides login and logout methods which can be called without authorization. diff --git a/docs/faq/distributed.rst b/docs/faq/distributed.rst index 710669ce..95dbad70 100644 --- a/docs/faq/distributed.rst +++ b/docs/faq/distributed.rst @@ -311,6 +311,12 @@ Automatic worker nodes discovery Instead of setting ``${build:workers}`` option explicitly it is also possible to configure services to load worker list dynamically. To do so, the ``ahriman.core.distributed.WorkerLoaderTrigger`` and ``ahriman.core.distributed.WorkerTrigger`` must be used for **master** and **worker** nodes respectively. See recipes for more details. +Those triggers have to be installed as a separate package: + +.. code-block:: shell + + yay -S ahriman-triggers + Known limitations """"""""""""""""" diff --git a/docs/faq/maintenance-packages.rst b/docs/faq/maintenance-packages.rst index 01259fb4..56024731 100644 --- a/docs/faq/maintenance-packages.rst +++ b/docs/faq/maintenance-packages.rst @@ -1,6 +1,12 @@ Maintenance packages -------------------- +Those features require extensions package to be installed before, e.g.: + +.. code-block:: shell + + yay -S ahriman-triggers + Generate keyring package ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/faq/web.rst b/docs/faq/web.rst index eb385db2..d1c08be7 100644 --- a/docs/faq/web.rst +++ b/docs/faq/web.rst @@ -5,11 +5,11 @@ How to setup web service ^^^^^^^^^^^^^^^^^^^^^^^^ #. - Install dependencies: + Install web service: .. code-block:: shell - yay -S --asdeps python-aiohttp python-aiohttp-jinja2 python-aiohttp-apispec>=3.0.0 python-aiohttp-cors + yay -S -ahriman-web #. Configure service: diff --git a/docs/index.rst b/docs/index.rst index 977647bd..c967cebc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -36,6 +36,6 @@ Contents faq/index migrations/index architecture - advanced-usage + advanced-usage/index triggers modules diff --git a/package/archlinux/PKGBUILD b/package/archlinux/PKGBUILD index 4e093d24..303a2479 100644 --- a/package/archlinux/PKGBUILD +++ b/package/archlinux/PKGBUILD @@ -1,51 +1,87 @@ # Maintainer: Evgeniy Alekseev -pkgname='ahriman' +pkgbase='ahriman' +pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web') pkgver=2.15.2 pkgrel=1 pkgdesc="ArcH linux ReposItory MANager" arch=('any') -url="https://github.com/arcan1s/ahriman" -license=('GPL3') +url="https://ahriman.readthedocs.io/" +license=('GPL-3.0-or-later') 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') -optdepends=('python-aioauth-client: web server with OAuth2 authorization' - 'python-aiohttp: web server' - 'python-aiohttp-apispec>=3.0.0: web server' - 'python-aiohttp-cors: web server' - 'python-aiohttp-jinja2: web server' - 'python-aiohttp-security: web server with authorization' - 'python-aiohttp-session: web server with authorization' - 'python-boto3: sync to s3' - 'python-cerberus: configuration validator' - 'python-cryptography: web server with authorization' - 'python-matplotlib: usage statistics chart' - 'python-requests-unixsocket2: client report to web server by unix socket' - 'python-jinja: html report generation' - 'python-systemd: journal support' - 'rsync: sync by using rsync') -source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgname-$pkgver.tar.gz" - 'ahriman.sysusers' - 'ahriman.tmpfiles') -install="$pkgname.install" -backup=('etc/ahriman.ini' - 'etc/ahriman.ini.d/logging.ini') +source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgbase-$pkgver.tar.gz" + "$pkgbase.sysusers" + "$pkgbase.tmpfiles") build() { - cd "$pkgname-$pkgver" + cd "$pkgbase-$pkgver" - python -m build --wheel --no-isolation + python -m build --wheel --no-isolation } -package() { - cd "$pkgname-$pkgver" - - python -m installer --destdir="$pkgdir" "dist/$pkgname-$pkgver-py3-none-any.whl" - - # keep usr/share configs as reference and copy them to /etc - install -Dm644 "$pkgdir/usr/share/$pkgname/settings/ahriman.ini" "$pkgdir/etc/ahriman.ini" - install -Dm644 "$pkgdir/usr/share/$pkgname/settings/ahriman.ini.d/logging.ini" "$pkgdir/etc/ahriman.ini.d/logging.ini" - - install -Dm644 "$srcdir/$pkgname.sysusers" "$pkgdir/usr/lib/sysusers.d/$pkgname.conf" - install -Dm644 "$srcdir/$pkgname.tmpfiles" "$pkgdir/usr/lib/tmpfiles.d/$pkgname.conf" +package_ahriman() { + pkgname='ahriman' + pkgdesc="ArcH linux ReposItory MANager (meta package)" + depends=("$pkgbase-core=$pkgver" "$pkgbase-triggers=$pkgver" "$pkgbase-web=$pkgver") +} + +package_ahriman-core() { + pkgname='ahriman-core' + optdepends=('ahriman-triggers: additional extensions for the application' + 'ahriman-web: web server' + 'python-boto3: sync to s3' + 'python-cerberus: configuration validator' + 'python-matplotlib: usage statistics chart' + 'python-requests-unixsocket2: client report to web server by unix socket' + 'python-jinja: html report generation' + 'python-systemd: journal support' + 'rsync: sync by using rsync') + install="$pkgbase.install" + backup=('etc/ahriman.ini' + 'etc/ahriman.ini.d/logging.ini') + + cd "$pkgbase-$pkgver" + + python -m installer --destdir="$pkgdir" "dist/$pkgbase-$pkgver-py3-none-any.whl" + python subpackages.py "$pkgdir" "$pkgname" + + # keep usr/share configs as reference and copy them to /etc + install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini" "$pkgdir/etc/ahriman.ini" + install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini.d/logging.ini" "$pkgdir/etc/ahriman.ini.d/logging.ini" + + install -Dm644 "$srcdir/$pkgbase.sysusers" "$pkgdir/usr/lib/sysusers.d/$pkgbase.conf" + install -Dm644 "$srcdir/$pkgbase.tmpfiles" "$pkgdir/usr/lib/tmpfiles.d/$pkgbase.conf" +} + +package_ahriman-triggers() { + pkgname='ahriman-triggers' + pkgdesc="ArcH linux ReposItory MANager, additional extensions" + depends=("$pkgbase-core=$pkgver") + backup=('etc/ahriman.ini.d/00-triggers.ini') + + cd "$pkgbase-$pkgver" + + python -m installer --destdir="$pkgdir" "dist/$pkgbase-$pkgver-py3-none-any.whl" + python subpackages.py "$pkgdir" "$pkgname" + + install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini.d/00-triggers.ini" "$pkgdir/etc/ahriman.ini.d/00-triggers.ini" +} + +package_ahriman-web() { + pkgname='ahriman-web' + pkgdesc="ArcH linux ReposItory MANager, web server" + depends=("$pkgbase-core=$pkgver" 'python-aiohttp-apispec>=3.0.0' 'python-aiohttp-cors' 'python-aiohttp-jinja2') + optdepends=('python-aioauth-client: OAuth2 authorization support' + 'python-aiohttp-security: authorization support' + 'python-aiohttp-session: authorization support' + 'python-cryptography: authorization support') + backup=('etc/ahriman.ini.d/00-web.ini') + + cd "$pkgbase-$pkgver" + + python -m installer --destdir="$pkgdir" "dist/$pkgbase-$pkgver-py3-none-any.whl" + python subpackages.py "$pkgdir" "$pkgname" + + install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini.d/00-web.ini" "$pkgdir/etc/ahriman.ini.d/00-web.ini" } diff --git a/package/archlinux/ahriman.install b/package/archlinux/ahriman.install index da9a0d70..09bfb517 100644 --- a/package/archlinux/ahriman.install +++ b/package/archlinux/ahriman.install @@ -2,6 +2,7 @@ post_upgrade() { local breakpoints=( 2.9.0-1 2.12.0-1 + 2.16.0-1 ) for v in "${breakpoints[@]}"; do diff --git a/package/share/ahriman/settings/ahriman.ini b/package/share/ahriman/settings/ahriman.ini index 0f543866..d7e2f328 100644 --- a/package/share/ahriman/settings/ahriman.ini +++ b/package/share/ahriman/settings/ahriman.ini @@ -23,32 +23,6 @@ sync_files_database = yes ; as additional option for some subcommands). If set to no, databases must be synchronized manually. use_ahriman_cache = yes -[auth] -; Authentication provider, must be one of disabled, configuration, pam, oauth. -target = disabled -; Allow read-only endpoint to be called without authentication. -allow_read_only = yes -; OAuth2 application client ID and secret. Required if oauth is used. -;client_id = -;client_secret = -; Cookie secret key to be used for cookies encryption. Must be valid 32 bytes URL-safe base64-encoded string. -; If not set, it will be generated automatically. -;cookie_secret_key = -; Name of the secondary group to be used as admin group in the service. Required if pam is used. -;full_access_group = wheel -; Authentication cookie expiration in seconds. -;max_age = 604800 -; OAuth2 provider icon for the web interface. -;oauth_icon = google -; OAuth2 provider class name, one of provided by aioauth-client. Required if oauth is used. -;oauth_provider = GoogleClient -; Scopes list for OAuth2 provider. Required if oauth is used. -;oauth_scopes = https://www.googleapis.com/auth/userinfo.email -; Allow login as root user (only applicable if PAM is used). -;permit_root_login = no -; Optional password salt. -;salt = - [build] ; List of additional flags passed to archbuild command. ;archbuild_flags = @@ -70,20 +44,12 @@ triggers[] = ahriman.core.report.ReportTrigger triggers[] = ahriman.core.upload.UploadTrigger triggers[] = ahriman.core.gitremote.RemotePushTrigger ; List of well-known triggers. Used only for configuration purposes. -triggers_known[] = ahriman.core.distributed.WorkerLoaderTrigger -triggers_known[] = ahriman.core.distributed.WorkerTrigger triggers_known[] = ahriman.core.gitremote.RemotePullTrigger triggers_known[] = ahriman.core.gitremote.RemotePushTrigger triggers_known[] = ahriman.core.report.ReportTrigger -triggers_known[] = ahriman.core.support.KeyringTrigger -triggers_known[] = ahriman.core.support.MirrorlistTrigger triggers_known[] = ahriman.core.upload.UploadTrigger ; Maximal age in seconds of the VCS packages before their version will be updated with its remote source. ;vcs_allowed_age = 604800 -; List of worker nodes addresses used for build process, e.g.: -; workers = http://10.0.0.1:8080 http://10.0.0.3:8080 -; Empty list means run on the local instance. -;workers = [repository] ; Application root. @@ -112,79 +78,6 @@ suppress_http_log_errors = yes ; Optional username for authentication (if enabled). ;username = -[web] -; External address of the web service. Will be used for some features like OAuth. If none set will be generated as -; address = http://${web:host}:${web:port} -;address = http://${web:host}:${web:port} -; Enable file upload endpoint used by some triggers. -;enable_archive_upload = no -; Address to bind the server. -host = 127.0.0.1 -; Full URL to the repository index page used by templates. -;index_url = -; Max file size in bytes which can be uploaded to the server. Requires ${web:enable_archive_upload} to be enabled. -;max_body_size = -; Port to listen. Must be set, if the web service is enabled. -;port = -; Disable status (e.g. package status, logs, etc) endpoints. Useful for build only modes. -;service_only = no -; Path to directory with static files. -static_path = ${templates}/static -; List of directories with templates. -templates[] = ${prefix}/share/ahriman/templates -; Path to unix socket. If none set, unix socket will be disabled. -;unix_socket = -; Allow unix socket to be world readable. -;unix_socket_unsafe = yes -; Maximum amount of time in seconds to be waited before lock will be free, used by spawned processes (0 is infinite). -;wait_timeout = - -[keyring] -; List of configuration section names for keyring generator plugin, e.g.: -; target = keyring-trigger -target = - -; Keyring generator trigger sample. -;[keyring-trigger] -; Generator type name. -;type = keyring-generator -; Optional keyring package description. -;description= -; Optional URL to the repository homepage. -;homepage= -; Keyring package licenses list. -;license = Unlicense -; Optional keyring package name. -;package = -; Optional packager PGP keys list. If none set, it will read from database. -;packagers = -; List of revoked PGP keys. -;revoked = -; List of master PGP keys. If none set, the sign.key value will be used. -;trusted = - -[mirrorlist] -; List of configuration section names for mirrorlist generator plugin, e.g.: -; target = mirrorlist-trigger -target = - -; Mirror list generator trigger sample. -;[mirrorlist-trigger] -; Generator type name. -;type = mirrorlist-generator -; Optional mirrorlist package description. -;description= -; Optional URL to the repository homepage. -;homepage= -; Mirrorlist package licenses list. -;license = Unlicense -; Optional mirrorlist package name. -;package = -; Absolute path to generated mirrorlist file, usually path inside /etc/pacman.d directory. -;path = -; List of repository mirrors. -;servers = - [remote-pull] ; List of configuration section names for git remote pull plugin, e.g.: ; target = remote-pull-trigger diff --git a/package/share/ahriman/settings/ahriman.ini.d/00-triggers.ini b/package/share/ahriman/settings/ahriman.ini.d/00-triggers.ini new file mode 100644 index 00000000..ae29f15c --- /dev/null +++ b/package/share/ahriman/settings/ahriman.ini.d/00-triggers.ini @@ -0,0 +1,56 @@ +[build] +; List of well-known triggers. Used only for configuration purposes. +triggers_known[] = ahriman.core.distributed.WorkerLoaderTrigger +triggers_known[] = ahriman.core.distributed.WorkerTrigger +triggers_known[] = ahriman.core.support.KeyringTrigger +triggers_known[] = ahriman.core.support.MirrorlistTrigger +; List of worker nodes addresses used for build process, e.g.: +; workers = http://10.0.0.1:8080 http://10.0.0.3:8080 +; Empty list means run on the local instance. +;workers = + +[keyring] +; List of configuration section names for keyring generator plugin, e.g.: +; target = keyring-trigger +target = + +; Keyring generator trigger sample. +;[keyring-trigger] +; Generator type name. +;type = keyring-generator +; Optional keyring package description. +;description= +; Optional URL to the repository homepage. +;homepage= +; Keyring package licenses list. +;license = Unlicense +; Optional keyring package name. +;package = +; Optional packager PGP keys list. If none set, it will read from database. +;packagers = +; List of revoked PGP keys. +;revoked = +; List of master PGP keys. If none set, the sign.key value will be used. +;trusted = + +[mirrorlist] +; List of configuration section names for mirrorlist generator plugin, e.g.: +; target = mirrorlist-trigger +target = + +; Mirror list generator trigger sample. +;[mirrorlist-trigger] +; Generator type name. +;type = mirrorlist-generator +; Optional mirrorlist package description. +;description= +; Optional URL to the repository homepage. +;homepage= +; Mirrorlist package licenses list. +;license = Unlicense +; Optional mirrorlist package name. +;package = +; Absolute path to generated mirrorlist file, usually path inside /etc/pacman.d directory. +;path = +; List of repository mirrors. +;servers = diff --git a/package/share/ahriman/settings/ahriman.ini.d/00-web.ini b/package/share/ahriman/settings/ahriman.ini.d/00-web.ini new file mode 100644 index 00000000..4279de26 --- /dev/null +++ b/package/share/ahriman/settings/ahriman.ini.d/00-web.ini @@ -0,0 +1,52 @@ +[auth] +; Authentication provider, must be one of disabled, configuration, pam, oauth. +target = disabled +; Allow read-only endpoint to be called without authentication. +allow_read_only = yes +; OAuth2 application client ID and secret. Required if oauth is used. +;client_id = +;client_secret = +; Cookie secret key to be used for cookies encryption. Must be valid 32 bytes URL-safe base64-encoded string. +; If not set, it will be generated automatically. +;cookie_secret_key = +; Name of the secondary group to be used as admin group in the service. Required if pam is used. +;full_access_group = wheel +; Authentication cookie expiration in seconds. +;max_age = 604800 +; OAuth2 provider icon for the web interface. +;oauth_icon = google +; OAuth2 provider class name, one of provided by aioauth-client. Required if oauth is used. +;oauth_provider = GoogleClient +; Scopes list for OAuth2 provider. Required if oauth is used. +;oauth_scopes = https://www.googleapis.com/auth/userinfo.email +; Allow login as root user (only applicable if PAM is used). +;permit_root_login = no +; Optional password salt. +;salt = + +[web] +; External address of the web service. Will be used for some features like OAuth. If none set will be generated as +; address = http://${web:host}:${web:port} +;address = http://${web:host}:${web:port} +; Enable file upload endpoint used by some triggers. +;enable_archive_upload = no +; Address to bind the server. +host = 127.0.0.1 +; Full URL to the repository index page used by templates. +;index_url = +; Max file size in bytes which can be uploaded to the server. Requires ${web:enable_archive_upload} to be enabled. +;max_body_size = +; Port to listen. Must be set, if the web service is enabled. +;port = +; Disable status (e.g. package status, logs, etc) endpoints. Useful for build only modes. +;service_only = no +; Path to directory with static files. +static_path = ${templates}/static +; List of directories with templates. +templates[] = ${prefix}/share/ahriman/templates +; Path to unix socket. If none set, unix socket will be disabled. +;unix_socket = +; Allow unix socket to be world readable. +;unix_socket_unsafe = yes +; Maximum amount of time in seconds to be waited before lock will be free, used by spawned processes (0 is infinite). +;wait_timeout = diff --git a/pylint_plugins/import_order.py b/pylint_plugins/import_order.py index 06bfd622..923cff79 100644 --- a/pylint_plugins/import_order.py +++ b/pylint_plugins/import_order.py @@ -30,12 +30,14 @@ class ImportType(StrEnum): import type enumeration Attributes: + Future(MethodTypeOrder): (class attribute) from __future__ import Package(MethodTypeOrder): (class attribute) package import PackageFrom(MethodTypeOrder): (class attribute) package import, from clause System(ImportType): (class attribute) system installed packages SystemFrom(MethodTypeOrder): (class attribute) system installed packages, from clause """ + Future = "future" Package = "package" PackageFrom = "package-from" System = "system" @@ -70,6 +72,7 @@ class ImportOrder(BaseRawFileChecker): "import-type-order", { "default": [ + "future", "system", "system-from", "package", @@ -91,7 +94,7 @@ class ImportOrder(BaseRawFileChecker): ) @staticmethod - def imports(source: Iterable[Any], start_lineno: int = 0) -> list[nodes.Import | nodes.ImportFrom]: + def imports(source: Iterable[Any], start_lineno: int = 0) -> Iterable[nodes.Import | nodes.ImportFrom]: """ extract import nodes from list of raw nodes @@ -100,7 +103,7 @@ class ImportOrder(BaseRawFileChecker): start_lineno(int, optional): minimal allowed line number (Default value = 0) Returns: - list[nodes.Import | nodes.ImportFrom]: list of import nodes + Iterable[nodes.Import | nodes.ImportFrom]: list of import nodes """ def is_defined_import(imports: Any) -> bool: @@ -108,7 +111,7 @@ class ImportOrder(BaseRawFileChecker): and imports.lineno is not None \ and imports.lineno >= start_lineno - return list(filter(is_defined_import, source)) + return sorted(filter(is_defined_import, source), key=lambda imports: imports.lineno) def check_from_imports(self, imports: nodes.ImportFrom) -> None: """ @@ -124,30 +127,36 @@ class ImportOrder(BaseRawFileChecker): self.add_message("from-imports-out-of-order", line=imports.lineno, args=(real, expected)) break - def check_imports(self, imports: list[nodes.Import | nodes.ImportFrom], root_package: str) -> None: + def check_imports(self, imports: Iterable[nodes.Import | nodes.ImportFrom], root_package: str) -> None: """ check imports Args: - imports(list[nodes.Import | nodes.ImportFrom]): list of imports in their defined order + imports(Iterable[nodes.Import | nodes.ImportFrom]): list of imports in their defined order root_package(str): root package name """ last_statement: tuple[int, str] | None = None for statement in imports: # define types and perform specific checks - if isinstance(statement, nodes.ImportFrom): - import_name = statement.modname - root, *_ = import_name.split(".", maxsplit=1) - import_type = ImportType.PackageFrom if root_package == root else ImportType.SystemFrom - # check from import itself - self.check_from_imports(statement) - else: - import_name = next(name for name, _ in statement.names) - root, *_ = import_name.split(".", maxsplit=1)[0] - import_type = ImportType.Package if root_package == root else ImportType.System - # check import itself - self.check_package_imports(statement) + match statement: + case nodes.ImportFrom() if statement.modname == "__future__": + import_name = statement.modname + import_type = ImportType.Future + case nodes.ImportFrom(): + import_name = statement.modname + root, *_ = import_name.split(".", maxsplit=1) + import_type = ImportType.PackageFrom if root_package == root else ImportType.SystemFrom + # check from import itself + self.check_from_imports(statement) + case nodes.Import(): + import_name = next(name for name, _ in statement.names) + root, *_ = import_name.split(".", maxsplit=1) + import_type = ImportType.Package if root_package == root else ImportType.System + # check import itself + self.check_package_imports(statement) + case _: + continue # extract index try: diff --git a/pyproject.toml b/pyproject.toml index 5124c97d..d878626d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,6 +95,7 @@ include = [ "CONTRIBUTING.md", "SECURITY.md", "package", + "subpackages.py", "web.png", ] exclude = [ diff --git a/src/ahriman/application/ahriman.py b/src/ahriman/application/ahriman.py index 53f3ce3f..53bc7953 100644 --- a/src/ahriman/application/ahriman.py +++ b/src/ahriman/application/ahriman.py @@ -17,36 +17,23 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -# pylint: disable=too-many-lines import argparse from pathlib import Path -from typing import TypeVar + +import ahriman.application.handlers from ahriman import __version__ -from ahriman.application import handlers +from ahriman.application.handlers.handler import Handler from ahriman.application.help_formatter import _HelpFormatter -from ahriman.core.utils import enum_values, extract_user -from ahriman.models.action import Action -from ahriman.models.build_status import BuildStatusEnum -from ahriman.models.event import EventType +from ahriman.core.module_loader import implementations +from ahriman.core.utils import enum_values from ahriman.models.log_handler import LogHandler -from ahriman.models.package_source import PackageSource -from ahriman.models.sign_settings import SignSettings -from ahriman.models.user_access import UserAccess __all__: list[str] = [] -# this workaround is for several things -# firstly python devs don't think that is it error and asking you for workarounds https://bugs.python.org/issue41592 -# secondly linters don't like when you are importing private members -# thirdly new mypy doesn't like _SubParsersAction and thinks it is a template -SubParserAction = TypeVar("SubParserAction", bound="argparse._SubParsersAction[argparse.ArgumentParser]") - - -# pylint: disable=too-many-statements def _parser() -> argparse.ArgumentParser: """ command line parser generator @@ -102,1107 +89,17 @@ Start web service (requires additional configuration): subparsers = parser.add_subparsers(title="command", help="command to run", dest="command") - _set_aur_search_parser(subparsers) - _set_help_commands_unsafe_parser(subparsers) - _set_help_parser(subparsers) - _set_help_updates_parser(subparsers) - _set_help_version_parser(subparsers) - _set_package_add_parser(subparsers) - _set_package_changes_parser(subparsers) - _set_package_changes_remove_parser(subparsers) - _set_package_copy_parser(subparsers) - _set_package_remove_parser(subparsers) - _set_package_status_parser(subparsers) - _set_package_status_remove_parser(subparsers) - _set_package_status_update_parser(subparsers) - _set_patch_add_parser(subparsers) - _set_patch_list_parser(subparsers) - _set_patch_remove_parser(subparsers) - _set_patch_set_add_parser(subparsers) - _set_repo_backup_parser(subparsers) - _set_repo_check_parser(subparsers) - _set_repo_create_keyring_parser(subparsers) - _set_repo_create_mirrorlist_parser(subparsers) - _set_repo_daemon_parser(subparsers) - _set_repo_rebuild_parser(subparsers) - _set_repo_remove_unknown_parser(subparsers) - _set_repo_report_parser(subparsers) - _set_repo_restore_parser(subparsers) - _set_repo_sign_parser(subparsers) - _set_repo_statistics_parser(subparsers) - _set_repo_status_update_parser(subparsers) - _set_repo_sync_parser(subparsers) - _set_repo_tree_parser(subparsers) - _set_repo_triggers_parser(subparsers) - _set_repo_update_parser(subparsers) - _set_service_clean_parser(subparsers) - _set_service_config_parser(subparsers) - _set_service_config_validate_parser(subparsers) - _set_service_key_import_parser(subparsers) - _set_service_repositories(subparsers) - _set_service_run(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) - _set_web_parser(subparsers) + for handler in implementations(ahriman.application.handlers, Handler): + for subparser_parser in handler.arguments: + subparser = subparser_parser(subparsers) + subparser.formatter_class = _HelpFormatter + subparser.set_defaults(handler=handler, parser=_parser) - return parser + # sort actions alphabetically in both choices and help message + # pylint: disable=protected-access + subparsers._choices_actions = sorted(subparsers._choices_actions, key=lambda action: action.dest) + subparsers.choices = dict(sorted(subparsers.choices.items())) - -def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for AUR search subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("aur-search", aliases=["search"], help="search for package", - description="search for package in AUR using API", formatter_class=_HelpFormatter) - parser.add_argument("search", help="search terms, can be specified multiple times, the result will match all terms", - nargs="+") - parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") - parser.add_argument("--info", help="show additional package information", - action=argparse.BooleanOptionalAction, default=False) - 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, quiet=True, report=False, - repository="", unsafe=True) - return parser - - -def _set_help_commands_unsafe_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for listing unsafe commands - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("help-commands-unsafe", help="list unsafe commands", - description="list unsafe commands as defined in default args", - formatter_class=_HelpFormatter) - parser.add_argument("subcommand", 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, quiet=True, report=False, - repository="", unsafe=True, parser=_parser) - return parser - - -def _set_help_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for listing help subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("help", help="show help message", - description="show help message for application or command and exit", - formatter_class=_HelpFormatter) - parser.add_argument("subcommand", help="show help message for specific command", nargs="?") - parser.set_defaults(handler=handlers.Help, architecture="", lock=None, quiet=True, report=False, repository="", - unsafe=True, parser=_parser) - return parser - - -def _set_help_updates_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for service update check subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("help-updates", help="check for service updates", - description="request AUR for current version and compare with current service version", - formatter_class=_HelpFormatter) - 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, quiet=True, report=False, - repository="", unsafe=True) - return parser - - -def _set_help_version_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for version subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("help-version", aliases=["version"], help="application version", - description="print application and its dependencies versions", - formatter_class=_HelpFormatter) - parser.set_defaults(handler=handlers.Versions, architecture="", lock=None, quiet=True, report=False, - repository="", unsafe=True) - return parser - - -def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for package addition subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("package-add", aliases=["add", "package-update"], help="add package", - description="add existing or new package to the build queue", - epilog="This subcommand should be used for new package addition. It also supports flag " - "--now in case if you would like to build the package immediately. " - "You can add new package from one of supported sources:\n\n" - "1. If it is already built package you can specify the path to the archive.\n" - "2. You can also add built packages from the directory (e.g. during the migration " - "from another repository source).\n" - "3. It is also possible to add package from local PKGBUILD, but in this case it " - "will be ignored during the next automatic updates.\n" - "4. Ahriman supports downloading archives from remote (e.g. HTTP) sources.\n" - "5. Finally you can add package from AUR.", - formatter_class=_HelpFormatter) - parser.add_argument("package", help="package source (base name, path to local files, remote URL)", nargs="+") - parser.add_argument("--changes", help="calculate changes from the latest known commit if available", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--dependencies", help="process missing package dependencies", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") - parser.add_argument("--increment", help="increment package release (pkgrel) version on duplicate", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("-n", "--now", help="run update function after", action="store_true") - parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, " - "-yy to force refresh even if up to date", - action="count", default=False) - parser.add_argument("-s", "--source", help="explicitly specify the package source for this command", - type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto) - parser.add_argument("-u", "--username", help="build as user", default=extract_user()) - parser.add_argument("-v", "--variable", help="apply specified makepkg variables to the next build", action="append") - parser.set_defaults(handler=handlers.Add) - return parser - - -def _set_package_changes_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for package changes subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("package-changes", help="get package changes", - description="retrieve package changes stored in database", - epilog="This feature requests package status from the web interface if it is available.", - formatter_class=_HelpFormatter) - parser.add_argument("package", help="package base") - parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") - parser.set_defaults(handler=handlers.Change, action=Action.List, lock=None, quiet=True, report=False, unsafe=True) - return parser - - -def _set_package_changes_remove_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for package change remove subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("package-changes-remove", help="remove package changes", - description="remove the package changes stored remotely", - formatter_class=_HelpFormatter) - parser.add_argument("package", help="package base") - parser.set_defaults(handler=handlers.Change, action=Action.Remove, exit_code=False, lock=None, quiet=True, - report=False, unsafe=True) - return parser - - -def _set_package_copy_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for package copy subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("package-copy", aliases=["copy"], help="copy package from another repository", - description="copy package and its metadata from another repository", - formatter_class=_HelpFormatter) - parser.add_argument("source", help="source repository name") - parser.add_argument("package", help="package base", nargs="+") - parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") - parser.add_argument("--remove", help="remove package from the source repository after", action="store_true") - parser.set_defaults(handler=handlers.Copy) - return parser - - -def _set_package_remove_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for package removal subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("package-remove", aliases=["remove"], help="remove package", - description="remove package from the repository", formatter_class=_HelpFormatter) - parser.add_argument("package", help="package name or base", nargs="+") - parser.set_defaults(handler=handlers.Remove) - return parser - - -def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for package status subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("package-status", aliases=["status"], help="get package status", - description="request status of the package", - epilog="This feature requests package status from the web interface if it is available.", - formatter_class=_HelpFormatter) - parser.add_argument("package", help="filter status by package base", nargs="*") - parser.add_argument("--ahriman", help="get service status itself", action="store_true") - parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") - parser.add_argument("--info", help="show additional package information", - 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, quiet=True, report=False, unsafe=True) - return parser - - -def _set_package_status_remove_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for package status remove subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("package-status-remove", help="remove package status", - description="remove the package from the status page", - epilog="Please note that this subcommand does not remove the package itself, it just " - "clears the status page.", - formatter_class=_HelpFormatter) - parser.add_argument("package", help="remove specified packages from status page", nargs="+") - parser.set_defaults(handler=handlers.StatusUpdate, action=Action.Remove, lock=None, quiet=True, report=False, - unsafe=True) - return parser - - -def _set_package_status_update_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for package status update subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("package-status-update", aliases=["status-update"], help="update package status", - description="update package status on the status page", - formatter_class=_HelpFormatter) - parser.add_argument("package", help="set status for specified packages. " - "If no packages supplied, service status will be updated", - 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, quiet=True, report=False, - unsafe=True) - return parser - - -def _set_patch_add_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for new single-function patch subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("patch-add", help="add patch for PKGBUILD function", - description="create or update patched PKGBUILD function or variable", - epilog="Unlike ``patch-set-add``, this function allows to patch only one PKGBUILD " - "function, e.g. typing ``ahriman patch-add ahriman pkgver`` it will change the " - "``pkgver`` inside PKGBUILD, typing ``ahriman patch-add ahriman build()`` " - "it will change ``build()`` function inside PKGBUILD.", - formatter_class=_HelpFormatter) - parser.add_argument("package", help="package base") - parser.add_argument("variable", help="PKGBUILD variable or function name. If variable is a function, " - "it must end with ()") - parser.add_argument("patch", help="path to file which contains function or variable value. If not set, " - "the value will be read from stdin", type=Path, nargs="?") - parser.set_defaults(handler=handlers.Patch, action=Action.Update, architecture="", exit_code=False, lock=None, - report=False, repository="") - return parser - - -def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for list patches subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("patch-list", help="list patch sets", description="list available patches for the package", - formatter_class=_HelpFormatter) - parser.add_argument("package", help="package base") - parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") - parser.add_argument("-v", "--variable", help="if set, show only patches for specified PKGBUILD variables", - action="append") - parser.set_defaults(handler=handlers.Patch, action=Action.List, architecture="", lock=None, report=False, - repository="", unsafe=True) - return parser - - -def _set_patch_remove_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for remove patches subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("patch-remove", help="remove patch set", description="remove patches for the package", - formatter_class=_HelpFormatter) - parser.add_argument("package", help="package base") - parser.add_argument("-v", "--variable", help="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", - action="append") - parser.set_defaults(handler=handlers.Patch, action=Action.Remove, architecture="", exit_code=False, lock=None, - report=False, repository="") - return parser - - -def _set_patch_set_add_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for new full-diff patch subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("patch-set-add", help="add patch set", description="create or update source patches", - epilog="In order to add a patch set for the package you will need to:\n\n" - "1. Clone the AUR package manually.\n" - "2. Add required changes (e.g. external patches, edit PKGBUILD).\n" - "3. Run command, e.g. ``ahriman patch-set-add path/to/directory``.\n\n" - "By default it tracks ``*.patch`` and ``*.diff`` files, but this behavior can be " - "changed by using ``--track`` option.", - formatter_class=_HelpFormatter) - parser.add_argument("package", help="path to directory with changed files for patch addition/update", type=Path) - parser.add_argument("-t", "--track", help="files which has to be tracked", action="append", - default=["*.diff", "*.patch"]) - parser.set_defaults(handler=handlers.Patch, action=Action.Update, architecture="", exit_code=False, lock=None, - report=False, repository="", variable=None) - return parser - - -def _set_repo_backup_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for repository backup subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-backup", help="backup repository data", - description="backup repository settings and database", - formatter_class=_HelpFormatter) - parser.add_argument("path", help="path of the output archive", type=Path) - parser.set_defaults(handler=handlers.Backup, architecture="", lock=None, report=False, repository="", - unsafe=True) - return parser - - -def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for repository check subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-check", aliases=["check"], help="check for updates", - description="check for packages updates. Same as repo-update --dry-run --no-manual", - formatter_class=_HelpFormatter) - parser.add_argument("package", help="filter check by package base", nargs="*") - parser.add_argument("--changes", help="calculate changes from the latest known commit if available", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--check-files", help="enable or disable checking of broken dependencies " - "(e.g. dynamically linked libraries or modules directories)", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") - parser.add_argument("--vcs", help="fetch actual version of VCS packages", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, " - "-yy to force refresh even if up to date", - action="count", default=False) - parser.set_defaults(handler=handlers.Update, aur=True, dependencies=False, dry_run=True, increment=False, - local=True, manual=False, username=None) - return parser - - -def _set_repo_create_keyring_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for create-keyring subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-create-keyring", help="create keyring package", - description="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", - formatter_class=_HelpFormatter) - parser.set_defaults(handler=handlers.Triggers, trigger=["ahriman.core.support.KeyringTrigger"]) - return parser - - -def _set_repo_create_mirrorlist_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for create-mirrorlist subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-create-mirrorlist", help="create mirrorlist package", - description="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", - formatter_class=_HelpFormatter) - parser.set_defaults(handler=handlers.Triggers, trigger=["ahriman.core.support.MirrorlistTrigger"]) - return parser - - -def _set_repo_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for daemon subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-daemon", aliases=["daemon"], help="run application as daemon", - description="start process which periodically will run update process", - formatter_class=_HelpFormatter) - parser.add_argument("-i", "--interval", help="interval between runs in seconds", type=int, default=60 * 60 * 12) - parser.add_argument("--aur", help="enable or disable checking for AUR updates", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--changes", help="calculate changes from the latest known commit if available. " - "Only applicable in dry run mode", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--check-files", help="enable or disable checking of broken dependencies " - "(e.g. dynamically linked libraries or modules directories)", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--dependencies", help="process missing package dependencies", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true") - parser.add_argument("--increment", help="increment package release (pkgrel) on duplicate", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--local", help="enable or disable checking of local packages for updates", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--manual", help="include or exclude manual updates", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--partitions", help="instead of updating whole repository, split updates into chunks", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("-u", "--username", help="build as user", default=extract_user()) - parser.add_argument("--vcs", help="fetch actual version of VCS packages", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, " - "-yy to force refresh even if up to date", - action="count", default=False) - parser.set_defaults(handler=handlers.Daemon, exit_code=False, lock=Path("ahriman-daemon.pid"), package=[]) - return parser - - -def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for repository rebuild subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-rebuild", aliases=["rebuild"], help="rebuild repository", - description="force rebuild whole repository", formatter_class=_HelpFormatter) - parser.add_argument("--depends-on", help="only rebuild packages that depend on specified packages", action="append") - parser.add_argument("--dry-run", help="just perform check for packages without rebuild process itself", - action="store_true") - parser.add_argument("--from-database", - help="read packages from database instead of filesystem. This feature in particular is " - "required in case if you would like to restore repository from another repository " - "instance. Note, however, that in order to restore packages you need to have original " - "ahriman instance run with web service and have run repo-update at least once.", - action="store_true") - parser.add_argument("--increment", help="increment package release (pkgrel) on duplicate", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") - parser.add_argument("-s", "--status", help="filter packages by status. Requires --from-database to be set", - type=BuildStatusEnum, choices=enum_values(BuildStatusEnum)) - parser.add_argument("-u", "--username", help="build as user", default=extract_user()) - parser.set_defaults(handler=handlers.Rebuild) - return parser - - -def _set_repo_remove_unknown_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for remove unknown packages subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-remove-unknown", aliases=["remove-unknown"], help="remove unknown packages", - description="remove packages which are missing in AUR and do not have local PKGBUILDs", - formatter_class=_HelpFormatter) - parser.add_argument("--dry-run", help="just perform check for packages without removal", action="store_true") - parser.set_defaults(handler=handlers.RemoveUnknown) - return parser - - -def _set_repo_report_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for report subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-report", aliases=["report"], help="generate report", - description="generate repository report according to current settings", - epilog="Create and/or update repository report as configured.", - formatter_class=_HelpFormatter) - parser.set_defaults(handler=handlers.Triggers, trigger=["ahriman.core.report.ReportTrigger"]) - return parser - - -def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for repository restore subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-restore", help="restore repository data", - description="restore settings and database", formatter_class=_HelpFormatter) - parser.add_argument("path", help="path of the input archive", type=Path) - parser.add_argument("-o", "--output", help="root path of the extracted files", type=Path, default=Path("/")) - parser.set_defaults(handler=handlers.Restore, architecture="", lock=None, report=False, repository="", - unsafe=True) - return parser - - -def _set_repo_sign_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for sign subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-sign", aliases=["sign"], help="sign packages", - description="(re-)sign packages and repository database according to current settings", - epilog="Sign repository and/or packages as configured.", - formatter_class=_HelpFormatter) - parser.add_argument("package", help="sign only specified packages", nargs="*") - parser.set_defaults(handler=handlers.Sign) - return parser - - -def _set_repo_statistics_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for repository statistics subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-statistics", help="repository statistics", - description="fetch repository statistics", formatter_class=_HelpFormatter) - parser.add_argument("package", help="fetch only events for the specified package", nargs="?") - parser.add_argument("--chart", help="create updates chart and save it to the specified path", type=Path) - parser.add_argument("-e", "--event", help="event type filter", - type=EventType, choices=enum_values(EventType), default=EventType.PackageUpdated) - parser.add_argument("--from-date", help="only fetch events which are newer than the date") - parser.add_argument("--limit", help="limit response by specified amount of events", type=int, default=-1) - parser.add_argument("--offset", help="skip specified amount of events", type=int, default=0) - parser.add_argument("--to-date", help="only fetch events which are older than the date") - parser.set_defaults(handler=handlers.Statistics, lock=None, quiet=True, report=False, unsafe=True) - return parser - - -def _set_repo_status_update_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for repository status update subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-status-update", help="update repository status", - description="update repository status on the status page", - formatter_class=_HelpFormatter) - 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=[], quiet=True, - report=False, unsafe=True) - return parser - - -def _set_repo_sync_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for repository sync subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-sync", aliases=["sync"], help="sync repository", - description="sync repository files to remote server according to current settings", - epilog="Synchronize the repository to remote services as configured.", - formatter_class=_HelpFormatter) - parser.set_defaults(handler=handlers.Triggers, trigger=["ahriman.core.upload.UploadTrigger"]) - return parser - - -def _set_repo_tree_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for repository tree subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-tree", help="dump repository tree", - description="dump repository tree based on packages dependencies", - formatter_class=_HelpFormatter) - parser.add_argument("-p", "--partitions", help="also divide packages by independent partitions", - type=int, default=1) - parser.set_defaults(handler=handlers.Structure, lock=None, quiet=True, report=False, unsafe=True) - return parser - - -def _set_repo_triggers_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for repository triggers subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-triggers", help="run triggers", - description="run triggers on empty build result as configured by settings", - formatter_class=_HelpFormatter) - parser.add_argument("trigger", help="instead of running all triggers as set by configuration, just process " - "specified ones in order of mention", nargs="*") - parser.set_defaults(handler=handlers.Triggers) - return parser - - -def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for repository update subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("repo-update", aliases=["update"], help="update packages", - description="check for packages updates and run build process if requested", - formatter_class=_HelpFormatter) - parser.add_argument("package", help="filter check by package base", nargs="*") - parser.add_argument("--aur", help="enable or disable checking for AUR updates", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--changes", help="calculate changes from the latest known commit if available. " - "Only applicable in dry run mode", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--check-files", help="enable or disable checking of broken dependencies " - "(e.g. dynamically linked libraries or modules directories)", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--dependencies", help="process missing package dependencies", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true") - parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") - parser.add_argument("--increment", help="increment package release (pkgrel) on duplicate", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--local", help="enable or disable checking of local packages for updates", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--manual", help="include or exclude manual updates", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("-u", "--username", help="build as user", default=extract_user()) - parser.add_argument("--vcs", help="fetch actual version of VCS packages", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, " - "-yy to force refresh even if up to date", - action="count", default=False) - parser.set_defaults(handler=handlers.Update) - return parser - - -def _set_service_clean_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for repository clean subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("service-clean", aliases=["clean", "repo-clean"], help="clean local caches", - description="remove local caches", - epilog="The subcommand clears every temporary directories (builds, caches etc). Normally " - "you should not run this command manually. Also in case if you are going to clear " - "the chroot directories you will need root privileges.", - formatter_class=_HelpFormatter) - parser.add_argument("--cache", help="clear directory with package caches", - action=argparse.BooleanOptionalAction, default=False) - parser.add_argument("--chroot", help="clear build chroot", action=argparse.BooleanOptionalAction, default=False) - parser.add_argument("--manual", help="clear manually added packages queue", - action=argparse.BooleanOptionalAction, default=False) - parser.add_argument("--packages", help="clear directory with built packages", - action=argparse.BooleanOptionalAction, default=False) - parser.add_argument("--pacman", help="clear directory with pacman local database cache", - action=argparse.BooleanOptionalAction, default=False) - parser.set_defaults(handler=handlers.Clean, lock=None, quiet=True, unsafe=True) - return parser - - -def _set_service_config_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for config subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("service-config", aliases=["config", "repo-config"], help="dump configuration", - description="dump configuration for the specified architecture", - formatter_class=_HelpFormatter) - parser.add_argument("section", help="filter settings by section", nargs="?") - parser.add_argument("key", help="filter settings by key", nargs="?") - parser.add_argument("--info", help="show additional information, e.g. configuration files", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--secure", help="hide passwords and secrets from output", - action=argparse.BooleanOptionalAction, default=True) - parser.set_defaults(handler=handlers.Dump, lock=None, quiet=True, report=False, unsafe=True) - return parser - - -def _set_service_config_validate_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for config validation subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("service-config-validate", aliases=["config-validate", "repo-config-validate"], - help="validate system configuration", - description="validate configuration and print found errors", - formatter_class=_HelpFormatter) - 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, quiet=True, report=False, unsafe=True) - return parser - - -def _set_service_key_import_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for key import subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("service-key-import", aliases=["key-import"], help="import PGP key", - description="import PGP key from public sources to the repository user", - epilog="By default ahriman runs build process with package sources validation " - "(in case if signature and keys are available in PKGBUILD). This process will " - "fail in case if key is not known for build user. This subcommand can be used " - "in order to import the PGP key to user keychain.", - formatter_class=_HelpFormatter) - parser.add_argument("--key-server", help="key server for key import", default="keyserver.ubuntu.com") - parser.add_argument("key", help="PGP key to import from public server") - parser.set_defaults(handler=handlers.KeyImport, architecture="", lock=None, report=False, repository="") - return parser - - -def _set_service_repositories(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for repositories listing - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("service-repositories", help="show repositories", - description="list all available repositories", - formatter_class=_HelpFormatter) - parser.add_argument("--id-only", help="show machine readable identifier instead", - action=argparse.BooleanOptionalAction, default=False) - parser.set_defaults(handler=handlers.Repositories, architecture="", lock=None, report=False, repository="", - unsafe=True) - return parser - - -def _set_service_run(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for multicommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("service-run", aliases=["run"], help="run multiple commands", - description="run multiple commands on success run of the previous command", - epilog="Commands must be quoted by using usual bash rules. Processes will be spawned " - "under the same user as this command.", - formatter_class=_HelpFormatter) - parser.add_argument("command", help="command to be run (quoted) without ``ahriman``", nargs="+") - parser.set_defaults(handler=handlers.Run, architecture="", lock=None, report=False, repository="", - unsafe=True, parser=_parser) - return parser - - -def _set_service_setup_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for setup subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("service-setup", aliases=["init", "repo-init", "repo-setup", "setup"], - help="initial service configuration", - description="create initial service configuration, requires root", - epilog="Create **minimal** configuration for the service according to provided options.", - formatter_class=_HelpFormatter) - parser.add_argument("--build-as-user", help="force makepkg user to the specific one") - parser.add_argument("--from-configuration", help="path to default devtools pacman configuration", - type=Path, default=Path("/") / "usr" / "share" / "devtools" / "pacman.conf.d" / "extra.conf") - parser.add_argument("--generate-salt", help="generate salt for user passwords", - action=argparse.BooleanOptionalAction, default=False) - parser.add_argument("--makeflags-jobs", help="append MAKEFLAGS variable with parallelism set to number of cores", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--mirror", help="use the specified explicitly mirror instead of including mirrorlist") - parser.add_argument("--multilib", help="add or do not multilib repository", - action=argparse.BooleanOptionalAction, default=True) - parser.add_argument("--packager", help="packager name and email", required=True) - parser.add_argument("--server", help="server to be used for devtools. If none set, local files will be used") - parser.add_argument("--sign-key", help="sign key id") - parser.add_argument("--sign-target", help="sign options", action="append", - 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, quiet=True, report=False, unsafe=True) - return parser - - -def _set_service_shell_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for shell subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("service-shell", aliases=["shell"], help="invoke python shell", - description="drop into python shell", formatter_class=_HelpFormatter) - parser.add_argument("code", help="instead of dropping into shell, just execute the specified code", nargs="?") - parser.add_argument("-v", "--verbose", help=argparse.SUPPRESS, action="store_true") - parser.set_defaults(handler=handlers.Shell, lock=None, report=False) - 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=_HelpFormatter) - 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 - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("user-add", help="create or update user", - description="update user for web services with the given password and role. " - "In case if password was not entered it will be asked interactively", - formatter_class=_HelpFormatter) - parser.add_argument("username", help="username for web service") - parser.add_argument("--key", help="optional PGP key used by this user. The private key must be imported") - parser.add_argument("--packager", help="optional packager id used for build process in form of " - "`Name Surname `") - parser.add_argument("-p", "--password", help="user password. Blank password will be treated as empty password, " - "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="", exit_code=False, lock=None, - quiet=True, report=False, repository="") - return parser - - -def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for user list subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("user-list", help="user known users and their access", - description="list users from the user mapping and their roles", - formatter_class=_HelpFormatter) - 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, quiet=True, - report=False, repository="", unsafe=True) - return parser - - -def _set_user_remove_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for user removal subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("user-remove", help="remove user", - description="remove user from the user mapping and update the configuration", - formatter_class=_HelpFormatter) - parser.add_argument("username", help="username for web service") - parser.set_defaults(handler=handlers.Users, action=Action.Remove, architecture="", exit_code=False, lock=None, - quiet=True, report=False, repository="") - return parser - - -def _set_web_parser(root: SubParserAction) -> argparse.ArgumentParser: - """ - add parser for web subcommand - - Args: - root(SubParserAction): subparsers for the commands - - Returns: - argparse.ArgumentParser: created argument parser - """ - parser = root.add_parser("web", help="web server", description="start web server", - formatter_class=_HelpFormatter) - parser.set_defaults(handler=handlers.Web, architecture="", lock=Path("ahriman-web.pid"), report=False, - repository="", parser=_parser) return parser @@ -1213,11 +110,11 @@ def run() -> int: Returns: int: application status code """ - args_parser = _parser() - args = args_parser.parse_args() + parser = _parser() + args = parser.parse_args() if args.command is None: # in case of empty command we would like to print help message - args_parser.exit(status=2, message=args_parser.format_help()) + parser.exit(status=2, message=parser.format_help()) - handler: handlers.Handler = args.handler + handler: Handler = args.handler return handler.execute(args) diff --git a/src/ahriman/application/handlers/__init__.py b/src/ahriman/application/handlers/__init__.py index 23b88a0f..78e01321 100644 --- a/src/ahriman/application/handlers/__init__.py +++ b/src/ahriman/application/handlers/__init__.py @@ -17,37 +17,3 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from ahriman.application.handlers.add import Add -from ahriman.application.handlers.backup import Backup -from ahriman.application.handlers.change import Change -from ahriman.application.handlers.clean import Clean -from ahriman.application.handlers.copy import Copy -from ahriman.application.handlers.daemon import Daemon -from ahriman.application.handlers.dump import Dump -from ahriman.application.handlers.handler import Handler -from ahriman.application.handlers.help import Help -from ahriman.application.handlers.key_import import KeyImport -from ahriman.application.handlers.patch import Patch -from ahriman.application.handlers.rebuild import Rebuild -from ahriman.application.handlers.remove import Remove -from ahriman.application.handlers.remove_unknown import RemoveUnknown -from ahriman.application.handlers.repositories import Repositories -from ahriman.application.handlers.restore import Restore -from ahriman.application.handlers.run import Run -from ahriman.application.handlers.search import Search -from ahriman.application.handlers.service_updates import ServiceUpdates -from ahriman.application.handlers.setup import Setup -from ahriman.application.handlers.shell import Shell -from ahriman.application.handlers.sign import Sign -from ahriman.application.handlers.statistics import Statistics -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 -from ahriman.application.handlers.users import Users -from ahriman.application.handlers.validate import Validate -from ahriman.application.handlers.versions import Versions -from ahriman.application.handlers.web import Web diff --git a/src/ahriman/application/handlers/add.py b/src/ahriman/application/handlers/add.py index 7fe1817b..b4a67519 100644 --- a/src/ahriman/application/handlers/add.py +++ b/src/ahriman/application/handlers/add.py @@ -20,8 +20,10 @@ import argparse from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration +from ahriman.core.utils import enum_values, extract_user +from ahriman.models.package_source import PackageSource from ahriman.models.packagers import Packagers from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.repository_id import RepositoryId @@ -66,3 +68,49 @@ class Add(Handler): application.print_updates(packages, log_fn=application.logger.info) result = application.update(packages, packagers, bump_pkgrel=args.increment) Add.check_status(args.exit_code, not result.is_empty) + + @staticmethod + def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for package addition subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("package-add", aliases=["add", "package-update"], help="add package", + description="add existing or new package to the build queue", + epilog="This subcommand should be used for new package addition. " + "It also supports flag --now in case if you would like to build " + "the package immediately. You can add new package from one of " + "supported sources:\n\n" + "1. If it is already built package you can specify the path to the archive.\n" + "2. You can also add built packages from the directory (e.g. during the " + "migration from another repository source).\n" + "3. It is also possible to add package from local PKGBUILD, but in this case " + "it will be ignored during the next automatic updates.\n" + "4. Ahriman supports downloading archives from remote (e.g. HTTP) sources.\n" + "5. Finally you can add package from AUR.") + parser.add_argument("package", help="package source (base name, path to local files, remote URL)", nargs="+") + parser.add_argument("--changes", help="calculate changes from the latest known commit if available", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--dependencies", help="process missing package dependencies", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", + action="store_true") + parser.add_argument("--increment", help="increment package release (pkgrel) version on duplicate", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("-n", "--now", help="run update function after", action="store_true") + parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, " + "-yy to force refresh even if up to date", + action="count", default=False) + parser.add_argument("-s", "--source", help="explicitly specify the package source for this command", + type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto) + parser.add_argument("-u", "--username", help="build as user", default=extract_user()) + parser.add_argument("-v", "--variable", help="apply specified makepkg variables to the next build", + action="append") + return parser + + arguments = [_set_package_add_parser] diff --git a/src/ahriman/application/handlers/backup.py b/src/ahriman/application/handlers/backup.py index 17e03d3a..dec50661 100644 --- a/src/ahriman/application/handlers/backup.py +++ b/src/ahriman/application/handlers/backup.py @@ -23,7 +23,7 @@ import tarfile from pathlib import Path from pwd import getpwuid -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite from ahriman.models.repository_id import RepositoryId @@ -53,6 +53,23 @@ class Backup(Handler): for backup_path in backup_paths: archive.add(backup_path) + @staticmethod + def _set_repo_backup_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for repository backup subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-backup", help="backup repository data", + description="backup repository settings and database") + parser.add_argument("path", help="path of the output archive", type=Path) + parser.set_defaults(architecture="", lock=None, report=False, repository="", unsafe=True) + return parser + @staticmethod def get_paths(configuration: Configuration) -> set[Path]: """ @@ -83,3 +100,5 @@ class Backup(Handler): paths.add(gnupg_home) return paths + + arguments = [_set_repo_backup_parser] diff --git a/src/ahriman/application/handlers/change.py b/src/ahriman/application/handlers/change.py index f2de007f..5dd4999b 100644 --- a/src/ahriman/application/handlers/change.py +++ b/src/ahriman/application/handlers/change.py @@ -20,7 +20,7 @@ import argparse from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.formatters import ChangesPrinter from ahriman.models.action import Action @@ -57,3 +57,43 @@ class Change(Handler): Change.check_status(args.exit_code, not changes.is_empty) case Action.Remove: client.package_changes_update(args.package, Changes()) + + @staticmethod + def _set_package_changes_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for package changes subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("package-changes", help="get package changes", + description="retrieve package changes stored in database", + epilog="This command requests package status from the web interface " + "if it is available.") + parser.add_argument("package", help="package base") + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", + action="store_true") + parser.set_defaults(action=Action.List, lock=None, quiet=True, report=False, unsafe=True) + return parser + + @staticmethod + def _set_package_changes_remove_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for package change remove subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("package-changes-remove", help="remove package changes", + description="remove the package changes stored remotely") + parser.add_argument("package", help="package base") + parser.set_defaults(action=Action.Remove, exit_code=False, lock=None, quiet=True, report=False, unsafe=True) + return parser + + arguments = [_set_package_changes_parser, _set_package_changes_remove_parser] diff --git a/src/ahriman/application/handlers/clean.py b/src/ahriman/application/handlers/clean.py index 49b7a56c..95e533a7 100644 --- a/src/ahriman/application/handlers/clean.py +++ b/src/ahriman/application/handlers/clean.py @@ -20,7 +20,7 @@ import argparse from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.models.repository_id import RepositoryId @@ -46,3 +46,33 @@ class Clean(Handler): application.on_start() application.clean(cache=args.cache, chroot=args.chroot, manual=args.manual, packages=args.packages, pacman=args.pacman) + + @staticmethod + def _set_service_clean_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for repository clean subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("service-clean", aliases=["clean", "repo-clean"], help="clean local caches", + description="remove local caches", + epilog="The subcommand clears every temporary directories (builds, caches etc). " + "Normally you should not run this command manually. Also in case if " + "you are going to clear the chroot directories you will need root privileges.") + parser.add_argument("--cache", help="clear directory with package caches", + action=argparse.BooleanOptionalAction, default=False) + parser.add_argument("--chroot", help="clear build chroot", action=argparse.BooleanOptionalAction, default=False) + parser.add_argument("--manual", help="clear manually added packages queue", + action=argparse.BooleanOptionalAction, default=False) + parser.add_argument("--packages", help="clear directory with built packages", + action=argparse.BooleanOptionalAction, default=False) + parser.add_argument("--pacman", help="clear directory with pacman local database cache", + action=argparse.BooleanOptionalAction, default=False) + parser.set_defaults(lock=None, quiet=True, unsafe=True) + return parser + + arguments = [_set_service_clean_parser] diff --git a/src/ahriman/application/handlers/copy.py b/src/ahriman/application/handlers/copy.py index 7add09ad..382d5733 100644 --- a/src/ahriman/application/handlers/copy.py +++ b/src/ahriman/application/handlers/copy.py @@ -20,7 +20,7 @@ import argparse from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.models.build_status import BuildStatusEnum from ahriman.models.package import Package @@ -67,6 +67,26 @@ class Copy(Handler): if args.remove: source_application.remove(args.package) + @staticmethod + def _set_package_copy_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for package copy subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("package-copy", aliases=["copy"], help="copy package from another repository", + description="copy package and its metadata from another repository") + parser.add_argument("source", help="source repository name") + parser.add_argument("package", help="package base", nargs="+") + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", + action="store_true") + parser.add_argument("--remove", help="remove package from the source repository after", action="store_true") + return parser + @staticmethod def copy_package(package: Package, application: Application, source_application: Application) -> None: """ @@ -93,3 +113,5 @@ class Copy(Handler): package.base, source_application.reporter.package_dependencies_get(package.base) ) application.reporter.package_update(package, BuildStatusEnum.Pending) + + arguments = [_set_package_copy_parser] diff --git a/src/ahriman/application/handlers/daemon.py b/src/ahriman/application/handlers/daemon.py index 4979ab4c..3fb3ba8d 100644 --- a/src/ahriman/application/handlers/daemon.py +++ b/src/ahriman/application/handlers/daemon.py @@ -19,11 +19,14 @@ # import argparse +from pathlib import Path + from ahriman.application.application import Application from ahriman.application.application.updates_iterator import FixedUpdatesIterator, UpdatesIterator -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.application.handlers.update import Update from ahriman.core.configuration import Configuration +from ahriman.core.utils import extract_user from ahriman.models.repository_id import RepositoryId @@ -56,3 +59,48 @@ class Daemon(Handler): args.package = packages Update.run(args, repository_id, configuration, report=report) + + @staticmethod + def _set_repo_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for daemon subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-daemon", aliases=["daemon"], help="run application as daemon", + description="start process which periodically will run update process") + parser.add_argument("-i", "--interval", help="interval between runs in seconds", type=int, default=60 * 60 * 12) + parser.add_argument("--aur", help="enable or disable checking for AUR updates", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--changes", help="calculate changes from the latest known commit if available. " + "Only applicable in dry run mode", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--check-files", help="enable or disable checking of broken dependencies " + "(e.g. dynamically linked libraries or modules directories)", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--dependencies", help="process missing package dependencies", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--dry-run", help="just perform check for updates, same as check command", + action="store_true") + parser.add_argument("--increment", help="increment package release (pkgrel) on duplicate", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--local", help="enable or disable checking of local packages for updates", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--manual", help="include or exclude manual updates", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--partitions", help="instead of updating whole repository, split updates into chunks", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("-u", "--username", help="build as user", default=extract_user()) + parser.add_argument("--vcs", help="fetch actual version of VCS packages", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, " + "-yy to force refresh even if up to date", + action="count", default=False) + parser.set_defaults(exit_code=False, lock=Path("ahriman-daemon.pid"), package=[]) + return parser + + arguments = [_set_repo_daemon_parser] diff --git a/src/ahriman/application/handlers/dump.py b/src/ahriman/application/handlers/dump.py index 1226d6fc..c9e8bc6f 100644 --- a/src/ahriman/application/handlers/dump.py +++ b/src/ahriman/application/handlers/dump.py @@ -19,7 +19,7 @@ # import argparse -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.formatters import ConfigurationPathsPrinter, ConfigurationPrinter, StringPrinter from ahriman.models.repository_id import RepositoryId @@ -59,3 +59,27 @@ class Dump(Handler): case section, key: # key only value = configuration.get(section, key, fallback="") StringPrinter(value)(verbose=False) + + @staticmethod + def _set_service_config_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for config subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("service-config", aliases=["config", "repo-config"], help="dump configuration", + description="dump configuration for the specified architecture") + parser.add_argument("section", help="filter settings by section", nargs="?") + parser.add_argument("key", help="filter settings by key", nargs="?") + parser.add_argument("--info", help="show additional information, e.g. configuration files", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--secure", help="hide passwords and secrets from output", + action=argparse.BooleanOptionalAction, default=True) + parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True) + return parser + + arguments = [_set_service_config_parser] diff --git a/src/ahriman/application/handlers/handler.py b/src/ahriman/application/handlers/handler.py index 615a01a5..723ed61b 100644 --- a/src/ahriman/application/handlers/handler.py +++ b/src/ahriman/application/handlers/handler.py @@ -22,6 +22,7 @@ import logging from collections.abc import Callable, Iterable from multiprocessing import Pool +from typing import TypeVar from ahriman.application.lock import Lock from ahriman.core.configuration import Configuration @@ -32,12 +33,21 @@ from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_paths import RepositoryPaths +# this workaround is for several things +# firstly python devs don't think that is it error and asking you for workarounds https://bugs.python.org/issue41592 +# secondly linters don't like when you are importing private members +# thirdly new mypy doesn't like _SubParsersAction and thinks it is a template +SubParserAction = TypeVar("SubParserAction", bound="argparse._SubParsersAction[argparse.ArgumentParser]") + + class Handler: """ base handler class for command callbacks Attributes: ALLOW_MULTI_ARCHITECTURE_RUN(bool): (class attribute) allow running with multiple architectures + arguments(list[Callable[[SubParserAction], argparse.ArgumentParser]]): (class attribute) argument parser + methods, which will be called to create command line parsers Examples: Wrapper for all command line actions, though each derived class implements :func:`run()` method, it usually @@ -49,6 +59,7 @@ class Handler: """ ALLOW_MULTI_ARCHITECTURE_RUN = True + arguments: list[Callable[[SubParserAction], argparse.ArgumentParser]] @classmethod def call(cls, args: argparse.Namespace, repository_id: RepositoryId) -> bool: diff --git a/src/ahriman/application/handlers/help.py b/src/ahriman/application/handlers/help.py index 793564ae..cb4bbb3d 100644 --- a/src/ahriman/application/handlers/help.py +++ b/src/ahriman/application/handlers/help.py @@ -19,7 +19,7 @@ # import argparse -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.models.repository_id import RepositoryId @@ -48,3 +48,22 @@ class Help(Handler): parser.parse_args(["--help"]) else: parser.parse_args([args.subcommand, "--help"]) + + @staticmethod + def _set_help_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for listing help subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("help", help="show help message", + description="show help message for application or command and exit") + parser.add_argument("subcommand", help="show help message for specific command", nargs="?") + parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True) + return parser + + arguments = [_set_help_parser] diff --git a/src/ahriman/application/handlers/key_import.py b/src/ahriman/application/handlers/key_import.py index 16ae8001..5c48b428 100644 --- a/src/ahriman/application/handlers/key_import.py +++ b/src/ahriman/application/handlers/key_import.py @@ -20,7 +20,7 @@ import argparse from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.models.repository_id import RepositoryId @@ -46,3 +46,27 @@ class KeyImport(Handler): """ application = Application(repository_id, configuration, report=report) application.repository.sign.key_import(args.key_server, args.key) + + @staticmethod + def _set_service_key_import_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for key import subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("service-key-import", aliases=["key-import"], help="import PGP key", + description="import PGP key from public sources to the repository user", + epilog="By default ahriman runs build process with package sources validation " + "(in case if signature and keys are available in PKGBUILD). This process will " + "fail in case if key is not known for build user. This subcommand can be used " + "in order to import the PGP key to user keychain.") + parser.add_argument("--key-server", help="key server for key import", default="keyserver.ubuntu.com") + parser.add_argument("key", help="PGP key to import from public server") + parser.set_defaults(architecture="", lock=None, report=False, repository="") + return parser + + arguments = [_set_service_key_import_parser] diff --git a/src/ahriman/application/handlers/patch.py b/src/ahriman/application/handlers/patch.py index d24a7753..124fa795 100644 --- a/src/ahriman/application/handlers/patch.py +++ b/src/ahriman/application/handlers/patch.py @@ -23,7 +23,7 @@ import sys from pathlib import Path from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.build_tools.sources import Sources from ahriman.core.configuration import Configuration from ahriman.core.formatters import PatchPrinter @@ -67,6 +67,100 @@ class Patch(Handler): case Action.Remove: Patch.patch_set_remove(application, args.package, args.variable) + @staticmethod + def _set_patch_add_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for new single-function patch subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("patch-add", help="add patch for PKGBUILD function", + description="create or update patched PKGBUILD function or variable", + epilog="Unlike ``patch-set-add``, this function allows to patch only one PKGBUILD " + "function, e.g. typing ``ahriman patch-add ahriman pkgver`` it will change the " + "``pkgver`` inside PKGBUILD, typing ``ahriman patch-add ahriman build()`` " + "it will change ``build()`` function inside PKGBUILD.") + parser.add_argument("package", help="package base") + parser.add_argument("variable", help="PKGBUILD variable or function name. If variable is a function, " + "it must end with ()") + parser.add_argument("patch", help="path to file which contains function or variable value. If not set, " + "the value will be read from stdin", type=Path, nargs="?") + parser.set_defaults(action=Action.Update, architecture="", exit_code=False, lock=None, report=False, + repository="") + return parser + + @staticmethod + def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for list patches subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("patch-list", help="list patch sets", + description="list available patches for the package") + parser.add_argument("package", help="package base") + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", + action="store_true") + parser.add_argument("-v", "--variable", help="if set, show only patches for specified PKGBUILD variables", + action="append") + parser.set_defaults(action=Action.List, architecture="", lock=None, report=False, repository="", unsafe=True) + return parser + + @staticmethod + def _set_patch_remove_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for remove patches subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("patch-remove", help="remove patch set", description="remove patches for the package") + parser.add_argument("package", help="package base") + parser.add_argument("-v", "--variable", + help="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", + action="append") + parser.set_defaults(action=Action.Remove, architecture="", exit_code=False, lock=None, report=False, + repository="") + return parser + + @staticmethod + def _set_patch_set_add_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for new full-diff patch subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("patch-set-add", help="add patch set", description="create or update source patches", + epilog="In order to add a patch set for the package you will need to:\n\n" + "1. Clone the AUR package manually.\n" + "2. Add required changes (e.g. external patches, edit PKGBUILD).\n" + "3. Run command, e.g. ``ahriman patch-set-add path/to/directory``.\n\n" + "By default it tracks ``*.patch`` and ``*.diff`` files, but this behavior " + "can be changed by using ``--track`` option.") + parser.add_argument("package", help="path to directory with changed files for patch addition/update", type=Path) + parser.add_argument("-t", "--track", help="files which has to be tracked", action="append", + default=["*.diff", "*.patch"]) + parser.set_defaults(action=Action.Update, architecture="", exit_code=False, lock=None, report=False, + repository="", variable=None) + return parser + @staticmethod def patch_create_from_diff(sources_dir: Path, architecture: str, track: list[str]) -> tuple[str, PkgbuildPatch]: """ @@ -155,3 +249,10 @@ class Patch(Handler): application.reporter.package_patches_remove(package_base, variable) else: application.reporter.package_patches_remove(package_base, None) # just pass as is + + arguments = [ + _set_patch_add_parser, + _set_patch_list_parser, + _set_patch_remove_parser, + _set_patch_set_add_parser, + ] diff --git a/src/ahriman/application/handlers/rebuild.py b/src/ahriman/application/handlers/rebuild.py index 40f20f34..ad5dfa43 100644 --- a/src/ahriman/application/handlers/rebuild.py +++ b/src/ahriman/application/handlers/rebuild.py @@ -20,8 +20,9 @@ import argparse from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration +from ahriman.core.utils import enum_values, extract_user from ahriman.models.build_status import BuildStatusEnum from ahriman.models.package import Package from ahriman.models.packagers import Packagers @@ -59,6 +60,38 @@ class Rebuild(Handler): result = application.update(packages, Packagers(args.username), bump_pkgrel=args.increment) Rebuild.check_status(args.exit_code, not result.is_empty) + @staticmethod + def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for repository rebuild subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-rebuild", aliases=["rebuild"], help="rebuild repository", + description="force rebuild whole repository") + parser.add_argument("--depends-on", help="only rebuild packages that depend on specified packages", + action="append") + parser.add_argument("--dry-run", help="just perform check for packages without rebuild process itself", + action="store_true") + parser.add_argument("--from-database", + help="read packages from database instead of filesystem. This feature in particular is " + "required in case if you would like to restore repository from another repository " + "instance. Note, however, that in order to restore packages you need to have original " + "ahriman instance run with web service and have run repo-update at least once.", + action="store_true") + parser.add_argument("--increment", help="increment package release (pkgrel) on duplicate", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", + action="store_true") + parser.add_argument("-s", "--status", help="filter packages by status. Requires --from-database to be set", + type=BuildStatusEnum, choices=enum_values(BuildStatusEnum)) + parser.add_argument("-u", "--username", help="build as user", default=extract_user()) + return parser + @staticmethod def extract_packages(application: Application, status: BuildStatusEnum | None, *, from_database: bool) -> list[Package]: @@ -81,3 +114,5 @@ class Rebuild(Handler): ] return application.repository.packages() + + arguments = [_set_repo_rebuild_parser] diff --git a/src/ahriman/application/handlers/remove.py b/src/ahriman/application/handlers/remove.py index 033f34ca..8a553da0 100644 --- a/src/ahriman/application/handlers/remove.py +++ b/src/ahriman/application/handlers/remove.py @@ -20,7 +20,7 @@ import argparse from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.models.repository_id import RepositoryId @@ -45,3 +45,21 @@ class Remove(Handler): application = Application(repository_id, configuration, report=report) application.on_start() application.remove(args.package) + + @staticmethod + def _set_package_remove_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for package removal subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("package-remove", aliases=["remove"], help="remove package", + description="remove package from the repository") + parser.add_argument("package", help="package name or base", nargs="+") + return parser + + arguments = [_set_package_remove_parser] diff --git a/src/ahriman/application/handlers/remove_unknown.py b/src/ahriman/application/handlers/remove_unknown.py index d062416e..200c0be5 100644 --- a/src/ahriman/application/handlers/remove_unknown.py +++ b/src/ahriman/application/handlers/remove_unknown.py @@ -20,7 +20,7 @@ import argparse from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.formatters import StringPrinter from ahriman.models.repository_id import RepositoryId @@ -53,3 +53,21 @@ class RemoveUnknown(Handler): return application.remove(unknown_packages) + + @staticmethod + def _set_repo_remove_unknown_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for remove unknown packages subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-remove-unknown", aliases=["remove-unknown"], help="remove unknown packages", + description="remove packages which are missing in AUR and do not have local PKGBUILDs") + parser.add_argument("--dry-run", help="just perform check for packages without removal", action="store_true") + return parser + + arguments = [_set_repo_remove_unknown_parser] diff --git a/src/ahriman/application/handlers/repositories.py b/src/ahriman/application/handlers/repositories.py index 049d777e..fbc790c0 100644 --- a/src/ahriman/application/handlers/repositories.py +++ b/src/ahriman/application/handlers/repositories.py @@ -19,7 +19,7 @@ # import argparse -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.formatters import RepositoryPrinter from ahriman.models.repository_id import RepositoryId @@ -52,3 +52,23 @@ class Repositories(Handler): ) for repository in cls.repositories_extract(dummy_args): RepositoryPrinter(repository)(verbose=not args.id_only) + + @staticmethod + def _set_service_repositories(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for repositories listing + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("service-repositories", help="show repositories", + description="list all available repositories") + parser.add_argument("--id-only", help="show machine readable identifier instead", + action=argparse.BooleanOptionalAction, default=False) + parser.set_defaults(architecture="", lock=None, report=False, repository="", unsafe=True) + return parser + + arguments = [_set_service_repositories] diff --git a/src/ahriman/application/handlers/restore.py b/src/ahriman/application/handlers/restore.py index 969d67fc..94c4dd95 100644 --- a/src/ahriman/application/handlers/restore.py +++ b/src/ahriman/application/handlers/restore.py @@ -20,7 +20,9 @@ import argparse import tarfile -from ahriman.application.handlers.handler import Handler +from pathlib import Path + +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.models.repository_id import RepositoryId @@ -46,3 +48,23 @@ class Restore(Handler): """ with tarfile.open(args.path) as archive: archive.extractall(path=args.output) # nosec + + @staticmethod + def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for repository restore subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-restore", help="restore repository data", + description="restore settings and database") + parser.add_argument("path", help="path of the input archive", type=Path) + parser.add_argument("-o", "--output", help="root path of the extracted files", type=Path, default=Path("/")) + parser.set_defaults(architecture="", lock=None, report=False, repository="", unsafe=True) + return parser + + arguments = [_set_repo_restore_parser] diff --git a/src/ahriman/application/handlers/run.py b/src/ahriman/application/handlers/run.py index 051c608a..c244c0e1 100644 --- a/src/ahriman/application/handlers/run.py +++ b/src/ahriman/application/handlers/run.py @@ -20,7 +20,7 @@ import argparse import shlex -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.models.repository_id import RepositoryId @@ -49,6 +49,25 @@ class Run(Handler): status = Run.run_command(shlex.split(command), parser) Run.check_status(True, status) + @staticmethod + def _set_service_run(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for multicommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("service-run", aliases=["run"], help="run multiple commands", + description="run multiple commands on success run of the previous command", + epilog="Commands must be quoted by using usual bash rules. Processes will be spawned " + "under the same user as this command.") + parser.add_argument("command", help="command to be run (quoted) without ``ahriman``", nargs="+") + parser.set_defaults(architecture="", lock=None, report=False, repository="", unsafe=True) + return parser + @staticmethod def run_command(command: list[str], parser: argparse.ArgumentParser) -> bool: """ @@ -64,3 +83,5 @@ class Run(Handler): args = parser.parse_args(command) handler: Handler = args.handler return handler.execute(args) == 0 + + arguments = [_set_service_run] diff --git a/src/ahriman/application/handlers/search.py b/src/ahriman/application/handlers/search.py index 5df8a02c..875d1423 100644 --- a/src/ahriman/application/handlers/search.py +++ b/src/ahriman/application/handlers/search.py @@ -22,7 +22,7 @@ import argparse from collections.abc import Callable, Iterable from dataclasses import fields -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.alpm.remote import AUR, Official from ahriman.core.configuration import Configuration from ahriman.core.exceptions import OptionError @@ -68,6 +68,33 @@ class Search(Handler): for package in Search.sort(packages_list, args.sort_by): AurPrinter(package)(verbose=args.info) + @staticmethod + def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for AUR search subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("aur-search", aliases=["search"], help="search for package", + description="search for package in AUR using API") + parser.add_argument("search", + help="search terms, can be specified multiple times, the result will match all terms", + nargs="+") + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", + action="store_true") + parser.add_argument("--info", help="show additional package information", + action=argparse.BooleanOptionalAction, default=False) + 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(Search.SORT_FIELDS)) + parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True) + return parser + @staticmethod def sort(packages: Iterable[AURPackage], sort_by: str) -> list[AURPackage]: """ @@ -90,3 +117,5 @@ class Search(Handler): comparator: Callable[[AURPackage], tuple[str, str]] =\ lambda package: (getattr(package, sort_by), package.name) return sorted(packages, key=comparator) + + arguments = [_set_aur_search_parser] diff --git a/src/ahriman/application/handlers/service_updates.py b/src/ahriman/application/handlers/service_updates.py index db2582aa..3e073818 100644 --- a/src/ahriman/application/handlers/service_updates.py +++ b/src/ahriman/application/handlers/service_updates.py @@ -20,7 +20,7 @@ import argparse from ahriman import __version__ -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.formatters import UpdatePrinter from ahriman.models.package import Package @@ -58,3 +58,23 @@ class ServiceUpdates(Handler): UpdatePrinter(remote, local_version)(verbose=True, separator=" -> ") ServiceUpdates.check_status(args.exit_code, same_version) + + @staticmethod + def _set_help_updates_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for service update check subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("help-updates", help="check for service updates", + description="request AUR for current version and compare with current service version") + parser.add_argument("-e", "--exit-code", help="return non-zero exit code if updates available", + action="store_true") + parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True) + return parser + + arguments = [_set_help_updates_parser] diff --git a/src/ahriman/application/handlers/setup.py b/src/ahriman/application/handlers/setup.py index eb2e6605..89385444 100644 --- a/src/ahriman/application/handlers/setup.py +++ b/src/ahriman/application/handlers/setup.py @@ -24,11 +24,13 @@ from pwd import getpwuid from urllib.parse import quote_plus as urlencode from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.exceptions import MissingArchitectureError +from ahriman.core.utils import enum_values from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_paths import RepositoryPaths +from ahriman.models.sign_settings import SignSettings from ahriman.models.user import User @@ -80,6 +82,44 @@ class Setup(Handler): # lazy database sync application.repository.pacman.handle # pylint: disable=pointless-statement + @staticmethod + def _set_service_setup_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for setup subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("service-setup", aliases=["init", "repo-init", "repo-setup", "setup"], + help="initial service configuration", + description="create initial service configuration, requires root", + epilog="Create minimal configuration for the service according to provided options.") + parser.add_argument("--build-as-user", help="force makepkg user to the specific one") + parser.add_argument("--from-configuration", help="path to default devtools pacman configuration", + type=Path, + default=Path("/") / "usr" / "share" / "devtools" / "pacman.conf.d" / "extra.conf") + parser.add_argument("--generate-salt", help="generate salt for user passwords", + action=argparse.BooleanOptionalAction, default=False) + parser.add_argument("--makeflags-jobs", + help="append MAKEFLAGS variable with parallelism set to number of cores", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--mirror", help="use the specified explicitly mirror instead of including mirrorlist") + parser.add_argument("--multilib", help="add or do not multilib repository", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--packager", help="packager name and email", required=True) + parser.add_argument("--server", help="server to be used for devtools. If none set, local files will be used") + parser.add_argument("--sign-key", help="sign key id") + parser.add_argument("--sign-target", help="sign options", action="append", + 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(lock=None, quiet=True, report=False, unsafe=True) + return parser + @staticmethod def build_command(root: Path, repository_id: RepositoryId) -> Path: """ @@ -240,3 +280,5 @@ class Setup(Handler): command.unlink(missing_ok=True) command.symlink_to(Setup.ARCHBUILD_COMMAND_PATH) paths.chown(command) # we would like to keep owner inside ahriman's home + + arguments = [_set_service_setup_parser] diff --git a/src/ahriman/application/handlers/shell.py b/src/ahriman/application/handlers/shell.py index c7ae94c6..750f3a09 100644 --- a/src/ahriman/application/handlers/shell.py +++ b/src/ahriman/application/handlers/shell.py @@ -23,7 +23,7 @@ import sys from pathlib import Path -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.formatters import StringPrinter from ahriman.models.repository_id import RepositoryId @@ -63,3 +63,23 @@ class Shell(Handler): code.interact(local=local_variables) else: code.InteractiveConsole(locals=local_variables).runcode(args.code) + + @staticmethod + def _set_service_shell_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for shell subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("service-shell", aliases=["shell"], help="invoke python shell", + description="drop into python shell") + parser.add_argument("code", help="instead of dropping into shell, just execute the specified code", nargs="?") + parser.add_argument("-v", "--verbose", help=argparse.SUPPRESS, action="store_true") + parser.set_defaults(lock=None, report=False) + return parser + + arguments = [_set_service_shell_parser] diff --git a/src/ahriman/application/handlers/sign.py b/src/ahriman/application/handlers/sign.py index 50af427b..5cfc31cc 100644 --- a/src/ahriman/application/handlers/sign.py +++ b/src/ahriman/application/handlers/sign.py @@ -20,7 +20,7 @@ import argparse from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.models.repository_id import RepositoryId @@ -43,3 +43,22 @@ class Sign(Handler): report(bool): force enable or disable reporting """ Application(repository_id, configuration, report=report).sign(args.package) + + @staticmethod + def _set_repo_sign_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for sign subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-sign", aliases=["sign"], help="sign packages", + description="(re-)sign packages and repository database according to current settings", + epilog="Sign repository and/or packages as configured.") + parser.add_argument("package", help="sign only specified packages", nargs="*") + return parser + + arguments = [_set_repo_sign_parser] diff --git a/src/ahriman/application/handlers/statistics.py b/src/ahriman/application/handlers/statistics.py index aef8320c..c538b621 100644 --- a/src/ahriman/application/handlers/statistics.py +++ b/src/ahriman/application/handlers/statistics.py @@ -25,11 +25,11 @@ from collections.abc import Callable from pathlib import Path from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.formatters import EventStatsPrinter, PackageStatsPrinter -from ahriman.core.utils import pretty_datetime -from ahriman.models.event import Event +from ahriman.core.utils import enum_values, pretty_datetime +from ahriman.models.event import Event, EventType from ahriman.models.repository_id import RepositoryId @@ -68,6 +68,30 @@ class Statistics(Handler): case _: Statistics.stats_for_package(args.event, events, args.chart) + @staticmethod + def _set_repo_statistics_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for repository statistics subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-statistics", help="repository statistics", + description="fetch repository statistics") + parser.add_argument("package", help="fetch only events for the specified package", nargs="?") + parser.add_argument("--chart", help="create updates chart and save it to the specified path", type=Path) + parser.add_argument("-e", "--event", help="event type filter", + type=EventType, choices=enum_values(EventType), default=EventType.PackageUpdated) + parser.add_argument("--from-date", help="only fetch events which are newer than the date") + parser.add_argument("--limit", help="limit response by specified amount of events", type=int, default=-1) + parser.add_argument("--offset", help="skip specified amount of events", type=int, default=0) + parser.add_argument("--to-date", help="only fetch events which are older than the date") + parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True) + return parser + @staticmethod def event_stats(event_type: str, events: list[Event]) -> None: """ @@ -168,3 +192,5 @@ class Statistics(Handler): # chart if enabled if chart_path is not None: Statistics.plot_packages(event_type, by_object_id, chart_path) + + arguments = [_set_repo_statistics_parser] diff --git a/src/ahriman/application/handlers/status.py b/src/ahriman/application/handlers/status.py index ddbfca3d..2d007b05 100644 --- a/src/ahriman/application/handlers/status.py +++ b/src/ahriman/application/handlers/status.py @@ -22,10 +22,11 @@ import argparse from collections.abc import Callable from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.formatters import PackagePrinter, StatusPrinter -from ahriman.models.build_status import BuildStatus +from ahriman.core.utils import enum_values +from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.package import Package from ahriman.models.repository_id import RepositoryId @@ -68,3 +69,31 @@ class Status(Handler): lambda item: args.status is None or item[1].status == args.status for package, package_status in sorted(filter(filter_fn, packages), key=comparator): PackagePrinter(package, package_status)(verbose=args.info) + + @staticmethod + def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for package status subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("package-status", aliases=["status"], help="get package status", + description="request status of the package", + epilog="This command requests package status from the web interface " + "if it is available.") + parser.add_argument("package", help="filter status by package base", nargs="*") + parser.add_argument("--ahriman", help="get service status itself", action="store_true") + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", + action="store_true") + parser.add_argument("--info", help="show additional package information", + action=argparse.BooleanOptionalAction, default=False) + parser.add_argument("-s", "--status", help="filter packages by status", + type=BuildStatusEnum, choices=enum_values(BuildStatusEnum)) + parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True) + return parser + + arguments = [_set_package_status_parser] diff --git a/src/ahriman/application/handlers/status_update.py b/src/ahriman/application/handlers/status_update.py index 085060fe..d46103be 100644 --- a/src/ahriman/application/handlers/status_update.py +++ b/src/ahriman/application/handlers/status_update.py @@ -20,9 +20,11 @@ import argparse from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration +from ahriman.core.utils import enum_values from ahriman.models.action import Action +from ahriman.models.build_status import BuildStatusEnum from ahriman.models.repository_id import RepositoryId @@ -59,3 +61,67 @@ class StatusUpdate(Handler): case Action.Remove: for package in args.package: client.package_remove(package) + + @staticmethod + def _set_package_status_remove_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for package status remove subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("package-status-remove", help="remove package status", + description="remove the package from the status page", + epilog="Please note that this subcommand does not remove the package itself, it just " + "clears the status page.") + parser.add_argument("package", help="remove specified packages from status page", nargs="+") + parser.set_defaults(action=Action.Remove, lock=None, quiet=True, report=False, unsafe=True) + return parser + + @staticmethod + def _set_package_status_update_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for package status update subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("package-status-update", aliases=["status-update"], help="update package status", + description="update package status on the status page") + parser.add_argument("package", help="set status for specified packages. " + "If no packages supplied, service status will be updated", + nargs="*") + parser.add_argument("-s", "--status", help="new package build status", + type=BuildStatusEnum, choices=enum_values(BuildStatusEnum), default=BuildStatusEnum.Success) + parser.set_defaults(action=Action.Update, lock=None, quiet=True, report=False, unsafe=True) + return parser + + @staticmethod + def _set_repo_status_update_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for repository status update subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-status-update", help="update repository status", + description="update repository status on the status page") + parser.add_argument("-s", "--status", help="new status", + type=BuildStatusEnum, choices=enum_values(BuildStatusEnum), default=BuildStatusEnum.Success) + parser.set_defaults(action=Action.Update, lock=None, package=[], quiet=True, report=False, unsafe=True) + return parser + + arguments = [ + _set_package_status_remove_parser, + _set_package_status_update_parser, + _set_repo_status_update_parser, + ] diff --git a/src/ahriman/application/handlers/structure.py b/src/ahriman/application/handlers/structure.py index efb2d11e..e546a785 100644 --- a/src/ahriman/application/handlers/structure.py +++ b/src/ahriman/application/handlers/structure.py @@ -20,7 +20,7 @@ import argparse from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.formatters import StringPrinter, TreePrinter from ahriman.core.tree import Tree @@ -58,3 +58,23 @@ class Structure(Handler): # empty line StringPrinter("")(verbose=False) + + @staticmethod + def _set_repo_tree_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for repository tree subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-tree", help="dump repository tree", + description="dump repository tree based on packages dependencies") + parser.add_argument("-p", "--partitions", help="also divide packages by independent partitions", + type=int, default=1) + parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True) + return parser + + arguments = [_set_repo_tree_parser] diff --git a/src/ahriman/application/handlers/tree_migrate.py b/src/ahriman/application/handlers/tree_migrate.py index e29598fd..1984e132 100644 --- a/src/ahriman/application/handlers/tree_migrate.py +++ b/src/ahriman/application/handlers/tree_migrate.py @@ -19,7 +19,7 @@ # import argparse -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_paths import RepositoryPaths @@ -50,6 +50,22 @@ class TreeMigrate(Handler): # perform migration TreeMigrate.tree_move(current_tree, target_tree) + @staticmethod + 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") + parser.set_defaults(lock=None, quiet=True, report=False) + return parser + @staticmethod def tree_move(from_tree: RepositoryPaths, to_tree: RepositoryPaths) -> None: """ @@ -66,3 +82,5 @@ class TreeMigrate(Handler): RepositoryPaths.repository, ): attribute.fget(from_tree).rename(attribute.fget(to_tree)) # type: ignore[attr-defined] + + arguments = [_set_service_tree_migrate_parser] diff --git a/src/ahriman/application/handlers/triggers.py b/src/ahriman/application/handlers/triggers.py index f3739d90..9d0ea46a 100644 --- a/src/ahriman/application/handlers/triggers.py +++ b/src/ahriman/application/handlers/triggers.py @@ -20,7 +20,7 @@ import argparse from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.models.repository_id import RepositoryId from ahriman.models.result import Result @@ -49,3 +49,60 @@ class Triggers(Handler): loader.triggers = [loader.load_trigger(trigger, repository_id, configuration) for trigger in args.trigger] application.on_start() application.on_result(Result()) + + @staticmethod + def _set_repo_report_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for report subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-report", aliases=["report"], help="generate report", + description="generate repository report according to current settings", + epilog="Create and/or update repository report as configured.") + parser.set_defaults(trigger=["ahriman.core.report.ReportTrigger"]) + return parser + + @staticmethod + def _set_repo_sync_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for repository sync subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-sync", aliases=["sync"], help="sync repository", + description="sync repository files to remote server according to current settings", + epilog="Synchronize the repository to remote services as configured.") + parser.set_defaults(trigger=["ahriman.core.upload.UploadTrigger"]) + return parser + + @staticmethod + def _set_repo_triggers_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for repository triggers subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-triggers", help="run triggers", + description="run triggers on empty build result as configured by settings") + parser.add_argument("trigger", help="instead of running all triggers as set by configuration, just process " + "specified ones in order of mention", nargs="*") + return parser + + arguments = [ + _set_repo_report_parser, + _set_repo_sync_parser, + _set_repo_triggers_parser, + ] diff --git a/src/ahriman/application/handlers/triggers_support.py b/src/ahriman/application/handlers/triggers_support.py new file mode 100644 index 00000000..f7e31691 --- /dev/null +++ b/src/ahriman/application/handlers/triggers_support.py @@ -0,0 +1,70 @@ +# +# Copyright (c) 2021-2024 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 . +# +import argparse + +from ahriman.application.handlers.handler import SubParserAction +from ahriman.application.handlers.triggers import Triggers + + +class TriggersSupport(Triggers): + """ + additional triggers handlers for support commands + """ + + @staticmethod + def _set_repo_create_keyring_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for create-keyring subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-create-keyring", help="create keyring package", + description="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") + parser.set_defaults(trigger=["ahriman.core.support.KeyringTrigger"]) + return parser + + @staticmethod + def _set_repo_create_mirrorlist_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for create-mirrorlist subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-create-mirrorlist", help="create mirrorlist package", + description="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") + parser.set_defaults(trigger=["ahriman.core.support.MirrorlistTrigger"]) + return parser + + arguments = [ + _set_repo_create_keyring_parser, + _set_repo_create_mirrorlist_parser, + ] diff --git a/src/ahriman/application/handlers/unsafe_commands.py b/src/ahriman/application/handlers/unsafe_commands.py index 11a6347d..7f6da703 100644 --- a/src/ahriman/application/handlers/unsafe_commands.py +++ b/src/ahriman/application/handlers/unsafe_commands.py @@ -19,7 +19,7 @@ # import argparse -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.formatters import StringPrinter from ahriman.models.repository_id import RepositoryId @@ -52,6 +52,25 @@ class UnsafeCommands(Handler): for command in unsafe_commands: StringPrinter(command)(verbose=True) + @staticmethod + def _set_help_commands_unsafe_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for listing unsafe commands + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("help-commands-unsafe", help="list unsafe commands", + description="list unsafe commands as defined in default args") + parser.add_argument("subcommand", + 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(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True) + return parser + @staticmethod def check_unsafe(command: list[str], unsafe_commands: list[str], parser: argparse.ArgumentParser) -> None: """ @@ -81,3 +100,5 @@ class UnsafeCommands(Handler): subparser = next((action for action in parser._actions if isinstance(action, argparse._SubParsersAction)), None) actions = subparser.choices if subparser is not None else {} return sorted(action_name for action_name, action in actions.items() if action.get_default("unsafe")) + + arguments = [_set_help_commands_unsafe_parser] diff --git a/src/ahriman/application/handlers/update.py b/src/ahriman/application/handlers/update.py index dafcac61..1f965c2d 100644 --- a/src/ahriman/application/handlers/update.py +++ b/src/ahriman/application/handlers/update.py @@ -22,8 +22,9 @@ import argparse from collections.abc import Callable from ahriman.application.application import Application -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration +from ahriman.core.utils import extract_user from ahriman.models.packagers import Packagers from ahriman.models.repository_id import RepositoryId @@ -64,6 +65,78 @@ class Update(Handler): result = application.update(packages, packagers, bump_pkgrel=args.increment) Update.check_status(args.exit_code, not result.is_empty) + @staticmethod + def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for repository check subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-check", aliases=["check"], help="check for updates", + description="check for packages updates. Same as repo-update --dry-run --no-manual") + parser.add_argument("package", help="filter check by package base", nargs="*") + parser.add_argument("--changes", help="calculate changes from the latest known commit if available", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--check-files", help="enable or disable checking of broken dependencies " + "(e.g. dynamically linked libraries or modules directories)", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", + action="store_true") + parser.add_argument("--vcs", help="fetch actual version of VCS packages", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, " + "-yy to force refresh even if up to date", + action="count", default=False) + parser.set_defaults(aur=True, dependencies=False, dry_run=True, increment=False, local=True, manual=False, + username=None) + return parser + + @staticmethod + def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for repository update subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("repo-update", aliases=["update"], help="update packages", + description="check for packages updates and run build process if requested") + parser.add_argument("package", help="filter check by package base", nargs="*") + parser.add_argument("--aur", help="enable or disable checking for AUR updates", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--changes", help="calculate changes from the latest known commit if available. " + "Only applicable in dry run mode", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--check-files", help="enable or disable checking of broken dependencies " + "(e.g. dynamically linked libraries or modules directories)", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--dependencies", help="process missing package dependencies", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--dry-run", help="just perform check for updates, same as check command", + action="store_true") + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", + action="store_true") + parser.add_argument("--increment", help="increment package release (pkgrel) on duplicate", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--local", help="enable or disable checking of local packages for updates", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("--manual", help="include or exclude manual updates", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("-u", "--username", help="build as user", default=extract_user()) + parser.add_argument("--vcs", help="fetch actual version of VCS packages", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, " + "-yy to force refresh even if up to date", + action="count", default=False) + return parser + @staticmethod def log_fn(application: Application, dry_run: bool) -> Callable[[str], None]: """ @@ -79,3 +152,8 @@ class Update(Handler): def inner(line: str) -> None: return print(line) if dry_run else application.logger.info(line) # pylint: disable=bad-builtin return inner + + arguments = [ + _set_repo_check_parser, + _set_repo_update_parser, + ] diff --git a/src/ahriman/application/handlers/users.py b/src/ahriman/application/handlers/users.py index c34d6201..53587edb 100644 --- a/src/ahriman/application/handlers/users.py +++ b/src/ahriman/application/handlers/users.py @@ -20,14 +20,16 @@ import argparse import getpass -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite from ahriman.core.exceptions import PasswordError from ahriman.core.formatters import UserPrinter +from ahriman.core.utils import enum_values from ahriman.models.action import Action from ahriman.models.repository_id import RepositoryId from ahriman.models.user import User +from ahriman.models.user_access import UserAccess class Users(Handler): @@ -65,6 +67,73 @@ class Users(Handler): case Action.Remove: database.user_remove(args.username) + @staticmethod + def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for create user subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("user-add", help="create or update user", + description="update user for web services with the given password and role. " + "In case if password was not entered it will be asked interactively") + parser.add_argument("username", help="username for web service") + parser.add_argument("--key", help="optional PGP key used by this user. The private key must be imported") + parser.add_argument("--packager", help="optional packager id used for build process in form of " + "`Name Surname `") + parser.add_argument( + "-p", "--password", help="user password. Blank password will be treated as empty password, " + "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(action=Action.Update, architecture="", exit_code=False, lock=None, quiet=True, + report=False, repository="") + return parser + + @staticmethod + def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for user list subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("user-list", help="user known users and their access", + description="list users from the user mapping and their roles") + 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(action=Action.List, architecture="", lock=None, quiet=True, report=False, repository="", + unsafe=True) + return parser + + @staticmethod + def _set_user_remove_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for user removal subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("user-remove", help="remove user", + description="remove user from the user mapping and update the configuration") + parser.add_argument("username", help="username for web service") + parser.set_defaults(action=Action.Remove, architecture="", exit_code=False, lock=None, quiet=True, + report=False, repository="") + return parser + @staticmethod def user_create(args: argparse.Namespace) -> User: """ @@ -92,3 +161,9 @@ class Users(Handler): return User(username=args.username, password=password, access=args.role, packager_id=args.packager, key=args.key) + + arguments = [ + _set_user_add_parser, + _set_user_list_parser, + _set_user_remove_parser, + ] diff --git a/src/ahriman/application/handlers/validate.py b/src/ahriman/application/handlers/validate.py index 34694a51..336893f5 100644 --- a/src/ahriman/application/handlers/validate.py +++ b/src/ahriman/application/handlers/validate.py @@ -22,7 +22,7 @@ import copy from typing import Any -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.configuration.schema import CONFIGURATION_SCHEMA, ConfigurationSchema from ahriman.core.exceptions import ExtensionError @@ -63,6 +63,25 @@ class Validate(Handler): # as we reach this part it means that we always have errors Validate.check_status(args.exit_code, False) + @staticmethod + def _set_service_config_validate_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for config validation subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("service-config-validate", aliases=["config-validate", "repo-config-validate"], + help="validate system configuration", + description="validate configuration and print found errors") + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if configuration is invalid", + action="store_true") + parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True) + return parser + @staticmethod def schema(repository_id: RepositoryId, configuration: Configuration) -> ConfigurationSchema: """ @@ -136,3 +155,5 @@ class Validate(Handler): Validate.schema_merge(value, schema[key]) return schema + + arguments = [_set_service_config_validate_parser] diff --git a/src/ahriman/application/handlers/versions.py b/src/ahriman/application/handlers/versions.py index a187734e..75379cc1 100644 --- a/src/ahriman/application/handlers/versions.py +++ b/src/ahriman/application/handlers/versions.py @@ -25,7 +25,7 @@ from collections.abc import Generator from importlib import metadata from ahriman import __version__ -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.formatters import VersionPrinter from ahriman.models.repository_id import RepositoryId @@ -59,6 +59,22 @@ class Versions(Handler): packages = Versions.package_dependencies("ahriman") VersionPrinter("Installed packages", dict(packages))(verbose=False, separator=" ") + @staticmethod + def _set_help_version_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for version subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("help-version", aliases=["version"], help="application version", + description="print application and its dependencies versions") + parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True) + return parser + @staticmethod def package_dependencies(root: str) -> Generator[tuple[str, str], None, None]: """ @@ -96,3 +112,5 @@ class Versions(Handler): yield distribution.name, distribution.version except metadata.PackageNotFoundError: continue + + arguments = [_set_help_version_parser] diff --git a/src/ahriman/application/handlers/web.py b/src/ahriman/application/handlers/web.py index 5a8b71a6..552eed6c 100644 --- a/src/ahriman/application/handlers/web.py +++ b/src/ahriman/application/handlers/web.py @@ -20,8 +20,9 @@ import argparse from collections.abc import Generator +from pathlib import Path -from ahriman.application.handlers.handler import Handler +from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration from ahriman.core.spawn import Spawn from ahriman.core.triggers import TriggerLoader @@ -71,6 +72,21 @@ class Web(Handler): spawner.stop() spawner.join() + @staticmethod + def _set_web_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for web subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("web", help="web server", description="start web server") + parser.set_defaults(architecture="", lock=Path("ahriman-web.pid"), report=False, repository="") + return parser + @staticmethod def extract_arguments(args: argparse.Namespace, configuration: Configuration) -> Generator[str, None, None]: """ @@ -100,3 +116,5 @@ class Web(Handler): # arguments from configuration if (wait_timeout := configuration.getint("web", "wait_timeout", fallback=None)) is not None: yield from ["--wait-timeout", str(wait_timeout)] + + arguments = [_set_web_parser] diff --git a/src/ahriman/core/auth/helpers.py b/src/ahriman/core/auth/helpers.py index 70a4484f..42d2882e 100644 --- a/src/ahriman/core/auth/helpers.py +++ b/src/ahriman/core/auth/helpers.py @@ -17,14 +17,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from typing import Any - try: import aiohttp_security _has_aiohttp_security = True except ImportError: _has_aiohttp_security = False +from typing import Any + __all__ = ["authorized_userid", "check_authorized", "forget", "remember"] diff --git a/src/ahriman/core/log/journal_handler.py b/src/ahriman/core/log/journal_handler.py index 6641ccc7..2cb9b8bb 100644 --- a/src/ahriman/core/log/journal_handler.py +++ b/src/ahriman/core/log/journal_handler.py @@ -17,7 +17,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from logging import NullHandler # pylint: disable=imports-out-of-order +# pylint: disable=imports-out-of-order +from logging import NullHandler from typing import Any diff --git a/src/ahriman/core/module_loader.py b/src/ahriman/core/module_loader.py new file mode 100644 index 00000000..1f870504 --- /dev/null +++ b/src/ahriman/core/module_loader.py @@ -0,0 +1,78 @@ +# +# Copyright (c) 2021-2024 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 . +# +import inspect + +from collections.abc import Generator +from importlib import import_module +from pathlib import Path +from pkgutil import ModuleInfo, walk_packages +from types import ModuleType +from typing import Any, TypeGuard, TypeVar + + +__all__ = ["implementations"] + + +T = TypeVar("T") + + +def _modules(module_root: Path, prefix: str) -> Generator[ModuleInfo, None, None]: + """ + extract available modules from package + + Args: + module_root(Path): module root path + prefix(str): modules package prefix + + Yields: + ModuleInfo: module information each available module + """ + prefix = f"{prefix}." if prefix else "" + for module_info in walk_packages([str(module_root)], prefix): + if module_info.ispkg: + yield from _modules(module_root / module_info.name, prefix) + else: + yield module_info + + +def implementations(root_module: ModuleType, base_class: type[T]) -> Generator[type[T], None, None]: + """ + extract implementations of the ``base_class`` from the module + + Args: + root_module(ModuleType): root module + base_class(type[T]): base class type + + Yields: + type[T]: found implementations + """ + def is_base_class(clazz: Any) -> TypeGuard[type[T]]: + return inspect.isclass(clazz) \ + and issubclass(clazz, base_class) and clazz != base_class \ + and clazz.__module__ == module.__name__ + + prefix = root_module.__name__ + + for module_root in root_module.__path__: + for module_info in _modules(Path(module_root), prefix): + module = import_module(module_info.name) + + for _, attribute in inspect.getmembers(module, is_base_class): + yield attribute diff --git a/src/ahriman/core/utils.py b/src/ahriman/core/utils.py index bf50f9e5..c7ecb73e 100644 --- a/src/ahriman/core/utils.py +++ b/src/ahriman/core/utils.py @@ -461,7 +461,7 @@ def trim_package(package_name: str) -> str: str: package name without description or version bound """ for symbol in ("<", "=", ">", ":"): - package_name = package_name.partition(symbol)[0] + package_name, *_ = package_name.split(symbol, maxsplit=1) return package_name @@ -478,7 +478,6 @@ def utcnow() -> datetime.datetime: def walk(directory_path: Path) -> Generator[Path, None, None]: """ list all file paths in given directory - Credits to https://stackoverflow.com/a/64915960 Args: directory_path(Path): root directory path @@ -487,18 +486,13 @@ def walk(directory_path: Path) -> Generator[Path, None, None]: Path: all found files in given directory with full path Examples: - Since the :mod:`pathlib` module does not provide an alternative to :func:`os.walk()`, this wrapper - can be used instead:: + Wrapper around :func:`pathlib.Path.walk`, which yields only files instead:: >>> from pathlib import Path >>> >>> for file_path in walk(Path.cwd()): >>> print(file_path) - - Note, however, that unlike the original method, it does not yield directories. """ - for element in directory_path.iterdir(): - if element.is_dir(): - yield from walk(element) - continue - yield element + for root, _, files in directory_path.walk(follow_symlinks=True): + for file in files: + yield root / file diff --git a/src/ahriman/web/routes.py b/src/ahriman/web/routes.py index 3729ca29..8e96b811 100644 --- a/src/ahriman/web/routes.py +++ b/src/ahriman/web/routes.py @@ -18,93 +18,35 @@ # along with this program. If not, see . # from aiohttp.web import Application, View -from collections.abc import Generator -from importlib.machinery import SourceFileLoader -from pathlib import Path -from pkgutil import ModuleInfo, iter_modules -from types import ModuleType -from typing import Any, Type, TypeGuard + +import ahriman.web.views from ahriman.core.configuration import Configuration +from ahriman.core.module_loader import implementations from ahriman.web.views.base import BaseView __all__ = ["setup_routes"] -def _dynamic_routes(module_root: Path, configuration: Configuration) -> dict[str, Type[View]]: +def _dynamic_routes(configuration: Configuration) -> dict[str, type[View]]: """ extract dynamic routes based on views Args: - module_root(Path): root module path with views configuration(Configuration): configuration instance Returns: - dict[str, Type[View]]: map of the route to its view + dict[str, type[View]]: map of the route to its view """ - def is_base_view(clazz: Any) -> TypeGuard[Type[BaseView]]: - return isinstance(clazz, type) and issubclass(clazz, BaseView) - - routes: dict[str, Type[View]] = {} - for module_info in _modules(module_root): - module = _module(module_info) - - for attribute_name in dir(module): - view = getattr(module, attribute_name) - if not is_base_view(view): - continue - - view_routes = view.routes(configuration) - routes.update([(route, view) for route in view_routes]) + routes: dict[str, type[View]] = {} + for view in implementations(ahriman.web.views, BaseView): + view_routes = view.routes(configuration) + routes.update([(route, view) for route in view_routes]) return routes -def _module(module_info: ModuleInfo) -> ModuleType: - """ - load module from its info - - Args: - module_info(ModuleInfo): module info descriptor - - Returns: - ModuleType: loaded module - - Raises: - ValueError: if loader is not an instance of :class:`importlib.machinery.SourceFileLoader` - """ - module_spec = module_info.module_finder.find_spec(module_info.name, None) - if module_spec is None: - raise ValueError(f"Module specification of {module_info.name} is empty") - - loader = module_spec.loader - if not isinstance(loader, SourceFileLoader): - raise ValueError(f"Module {module_info.name} loader is not an instance of SourceFileLoader") - - module = ModuleType(loader.name) - loader.exec_module(module) - - return module - - -def _modules(module_root: Path) -> Generator[ModuleInfo, None, None]: - """ - extract available modules from package - - Args: - module_root(Path): module root path - - Yields: - ModuleInfo: module information each available module - """ - for module_info in iter_modules([str(module_root)]): - if module_info.ispkg: - yield from _modules(module_root / module_info.name) - else: - yield module_info - - def setup_routes(application: Application, configuration: Configuration) -> None: """ setup all defined routes @@ -115,6 +57,5 @@ def setup_routes(application: Application, configuration: Configuration) -> None """ application.router.add_static("/static", configuration.getpath("web", "static_path"), follow_symlinks=True) - views_root = Path(__file__).parent / "views" - for route, view in _dynamic_routes(views_root, configuration).items(): + for route, view in _dynamic_routes(configuration).items(): application.router.add_view(route, view) diff --git a/subpackages.py b/subpackages.py new file mode 100644 index 00000000..2c9b4c83 --- /dev/null +++ b/subpackages.py @@ -0,0 +1,133 @@ +# +# Copyright (c) 2021-2024 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 . +# +import argparse +import shutil +import site +import sys + +from pathlib import Path + + +prefix = Path(sys.prefix).relative_to("/") +site_packages = Path(site.getsitepackages()[0]).relative_to("/") +SUBPACKAGES = { + "ahriman-core": [ + prefix / "bin", + prefix / "lib" / "systemd", + prefix / "share", + site_packages, + ], + "ahriman-triggers": [ + prefix / "share" / "ahriman" / "settings" / "ahriman.ini.d" / "00-triggers.ini", + site_packages / "ahriman" / "application" / "handlers" / "triggers_support.py", + site_packages / "ahriman" / "core" / "distributed", + site_packages / "ahriman" / "core" / "support", + ], + "ahriman-web": [ + prefix / "lib" / "systemd" / "system" / "ahriman-web.service", + prefix / "lib" / "systemd" / "system" / "ahriman-web@.service", + prefix / "share" / "ahriman" / "settings" / "ahriman.ini.d" / "00-web.ini", + prefix / "share" / "ahriman" / "templates" / "api.jinja2", + prefix / "share" / "ahriman" / "templates" / "build-status", + prefix / "share" / "ahriman" / "templates" / "build-status.jinja2", + prefix / "share" / "ahriman" / "templates" / "error.jinja2", + site_packages / "ahriman" / "application" / "handlers" / "web.py", + site_packages / "ahriman" / "core" / "auth", + site_packages / "ahriman" / "web", + ], +} + + +def subpackages(root: Path) -> dict[str, list[Path]]: + """ + extend list of subpackages + + Args: + root(Path): root directory + + Returns: + dict[str, list[Path]]: extended list of files which belong to a specific package + """ + for package, paths in SUBPACKAGES.items(): + new_paths = [] + for path in paths: + full_path = root / path + + match path.suffix: + case ".py": + pycache_path = full_path.parent / "__pycache__" + new_paths.extend( + new_path.relative_to(root) for new_path in pycache_path.glob(f"{full_path.stem}.*.pyc") + ) + + SUBPACKAGES[package].extend(new_paths) + + return SUBPACKAGES + + +def process(root: Path, include: list[Path], exclude: list[Path]) -> None: + """ + remove files based on patterns + + Args: + root(Path): root directory + include(list[Path]): list of files to include to the subpackage + exclude(list[Path]): list of files to exclude from the subpackage + """ + for subdirectory, _, files in root.walk(top_down=False): + for file in files: + full_path = subdirectory / file + relative_path = full_path.relative_to(root) + + if not any(relative_path.is_relative_to(path) for path in include): + full_path.unlink() + elif any(relative_path.is_relative_to(path) for path in exclude): + full_path.unlink() + + content = list(subdirectory.iterdir()) + if not content: + shutil.rmtree(subdirectory) + + +def run() -> None: + """ + run application + """ + parser = argparse.ArgumentParser(description="Split package into subpackages") + parser.add_argument("root", help="package root", type=Path) + parser.add_argument("subpackage", help="subpackage name", choices=SUBPACKAGES.keys()) + + args = parser.parse_args() + + full_subpackages = subpackages(args.root) + include = full_subpackages[args.subpackage] + exclude = [ + path + for subpackage, portion in full_subpackages.items() + if subpackage != args.subpackage + for path in portion + if not any(include_path.is_relative_to(path) for include_path in include) + ] + + process(args.root, include, exclude) + + +if __name__ == "__main__": + run() diff --git a/tests/ahriman/application/handlers/test_handler.py b/tests/ahriman/application/handlers/test_handler.py index 49307cd0..92af8dda 100644 --- a/tests/ahriman/application/handlers/test_handler.py +++ b/tests/ahriman/application/handlers/test_handler.py @@ -4,7 +4,7 @@ import pytest from pathlib import Path from pytest_mock import MockerFixture -from ahriman.application.handlers import Handler +from ahriman.application.handlers.handler import Handler from ahriman.core.configuration import Configuration from ahriman.core.exceptions import ExitCode, MissingArchitectureError, MultipleArchitecturesError from ahriman.models.log_handler import LogHandler @@ -19,7 +19,7 @@ def test_call(args: argparse.Namespace, configuration: Configuration, mocker: Mo args.log_handler = LogHandler.Console args.quiet = False args.report = False - mocker.patch("ahriman.application.handlers.Handler.run") + mocker.patch("ahriman.application.handlers.handler.Handler.run") configuration_mock = mocker.patch("ahriman.core.configuration.Configuration.from_path", return_value=configuration) 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") @@ -76,7 +76,7 @@ def test_execute(args: argparse.Namespace, mocker: MockerFixture) -> None: RepositoryId("i686", "aur"), RepositoryId("x86_64", "aur"), ] - mocker.patch("ahriman.application.handlers.Handler.repositories_extract", return_value=ids) + mocker.patch("ahriman.application.handlers.handler.Handler.repositories_extract", return_value=ids) starmap_mock = mocker.patch("multiprocessing.pool.Pool.starmap") Handler.execute(args) @@ -88,7 +88,7 @@ def test_execute_multiple_not_supported(args: argparse.Namespace, mocker: Mocker must raise an exception if multiple architectures are not supported by the handler """ args.command = "web" - mocker.patch("ahriman.application.handlers.Handler.repositories_extract", return_value=[ + mocker.patch("ahriman.application.handlers.handler.Handler.repositories_extract", return_value=[ RepositoryId("i686", "aur"), RepositoryId("x86_64", "aur"), ]) @@ -102,7 +102,7 @@ def test_execute_single(args: argparse.Namespace, mocker: MockerFixture) -> None """ must run execution in current process if only one architecture supplied """ - mocker.patch("ahriman.application.handlers.Handler.repositories_extract", return_value=[ + mocker.patch("ahriman.application.handlers.handler.Handler.repositories_extract", return_value=[ RepositoryId("x86_64", "aur"), ]) starmap_mock = mocker.patch("multiprocessing.pool.Pool.starmap") diff --git a/tests/ahriman/application/handlers/test_handler_add.py b/tests/ahriman/application/handlers/test_handler_add.py index 2db6903d..f9507ab0 100644 --- a/tests/ahriman/application/handlers/test_handler_add.py +++ b/tests/ahriman/application/handlers/test_handler_add.py @@ -3,7 +3,7 @@ import pytest from pytest_mock import MockerFixture -from ahriman.application.handlers import Add +from ahriman.application.handlers.add import Add from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository from ahriman.models.package import Package @@ -82,7 +82,7 @@ def test_run_with_updates(args: argparse.Namespace, configuration: Configuration mocker.patch("ahriman.application.application.Application.add") mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) application_mock = mocker.patch("ahriman.application.application.Application.update", return_value=result) - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") changes_mock = mocker.patch("ahriman.application.application.Application.changes") updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman]) dependencies_mock = mocker.patch("ahriman.application.application.Application.with_dependencies", @@ -113,7 +113,7 @@ def test_run_no_changes(args: argparse.Namespace, configuration: Configuration, mocker.patch("ahriman.application.application.Application.add") mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) mocker.patch("ahriman.application.application.Application.update") - mocker.patch("ahriman.application.handlers.Handler.check_status") + mocker.patch("ahriman.application.handlers.handler.Handler.check_status") mocker.patch("ahriman.application.application.Application.updates") mocker.patch("ahriman.application.application.Application.with_dependencies") mocker.patch("ahriman.application.application.Application.print_updates") @@ -138,7 +138,7 @@ def test_run_empty_exception(args: argparse.Namespace, configuration: Configurat mocker.patch("ahriman.application.application.Application.with_dependencies") mocker.patch("ahriman.application.application.Application.updates") mocker.patch("ahriman.application.application.Application.print_updates") - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") _, repository_id = configuration.check_loaded() Add.run(args, repository_id, configuration, report=False) diff --git a/tests/ahriman/application/handlers/test_handler_backup.py b/tests/ahriman/application/handlers/test_handler_backup.py index 19b90760..6a9b6d2c 100644 --- a/tests/ahriman/application/handlers/test_handler_backup.py +++ b/tests/ahriman/application/handlers/test_handler_backup.py @@ -4,7 +4,7 @@ from pathlib import Path from pytest_mock import MockerFixture from unittest.mock import MagicMock -from ahriman.application.handlers import Backup +from ahriman.application.handlers.backup import Backup from ahriman.core.configuration import Configuration from ahriman.models.repository_paths import RepositoryPaths @@ -28,7 +28,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc must run command """ args = _default_args(args) - mocker.patch("ahriman.application.handlers.Backup.get_paths", return_value=[Path("path")]) + mocker.patch("ahriman.application.handlers.backup.Backup.get_paths", return_value=[Path("path")]) tarfile = MagicMock() add_mock = tarfile.__enter__.return_value = MagicMock() mocker.patch("ahriman.application.handlers.backup.tarfile.open", return_value=tarfile) diff --git a/tests/ahriman/application/handlers/test_handler_change.py b/tests/ahriman/application/handlers/test_handler_change.py index f96217d6..9e17456f 100644 --- a/tests/ahriman/application/handlers/test_handler_change.py +++ b/tests/ahriman/application/handlers/test_handler_change.py @@ -3,7 +3,7 @@ import pytest from pytest_mock import MockerFixture -from ahriman.application.handlers import Change +from ahriman.application.handlers.change import Change from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite from ahriman.core.repository import Repository @@ -36,7 +36,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) application_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_get", return_value=Changes("sha", "change")) - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") print_mock = mocker.patch("ahriman.core.formatters.Printer.print") _, repository_id = configuration.check_loaded() @@ -55,7 +55,7 @@ def test_run_empty_exception(args: argparse.Namespace, configuration: Configurat args.exit_code = True mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_get", return_value=Changes()) - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") _, repository_id = configuration.check_loaded() Change.run(args, repository_id, configuration, report=False) diff --git a/tests/ahriman/application/handlers/test_handler_clean.py b/tests/ahriman/application/handlers/test_handler_clean.py index 196c4138..8f4f0896 100644 --- a/tests/ahriman/application/handlers/test_handler_clean.py +++ b/tests/ahriman/application/handlers/test_handler_clean.py @@ -2,7 +2,7 @@ import argparse from pytest_mock import MockerFixture -from ahriman.application.handlers import Clean +from ahriman.application.handlers.clean import Clean from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository diff --git a/tests/ahriman/application/handlers/test_handler_copy.py b/tests/ahriman/application/handlers/test_handler_copy.py index c364d90c..d9278ab4 100644 --- a/tests/ahriman/application/handlers/test_handler_copy.py +++ b/tests/ahriman/application/handlers/test_handler_copy.py @@ -4,7 +4,7 @@ import pytest from pytest_mock import MockerFixture from ahriman.application.application import Application -from ahriman.application.handlers import Copy +from ahriman.application.handlers.copy import Copy from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository from ahriman.models.build_status import BuildStatusEnum @@ -37,7 +37,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: args = _default_args(args) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) mocker.patch("ahriman.core.repository.Repository.packages", return_value=[package_ahriman]) - application_mock = mocker.patch("ahriman.application.handlers.Copy.copy_package") + application_mock = mocker.patch("ahriman.application.handlers.copy.Copy.copy_package") update_mock = mocker.patch("ahriman.application.application.Application.update") remove_mock = mocker.patch("ahriman.application.application.Application.remove") on_start_mock = mocker.patch("ahriman.application.application.Application.on_start") @@ -59,7 +59,7 @@ def test_run_remove(args: argparse.Namespace, configuration: Configuration, repo args.remove = True mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) mocker.patch("ahriman.core.repository.Repository.packages", return_value=[package_ahriman]) - mocker.patch("ahriman.application.handlers.Copy.copy_package") + mocker.patch("ahriman.application.handlers.copy.Copy.copy_package") mocker.patch("ahriman.application.application.Application.update") remove_mock = mocker.patch("ahriman.application.application.Application.remove") @@ -77,7 +77,7 @@ def test_run_empty_exception(args: argparse.Namespace, configuration: Configurat args.exit_code = True mocker.patch("ahriman.core.repository.Repository.packages", return_value=[]) mocker.patch("ahriman.application.application.Application.update") - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") _, repository_id = configuration.check_loaded() Copy.run(args, repository_id, configuration, report=False) diff --git a/tests/ahriman/application/handlers/test_handler_daemon.py b/tests/ahriman/application/handlers/test_handler_daemon.py index ee897bea..79abccd2 100644 --- a/tests/ahriman/application/handlers/test_handler_daemon.py +++ b/tests/ahriman/application/handlers/test_handler_daemon.py @@ -2,7 +2,7 @@ import argparse from pytest_mock import MockerFixture -from ahriman.application.handlers import Daemon +from ahriman.application.handlers.daemon import Daemon from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository from ahriman.models.package import Package @@ -31,7 +31,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, package_ahr """ args = _default_args(args) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - run_mock = mocker.patch("ahriman.application.handlers.Update.run") + run_mock = mocker.patch("ahriman.application.handlers.update.Update.run") iter_mock = mocker.patch("ahriman.application.application.updates_iterator.UpdatesIterator.__iter__", return_value=iter([[package_ahriman.base]])) @@ -50,7 +50,7 @@ def test_run_no_partitions(args: argparse.Namespace, configuration: Configuratio args = _default_args(args) args.partitions = False mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - run_mock = mocker.patch("ahriman.application.handlers.Update.run") + run_mock = mocker.patch("ahriman.application.handlers.update.Update.run") iter_mock = mocker.patch("ahriman.application.application.updates_iterator.UpdatesIterator.__iter__", return_value=iter([[]])) @@ -67,7 +67,7 @@ def test_run_no_updates(args: argparse.Namespace, configuration: Configuration, """ args = _default_args(args) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - run_mock = mocker.patch("ahriman.application.handlers.Update.run") + run_mock = mocker.patch("ahriman.application.handlers.update.Update.run") iter_mock = mocker.patch("ahriman.application.application.updates_iterator.UpdatesIterator.__iter__", return_value=iter([[package_ahriman.base], None])) diff --git a/tests/ahriman/application/handlers/test_handler_dump.py b/tests/ahriman/application/handlers/test_handler_dump.py index dcb5f1c1..03541947 100644 --- a/tests/ahriman/application/handlers/test_handler_dump.py +++ b/tests/ahriman/application/handlers/test_handler_dump.py @@ -3,7 +3,7 @@ import pytest from pytest_mock import MockerFixture -from ahriman.application.handlers import Dump +from ahriman.application.handlers.dump import Dump from ahriman.core.configuration import Configuration diff --git a/tests/ahriman/application/handlers/test_handler_help.py b/tests/ahriman/application/handlers/test_handler_help.py index bb615a0c..73ea5b55 100644 --- a/tests/ahriman/application/handlers/test_handler_help.py +++ b/tests/ahriman/application/handlers/test_handler_help.py @@ -3,7 +3,7 @@ import argparse from pytest_mock import MockerFixture from ahriman.application.ahriman import _parser -from ahriman.application.handlers import Help +from ahriman.application.handlers.help import Help from ahriman.core.configuration import Configuration diff --git a/tests/ahriman/application/handlers/test_handler_key_import.py b/tests/ahriman/application/handlers/test_handler_key_import.py index dc8e9fd3..f133289b 100644 --- a/tests/ahriman/application/handlers/test_handler_key_import.py +++ b/tests/ahriman/application/handlers/test_handler_key_import.py @@ -2,7 +2,7 @@ import argparse from pytest_mock import MockerFixture -from ahriman.application.handlers import KeyImport +from ahriman.application.handlers.key_import import KeyImport from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository diff --git a/tests/ahriman/application/handlers/test_handler_patch.py b/tests/ahriman/application/handlers/test_handler_patch.py index 59307cdd..46aabd7f 100644 --- a/tests/ahriman/application/handlers/test_handler_patch.py +++ b/tests/ahriman/application/handlers/test_handler_patch.py @@ -6,7 +6,7 @@ from pathlib import Path from pytest_mock import MockerFixture from ahriman.application.application import Application -from ahriman.application.handlers import Patch +from ahriman.application.handlers.patch import Patch from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository from ahriman.models.action import Action @@ -40,9 +40,9 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: args = _default_args(args) args.action = Action.Update mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - patch_mock = mocker.patch("ahriman.application.handlers.Patch.patch_create_from_diff", + patch_mock = mocker.patch("ahriman.application.handlers.patch.Patch.patch_create_from_diff", return_value=(args.package, PkgbuildPatch(None, "patch"))) - application_mock = mocker.patch("ahriman.application.handlers.Patch.patch_set_create") + application_mock = mocker.patch("ahriman.application.handlers.patch.Patch.patch_set_create") _, repository_id = configuration.check_loaded() Patch.run(args, repository_id, configuration, report=False) @@ -61,8 +61,9 @@ def test_run_function(args: argparse.Namespace, configuration: Configuration, re args.variable = "version" patch = PkgbuildPatch(args.variable, args.patch) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - patch_mock = mocker.patch("ahriman.application.handlers.Patch.patch_create_from_function", return_value=patch) - application_mock = mocker.patch("ahriman.application.handlers.Patch.patch_set_create") + patch_mock = mocker.patch("ahriman.application.handlers.patch.Patch.patch_create_from_function", + return_value=patch) + application_mock = mocker.patch("ahriman.application.handlers.patch.Patch.patch_set_create") _, repository_id = configuration.check_loaded() Patch.run(args, repository_id, configuration, report=False) @@ -79,7 +80,7 @@ def test_run_list(args: argparse.Namespace, configuration: Configuration, reposi args.action = Action.List args.variable = ["version"] mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - application_mock = mocker.patch("ahriman.application.handlers.Patch.patch_set_list") + application_mock = mocker.patch("ahriman.application.handlers.patch.Patch.patch_set_list") _, repository_id = configuration.check_loaded() Patch.run(args, repository_id, configuration, report=False) @@ -95,7 +96,7 @@ def test_run_remove(args: argparse.Namespace, configuration: Configuration, repo args.action = Action.Remove args.variable = ["version"] mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - application_mock = mocker.patch("ahriman.application.handlers.Patch.patch_set_remove") + application_mock = mocker.patch("ahriman.application.handlers.patch.Patch.patch_set_remove") _, repository_id = configuration.check_loaded() Patch.run(args, repository_id, configuration, report=False) @@ -163,7 +164,7 @@ def test_patch_set_list(application: Application, mocker: MockerFixture) -> None get_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_get", return_value=[PkgbuildPatch(None, "patch"), PkgbuildPatch("version", "value")]) print_mock = mocker.patch("ahriman.core.formatters.Printer.print") - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") Patch.patch_set_list(application, "ahriman", ["version"], False) get_mock.assert_called_once_with("ahriman", None) @@ -178,7 +179,7 @@ def test_patch_set_list_all(application: Application, mocker: MockerFixture) -> get_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_get", return_value=[PkgbuildPatch(None, "patch")]) print_mock = mocker.patch("ahriman.core.formatters.Printer.print") - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") Patch.patch_set_list(application, "ahriman", None, False) get_mock.assert_called_once_with("ahriman", None) @@ -191,7 +192,7 @@ def test_patch_set_list_empty_exception(application: Application, mocker: Mocker must raise ExitCode exception on empty patch list """ mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_get", return_value={}) - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") Patch.patch_set_list(application, "ahriman", [], True) check_mock.assert_called_once_with(True, []) diff --git a/tests/ahriman/application/handlers/test_handler_rebuild.py b/tests/ahriman/application/handlers/test_handler_rebuild.py index a126b02a..0472ffd2 100644 --- a/tests/ahriman/application/handlers/test_handler_rebuild.py +++ b/tests/ahriman/application/handlers/test_handler_rebuild.py @@ -5,7 +5,7 @@ from pytest_mock import MockerFixture from unittest.mock import call as MockCall from ahriman.application.application import Application -from ahriman.application.handlers import Rebuild +from ahriman.application.handlers.rebuild import Rebuild from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository from ahriman.models.build_status import BuildStatus, BuildStatusEnum @@ -43,11 +43,12 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, configuration: result = Result() result.add_updated(package_ahriman) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - extract_mock = mocker.patch("ahriman.application.handlers.Rebuild.extract_packages", return_value=[package_ahriman]) + extract_mock = mocker.patch("ahriman.application.handlers.rebuild.Rebuild.extract_packages", + return_value=[package_ahriman]) application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages_depend_on", return_value=[package_ahriman]) application_mock = mocker.patch("ahriman.application.application.Application.update", return_value=result) - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") on_start_mock = mocker.patch("ahriman.application.application.Application.on_start") _, repository_id = configuration.check_loaded() @@ -70,7 +71,7 @@ def test_run_extract_packages(args: argparse.Namespace, configuration: Configura mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) mocker.patch("ahriman.application.application.Application.add") mocker.patch("ahriman.application.application.Application.print_updates") - extract_mock = mocker.patch("ahriman.application.handlers.Rebuild.extract_packages", return_value=[]) + extract_mock = mocker.patch("ahriman.application.handlers.rebuild.Rebuild.extract_packages", return_value=[]) _, repository_id = configuration.check_loaded() Rebuild.run(args, repository_id, configuration, report=False) @@ -85,9 +86,9 @@ def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, rep args = _default_args(args) args.dry_run = True mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - mocker.patch("ahriman.application.handlers.Rebuild.extract_packages", return_value=[package_ahriman]) + mocker.patch("ahriman.application.handlers.rebuild.Rebuild.extract_packages", return_value=[package_ahriman]) application_mock = mocker.patch("ahriman.application.application.Application.update") - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") print_mock = mocker.patch("ahriman.application.application.Application.print_updates") _, repository_id = configuration.check_loaded() @@ -106,7 +107,7 @@ def test_run_filter(args: argparse.Namespace, configuration: Configuration, repo args.depends_on = ["python-aur"] mocker.patch("ahriman.application.application.Application.update") mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - mocker.patch("ahriman.application.handlers.Rebuild.extract_packages", return_value=[]) + mocker.patch("ahriman.application.handlers.rebuild.Rebuild.extract_packages", return_value=[]) application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages_depend_on") _, repository_id = configuration.check_loaded() @@ -122,7 +123,7 @@ def test_run_without_filter(args: argparse.Namespace, configuration: Configurati args = _default_args(args) mocker.patch("ahriman.application.application.Application.update") mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - mocker.patch("ahriman.application.handlers.Rebuild.extract_packages", return_value=[]) + mocker.patch("ahriman.application.handlers.rebuild.Rebuild.extract_packages", return_value=[]) application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages_depend_on") _, repository_id = configuration.check_loaded() @@ -139,10 +140,10 @@ def test_run_update_empty_exception(args: argparse.Namespace, configuration: Con args.exit_code = True args.dry_run = True mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - mocker.patch("ahriman.application.handlers.Rebuild.extract_packages") + mocker.patch("ahriman.application.handlers.rebuild.Rebuild.extract_packages") mocker.patch("ahriman.core.repository.repository.Repository.packages_depend_on", return_value=[]) mocker.patch("ahriman.application.application.Application.print_updates") - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") _, repository_id = configuration.check_loaded() Rebuild.run(args, repository_id, configuration, report=False) @@ -157,10 +158,10 @@ def test_run_build_empty_exception(args: argparse.Namespace, configuration: Conf args = _default_args(args) args.exit_code = True mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - mocker.patch("ahriman.application.handlers.Rebuild.extract_packages") + mocker.patch("ahriman.application.handlers.rebuild.Rebuild.extract_packages") mocker.patch("ahriman.core.repository.repository.Repository.packages_depend_on", return_value=[package_ahriman]) mocker.patch("ahriman.application.application.Application.update", return_value=Result()) - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") _, repository_id = configuration.check_loaded() Rebuild.run(args, repository_id, configuration, report=False) diff --git a/tests/ahriman/application/handlers/test_handler_remove.py b/tests/ahriman/application/handlers/test_handler_remove.py index fba69bed..240105cd 100644 --- a/tests/ahriman/application/handlers/test_handler_remove.py +++ b/tests/ahriman/application/handlers/test_handler_remove.py @@ -2,7 +2,7 @@ import argparse from pytest_mock import MockerFixture -from ahriman.application.handlers import Remove +from ahriman.application.handlers.remove import Remove from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository diff --git a/tests/ahriman/application/handlers/test_handler_remove_unknown.py b/tests/ahriman/application/handlers/test_handler_remove_unknown.py index 0bb9910e..4b4062f4 100644 --- a/tests/ahriman/application/handlers/test_handler_remove_unknown.py +++ b/tests/ahriman/application/handlers/test_handler_remove_unknown.py @@ -3,7 +3,7 @@ import pytest from pytest_mock import MockerFixture -from ahriman.application.handlers import RemoveUnknown +from ahriman.application.handlers.remove_unknown import RemoveUnknown from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository from ahriman.models.package import Package diff --git a/tests/ahriman/application/handlers/test_handler_repositories.py b/tests/ahriman/application/handlers/test_handler_repositories.py index fb4f29ed..84ba72e2 100644 --- a/tests/ahriman/application/handlers/test_handler_repositories.py +++ b/tests/ahriman/application/handlers/test_handler_repositories.py @@ -3,7 +3,7 @@ import pytest from pytest_mock import MockerFixture -from ahriman.application.handlers import Repositories +from ahriman.application.handlers.repositories import Repositories from ahriman.core.configuration import Configuration @@ -29,7 +29,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc args = _default_args(args) print_mock = mocker.patch("ahriman.core.formatters.Printer.print") _, repository_id = configuration.check_loaded() - application_mock = mocker.patch("ahriman.application.handlers.Handler.repositories_extract", + application_mock = mocker.patch("ahriman.application.handlers.handler.Handler.repositories_extract", return_value=[repository_id]) Repositories.run(args, repository_id, configuration, report=False) diff --git a/tests/ahriman/application/handlers/test_handler_restore.py b/tests/ahriman/application/handlers/test_handler_restore.py index ee2697fb..9ccb0d42 100644 --- a/tests/ahriman/application/handlers/test_handler_restore.py +++ b/tests/ahriman/application/handlers/test_handler_restore.py @@ -4,7 +4,7 @@ from pathlib import Path from pytest_mock import MockerFixture from unittest.mock import MagicMock -from ahriman.application.handlers import Restore +from ahriman.application.handlers.restore import Restore from ahriman.core.configuration import Configuration diff --git a/tests/ahriman/application/handlers/test_handler_run.py b/tests/ahriman/application/handlers/test_handler_run.py index f49beb0b..48165b90 100644 --- a/tests/ahriman/application/handlers/test_handler_run.py +++ b/tests/ahriman/application/handlers/test_handler_run.py @@ -4,7 +4,7 @@ import pytest from pytest_mock import MockerFixture from ahriman.application.ahriman import _parser -from ahriman.application.handlers import Run +from ahriman.application.handlers.run import Run from ahriman.core.configuration import Configuration from ahriman.core.exceptions import ExitCode @@ -29,7 +29,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc must run command """ args = _default_args(args) - application_mock = mocker.patch("ahriman.application.handlers.Run.run_command") + application_mock = mocker.patch("ahriman.application.handlers.run.Run.run_command") _, repository_id = configuration.check_loaded() Run.run(args, repository_id, configuration, report=False) @@ -42,7 +42,7 @@ def test_run_failed(args: argparse.Namespace, configuration: Configuration, mock """ args = _default_args(args) args.command = ["help", "config"] - application_mock = mocker.patch("ahriman.application.handlers.Run.run_command", return_value=False) + application_mock = mocker.patch("ahriman.application.handlers.run.Run.run_command", return_value=False) _, repository_id = configuration.check_loaded() with pytest.raises(ExitCode): @@ -54,8 +54,13 @@ def test_run_command(mocker: MockerFixture) -> None: """ must correctly run external command """ - execute_mock = mocker.patch("ahriman.application.handlers.Help.execute") - Run.run_command(["help"], _parser()) + # because of dynamic load we need to patch exact instance of the object + parser = _parser() + subparser = next((action for action in parser._actions if isinstance(action, argparse._SubParsersAction)), None) + action = subparser.choices["help"] + execute_mock = mocker.patch.object(action.get_default("handler"), "execute") + + Run.run_command(["help"], parser) execute_mock.assert_called_once_with(pytest.helpers.anyvar(int)) diff --git a/tests/ahriman/application/handlers/test_handler_search.py b/tests/ahriman/application/handlers/test_handler_search.py index 25e9873a..e04ab28b 100644 --- a/tests/ahriman/application/handlers/test_handler_search.py +++ b/tests/ahriman/application/handlers/test_handler_search.py @@ -5,7 +5,7 @@ import pytest from pytest_mock import MockerFixture from unittest.mock import call as MockCall -from ahriman.application.handlers import Search +from ahriman.application.handlers.search import Search from ahriman.core.configuration import Configuration from ahriman.core.exceptions import OptionError from ahriman.core.repository import Repository @@ -39,7 +39,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: aur_search_mock = mocker.patch("ahriman.core.alpm.remote.AUR.multisearch", return_value=[aur_package_ahriman]) official_search_mock = mocker.patch("ahriman.core.alpm.remote.Official.multisearch", return_value=[aur_package_ahriman]) - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") print_mock = mocker.patch("ahriman.core.formatters.Printer.print") _, repository_id = configuration.check_loaded() @@ -64,7 +64,7 @@ def test_run_empty_exception(args: argparse.Namespace, configuration: Configurat mocker.patch("ahriman.core.alpm.remote.Official.multisearch", return_value=[]) mocker.patch("ahriman.core.formatters.Printer.print") mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") _, repository_id = configuration.check_loaded() Search.run(args, repository_id, configuration, report=False) @@ -80,7 +80,7 @@ def test_run_sort(args: argparse.Namespace, configuration: Configuration, reposi mocker.patch("ahriman.core.alpm.remote.AUR.multisearch", return_value=[aur_package_ahriman]) mocker.patch("ahriman.core.alpm.remote.Official.multisearch", return_value=[]) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - sort_mock = mocker.patch("ahriman.application.handlers.Search.sort") + sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort") _, repository_id = configuration.check_loaded() Search.run(args, repository_id, configuration, report=False) @@ -100,7 +100,7 @@ def test_run_sort_by(args: argparse.Namespace, configuration: Configuration, rep mocker.patch("ahriman.core.alpm.remote.AUR.multisearch", return_value=[aur_package_ahriman]) mocker.patch("ahriman.core.alpm.remote.Official.multisearch", return_value=[]) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - sort_mock = mocker.patch("ahriman.application.handlers.Search.sort") + sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort") _, repository_id = configuration.check_loaded() Search.run(args, repository_id, configuration, report=False) diff --git a/tests/ahriman/application/handlers/test_handler_service_updates.py b/tests/ahriman/application/handlers/test_handler_service_updates.py index c0449cde..7c18ddbc 100644 --- a/tests/ahriman/application/handlers/test_handler_service_updates.py +++ b/tests/ahriman/application/handlers/test_handler_service_updates.py @@ -4,7 +4,7 @@ import pytest from pytest_mock import MockerFixture from ahriman import __version__ -from ahriman.application.handlers import ServiceUpdates +from ahriman.application.handlers.service_updates import ServiceUpdates from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository from ahriman.models.package import Package @@ -34,7 +34,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) package_mock = mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman) application_mock = mocker.patch("ahriman.core.formatters.Printer.print") - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") _, repository_id = configuration.check_loaded() ServiceUpdates.run(args, repository_id, configuration, report=False) @@ -53,7 +53,7 @@ def test_run_skip(args: argparse.Namespace, configuration: Configuration, reposi mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman) application_mock = mocker.patch("ahriman.core.formatters.Printer.print") - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") _, repository_id = configuration.check_loaded() ServiceUpdates.run(args, repository_id, configuration, report=False) diff --git a/tests/ahriman/application/handlers/test_handler_setup.py b/tests/ahriman/application/handlers/test_handler_setup.py index 656ae6e5..3a292acc 100644 --- a/tests/ahriman/application/handlers/test_handler_setup.py +++ b/tests/ahriman/application/handlers/test_handler_setup.py @@ -7,7 +7,7 @@ from typing import Any from unittest.mock import call as MockCall from urllib.parse import quote_plus as urlencode -from ahriman.application.handlers import Setup +from ahriman.application.handlers.setup import Setup from ahriman.core.configuration import Configuration from ahriman.core.exceptions import MissingArchitectureError from ahriman.core.repository import Repository @@ -50,11 +50,11 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: """ args = _default_args(args) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - ahriman_configuration_mock = mocker.patch("ahriman.application.handlers.Setup.configuration_create_ahriman") - devtools_configuration_mock = mocker.patch("ahriman.application.handlers.Setup.configuration_create_devtools") - makepkg_configuration_mock = mocker.patch("ahriman.application.handlers.Setup.configuration_create_makepkg") - sudo_configuration_mock = mocker.patch("ahriman.application.handlers.Setup.configuration_create_sudo") - executable_mock = mocker.patch("ahriman.application.handlers.Setup.executable_create") + ahriman_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_ahriman") + devtools_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_devtools") + makepkg_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_makepkg") + sudo_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_sudo") + executable_mock = mocker.patch("ahriman.application.handlers.setup.Setup.executable_create") init_mock = mocker.patch("ahriman.core.alpm.repo.Repo.init") _, repository_id = configuration.check_loaded() @@ -95,12 +95,12 @@ def test_run_with_server(args: argparse.Namespace, configuration: Configuration, args = _default_args(args) args.server = "server" mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - mocker.patch("ahriman.application.handlers.Setup.configuration_create_ahriman") - mocker.patch("ahriman.application.handlers.Setup.configuration_create_makepkg") - mocker.patch("ahriman.application.handlers.Setup.configuration_create_sudo") - mocker.patch("ahriman.application.handlers.Setup.executable_create") + mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_ahriman") + mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_makepkg") + mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_sudo") + mocker.patch("ahriman.application.handlers.setup.Setup.executable_create") mocker.patch("ahriman.core.alpm.repo.Repo.init") - devtools_configuration_mock = mocker.patch("ahriman.application.handlers.Setup.configuration_create_devtools") + devtools_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_devtools") _, repository_id = configuration.check_loaded() Setup.run(args, repository_id, configuration, report=False) diff --git a/tests/ahriman/application/handlers/test_handler_shell.py b/tests/ahriman/application/handlers/test_handler_shell.py index 8d164f1e..23187cd9 100644 --- a/tests/ahriman/application/handlers/test_handler_shell.py +++ b/tests/ahriman/application/handlers/test_handler_shell.py @@ -3,7 +3,7 @@ import pytest from pytest_mock import MockerFixture -from ahriman.application.handlers import Shell +from ahriman.application.handlers.shell import Shell from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository diff --git a/tests/ahriman/application/handlers/test_handler_sign.py b/tests/ahriman/application/handlers/test_handler_sign.py index 9e1fa579..566d48f6 100644 --- a/tests/ahriman/application/handlers/test_handler_sign.py +++ b/tests/ahriman/application/handlers/test_handler_sign.py @@ -2,7 +2,7 @@ import argparse from pytest_mock import MockerFixture -from ahriman.application.handlers import Sign +from ahriman.application.handlers.sign import Sign from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository diff --git a/tests/ahriman/application/handlers/test_handler_statistics.py b/tests/ahriman/application/handlers/test_handler_statistics.py index 9d4dede9..111684c2 100644 --- a/tests/ahriman/application/handlers/test_handler_statistics.py +++ b/tests/ahriman/application/handlers/test_handler_statistics.py @@ -5,7 +5,7 @@ from pathlib import Path from pytest_mock import MockerFixture from unittest.mock import call as MockCall -from ahriman.application.handlers import Statistics +from ahriman.application.handlers.statistics import Statistics from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository from ahriman.core.utils import pretty_datetime, utcnow @@ -42,7 +42,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: events = [Event("1", "1"), Event("2", "2")] mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) events_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.event_get", return_value=events) - application_mock = mocker.patch("ahriman.application.handlers.Statistics.stats_per_package") + application_mock = mocker.patch("ahriman.application.handlers.statistics.Statistics.stats_per_package") _, repository_id = configuration.check_loaded() Statistics.run(args, repository_id, configuration, report=False) @@ -60,7 +60,7 @@ def test_run_for_package(args: argparse.Namespace, configuration: Configuration, events = [Event("1", "1"), Event("2", "2")] mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) events_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.event_get", return_value=events) - application_mock = mocker.patch("ahriman.application.handlers.Statistics.stats_for_package") + application_mock = mocker.patch("ahriman.application.handlers.statistics.Statistics.stats_for_package") _, repository_id = configuration.check_loaded() Statistics.run(args, repository_id, configuration, report=False) @@ -77,7 +77,7 @@ def test_run_convert_from_date(args: argparse.Namespace, configuration: Configur date = utcnow() args.from_date = date.isoformat() mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - mocker.patch("ahriman.application.handlers.Statistics.stats_per_package") + mocker.patch("ahriman.application.handlers.statistics.Statistics.stats_per_package") events_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.event_get", return_value=[]) _, repository_id = configuration.check_loaded() @@ -94,7 +94,7 @@ def test_run_convert_to_date(args: argparse.Namespace, configuration: Configurat date = utcnow() args.to_date = date.isoformat() mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - mocker.patch("ahriman.application.handlers.Statistics.stats_per_package") + mocker.patch("ahriman.application.handlers.statistics.Statistics.stats_per_package") events_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.event_get", return_value=[]) _, repository_id = configuration.check_loaded() @@ -147,8 +147,8 @@ def test_stats_for_package(mocker: MockerFixture) -> None: must print statistics for the package """ events = [Event("event", "1"), Event("event", "1")] - events_mock = mocker.patch("ahriman.application.handlers.Statistics.event_stats") - chart_plot = mocker.patch("ahriman.application.handlers.Statistics.plot_times") + events_mock = mocker.patch("ahriman.application.handlers.statistics.Statistics.event_stats") + chart_plot = mocker.patch("ahriman.application.handlers.statistics.Statistics.plot_times") Statistics.stats_for_package("event", events, None) events_mock.assert_called_once_with("event", events) @@ -161,8 +161,8 @@ def test_stats_for_package_with_chart(mocker: MockerFixture) -> None: """ local = Path("local") events = [Event("event", "1"), Event("event", "1")] - mocker.patch("ahriman.application.handlers.Statistics.event_stats") - chart_plot = mocker.patch("ahriman.application.handlers.Statistics.plot_times") + mocker.patch("ahriman.application.handlers.statistics.Statistics.event_stats") + chart_plot = mocker.patch("ahriman.application.handlers.statistics.Statistics.plot_times") Statistics.stats_for_package("event", events, local) chart_plot.assert_called_once_with("event", events, local) @@ -174,8 +174,8 @@ def test_stats_per_package(mocker: MockerFixture) -> None: """ events = [Event("event", "1"), Event("event", "2"), Event("event", "1")] print_mock = mocker.patch("ahriman.core.formatters.Printer.print") - events_mock = mocker.patch("ahriman.application.handlers.Statistics.event_stats") - chart_plot = mocker.patch("ahriman.application.handlers.Statistics.plot_packages") + events_mock = mocker.patch("ahriman.application.handlers.statistics.Statistics.event_stats") + chart_plot = mocker.patch("ahriman.application.handlers.statistics.Statistics.plot_packages") Statistics.stats_per_package("event", events, None) print_mock.assert_has_calls([ @@ -192,8 +192,8 @@ def test_stats_per_package_with_chart(mocker: MockerFixture) -> None: """ local = Path("local") events = [Event("event", "1"), Event("event", "2"), Event("event", "1")] - mocker.patch("ahriman.application.handlers.Statistics.event_stats") - chart_plot = mocker.patch("ahriman.application.handlers.Statistics.plot_packages") + mocker.patch("ahriman.application.handlers.statistics.Statistics.event_stats") + chart_plot = mocker.patch("ahriman.application.handlers.statistics.Statistics.plot_packages") Statistics.stats_per_package("event", events, local) chart_plot.assert_called_once_with("event", {"1": 2, "2": 1}, local) diff --git a/tests/ahriman/application/handlers/test_handler_status.py b/tests/ahriman/application/handlers/test_handler_status.py index 4ad84e25..a2ca0c57 100644 --- a/tests/ahriman/application/handlers/test_handler_status.py +++ b/tests/ahriman/application/handlers/test_handler_status.py @@ -4,7 +4,7 @@ import pytest from pytest_mock import MockerFixture from unittest.mock import call as MockCall -from ahriman.application.handlers import Status +from ahriman.application.handlers.status import Status from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite from ahriman.core.repository import Repository @@ -43,7 +43,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) application_mock = mocker.patch("ahriman.core.status.Client.status_get") packages_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_get", return_value=packages) - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") print_mock = mocker.patch("ahriman.core.formatters.Printer.print") _, repository_id = configuration.check_loaded() @@ -67,7 +67,7 @@ def test_run_empty_exception(args: argparse.Namespace, configuration: Configurat mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) mocker.patch("ahriman.core.status.Client.status_get") mocker.patch("ahriman.core.status.local_client.LocalClient.package_get", return_value=[]) - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") _, repository_id = configuration.check_loaded() Status.run(args, repository_id, configuration, report=False) diff --git a/tests/ahriman/application/handlers/test_handler_status_update.py b/tests/ahriman/application/handlers/test_handler_status_update.py index 289032e4..49e3bf21 100644 --- a/tests/ahriman/application/handlers/test_handler_status_update.py +++ b/tests/ahriman/application/handlers/test_handler_status_update.py @@ -2,7 +2,7 @@ import argparse from pytest_mock import MockerFixture -from ahriman.application.handlers import StatusUpdate +from ahriman.application.handlers.status_update import StatusUpdate from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite from ahriman.core.repository import Repository diff --git a/tests/ahriman/application/handlers/test_handler_structure.py b/tests/ahriman/application/handlers/test_handler_structure.py index 5a09e9f3..c3316e6b 100644 --- a/tests/ahriman/application/handlers/test_handler_structure.py +++ b/tests/ahriman/application/handlers/test_handler_structure.py @@ -4,7 +4,7 @@ import pytest from pytest_mock import MockerFixture from unittest.mock import call as MockCall -from ahriman.application.handlers import Structure +from ahriman.application.handlers.structure import Structure from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository from ahriman.models.package import Package diff --git a/tests/ahriman/application/handlers/test_handler_tree_migrate.py b/tests/ahriman/application/handlers/test_handler_tree_migrate.py index 60b731d2..af45aaba 100644 --- a/tests/ahriman/application/handlers/test_handler_tree_migrate.py +++ b/tests/ahriman/application/handlers/test_handler_tree_migrate.py @@ -4,7 +4,7 @@ from pathlib import Path from pytest_mock import MockerFixture from unittest.mock import call as MockCall -from ahriman.application.handlers import TreeMigrate +from ahriman.application.handlers.tree_migrate import TreeMigrate from ahriman.core.configuration import Configuration from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_paths import RepositoryPaths @@ -15,7 +15,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc 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") + application_mock = mocker.patch("ahriman.application.handlers.tree_migrate.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) diff --git a/tests/ahriman/application/handlers/test_handler_triggers.py b/tests/ahriman/application/handlers/test_handler_triggers.py index fcc4220d..47a41169 100644 --- a/tests/ahriman/application/handlers/test_handler_triggers.py +++ b/tests/ahriman/application/handlers/test_handler_triggers.py @@ -2,7 +2,7 @@ import argparse from pytest_mock import MockerFixture -from ahriman.application.handlers import Triggers +from ahriman.application.handlers.triggers import Triggers from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository from ahriman.models.package import Package diff --git a/tests/ahriman/application/handlers/test_handler_triggers_support.py b/tests/ahriman/application/handlers/test_handler_triggers_support.py new file mode 100644 index 00000000..85115d23 --- /dev/null +++ b/tests/ahriman/application/handlers/test_handler_triggers_support.py @@ -0,0 +1 @@ +# nothing to test here diff --git a/tests/ahriman/application/handlers/test_handler_unsafe_commands.py b/tests/ahriman/application/handlers/test_handler_unsafe_commands.py index 5fdc9f5a..617bb5fe 100644 --- a/tests/ahriman/application/handlers/test_handler_unsafe_commands.py +++ b/tests/ahriman/application/handlers/test_handler_unsafe_commands.py @@ -4,7 +4,7 @@ import pytest from pytest_mock import MockerFixture from ahriman.application.ahriman import _parser -from ahriman.application.handlers import UnsafeCommands +from ahriman.application.handlers.unsafe_commands import UnsafeCommands from ahriman.core.configuration import Configuration @@ -28,7 +28,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc must run command """ args = _default_args(args) - commands_mock = mocker.patch("ahriman.application.handlers.UnsafeCommands.get_unsafe_commands", + commands_mock = mocker.patch("ahriman.application.handlers.unsafe_commands.UnsafeCommands.get_unsafe_commands", return_value=["command"]) print_mock = mocker.patch("ahriman.core.formatters.Printer.print") @@ -44,9 +44,9 @@ def test_run_check(args: argparse.Namespace, configuration: Configuration, mocke """ args = _default_args(args) args.subcommand = ["clean"] - commands_mock = mocker.patch("ahriman.application.handlers.UnsafeCommands.get_unsafe_commands", + commands_mock = mocker.patch("ahriman.application.handlers.unsafe_commands.UnsafeCommands.get_unsafe_commands", return_value=["command"]) - check_mock = mocker.patch("ahriman.application.handlers.UnsafeCommands.check_unsafe") + check_mock = mocker.patch("ahriman.application.handlers.unsafe_commands.UnsafeCommands.check_unsafe") _, repository_id = configuration.check_loaded() UnsafeCommands.run(args, repository_id, configuration, report=False) @@ -58,7 +58,7 @@ def test_check_unsafe(mocker: MockerFixture) -> None: """ must check if command is unsafe """ - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") UnsafeCommands.check_unsafe(["service-clean"], ["service-clean"], _parser()) check_mock.assert_called_once_with(True, False) @@ -67,7 +67,7 @@ def test_check_unsafe_safe(mocker: MockerFixture) -> None: """ must check if command is safe """ - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") UnsafeCommands.check_unsafe(["package-status"], ["service-clean"], _parser()) check_mock.assert_called_once_with(True, True) diff --git a/tests/ahriman/application/handlers/test_handler_update.py b/tests/ahriman/application/handlers/test_handler_update.py index 8cf49132..428ddbde 100644 --- a/tests/ahriman/application/handlers/test_handler_update.py +++ b/tests/ahriman/application/handlers/test_handler_update.py @@ -5,7 +5,7 @@ from pytest_mock import MockerFixture from unittest.mock import call as MockCall from ahriman.application.application import Application -from ahriman.application.handlers import Update +from ahriman.application.handlers.update import Update from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository from ahriman.models.package import Package @@ -49,7 +49,7 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, configuration: result.add_updated(package_ahriman) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) application_mock = mocker.patch("ahriman.application.application.Application.update", return_value=result) - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") dependencies_mock = mocker.patch("ahriman.application.application.Application.with_dependencies", return_value=[package_ahriman]) updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman]) @@ -81,7 +81,7 @@ def test_run_empty_exception(args: argparse.Namespace, configuration: Configurat args.dry_run = True mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) mocker.patch("ahriman.application.application.Application.updates", return_value=[]) - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") _, repository_id = configuration.check_loaded() Update.run(args, repository_id, configuration, report=False) @@ -101,7 +101,7 @@ def test_run_update_empty_exception(args: argparse.Namespace, package_ahriman: P mocker.patch("ahriman.application.application.Application.with_dependencies", return_value=[package_ahriman]) mocker.patch("ahriman.application.application.Application.print_updates") mocker.patch("ahriman.application.application.Application.changes") - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") _, repository_id = configuration.check_loaded() Update.run(args, repository_id, configuration, report=False) @@ -117,7 +117,7 @@ def test_run_dry_run(args: argparse.Namespace, package_ahriman: Package, configu args.dry_run = True mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) application_mock = mocker.patch("ahriman.application.application.Application.update") - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman]) changes_mock = mocker.patch("ahriman.application.application.Application.changes") @@ -140,7 +140,7 @@ def test_run_no_changes(args: argparse.Namespace, configuration: Configuration, args.changes = False mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) mocker.patch("ahriman.application.application.Application.update") - mocker.patch("ahriman.application.handlers.Handler.check_status") + mocker.patch("ahriman.application.handlers.handler.Handler.check_status") mocker.patch("ahriman.application.application.Application.updates") changes_mock = mocker.patch("ahriman.application.application.Application.changes") diff --git a/tests/ahriman/application/handlers/test_handler_users.py b/tests/ahriman/application/handlers/test_handler_users.py index 607d4459..8e60ce59 100644 --- a/tests/ahriman/application/handlers/test_handler_users.py +++ b/tests/ahriman/application/handlers/test_handler_users.py @@ -4,7 +4,7 @@ import pytest from pytest_mock import MockerFixture from unittest.mock import call as MockCall -from ahriman.application.handlers import Users +from ahriman.application.handlers.users import Users from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite from ahriman.core.exceptions import PasswordError @@ -42,7 +42,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, database: S packager_id=args.packager, key=args.key) mocker.patch("ahriman.core.database.SQLite.load", return_value=database) mocker.patch("ahriman.models.user.User.hash_password", return_value=user) - create_user_mock = mocker.patch("ahriman.application.handlers.Users.user_create", return_value=user) + create_user_mock = mocker.patch("ahriman.application.handlers.users.Users.user_create", return_value=user) update_mock = mocker.patch("ahriman.core.database.SQLite.user_update") _, repository_id = configuration.check_loaded() @@ -60,7 +60,7 @@ def test_run_empty_salt(args: argparse.Namespace, configuration: Configuration, user = User(username=args.username, password=args.password, access=args.role, packager_id=args.packager, key=args.key) mocker.patch("ahriman.models.user.User.hash_password", return_value=user) - create_user_mock = mocker.patch("ahriman.application.handlers.Users.user_create", return_value=user) + create_user_mock = mocker.patch("ahriman.application.handlers.users.Users.user_create", return_value=user) update_mock = mocker.patch("ahriman.core.database.SQLite.user_update") _, repository_id = configuration.check_loaded() @@ -80,7 +80,7 @@ def test_run_empty_salt_without_password(args: argparse.Namespace, configuration packager_id=args.packager, key=args.key) mocker.patch("ahriman.core.database.SQLite.load", return_value=database) mocker.patch("ahriman.models.user.User.hash_password", return_value=user) - create_user_mock = mocker.patch("ahriman.application.handlers.Users.user_create", return_value=user) + create_user_mock = mocker.patch("ahriman.application.handlers.users.Users.user_create", return_value=user) update_mock = mocker.patch("ahriman.core.database.SQLite.user_update") _, repository_id = configuration.check_loaded() @@ -97,7 +97,7 @@ def test_run_list(args: argparse.Namespace, configuration: Configuration, databa args = _default_args(args) args.action = Action.List mocker.patch("ahriman.core.database.SQLite.load", return_value=database) - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") list_mock = mocker.patch("ahriman.core.database.SQLite.user_list", return_value=[user]) _, repository_id = configuration.check_loaded() @@ -116,7 +116,7 @@ def test_run_empty_exception(args: argparse.Namespace, configuration: Configurat args.exit_code = True mocker.patch("ahriman.core.database.SQLite.load", return_value=database) mocker.patch("ahriman.core.database.SQLite.user_list", return_value=[]) - check_mock = mocker.patch("ahriman.application.handlers.Handler.check_status") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") _, repository_id = configuration.check_loaded() Users.run(args, repository_id, configuration, report=False) diff --git a/tests/ahriman/application/handlers/test_handler_validate.py b/tests/ahriman/application/handlers/test_handler_validate.py index 8525fdc4..7d2bea99 100644 --- a/tests/ahriman/application/handlers/test_handler_validate.py +++ b/tests/ahriman/application/handlers/test_handler_validate.py @@ -4,7 +4,7 @@ import pytest from pytest_mock import MockerFixture -from ahriman.application.handlers import Validate +from ahriman.application.handlers.validate import Validate from ahriman.core.configuration import Configuration from ahriman.core.configuration.schema import CONFIGURATION_SCHEMA from ahriman.core.configuration.validator import Validator diff --git a/tests/ahriman/application/handlers/test_handler_versions.py b/tests/ahriman/application/handlers/test_handler_versions.py index be64b9c2..ec1c7b51 100644 --- a/tests/ahriman/application/handlers/test_handler_versions.py +++ b/tests/ahriman/application/handlers/test_handler_versions.py @@ -4,7 +4,7 @@ import pytest from pytest_mock import MockerFixture from unittest.mock import call as MockCall -from ahriman.application.handlers import Versions +from ahriman.application.handlers.versions import Versions from ahriman.core.configuration import Configuration @@ -12,7 +12,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc """ must run command """ - application_mock = mocker.patch("ahriman.application.handlers.Versions.package_dependencies") + application_mock = mocker.patch("ahriman.application.handlers.versions.Versions.package_dependencies") print_mock = mocker.patch("ahriman.core.formatters.Printer.print") _, repository_id = configuration.check_loaded() diff --git a/tests/ahriman/application/handlers/test_handler_web.py b/tests/ahriman/application/handlers/test_handler_web.py index fe2861c2..e99703bb 100644 --- a/tests/ahriman/application/handlers/test_handler_web.py +++ b/tests/ahriman/application/handlers/test_handler_web.py @@ -3,7 +3,7 @@ import pytest from pytest_mock import MockerFixture -from ahriman.application.handlers import Web +from ahriman.application.handlers.web import Web from ahriman.core.configuration import Configuration from ahriman.core.repository import Repository from ahriman.models.log_handler import LogHandler @@ -43,7 +43,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: stop_mock = mocker.patch("ahriman.core.spawn.Spawn.stop") join_mock = mocker.patch("ahriman.core.spawn.Spawn.join") _, repository_id = configuration.check_loaded() - mocker.patch("ahriman.application.handlers.Handler.repositories_extract", return_value=[repository_id]) + mocker.patch("ahriman.application.handlers.handler.Handler.repositories_extract", return_value=[repository_id]) Web.run(args, repository_id, configuration, report=False) setup_mock.assert_called_once_with(configuration, pytest.helpers.anyvar(int), [repository_id]) diff --git a/tests/ahriman/application/test_ahriman.py b/tests/ahriman/application/test_ahriman.py index b5b523b7..140a37be 100644 --- a/tests/ahriman/application/test_ahriman.py +++ b/tests/ahriman/application/test_ahriman.py @@ -5,7 +5,7 @@ from pathlib import Path from pytest_mock import MockerFixture from ahriman.application import ahriman -from ahriman.application.handlers import Handler +from ahriman.application.handlers.handler import Handler from ahriman.core.configuration import Configuration from ahriman.models.action import Action from ahriman.models.build_status import BuildStatusEnum diff --git a/tests/ahriman/core/test_module_loader.py b/tests/ahriman/core/test_module_loader.py new file mode 100644 index 00000000..fd9d5a65 --- /dev/null +++ b/tests/ahriman/core/test_module_loader.py @@ -0,0 +1,25 @@ +import ahriman.web.views + +from pathlib import Path + +from ahriman.core.module_loader import _modules, implementations +from ahriman.web.views.base import BaseView + + +def test_implementations() -> None: + """ + must load implementations from the package + """ + routes = list(implementations(ahriman.web.views, BaseView)) + assert routes + assert all(isinstance(view, type) for view in routes) + assert all(issubclass(view, BaseView) for view in routes) + + +def test_modules() -> None: + """ + must load modules + """ + modules = list(_modules(Path(__file__).parent.parent, "ahriman.web.views")) + assert modules + assert all(not module.ispkg for module in modules) diff --git a/tests/ahriman/core/upload/test_github.py b/tests/ahriman/core/upload/test_github.py index e39b1914..045ad7a4 100644 --- a/tests/ahriman/core/upload/test_github.py +++ b/tests/ahriman/core/upload/test_github.py @@ -132,7 +132,7 @@ def test_get_local_files(github: GitHub, resource_path_root: Path, mocker: Mocke """ must get all local files recursively """ - walk_mock = mocker.patch("ahriman.core.utils.walk") + walk_mock = mocker.patch("ahriman.core.upload.github.walk") github.get_local_files(resource_path_root) walk_mock.assert_called() diff --git a/tests/ahriman/core/upload/test_s3.py b/tests/ahriman/core/upload/test_s3.py index dc3ba4dc..6655af7d 100644 --- a/tests/ahriman/core/upload/test_s3.py +++ b/tests/ahriman/core/upload/test_s3.py @@ -103,7 +103,7 @@ def test_get_local_files(s3: S3, resource_path_root: Path, mocker: MockerFixture """ must get all local files recursively """ - walk_mock = mocker.patch("ahriman.core.utils.walk") + walk_mock = mocker.patch("ahriman.core.upload.s3.walk") s3.get_local_files(resource_path_root) walk_mock.assert_called() diff --git a/tests/ahriman/web/test_routes.py b/tests/ahriman/web/test_routes.py index 7d8ce98c..8d5c1253 100644 --- a/tests/ahriman/web/test_routes.py +++ b/tests/ahriman/web/test_routes.py @@ -1,14 +1,9 @@ -import pytest - from aiohttp.web import Application -from importlib.machinery import ModuleSpec from pathlib import Path -from pytest_mock import MockerFixture -from types import ModuleType from ahriman.core.configuration import Configuration from ahriman.core.utils import walk -from ahriman.web.routes import _dynamic_routes, _module, _modules, setup_routes +from ahriman.web.routes import _dynamic_routes, setup_routes def test_dynamic_routes(resource_path_root: Path, configuration: Configuration) -> None: @@ -22,54 +17,11 @@ def test_dynamic_routes(resource_path_root: Path, configuration: Configuration) if file.suffix == ".py" and file.name not in ("__init__.py", "base.py", "status_view_guard.py") ] - routes = _dynamic_routes(views_root, configuration) + routes = _dynamic_routes(configuration) assert all(isinstance(view, type) for view in routes.values()) assert len(set(routes.values())) == len(expected_views) -def test_module(mocker: MockerFixture) -> None: - """ - must load module - """ - exec_mock = mocker.patch("importlib.machinery.SourceFileLoader.exec_module") - module_info = next(_modules(Path(__file__).parent)) - - module = _module(module_info) - assert isinstance(module, ModuleType) - exec_mock.assert_called_once_with(pytest.helpers.anyvar(int)) - - -def test_module_no_spec(mocker: MockerFixture) -> None: - """ - must raise ValueError if spec is not available - """ - mocker.patch("importlib.machinery.FileFinder.find_spec", return_value=None) - module_info = next(_modules(Path(__file__).parent)) - - with pytest.raises(ValueError): - _module(module_info) - - -def test_module_no_loader(mocker: MockerFixture) -> None: - """ - must raise ValueError if loader is not available - """ - mocker.patch("importlib.machinery.FileFinder.find_spec", return_value=ModuleSpec("name", None)) - module_info = next(_modules(Path(__file__).parent)) - - with pytest.raises(ValueError): - _module(module_info) - - -def test_modules() -> None: - """ - must load modules - """ - modules = list(_modules(Path(__file__).parent.parent)) - assert modules - assert all(not module.ispkg for module in modules) - - def test_setup_routes(application: Application, configuration: Configuration) -> None: """ must generate non-empty list of routes diff --git a/tests/ahriman/web/views/test_view_static.py b/tests/ahriman/web/views/test_view_static.py index 8d25e3b6..bcbe3759 100644 --- a/tests/ahriman/web/views/test_view_static.py +++ b/tests/ahriman/web/views/test_view_static.py @@ -1,6 +1,7 @@ import pytest from aiohttp.test_utils import TestClient +from pytest_mock import MockerFixture from ahriman.models.user_access import UserAccess from ahriman.web.views.static import StaticView @@ -31,12 +32,12 @@ async def test_get(client_with_auth: TestClient) -> None: assert response.headers["Location"] == "/static/favicon.ico" -async def test_get_not_found(client_with_auth: TestClient) -> None: +async def test_get_not_found(client_with_auth: TestClient, mocker: MockerFixture) -> None: """ must raise not found if path is invalid """ - for route in client_with_auth.app.router.routes(): - if hasattr(route.handler, "ROUTES"): - route.handler.ROUTES = [] + print([route.handler for route in client_with_auth.app.router.routes()]) + static_route = next(route for route in client_with_auth.app.router.routes() if route.handler == StaticView) + mocker.patch.object(static_route.handler, "ROUTES", []) response = await client_with_auth.get("/favicon.ico", allow_redirects=False) assert response.status == 404