Compare commits

...

8 Commits

Author SHA1 Message Date
a11fd188a2 Release 2.0.0rc11 2022-05-10 06:03:33 +03:00
2431d5de0e fix bug with checking file
The bug appear when the file exists or doesn't, but we don't have
permissions to read it. This one must be treated as missed permission
2022-05-10 06:01:41 +03:00
88f71b240d Release 2.0.0rc10 2022-05-09 21:51:35 +03:00
99874845b5 triggers implementation (#62) 2022-05-09 20:00:20 +03:00
d98cfa3732 Release 2.0.0rc9 2022-05-08 03:58:53 +03:00
b6db2a8035 fix error with missing sources
In case if package has local cache it will fail to load because no
remote source set. Particially this case can be observed during tree
load
2022-05-08 03:56:54 +03:00
47c578ea08 Release 2.0.0rc8 2022-05-06 20:55:54 +03:00
98910240dd shorten public imports 2022-05-06 04:08:05 +03:00
182 changed files with 4383 additions and 4171 deletions

View File

@ -14,7 +14,7 @@ Wrapper for managing custom repository inspired by [repo-scripts](https://github
* Multi-architecture support.
* VCS packages support.
* Sign support with gpg (repository, package, per package settings).
* Synchronization to remote services (rsync, s3 and github) and report generation (email, html, telegram).
* Synchronization to remote services (rsync, s3 and github) and report generation (email, html, telegram) and even ability to write own extensions.
* Dependency manager.
* Ability to patch AUR packages and even create package from local PKGBUILDs.
* Repository status interface with optional authorization and control options:

View File

@ -8,7 +8,7 @@ Depending on the goal the package can be used in different ways. Nevertheless, i
from pathlib import Path
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
architecture = "x86_64"
configuration = Configuration.from_path(Path("/etc/ahriman.ini"), architecture, quiet=False)

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 570 KiB

After

Width:  |  Height:  |  Size: 517 KiB

View File

@ -3,7 +3,7 @@
ahriman
.SH SYNOPSIS
.B ahriman
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--no-report] [-q] [--unsafe] [-v] {aur-search,search,help,help-commands-unsafe,key-import,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,repo-backup,repo-check,check,repo-clean,clean,repo-config,config,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-setup,init,repo-init,setup,repo-sign,sign,repo-status-update,repo-sync,sync,repo-update,update,user-add,user-list,user-remove,web} ...
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--no-report] [-q] [--unsafe] [-v] {aur-search,search,help,help-commands-unsafe,key-import,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,repo-backup,repo-check,check,repo-clean,clean,repo-config,config,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-restore,repo-setup,init,repo-init,setup,repo-sign,sign,repo-status-update,repo-triggers,repo-update,update,user-add,user-list,user-remove,web} ...
.SH DESCRIPTION
ArcH Linux ReposItory MANager
@ -97,9 +97,6 @@ rebuild repository
\fBahriman\fR \fI\,repo-remove-unknown\/\fR
remove unknown packages
.TP
\fBahriman\fR \fI\,repo-report\/\fR
generate report
.TP
\fBahriman\fR \fI\,repo-restore\/\fR
restore repository data
.TP
@ -112,8 +109,8 @@ sign packages
\fBahriman\fR \fI\,repo-status-update\/\fR
update repository status
.TP
\fBahriman\fR \fI\,repo-sync\/\fR
sync repository
\fBahriman\fR \fI\,repo-triggers\/\fR
run triggers
.TP
\fBahriman\fR \fI\,repo-update\/\fR
update packages
@ -408,15 +405,6 @@ just perform check for packages without removal
\fB\-i\fR, \fB\-\-info\fR
show additional package information
.SH COMMAND \fI\,'ahriman repo-report'\/\fR
usage: ahriman repo-report [-h] [target ...]
generate repository report according to current settings
.TP
\fBtarget\fR
target to generate report
.SH COMMAND \fI\,'ahriman repo-restore'\/\fR
usage: ahriman repo-restore [-h] [-o OUTPUT] path
@ -497,14 +485,10 @@ update repository status on the status page
\fB\-s\fR \fI\,{BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}\/\fR, \fB\-\-status\fR \fI\,{BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}\/\fR
new status
.SH COMMAND \fI\,'ahriman repo-sync'\/\fR
usage: ahriman repo-sync [-h] [target ...]
.SH COMMAND \fI\,'ahriman repo-triggers'\/\fR
usage: ahriman repo-triggers [-h]
sync repository files to remote server according to current settings
.TP
\fBtarget\fR
target to sync
run triggers on empty build result as configured by settings
.SH COMMAND \fI\,'ahriman repo-update'\/\fR
usage: ahriman repo-update [-h] [--dry-run] [-e] [--no-aur] [--no-local] [--no-manual] [--no-vcs] [package ...]

View File

@ -92,14 +92,6 @@ ahriman.application.handlers.remove\_unknown module
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.report module
------------------------------------------
.. automodule:: ahriman.application.handlers.report
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.restore module
-------------------------------------------
@ -148,10 +140,10 @@ ahriman.application.handlers.status\_update module
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.sync module
----------------------------------------
ahriman.application.handlers.triggers module
--------------------------------------------
.. automodule:: ahriman.application.handlers.sync
.. automodule:: ahriman.application.handlers.triggers
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -44,6 +44,14 @@ ahriman.core.report.report module
:no-undoc-members:
:show-inheritance:
ahriman.core.report.report\_trigger module
------------------------------------------
.. automodule:: ahriman.core.report.report_trigger
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.report.telegram module
-----------------------------------

View File

@ -16,6 +16,7 @@ Subpackages
ahriman.core.repository
ahriman.core.sign
ahriman.core.status
ahriman.core.triggers
ahriman.core.upload
Submodules

View File

@ -0,0 +1,29 @@
ahriman.core.triggers package
=============================
Submodules
----------
ahriman.core.triggers.trigger module
------------------------------------
.. automodule:: ahriman.core.triggers.trigger
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.triggers.trigger\_loader module
--------------------------------------------
.. automodule:: ahriman.core.triggers.trigger_loader
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.triggers
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -44,6 +44,14 @@ ahriman.core.upload.upload module
:no-undoc-members:
:show-inheritance:
ahriman.core.upload.upload\_trigger module
------------------------------------------
.. automodule:: ahriman.core.upload.upload_trigger
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------

View File

@ -7,7 +7,7 @@ Package structure
Packages have strict rules of importing:
* ``ahriman.application`` package must not be used anywhere except for itself.
* ``ahriman.core`` and ``ahriman.models`` packages don't have any import restriction. Actually we would like to totally restrict importing of ``core`` package from ``models``\ , but it is impossible at the moment.
* ``ahriman.core`` and ``ahriman.models`` packages don't have any import restriction. Actually we would like to totally restrict importing of ``core`` package from ``models``, but it is impossible at the moment.
* ``ahriman.web`` package is allowed to be imported from ``ahriman.application`` (web handler only, only ``ahriman.web.web`` methods). It also must not be imported globally, only local import is allowed.
Full dependency diagram:
@ -30,13 +30,13 @@ This package contains application (aka executable) related classes and everythin
This package contains everything which is required for any time of application run and separated to several packages:
* ``ahriman.core.alpm`` package controls pacman related functions. It provides wrappers for ``pyalpm`` library and safe calls for repository tools (\ ``repo-add`` and ``repo-remove``\ ). Also this package contains ``ahriman.core.alpm.remote`` package which provides wrapper for remote sources (e.g. AUR RPC and official repositories RPC).
* ``ahriman.core.alpm`` package controls pacman related functions. It provides wrappers for ``pyalpm`` library and safe calls for repository tools (``repo-add`` and ``repo-remove``). Also this package contains ``ahriman.core.alpm.remote`` package which provides wrapper for remote sources (e.g. AUR RPC and official repositories RPC).
* ``ahriman.core.auth`` package provides classes for authorization methods used by web mostly. Base class is ``ahriman.core.auth.auth.Auth`` which must be called by ``load`` method.
* ``ahriman.core.build_tools`` is a package which provides wrapper for ``devtools`` commands.
* ``ahriman.core.database`` is everything including data and schema migrations for database.
* ``ahriman.core.formatters`` package provides ``Printer`` sub-classes for printing data (e.g. package properties) to stdout which are used by some handlers.
* ``ahriman.core.report`` is a package with reporting classes. Usually it must be called by ``ahriman.core.report.report.Report.load`` method.
* ``ahriman.core.repository`` contains several traits and base repository (\ ``ahriman.core.repository.repository.Repository`` class) implementation.
* ``ahriman.core.repository`` contains several traits and base repository (``ahriman.core.repository.repository.Repository`` class) implementation.
* ``ahriman.core.sign`` package provides sign feature (only gpg calls are available).
* ``ahriman.core.status`` contains helpers and watcher class which are required for web application. Reporter must be initialized by using ``ahriman.core.status.client.Client.load`` method.
* ``ahriman.core.upload`` package provides sync feature, must be called by ``ahriman.core.upload.upload.Upload.load`` method.
@ -68,7 +68,7 @@ Application run
* Parse command line arguments, find command and related handler which is set by parser.
* Call ``Handler.execute`` method.
* Define list of architectures to run. In case if there is more than one architecture specified run several subprocesses or process in current process otherwise. Class attribute ``ALLOW_MULTI_ARCHITECTURE_RUN`` controls whether application can be run in multiple processes or not - this feature is required for some handlers (e.g. ``Web``\ ) which should be able to spawn child process in daemon mode (it is impossible to do for daemonic processes).
* Define list of architectures to run. In case if there is more than one architecture specified run several subprocesses or process in current process otherwise. Class attribute ``ALLOW_MULTI_ARCHITECTURE_RUN`` controls whether application can be run in multiple processes or not - this feature is required for some handlers (e.g. ``Web``) which should be able to spawn child process in daemon mode (it is impossible to do for daemonic processes).
* In each child process call lock functions.
* After success checks pass control to ``Handler.run`` method defined by specific handler class.
* Return result (success or failure) of each subprocess and exit from application.
@ -102,7 +102,7 @@ Type conversions
By default, it parses rows into python dictionary. In addition, the following pseudo-types are supported:
* ``Dict[str, Any]``\ , ``List[Any]`` - for storing JSON data structures in database (technically there is no restriction on types for dictionary keys and values, but it is recommended to use only string keys). The type is stored as ``json`` datatype and ``json.loads`` and ``json.dumps`` methods are used in order to read and write from/to database respectively.
* ``Dict[str, Any]``, ``List[Any]`` - for storing JSON data structures in database (technically there is no restriction on types for dictionary keys and values, but it is recommended to use only string keys). The type is stored as ``json`` datatype and ``json.loads`` and ``json.dumps`` methods are used in order to read and write from/to database respectively.
Basic flows
-----------
@ -113,7 +113,7 @@ Add new packages or rebuild existing
Idea is to copy package to the directory from which it will be handled at the next update run. Different variants are supported:
* If supplied argument is file then application moves the file to the directory with built packages. Same rule applies for directory, but in this case it copies every package-like file from the specified directory.
* If supplied argument is directory and there is ``PKGBUILD`` file there it will be treated as local package. In this case it will queue this package to build and copy source files (\ ``PKGBUILD`` and ``.SRCINFO``\ ) to caches.
* If supplied argument is directory and there is ``PKGBUILD`` file there it will be treated as local package. In this case it will queue this package to build and copy source files (``PKGBUILD`` and ``.SRCINFO``) to caches.
* If supplied argument iis not file then application tries to lookup for the specified name in AUR and clones it into the directory with manual updates. This scenario can also handle package dependencies which are missing in repositories.
This logic can be overwritten by specifying the ``source`` parameter, which is partially useful if you would like to add package from AUR, but there is local directory cloned from AUR.
@ -142,7 +142,7 @@ This feature is divided into to stages: check AUR for updates and run rebuild fo
#. Build every package in clean chroot.
#. Sign packages if required.
#. Add packages to database and sign database if required.
#. Process sync and report methods.
#. Process triggers.
After any step any package data is being removed.
@ -152,7 +152,7 @@ Core functions reference
Configuration
^^^^^^^^^^^^^
``ahriman.core.configuration.Configuration`` class provides some additional methods (e.g. ``getpath`` and ``getlist``\ ) and also combines multiple files into single configuration dictionary using architecture overrides. It is the recommended way to deal with settings.
``ahriman.core.configuration.Configuration`` class provides some additional methods (e.g. ``getpath`` and ``getlist``) and also combines multiple files into single configuration dictionary using architecture overrides. It is the recommended way to deal with settings.
Utils
^^^^^
@ -176,7 +176,7 @@ Mapping (aka configuration) provider uses hashed passwords with salt from the da
* ``check_credentials`` - user password validation (authentication).
* ``verify_access`` - user permission validation (authorization).
Passwords must be stored in database as ``hash(password + salt)``\ , where ``password`` is user defined password (taken from user input), ``salt`` is random string (any length) defined globally in configuration and ``hash`` is secure hash function. Thus, the following configuration
Passwords must be stored in database as ``hash(password + salt)``, where ``password`` is user defined password (taken from user input), ``salt`` is random string (any length) defined globally in configuration and ``hash`` is secure hash function. Thus, the following configuration
.. code-block::
@ -185,20 +185,31 @@ Passwords must be stored in database as ``hash(password + salt)``\ , where ``pas
means that there is user ``username`` with ``read`` access and password ``password`` hashed by ``sha512`` with salt ``salt``.
OAuth provider uses library definitions (\ ``aioauth-client``\ ) in order *authenticate* users. It still requires user permission to be set in database, thus it inherits mapping provider without any changes. Whereas we could override ``check_credentials`` (authentication method) by something custom, OAuth flow is a bit more complex than just forward request, thus we have to implement the flow in login form.
OAuth provider uses library definitions (``aioauth-client``) in order *authenticate* users. It still requires user permission to be set in database, thus it inherits mapping provider without any changes. Whereas we could override ``check_credentials`` (authentication method) by something custom, OAuth flow is a bit more complex than just forward request, thus we have to implement the flow in login form.
OAuth's implementation also allows authenticating users via username + password (in the same way as mapping does) though it is not recommended for end-users and password must be left blank. In particular this feature is used by service reporting (aka robots).
In order to configure users there are special commands.
Triggers
^^^^^^^^
Triggers are extensions which can be used in order to perform any actions after the update process. The package provides two default extensions - one is report generation and another one is remote upload feature.
The main idea is to load classes by their full path (e.g. ``ahriman.core.upload.UploadTrigger``) by using ``importlib``: get the last part of the import and treat it as class name, join remain part by ``.`` and interpret as module path, import module and extract attribute from it.
The loaded triggers will be called with ``ahriman.models.result.Result`` and ``List[Packages]`` arguments, which describes the process result and current repository packages respectively. Any exception raised will be suppressed and will generate an exception message in logs.
For more details how to deal with the triggers, refer to :doc:`documentation <triggers>` and modules descriptions.
Remote synchronization
^^^^^^^^^^^^^^^^^^^^^^
There are several supported synchronization providers, currently they are ``rsync``\ , ``s3``\ , ``github``.
There are several supported synchronization providers, currently they are ``rsync``, ``s3``, ``github``.
``rsync`` provider does not have any specific logic except for running external rsync application with configured arguments. The service does not handle SSH configuration, thus it has to be configured before running application manually.
``s3`` provider uses ``boto3`` package and implements sync feature. The files are stored in architecture directory (e.g. if bucket is ``repository``\ , packages will be stored in ``repository/x86_64`` for the ``x86_64`` architecture), bucket must be created before any action and API key must have permissions to write to the bucket. No external configuration required. In order to upload only changed files the service compares calculated hashes with the Amazon ETags, used realization is described `here <https://teppen.io/2018/10/23/aws_s3_verify_etags/>`_.
``s3`` provider uses ``boto3`` package and implements sync feature. The files are stored in architecture directory (e.g. if bucket is ``repository``, packages will be stored in ``repository/x86_64`` for the ``x86_64`` architecture), bucket must be created before any action and API key must have permissions to write to the bucket. No external configuration required. In order to upload only changed files the service compares calculated hashes with the Amazon ETags, used realization is described `here <https://teppen.io/2018/10/23/aws_s3_verify_etags/>`_.
``github`` provider authenticates through basic auth, API key with repository write permissions is required. There will be created a release with the name of the architecture in case if it does not exist; files will be uploaded to the release assets. It also stores array of files and their MD5 checksums in release body in order to upload only changed ones. According to the Github API in case if there is already uploaded asset with the same name (e.g. database files), asset will be removed first.
@ -207,7 +218,7 @@ Additional features
Some features require optional dependencies to be installed:
* Version control executables (e.g. ``git``\ , ``svn``\ ) for VCS packages.
* Version control executables (e.g. ``git``, ``svn``) for VCS packages.
* ``gnupg`` application for package and repository sign feature.
* ``rsync`` application for rsync based repository sync.
* ``boto3`` python package for ``S3`` sync.
@ -220,7 +231,7 @@ Web application requires the following python packages to be installed:
* Core part requires ``aiohttp`` (application itself), ``aiohttp_jinja2`` and ``Jinja2`` (HTML generation from templates).
* In addition, ``aiohttp_debugtoolbar`` is required for debug panel. Please note that this option does not work together with authorization and basically must not be used in production.
* In addition, authorization feature requires ``aiohttp_security``\ , ``aiohttp_session`` and ``cryptography``.
* In addition, authorization feature requires ``aiohttp_security``, ``aiohttp_session`` and ``cryptography``.
* In addition to base authorization dependencies, OAuth2 also requires ``aioauth-client`` library.
Middlewares

View File

@ -1,5 +1,5 @@
Commands help
=============
Commands reference
==================
ahriman
-------

View File

@ -6,11 +6,11 @@ Some groups can be specified for each architecture separately. E.g. if there are
There are two variable types which have been added to default ones, they are paths and lists. List values will be read in the same way as shell does:
* By default, it splits value by spaces excluding empty elements.
* In case if quotation mark (\ ``"`` or ``'``\ ) will be found, any spaces inside will be ignored.
* In case if quotation mark (``"`` or ``'``) will be found, any spaces inside will be ignored.
* In order to use quotation mark inside value it is required to put it to another quotation mark, e.g. ``wor"'"d "with quote"`` will be parsed as ``["wor'd", "with quote"]`` and vice versa.
* Unclosed quotation mark is not allowed and will rise an exception.
Path values, except for casting to ``pathlib.Path`` type, will be also expanded to absolute paths relative to the configuration path. E.g. if path is set to ``ahriman.ini.d/logging.ini`` and root configuration path is ``/etc/ahriman.ini``\ , the value will be expanded to ``/etc/ahriman.ini.d/logging.ini``. In order to disable path expand, use the full path, e.g. ``/etc/ahriman.ini.d/logging.ini``.
Path values, except for casting to ``pathlib.Path`` type, will be also expanded to absolute paths relative to the configuration path. E.g. if path is set to ``ahriman.ini.d/logging.ini`` and root configuration path is ``/etc/ahriman.ini``, the value will be expanded to ``/etc/ahriman.ini.d/logging.ini``. In order to disable path expand, use the full path, e.g. ``/etc/ahriman.ini.d/logging.ini``.
``settings`` group
------------------
@ -35,12 +35,12 @@ libalpm and AUR related configuration.
Base authorization settings. ``OAuth`` provider requires ``aioauth-client`` library to be installed.
* ``target`` - specifies authorization provider, string, optional, default ``disabled``. Allowed values are ``disabled``\ , ``configuration``\ , ``oauth``.
* ``target`` - specifies authorization provider, string, optional, default ``disabled``. Allowed values are ``disabled``, ``configuration``, ``oauth``.
* ``client_id`` - OAuth2 application client ID, string, required in case if ``oauth`` is used.
* ``client_secret`` - OAuth2 application client secret key, string, required in case if ``oauth`` is used.
* ``max_age`` - parameter which controls both cookie expiration and token expiration inside the service, integer, optional, default is 7 days.
* ``oauth_provider`` - OAuth2 provider class name as is in ``aioauth-client`` (e.g. ``GoogleClient``\ , ``GithubClient`` etc), string, required in case if ``oauth`` is used.
* ``oauth_scopes`` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. ``https://www.googleapis.com/auth/userinfo.email`` for ``GoogleClient`` or ``user:email`` for ``GithubClient``\ , space separated list of strings, required in case if ``oauth`` is used.
* ``oauth_provider`` - OAuth2 provider class name as is in ``aioauth-client`` (e.g. ``GoogleClient``, ``GithubClient`` etc), string, required in case if ``oauth`` is used.
* ``oauth_scopes`` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. ``https://www.googleapis.com/auth/userinfo.email`` for ``GoogleClient`` or ``user:email`` for ``GithubClient``, space separated list of strings, required in case if ``oauth`` is used.
* ``safe_build_status`` - allow requesting status page without authorization, boolean, required.
* ``salt`` - password hash salt, string, required in case if authorization enabled (automatically generated by ``create-user`` subcommand).
@ -56,6 +56,7 @@ Build related configuration. Group name can refer to architecture, e.g. ``build:
* ``ignore_packages`` - list packages to ignore during a regular update (manual update will still work), space separated list of strings, optional.
* ``makepkg_flags`` - additional flags passed to ``makepkg`` command, space separated list of strings, optional.
* ``makechrootpkg_flags`` - additional flags passed to ``makechrootpkg`` command, space separated list of strings, optional.
* ``triggers`` - list of trigger classes (e.g. ``ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger``) which will be loaded and run at the end of processing, space separated list of strings, optional. You can also specify triggers by their paths, e.g. ``/usr/lib/python3.10/site-packages/ahriman/core/report/report.py.ReportTrigger``. Triggers are run in the order of mention.
``repository`` group
--------------------
@ -90,14 +91,14 @@ Type will be read from several ways:
``console`` type
^^^^^^^^^^^^^^^^
Section name must be either ``console`` (plus optional architecture name, e.g. ``console:x86_64``\ ) or random name with ``type`` set.
Section name must be either ``console`` (plus optional architecture name, e.g. ``console:x86_64``) or random name with ``type`` set.
* ``use_utf`` - use utf8 symbols in output if set and ascii otherwise, boolean, optional, default ``yes``.
``email`` type
^^^^^^^^^^^^^^
Section name must be either ``email`` (plus optional architecture name, e.g. ``email:x86_64``\ ) or random name with ``type`` set.
Section name must be either ``email`` (plus optional architecture name, e.g. ``email:x86_64``) or random name with ``type`` set.
* ``type`` - type of the report, string, optional, must be set to ``email`` if exists.
* ``full_template_path`` - path to Jinja2 template for full package description index, string, optional.
@ -109,14 +110,14 @@ Section name must be either ``email`` (plus optional architecture name, e.g. ``e
* ``port`` - SMTP port for sending emails, int, required.
* ``receivers`` - SMTP receiver addresses, space separated list of strings, required.
* ``sender`` - SMTP sender address, string, required.
* ``ssl`` - SSL mode for SMTP connection, one of ``ssl``\ , ``starttls``\ , ``disabled``\ , optional, default ``disabled``.
* ``ssl`` - SSL mode for SMTP connection, one of ``ssl``, ``starttls``, ``disabled``, optional, default ``disabled``.
* ``template_path`` - path to Jinja2 template, string, required.
* ``user`` - SMTP user to authenticate, string, optional.
``html`` type
^^^^^^^^^^^^^
Section name must be either ``html`` (plus optional architecture name, e.g. ``html:x86_64``\ ) or random name with ``type`` set.
Section name must be either ``html`` (plus optional architecture name, e.g. ``html:x86_64``) or random name with ``type`` set.
* ``type`` - type of the report, string, optional, must be set to ``html`` if exists.
* ``path`` - path to html report file, string, required.
@ -127,7 +128,7 @@ Section name must be either ``html`` (plus optional architecture name, e.g. ``ht
``telegram`` type
^^^^^^^^^^^^^^^^^
Section name must be either ``telegram`` (plus optional architecture name, e.g. ``telegram:x86_64``\ ) or random name with ``type`` set.
Section name must be either ``telegram`` (plus optional architecture name, e.g. ``telegram:x86_64``) or random name with ``type`` set.
* ``type`` - type of the report, string, optional, must be set to ``telegram`` if exists.
* ``api_key`` - telegram bot API key, string, required. Please refer FAQ about how to create chat and bot
@ -135,7 +136,7 @@ Section name must be either ``telegram`` (plus optional architecture name, e.g.
* ``homepage`` - link to homepage, string, optional.
* ``link_path`` - prefix for HTML links, string, required.
* ``template_path`` - path to Jinja2 template, string, required.
* ``template_type`` - ``parse_mode`` to be passed to telegram API, one of ``MarkdownV2``\ , ``HTML``\ , ``Markdown``\ , string, optional, default ``HTML``.
* ``template_type`` - ``parse_mode`` to be passed to telegram API, one of ``MarkdownV2``, ``HTML``, ``Markdown``, string, optional, default ``HTML``.
``upload`` group
----------------
@ -153,7 +154,7 @@ Type will be read from several ways:
``github`` type
^^^^^^^^^^^^^^^
This feature requires Github key creation (see below). Section name must be either ``github`` (plus optional architecture name, e.g. ``github:x86_64``\ ) or random name with ``type`` set.
This feature requires Github key creation (see below). Section name must be either ``github`` (plus optional architecture name, e.g. ``github:x86_64``) or random name with ``type`` set.
* ``type`` - type of the upload, string, optional, must be set to ``github`` if exists.
* ``owner`` - Github repository owner, string, required.
@ -170,30 +171,30 @@ This feature requires Github key creation (see below). Section name must be eith
``rsync`` type
^^^^^^^^^^^^^^
Requires ``rsync`` package to be installed. Do not forget to configure ssh for user ``ahriman``. Section name must be either ``rsync`` (plus optional architecture name, e.g. ``rsync:x86_64``\ ) or random name with ``type`` set.
Requires ``rsync`` package to be installed. Do not forget to configure ssh for user ``ahriman``. Section name must be either ``rsync`` (plus optional architecture name, e.g. ``rsync:x86_64``) or random name with ``type`` set.
* ``type`` - type of the upload, string, optional, must be set to ``rsync`` if exists.
* ``command`` - rsync command to run, space separated list of string, required.
* ``remote`` - remote server to rsync (e.g. ``1.2.3.4:path/to/sync``\ ), string, required.
* ``remote`` - remote server to rsync (e.g. ``1.2.3.4:path/to/sync``), string, required.
``s3`` type
^^^^^^^^^^^
Requires ``boto3`` library to be installed. Section name must be either ``s3`` (plus optional architecture name, e.g. ``s3:x86_64``\ ) or random name with ``type`` set.
Requires ``boto3`` library to be installed. Section name must be either ``s3`` (plus optional architecture name, e.g. ``s3:x86_64``) or random name with ``type`` set.
* ``type`` - type of the upload, string, optional, must be set to ``github`` if exists.
* ``access_key`` - AWS access key ID, string, required.
* ``bucket`` - bucket name (e.g. ``bucket``\ ), string, required.
* ``bucket`` - bucket name (e.g. ``bucket``), string, required.
* ``chunk_size`` - chunk size for calculating entity tags, int, optional, default 8 * 1024 * 1024.
* ``region`` - bucket region (e.g. ``eu-central-1``\ ), string, required.
* ``region`` - bucket region (e.g. ``eu-central-1``), string, required.
* ``secret_key`` - AWS secret access key, string, required.
``web:*`` groups
----------------
Web server settings. If any of ``host``\ /\ ``port`` is not set, web integration will be disabled. Group name can refer to architecture, e.g. ``web:x86_64`` can be used for x86_64 architecture specific settings. This feature requires ``aiohttp`` libraries to be installed.
Web server settings. If any of ``host``/``port`` is not set, web integration will be disabled. Group name can refer to architecture, e.g. ``web:x86_64`` can be used for x86_64 architecture specific settings. This feature requires ``aiohttp`` libraries to be installed.
* ``address`` - optional address in form ``proto://host:port`` (\ ``port`` can be omitted in case of default ``proto`` ports), will be used instead of ``http://{host}:{port}`` in case if set, string, optional. This option is required in case if ``OAuth`` provider is used.
* ``address`` - optional address in form ``proto://host:port`` (``port`` can be omitted in case of default ``proto`` ports), will be used instead of ``http://{host}:{port}`` in case if set, string, optional. This option is required in case if ``OAuth`` provider is used.
* ``debug`` - enable debug toolbar, boolean, optional, default ``no``.
* ``debug_check_host`` - check hosts to access debug toolbar, boolean, optional, default ``no``.
* ``debug_allowed_hosts`` - allowed hosts to get access to debug toolbar, space separated list of string, optional.

View File

@ -118,7 +118,7 @@ But I just wanted to change PKGBUILD from AUR a bit!
Well it is supported also.
#. Clone sources from AUR.
#. Make changes you would like to (e.g. edit ``PKGBUILD``\ , add external patches).
#. Make changes you would like to (e.g. edit ``PKGBUILD``, add external patches).
#. Run ``sudo -u ahriman ahriman patch-add /path/to/local/directory/with/PKGBUILD``.
The last command will calculate diff from current tree to the ``HEAD`` and will store it locally. Patches will be applied on any package actions (e.g. it can be used for dependency management).
@ -177,7 +177,7 @@ However, note that you do not need to rebuild repository in case if you just cha
Hmm, I have packages built, but how can I use it?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Add the following lines to your ``pacman.conf``\ :
Add the following lines to your ``pacman.conf``:
.. code-block:: ini
@ -238,7 +238,7 @@ The default action (in case if no arguments provided) is ``repo-update``. Basica
docker run -v /path/to/local/repo:/var/lib/ahriman -v /etc/ahriman.ini:/etc/ahriman.ini.d/10-overrides.ini arcan1s/ahriman:latest
By default, it runs ``repo-update``\ , but it can be overwritten to any other command you would like to, e.g.:
By default, it runs ``repo-update``, but it can be overwritten to any other command you would like to, e.g.:
.. code-block:: shell
@ -255,7 +255,7 @@ The following environment variables are supported:
* ``AHRIMAN_DEBUG`` - if set all commands will be logged to console.
* ``AHRIMAN_FORCE_ROOT`` - force run ahriman as root instead of guessing by subcommand.
* ``AHRIMAN_HOST`` - host for the web interface, default is ``0.0.0.0``.
* ``AHRIMAN_OUTPUT`` - controls logging handler, e.g. ``syslog``\ , ``console``. The name must be found in logging configuration. Note that if ``syslog`` (the default) handler is used you will need to mount ``/dev/log`` inside container because it is not available there.
* ``AHRIMAN_OUTPUT`` - controls logging handler, e.g. ``syslog``, ``console``. The name must be found in logging configuration. Note that if ``syslog`` (the default) handler is used you will need to mount ``/dev/log`` inside container because it is not available there.
* ``AHRIMAN_PACKAGER`` - packager name from which packages will be built, default is ``ahriman bot <ahriman@example.com>``.
* ``AHRIMAN_PORT`` - HTTP server port if any, default is empty.
* ``AHRIMAN_REPOSITORY`` - repository name, default is ``aur-clone``.
@ -279,7 +279,7 @@ Well for that you would need to have web container instance running forever; it
Note about ``AHRIMAN_PORT`` environment variable which is required in order to enable web service. An additional port bind by ``-p 8080:8080`` is required to pass docker port outside of container.
For every next container run use arguments ``-e AHRIMAN_PORT=8080 --net=host``\ , e.g.:
For every next container run use arguments ``-e AHRIMAN_PORT=8080 --net=host``, e.g.:
.. code-block:: shell
@ -294,7 +294,7 @@ Wait I would like to use the repository from another server
There are several choices:
#.
Easy and cheap, just share your local files through the internet, e.g. for ``nginx``\ :
Easy and cheap, just share your local files through the internet, e.g. for ``nginx``:
.. code-block::
@ -316,7 +316,7 @@ There are several choices:
[rsync]
remote = 192.168.0.1:/srv/repo
After that just add ``/srv/repo`` to the ``pacman.conf`` as usual. You can also upload to S3 (e.g. ``Server = https://s3.eu-central-1.amazonaws.com/repository/x86_64``\ ) or to Github (e.g. ``Server = https://github.com/ahriman/repository/releases/download/x86_64``\ ).
After that just add ``/srv/repo`` to the ``pacman.conf`` as usual. You can also upload to S3 (e.g. ``Server = https://s3.eu-central-1.amazonaws.com/repository/x86_64``) or to Github (e.g. ``Server = https://github.com/ahriman/repository/releases/download/x86_64``).
How do I configure S3?
^^^^^^^^^^^^^^^^^^^^^^
@ -474,7 +474,7 @@ I would like to get messages to my telegram account/channel
#. Make your channel public
#.
Get chat id if you want to use by numerical id or just use id prefixed with ``@`` (e.g. ``@ahriman``\ ). If you are not using chat the chat id is your user id. If you don't want to make channel public you can use `this guide <https://stackoverflow.com/a/33862907>`_.
Get chat id if you want to use by numerical id or just use id prefixed with ``@`` (e.g. ``@ahriman``). If you are not using chat the chat id is your user id. If you don't want to make channel public you can use `this guide <https://stackoverflow.com/a/33862907>`_.
#.
Configure the service:
@ -489,7 +489,7 @@ I would like to get messages to my telegram account/channel
chat_id = @ahriman
link_path = http://example.com/x86_64
``api_key`` is the one sent by `@BotFather <https://t.me/botfather>`_\ , ``chat_id`` is the value retrieved from previous step.
``api_key`` is the one sent by `@BotFather <https://t.me/botfather>`_, ``chat_id`` is the value retrieved from previous step.
If you did everything fine you should receive the message with the next update. Quick credentials check can be done by using the following command:
@ -606,7 +606,7 @@ The service provides several commands aim to do easy repository backup and resto
sudo ahriman repo-backup /tmp/repo.tar.gz
This command will pack all configuration files together with database file into the archive specified as command line argument (i.e. ``/tmp/repo.tar.gz``\ ). In addition it will also archive ``cache`` directory (the one which contains local clones used by e.g. local packages) and ``.gnupg`` of the ``ahriman`` user.
This command will pack all configuration files together with database file into the archive specified as command line argument (i.e. ``/tmp/repo.tar.gz``). In addition it will also archive ``cache`` directory (the one which contains local clones used by e.g. local packages) and ``.gnupg`` of the ``ahriman`` user.
#.
Copy created archive from source server ``server1.example.com`` to target ``server2.example.com``.
@ -621,7 +621,7 @@ The service provides several commands aim to do easy repository backup and resto
sudo ahriman repo-restore /tmp/repo.tar.gz
An additional argument ``-o``\ /\ ``--output`` can be used to specify extraction root (\ ``/`` by default).
An additional argument ``-o``/``--output`` can be used to specify extraction root (``/`` by default).
#.
Rebuild repository:
@ -684,7 +684,7 @@ I would like to check service logs
By default, the service writes logs to ``/dev/log`` which can be accessed by using ``journalctl`` command (logs are written to the journal of the user under which command is run).
You can also edit configuration and forward logs to ``stderr``\ , just change ``handlers`` value, e.g.:
You can also edit configuration and forward logs to ``stderr``, just change ``handlers`` value, e.g.:
.. code-block:: shell

View File

@ -10,7 +10,7 @@ Features
* Multi-architecture support.
* VCS packages support.
* Sign support with gpg (repository, package, per package settings).
* Synchronization to remote services (rsync, s3 and github) and report generation (email, html, telegram).
* Synchronization to remote services (rsync, s3 and github) and report generation (email, html, telegram) and even ability to write own extensions.
* Dependency manager.
* Ability to patch AUR packages and even create package from local PKGBUILDs.
* Repository status interface with optional authorization and control options.
@ -27,6 +27,7 @@ Contents
faq
architecture
advanced-usage
triggers
modules
Indices and tables

View File

@ -15,7 +15,7 @@ Initial setup
``repo-setup`` literally does the following steps:
#.
Create ``/var/lib/ahriman/.makepkg.conf`` with ``makepkg.conf`` overrides if required (at least you might want to set ``PACKAGER``\ ):
Create ``/var/lib/ahriman/.makepkg.conf`` with ``makepkg.conf`` overrides if required (at least you might want to set ``PACKAGER``):
.. code-block:: shell
@ -25,9 +25,9 @@ Initial setup
Configure build tools (it is required for correct dependency management system):
#.
Create build command, e.g. ``ln -s /usr/bin/archbuild /usr/local/bin/ahriman-x86_64-build`` (you can choose any name for command, basically it should be ``{name}-{arch}-build``\ ).
Create build command, e.g. ``ln -s /usr/bin/archbuild /usr/local/bin/ahriman-x86_64-build`` (you can choose any name for command, basically it should be ``{name}-{arch}-build``).
#.
Create configuration file, e.g. ``cp /usr/share/devtools/pacman-{extra,ahriman}.conf`` (same as previous ``pacman-{name}.conf``\ ).
Create configuration file, e.g. ``cp /usr/share/devtools/pacman-{extra,ahriman}.conf`` (same as previous ``pacman-{name}.conf``).
#.
Change configuration file, add your own repository, add multilib repository etc;
#.
@ -55,7 +55,7 @@ Initial setup
chmod 400 /etc/sudoers.d/ahriman
#.
Start and enable ``ahriman@.timer`` via ``systemctl``\ :
Start and enable ``ahriman@.timer`` via ``systemctl``:
.. code-block:: shell

62
docs/triggers.rst Normal file
View File

@ -0,0 +1,62 @@
Triggers
========
The package provides ability to write custom extensions which will be run on (the most) actions, e.g. after updates. By default ahriman provides two types of extensions - reporting and files uploading. Each extension must derive from the ``Trigger`` class and implement ``run`` method
Trigger example
---------------
Lets consider example of reporting trigger (e.g. `slack <https://slack.com/>`_, which provides easy HTTP API for triggers for integrations).
In order to post message to slack we will need a specific trigger url (something like ``https://hooks.slack.com/services/company_id/trigger_id``), channel (e.g. ``#archrepo``) and username (``repo-bot``). It can be retrieved from the same application instance.
As it has been mentioned, our trigger must derive from specific class
.. code-block:: python
from ahriman.core.triggers import Trigger
class SlackReporter(Trigger):
def __init__(self, architecture, configuration):
Trigger.__init__(self, architecture, configuration)
self.slack_url = configuration.get("slack", "url")
self.channel = configuration.get("slack", "channel")
self.username = configuration.get("slack", "username")
By now we have class with all required variables.
Lets implement run method. Slack API requires positing data with specific payload by HTTP, thus
.. code-block:: python
import json
import requests
def notify(result, slack_url, channel, username):
text = f"""Build has been completed with packages: {", ".join([package.name for package in result.success])}"""
payload = {"channel": channel, "username": username, "text": text}
response = requests.post(slack_url, data={"payload": json.dumps(payload)})
response.raise_for_status()
Obviously you can implement the specified method in class, but for guide purpose it has been done as separated method. Now we can merge this method into the class
.. code-block:: python
class SlackReporter(Trigger):
def __init__(self, architecture, configuration):
Trigger.__init__(self, architecture, configuration)
self.slack_url = configuration.get("slack", "url")
self.channel = configuration.get("slack", "channel")
self.username = configuration.get("slack", "username")
def run(self, result, packages):
notify(result, self.slack_url, channel, username)
Setup the trigger
-----------------
First, put the trigger in any path it can be exported, e.g. by packing the resource into python package (which will lead to import path as ``package.slack_reporter.SlackReporter``) or just put file somewhere it can be accessed by application (e.g. ``/usr/local/lib/slack_reporter.py.SlackReporter``.
After that run application as usual and receive notification in your slack channel.

View File

@ -1,7 +1,7 @@
# Maintainer: Evgeniy Alekseev
pkgname='ahriman'
pkgver=2.0.0rc7
pkgver=2.0.0rc11
pkgrel=1
pkgdesc="ArcH Linux ReposItory MANager"
arch=('any')

View File

@ -21,6 +21,7 @@ build_command = extra-x86_64-build
ignore_packages =
makechrootpkg_flags =
makepkg_flags = --nocolor
triggers = ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger
[repository]
name = aur-clone

View File

@ -98,12 +98,11 @@ def _parser() -> argparse.ArgumentParser:
_set_repo_config_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_setup_parser(subparsers)
_set_repo_sign_parser(subparsers)
_set_repo_status_update_parser(subparsers)
_set_repo_sync_parser(subparsers)
_set_repo_triggers_parser(subparsers)
_set_repo_update_parser(subparsers)
_set_user_add_parser(subparsers)
_set_user_list_parser(subparsers)
@ -496,25 +495,6 @@ def _set_repo_remove_unknown_parser(root: SubParserAction) -> argparse.ArgumentP
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=_formatter)
parser.add_argument("target", help="target to generate report", nargs="*")
parser.set_defaults(handler=handlers.Report)
return parser
def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository restore subcommand
@ -600,7 +580,7 @@ def _set_repo_status_update_parser(root: SubParserAction) -> argparse.ArgumentPa
return parser
def _set_repo_sync_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_triggers_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository sync subcommand
@ -610,12 +590,10 @@ def _set_repo_sync_parser(root: SubParserAction) -> argparse.ArgumentParser:
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 = root.add_parser("repo-triggers", help="run triggers",
description="run triggers on empty build result as configured by settings",
formatter_class=_formatter)
parser.add_argument("target", help="target to sync", nargs="*")
parser.set_defaults(handler=handlers.Sync)
parser.set_defaults(handler=handlers.Triggers)
return parser

View File

@ -56,8 +56,7 @@ class Application(ApplicationPackages, ApplicationRepository):
Args:
result(Result): build result
"""
self.report([], result)
self.sync([], result.success)
self.repository.process_triggers(result)
def _known_packages(self) -> Set[str]:
"""

View File

@ -85,9 +85,9 @@ class ApplicationPackages(ApplicationProperties):
self.database.build_queue_insert(package)
self.database.remote_update(package)
with tmpdir() as local_path:
Sources.load(local_path, package.remote, self.database.patches_get(package.base))
self._process_dependencies(local_path, known_packages, without_dependencies)
with tmpdir() as local_dir:
Sources.load(local_dir, package, self.database.patches_get(package.base), self.repository.paths)
self._process_dependencies(local_dir, known_packages, without_dependencies)
def _add_directory(self, source: str, *_: Any) -> None:
"""
@ -96,8 +96,8 @@ class ApplicationPackages(ApplicationProperties):
Args:
source(str): path to local directory
"""
local_path = Path(source)
for full_path in filter(package_like, local_path.iterdir()):
local_dir = Path(source)
for full_path in filter(package_like, local_dir.iterdir()):
self._add_archive(str(full_path))
def _add_local(self, source: str, known_packages: Set[str], without_dependencies: bool) -> None:
@ -146,19 +146,19 @@ class ApplicationPackages(ApplicationProperties):
self.database.remote_update(package)
# repository packages must not depend on unknown packages, thus we are not going to process dependencies
def _process_dependencies(self, local_path: Path, known_packages: Set[str], without_dependencies: bool) -> None:
def _process_dependencies(self, local_dir: Path, known_packages: Set[str], without_dependencies: bool) -> None:
"""
process package dependencies
Args:
local_path(Path): path to local package sources (i.e. cloned AUR repository)
local_dir(Path): path to local package sources (i.e. cloned AUR repository)
known_packages(Set[str]): list of packages which are known by the service
without_dependencies(bool): if set, dependency check will be disabled
"""
if without_dependencies:
return
dependencies = Package.dependencies(local_path)
dependencies = Package.dependencies(local_dir)
self.add(dependencies.difference(known_packages), PackageSource.AUR, without_dependencies)
def add(self, names: Iterable[str], source: PackageSource, without_dependencies: bool) -> None:

View File

@ -20,7 +20,7 @@
import logging
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
from ahriman.core.repository import Repository

View File

@ -24,7 +24,7 @@ from typing import Callable, Iterable, List
from ahriman.application.application.application_properties import ApplicationProperties
from ahriman.core.build_tools.sources import Sources
from ahriman.core.formatters.update_printer import UpdatePrinter
from ahriman.core.formatters import UpdatePrinter
from ahriman.core.tree import Tree
from ahriman.models.package import Package
from ahriman.models.result import Result
@ -66,17 +66,6 @@ class ApplicationRepository(ApplicationProperties):
if packages:
self.repository.clear_packages()
def report(self, target: Iterable[str], result: Result) -> None:
"""
generate report
Args:
target(Iterable[str]): list of targets to run (e.g. html)
result(Result): build result
"""
targets = target or None
self.repository.process_report(targets, result)
def sign(self, packages: Iterable[str]) -> None:
"""
sign packages and repository
@ -102,17 +91,6 @@ class ApplicationRepository(ApplicationProperties):
self.repository.sign.process_sign_repository(self.repository.repo.repo_path)
self._finalize(Result())
def sync(self, target: Iterable[str], built_packages: Iterable[Package]) -> None:
"""
sync to remote server
Args:
target(Iterable[str]): list of targets to run (e.g. s3)
built_packages(Iterable[Package]): list of packages which has just been built
"""
targets = target or None
self.repository.process_sync(targets, built_packages)
def unknown(self) -> List[str]:
"""
get packages which were not found in AUR
@ -169,7 +147,7 @@ class ApplicationRepository(ApplicationProperties):
process_update(packages, build_result)
# process manual packages
tree = Tree.load(updates, self.database)
tree = Tree.load(updates, self.repository.paths, self.database)
for num, level in enumerate(tree.levels()):
self.logger.info("processing level #%i %s", num, [package.base for package in level])
build_result = self.repository.process_build(level)

View File

@ -29,14 +29,13 @@ 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.report import Report
from ahriman.application.handlers.restore import Restore
from ahriman.application.handlers.search import Search
from ahriman.application.handlers.setup import Setup
from ahriman.application.handlers.sign import Sign
from ahriman.application.handlers.status import Status
from ahriman.application.handlers.status_update import StatusUpdate
from ahriman.application.handlers.sync import Sync
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

View File

@ -22,7 +22,7 @@ import argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration

View File

@ -24,9 +24,9 @@ from pathlib import Path
from tarfile import TarFile
from typing import Set, Type
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
class Backup(Handler):

View File

@ -22,7 +22,7 @@ import argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration

View File

@ -21,9 +21,9 @@ import argparse
from typing import Type
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.formatters.configuration_printer import ConfigurationPrinter
from ahriman.core.formatters import ConfigurationPrinter
class Dump(Handler):

View File

@ -71,10 +71,10 @@ class Handler:
if args.architecture: # architecture is specified explicitly
return sorted(set(args.architecture))
config = Configuration()
config.load(args.configuration)
configuration = Configuration()
configuration.load(args.configuration)
# wtf???
root = config.getpath("repository", "root") # pylint: disable=assignment-from-no-return
root = configuration.getpath("repository", "root") # pylint: disable=assignment-from-no-return
architectures = RepositoryPaths.known_architectures(root)
if not architectures: # well we did not find anything

View File

@ -21,7 +21,7 @@ import argparse
from typing import Type
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration

View File

@ -22,7 +22,7 @@ import argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration

View File

@ -23,10 +23,10 @@ from pathlib import Path
from typing import List, Optional, Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.build_tools.sources import Sources
from ahriman.core.configuration import Configuration
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters import StringPrinter
from ahriman.models.action import Action
from ahriman.models.package import Package

View File

@ -22,9 +22,9 @@ import argparse
from typing import List, Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.formatters.update_printer import UpdatePrinter
from ahriman.core.formatters import UpdatePrinter
from ahriman.models.package import Package

View File

@ -22,7 +22,7 @@ import argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration

View File

@ -22,9 +22,9 @@ import argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters import StringPrinter
class RemoveUnknown(Handler):

View File

@ -22,7 +22,7 @@ import argparse
from typing import Type
from tarfile import TarFile
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration

View File

@ -23,12 +23,11 @@ from dataclasses import fields
from typing import Callable, Iterable, List, Tuple, Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.core.alpm.remote.aur import AUR
from ahriman.core.alpm.remote.official import Official
from ahriman.application.handlers import Handler
from ahriman.core.alpm.remote import AUR, Official
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import InvalidOption
from ahriman.core.formatters.aur_printer import AurPrinter
from ahriman.core.formatters import AurPrinter
from ahriman.models.aur_package import AURPackage

View File

@ -23,7 +23,7 @@ from pathlib import Path
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.models.repository_paths import RepositoryPaths

View File

@ -22,7 +22,7 @@ import argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration

View File

@ -22,10 +22,9 @@ import argparse
from typing import Callable, Iterable, Tuple, Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.formatters.package_printer import PackagePrinter
from ahriman.core.formatters.status_printer import StatusPrinter
from ahriman.core.formatters import PackagePrinter, StatusPrinter
from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package

View File

@ -22,7 +22,7 @@ import argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.models.action import Action

View File

@ -22,14 +22,14 @@ import argparse
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.models.result import Result
class Report(Handler):
class Triggers(Handler):
"""
generate report handler
triggers handlers
"""
@classmethod
@ -45,4 +45,4 @@ class Report(Handler):
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
Application(architecture, configuration, no_report, unsafe).report(args.target, Result())
Application(architecture, configuration, no_report, unsafe).repository.process_triggers(Result())

View File

@ -22,9 +22,9 @@ import shlex
from typing import List, Type
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters import StringPrinter
class UnsafeCommands(Handler):

View File

@ -22,7 +22,7 @@ import argparse
from typing import Callable, Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration

View File

@ -23,10 +23,10 @@ import getpass
from pathlib import Path
from typing import Type
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.formatters.user_printer import UserPrinter
from ahriman.core.database import SQLite
from ahriman.core.formatters import UserPrinter
from ahriman.models.action import Action
from ahriman.models.user import User

View File

@ -21,7 +21,7 @@ import argparse
from typing import Type
from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.spawn import Spawn

View File

@ -17,3 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.alpm.remote.remote import Remote
from ahriman.core.alpm.remote.aur import AUR
from ahriman.core.alpm.remote.official import Official
from ahriman.core.alpm.remote.official_syncdb import OfficialSyncdb

View File

@ -22,7 +22,7 @@ import requests
from typing import Any, Dict, List, Type
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.remote import Remote
from ahriman.core.alpm.remote import Remote
from ahriman.core.exceptions import InvalidPackageInfo
from ahriman.core.util import exception_response_text
from ahriman.models.aur_package import AURPackage

View File

@ -22,7 +22,7 @@ import requests
from typing import Any, Dict, List, Type
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.remote import Remote
from ahriman.core.alpm.remote import Remote
from ahriman.core.exceptions import InvalidPackageInfo
from ahriman.core.util import exception_response_text
from ahriman.models.aur_package import AURPackage

View File

@ -18,7 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.official import Official
from ahriman.core.alpm.remote import Official
from ahriman.models.aur_package import AURPackage

View File

@ -38,8 +38,7 @@ class Remote:
These classes are designed to be used without instancing. In order to achieve it several class methods are
provided: ``info``, ``multisearch`` and ``search``. Thus, the basic flow is the following::
>>> from ahriman.core.alpm.remote.aur import AUR
>>> from ahriman.core.alpm.remote.official import Official
>>> from ahriman.core.alpm.remote import AUR, Official
>>>
>>> package = AUR.info("ahriman", pacman=pacman)
>>> search_result = Official.multisearch("pacman", "manager", pacman=pacman)

View File

@ -17,3 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.auth.auth import Auth
from ahriman.core.auth.mapping import Mapping
from ahriman.core.auth.oauth import OAuth

View File

@ -24,7 +24,7 @@ import logging
from typing import Optional, Type
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
from ahriman.models.auth_settings import AuthSettings
from ahriman.models.user_access import UserAccess
@ -82,10 +82,10 @@ class Auth:
"""
provider = AuthSettings.from_option(configuration.get("auth", "target", fallback="disabled"))
if provider == AuthSettings.Configuration:
from ahriman.core.auth.mapping import Mapping
from ahriman.core.auth import Mapping
return Mapping(configuration, database)
if provider == AuthSettings.OAuth:
from ahriman.core.auth.oauth import OAuth
from ahriman.core.auth import OAuth
return OAuth(configuration, database)
return cls(configuration)

View File

@ -19,10 +19,9 @@
#
from typing import Optional
from ahriman.core.auth.auth import Auth
from ahriman.core.auth import Auth
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
from ahriman.models.auth_settings import AuthSettings
from ahriman.models.user import User
from ahriman.models.user_access import UserAccess

View File

@ -21,9 +21,9 @@ import aioauth_client
from typing import Optional, Type
from ahriman.core.auth.mapping import Mapping
from ahriman.core.auth import Mapping
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
from ahriman.core.exceptions import InvalidOption
from ahriman.models.auth_settings import AuthSettings

View File

@ -24,7 +24,9 @@ from pathlib import Path
from typing import List, Optional
from ahriman.core.util import check_output, walk
from ahriman.models.package import Package
from ahriman.models.remote_source import RemoteSource
from ahriman.models.repository_paths import RepositoryPaths
class Sources:
@ -43,7 +45,7 @@ class Sources:
_check_output = check_output
@staticmethod
def add(sources_dir: Path, *pattern: str) -> None:
def _add(sources_dir: Path, *pattern: str) -> None:
"""
track found files via git
@ -64,7 +66,7 @@ class Sources:
exception=None, cwd=sources_dir, logger=Sources.logger)
@staticmethod
def diff(sources_dir: Path) -> str:
def _diff(sources_dir: Path) -> str:
"""
generate diff from the current version and write it to the output file
@ -76,6 +78,21 @@ class Sources:
"""
return Sources._check_output("git", "diff", exception=None, cwd=sources_dir, logger=Sources.logger)
@staticmethod
def _move(pkgbuild_dir: Path, sources_dir: Path) -> None:
"""
move content from pkgbuild_dir to sources_dir
Args:
pkgbuild_dir(Path): path to directory with pkgbuild from which need to move
sources_dir(Path): path to target directory
"""
if pkgbuild_dir == sources_dir:
return # directories are the same, no need to move
for src in walk(pkgbuild_dir):
dst = sources_dir / src.relative_to(pkgbuild_dir)
shutil.move(src, dst)
@staticmethod
def fetch(sources_dir: Path, remote: Optional[RemoteSource]) -> None:
"""
@ -103,7 +120,8 @@ class Sources:
remote.git_url, str(sources_dir),
exception=None, cwd=sources_dir, logger=Sources.logger)
else:
Sources.logger.warning("%s is not initialized, but no remote provided", sources_dir)
# it will cause an exception later
Sources.logger.error("%s is not initialized, but no remote provided", sources_dir)
# and now force reset to our branch
Sources._check_output("git", "checkout", "--force", branch,
@ -114,7 +132,7 @@ class Sources:
# move content if required
# we are using full path to source directory in order to make append possible
pkgbuild_dir = remote.pkgbuild_dir if remote is not None else sources_dir.resolve()
Sources.move((sources_dir / pkgbuild_dir).resolve(), sources_dir)
Sources._move((sources_dir / pkgbuild_dir).resolve(), sources_dir)
@staticmethod
def has_remotes(sources_dir: Path) -> bool:
@ -142,36 +160,26 @@ class Sources:
exception=None, cwd=sources_dir, logger=Sources.logger)
@staticmethod
def load(sources_dir: Path, remote: Optional[RemoteSource], patch: Optional[str]) -> None:
def load(sources_dir: Path, package: Package, patch: Optional[str], paths: RepositoryPaths) -> None:
"""
fetch sources from remote and apply patches
Args:
sources_dir(Path): local path to fetch
remote(Optional[RemoteSource]): remote target (from where to fetch)
package(Package): package definitions
patch(Optional[str]): optional patch to be applied
paths(RepositoryPaths): repository paths instance
"""
Sources.fetch(sources_dir, remote)
if (cache_dir := paths.cache_for(package.base)).is_dir() and cache_dir != sources_dir:
# no need to clone whole repository, just copy from cache first
shutil.copytree(cache_dir, sources_dir, dirs_exist_ok=True)
Sources.fetch(sources_dir, package.remote)
if patch is None:
Sources.logger.info("no patches found")
return
Sources.patch_apply(sources_dir, patch)
@staticmethod
def move(pkgbuild_dir: Path, sources_dir: Path) -> None:
"""
move content from pkgbuild_dir to sources_dir
Args:
pkgbuild_dir(Path): path to directory with pkgbuild from which need to move
sources_dir(Path): path to target directory
"""
if pkgbuild_dir == sources_dir:
return # directories are the same, no need to move
for src in walk(pkgbuild_dir):
dst = sources_dir / src.relative_to(pkgbuild_dir)
shutil.move(src, dst)
@staticmethod
def patch_apply(sources_dir: Path, patch: str) -> None:
"""
@ -198,6 +206,6 @@ class Sources:
Returns:
str: patch as plain text
"""
Sources.add(sources_dir, *pattern)
diff = Sources.diff(sources_dir)
Sources._add(sources_dir, *pattern)
diff = Sources._diff(sources_dir)
return f"{diff}\n" # otherwise, patch will be broken

View File

@ -18,14 +18,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
import shutil
from pathlib import Path
from typing import List
from ahriman.core.build_tools.sources import Sources
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
from ahriman.core.exceptions import BuildFailed
from ahriman.core.util import check_output
from ahriman.models.package import Package
@ -66,12 +65,12 @@ class Task:
self.makepkg_flags = configuration.getlist("build", "makepkg_flags", fallback=[])
self.makechrootpkg_flags = configuration.getlist("build", "makechrootpkg_flags", fallback=[])
def build(self, sources_path: Path) -> List[Path]:
def build(self, sources_dir: Path) -> List[Path]:
"""
run package build
Args:
sources_path(Path): path to where sources are
sources_dir(Path): path to where sources are
Returns:
List[Path]: paths of produced packages
@ -85,26 +84,23 @@ class Task:
Task._check_output(
*command,
exception=BuildFailed(self.package.base),
cwd=sources_path,
cwd=sources_dir,
logger=self.build_logger,
user=self.uid)
# well it is not actually correct, but we can deal with it
packages = Task._check_output("makepkg", "--packagelist",
exception=BuildFailed(self.package.base),
cwd=sources_path,
cwd=sources_dir,
logger=self.build_logger).splitlines()
return [Path(package) for package in packages]
def init(self, path: Path, database: SQLite) -> None:
def init(self, sources_dir: Path, database: SQLite) -> None:
"""
fetch package from git
Args:
path(Path): local path to fetch
sources_dir(Path): local path to fetch
database(SQLite): database instance
"""
if self.paths.cache_for(self.package.base).is_dir():
# no need to clone whole repository, just copy from cache first
shutil.copytree(self.paths.cache_for(self.package.base), path, dirs_exist_ok=True)
Sources.load(path, self.package.remote, database.patches_get(self.package.base))
Sources.load(sources_dir, self.package, database.patches_get(self.package.base), self.paths)

View File

@ -125,11 +125,11 @@ class Configuration(configparser.RawConfigParser):
Returns:
Configuration: configuration instance
"""
config = cls()
config.load(path)
config.merge_sections(architecture)
config.load_logging(quiet)
return config
configuration = cls()
configuration.load(path)
configuration.merge_sections(architecture)
configuration.load_logging(quiet)
return configuration
@staticmethod
def __convert_list(value: str) -> List[str]:

View File

@ -17,3 +17,4 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.database.sqlite import SQLite

View File

@ -27,8 +27,7 @@ from ahriman.core.database.data.users import migrate_users_data
from ahriman.models.migration_result import MigrationResult
def migrate_data(
result: MigrationResult, connection: Connection, configuration: Configuration) -> None:
def migrate_data(result: MigrationResult, connection: Connection, configuration: Configuration) -> None:
"""
perform data migration

View File

@ -70,6 +70,12 @@ class InitializeException(RuntimeError):
RuntimeError.__init__(self, f"Could not load service: {details}")
class InvalidExtension(RuntimeError):
"""
exception being raised by trigger load in case of errors
"""
class InvalidOption(ValueError):
"""
exception which will be raised on configuration errors

View File

@ -1,19 +1,29 @@
#
# Copyright (c) 2021-2022 ahriman team.
# copyright (c) 2021-2022 ahriman team.
#
# This file is part of ahriman
# 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
# 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.
# this program is distributed in the hope that it will be useful,
# but without any warranty; without even the implied warranty of
# merchantability or fitness for a particular purpose. see the
# gnu general public license for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# you should have received a copy of the gnu general public license
# along with this program. if not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.formatters.printer import Printer
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters.aur_printer import AurPrinter
from ahriman.core.formatters.build_printer import BuildPrinter
from ahriman.core.formatters.configuration_printer import ConfigurationPrinter
from ahriman.core.formatters.package_printer import PackagePrinter
from ahriman.core.formatters.status_printer import StatusPrinter
from ahriman.core.formatters.update_printer import UpdatePrinter
from ahriman.core.formatters.user_printer import UserPrinter

View File

@ -19,7 +19,7 @@
#
from typing import List
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters import StringPrinter
from ahriman.core.util import pretty_datetime
from ahriman.models.aur_package import AURPackage
from ahriman.models.property import Property

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters import StringPrinter
from ahriman.models.package import Package

View File

@ -19,7 +19,7 @@
#
from typing import Dict, List
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters import StringPrinter
from ahriman.models.property import Property

View File

@ -19,7 +19,7 @@
#
from typing import List
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters import StringPrinter
from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package
from ahriman.models.property import Property

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters import StringPrinter
from ahriman.models.build_status import BuildStatus

View File

@ -19,7 +19,7 @@
#
from typing import Optional
from ahriman.core.formatters.printer import Printer
from ahriman.core.formatters import Printer
class StringPrinter(Printer):

View File

@ -19,7 +19,7 @@
#
from typing import List, Optional
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters import StringPrinter
from ahriman.models.package import Package
from ahriman.models.property import Property

View File

@ -19,7 +19,7 @@
#
from typing import List
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters import StringPrinter
from ahriman.models.property import Property
from ahriman.models.user import User

View File

@ -17,3 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.report.report import Report
from ahriman.core.report.jinja_template import JinjaTemplate
from ahriman.core.report.console import Console
from ahriman.core.report.email import Email
from ahriman.core.report.html import HTML
from ahriman.core.report.telegram import Telegram
from ahriman.core.report.report_trigger import ReportTrigger

View File

@ -20,8 +20,8 @@
from typing import Iterable
from ahriman.core.configuration import Configuration
from ahriman.core.formatters.build_printer import BuildPrinter
from ahriman.core.report.report import Report
from ahriman.core.formatters import BuildPrinter
from ahriman.core.report import Report
from ahriman.models.package import Package
from ahriman.models.result import Result

View File

@ -25,8 +25,7 @@ from email.mime.text import MIMEText
from typing import Dict, Iterable
from ahriman.core.configuration import Configuration
from ahriman.core.report.jinja_template import JinjaTemplate
from ahriman.core.report.report import Report
from ahriman.core.report import JinjaTemplate, Report
from ahriman.core.util import pretty_datetime
from ahriman.models.package import Package
from ahriman.models.result import Result

View File

@ -20,8 +20,7 @@
from typing import Iterable
from ahriman.core.configuration import Configuration
from ahriman.core.report.jinja_template import JinjaTemplate
from ahriman.core.report.report import Report
from ahriman.core.report import JinjaTemplate, Report
from ahriman.models.package import Package
from ahriman.models.result import Result

View File

@ -87,16 +87,16 @@ class Report:
section, provider_name = configuration.gettype(target, architecture)
provider = ReportSettings.from_option(provider_name)
if provider == ReportSettings.HTML:
from ahriman.core.report.html import HTML
from ahriman.core.report import HTML
return HTML(architecture, configuration, section)
if provider == ReportSettings.Email:
from ahriman.core.report.email import Email
from ahriman.core.report import Email
return Email(architecture, configuration, section)
if provider == ReportSettings.Console:
from ahriman.core.report.console import Console
from ahriman.core.report import Console
return Console(architecture, configuration, section)
if provider == ReportSettings.Telegram:
from ahriman.core.report.telegram import Telegram
from ahriman.core.report import Telegram
return Telegram(architecture, configuration, section)
return cls(architecture, configuration) # should never happen
@ -109,13 +109,13 @@ class Report:
result(Result): build result
"""
def run(self, packages: Iterable[Package], result: Result) -> None:
def run(self, result: Result, packages: Iterable[Package]) -> None:
"""
run report generation
Args:
packages(Iterable[Package]): list of packages to generate report
result(Result): build result
packages(Iterable[Package]): list of packages to generate report
Raises:
ReportFailed: in case of any report unmatched exception

View File

@ -17,31 +17,42 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import argparse
from typing import Iterable
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.triggers import Trigger
from ahriman.core.report import Report
from ahriman.models.package import Package
from ahriman.models.result import Result
class Sync(Handler):
class ReportTrigger(Trigger):
"""
remote sync handler
report trigger
Attributes:
targets(List[str]): report target list
"""
@classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
def __init__(self, architecture: str, configuration: Configuration) -> None:
"""
callback for command line
default constructor
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
Application(architecture, configuration, no_report, unsafe).sync(args.target, [])
Trigger.__init__(self, architecture, configuration)
self.targets = configuration.getlist("report", "target")
def run(self, result: Result, packages: Iterable[Package]) -> None:
"""
run trigger
Args:
result(Result): build result
packages(Iterable[Package]): list of all available packages
"""
for target in self.targets:
runner = Report.load(self.architecture, self.configuration, target)
runner.run(result, packages)

View File

@ -23,8 +23,7 @@ import requests
from typing import Iterable
from ahriman.core.configuration import Configuration
from ahriman.core.report.jinja_template import JinjaTemplate
from ahriman.core.report.report import Report
from ahriman.core.report import JinjaTemplate, Report
from ahriman.core.util import exception_response_text
from ahriman.models.package import Package
from ahriman.models.result import Result

View File

@ -23,9 +23,7 @@ from pathlib import Path
from typing import Iterable, List, Optional, Set
from ahriman.core.build_tools.task import Task
from ahriman.core.report.report import Report
from ahriman.core.repository.cleaner import Cleaner
from ahriman.core.upload.upload import Upload
from ahriman.core.util import tmpdir
from ahriman.models.package import Package
from ahriman.models.result import Result
@ -143,35 +141,14 @@ class Executor(Cleaner):
return self.repo.repo_path
def process_report(self, targets: Optional[Iterable[str]], result: Result) -> None:
def process_triggers(self, result: Result) -> None:
"""
generate reports
process triggers setup by settings
Args:
targets(Optional[Iterable[str]]): list of targets to generate reports. Configuration option will be used
if it is not set
result(Result): build result
"""
if targets is None:
targets = self.configuration.getlist("report", "target")
for target in targets:
runner = Report.load(self.architecture, self.configuration, target)
runner.run(self.packages(), result)
def process_sync(self, targets: Optional[Iterable[str]], built_packages: Iterable[Package]) -> None:
"""
process synchronization to remote servers
Args:
targets(Optional[Iterable[str]]): list of targets to sync. Configuration option will be used
if it is not set
built_packages(Iterable[Package]): list of packages which has just been built
"""
if targets is None:
targets = self.configuration.getlist("upload", "target")
for target in targets:
runner = Upload.load(self.architecture, self.configuration, target)
runner.run(self.paths.repository, built_packages)
self.triggers.process(result, self.packages())
def process_update(self, packages: Iterable[Path]) -> Result:
"""

View File

@ -35,7 +35,7 @@ class Repository(Executor, UpdateHandler):
sync local repository to remote, generate report, etc::
>>> from ahriman.core.configuration import Configuration
>>> from ahriman.core.database.sqlite import SQLite
>>> from ahriman.core.database import SQLite
>>>
>>> configuration = Configuration()
>>> database = SQLite.load(configuration)

View File

@ -22,10 +22,11 @@ import logging
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.repo import Repo
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
from ahriman.core.exceptions import UnsafeRun
from ahriman.core.sign.gpg import GPG
from ahriman.core.status.client import Client
from ahriman.core.triggers import TriggerLoader
from ahriman.core.util import check_user
@ -45,6 +46,7 @@ class RepositoryProperties:
repo(Repo): repo commands wrapper instance
reporter(Client): build status reporter instance
sign(GPG): GPG wrapper instance
triggers(TriggerLoader): triggers holder
"""
def __init__(self, architecture: str, configuration: Configuration, database: SQLite,
@ -78,3 +80,4 @@ class RepositoryProperties:
self.sign = GPG(architecture, configuration)
self.repo = Repo(self.name, self.paths, self.sign.repository_sign_args)
self.reporter = Client() if no_report else Client.load(configuration)
self.triggers = TriggerLoader(architecture, configuration)

View File

@ -22,7 +22,7 @@ import logging
from typing import Dict, List, Optional, Tuple
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
from ahriman.core.exceptions import UnknownPackage
from ahriman.core.repository import Repository
from ahriman.models.build_status import BuildStatus, BuildStatusEnum

View File

@ -22,9 +22,10 @@ from __future__ import annotations
from typing import Iterable, List, Set, Type
from ahriman.core.build_tools.sources import Sources
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
from ahriman.core.util import tmpdir
from ahriman.models.package import Package
from ahriman.models.repository_paths import RepositoryPaths
class Leaf:
@ -58,19 +59,20 @@ class Leaf:
return self.package.packages.keys()
@classmethod
def load(cls: Type[Leaf], package: Package, database: SQLite) -> Leaf:
def load(cls: Type[Leaf], package: Package, paths: RepositoryPaths, database: SQLite) -> Leaf:
"""
load leaf from package with dependencies
Args:
package(Package): package properties
paths(RepositoryPaths): repository paths instance
database(SQLite): database instance
Returns:
Leaf: loaded class
"""
with tmpdir() as clone_dir:
Sources.load(clone_dir, package.remote, database.patches_get(package.base))
Sources.load(clone_dir, package, database.patches_get(package.base), paths)
dependencies = Package.dependencies(clone_dir)
return cls(package, dependencies)
@ -102,7 +104,7 @@ class Tree:
method::
>>> from ahriman.core.configuration import Configuration
>>> from ahriman.core.database.sqlite import SQLite
>>> from ahriman.core.database import SQLite
>>> from ahriman.core.repository import Repository
>>>
>>> configuration = Configuration()
@ -110,7 +112,7 @@ class Tree:
>>> repository = Repository("x86_64", configuration, database, no_report=False, unsafe=False)
>>> packages = repository.packages()
>>>
>>> tree = Tree.load(packages, database)
>>> tree = Tree.load(packages, configuration.repository_paths, database)
>>> for tree_level in tree.levels():
>>> for package in tree_level:
>>> print(package.base)
@ -138,18 +140,19 @@ class Tree:
self.leaves = leaves
@classmethod
def load(cls: Type[Tree], packages: Iterable[Package], database: SQLite) -> Tree:
def load(cls: Type[Tree], packages: Iterable[Package], paths: RepositoryPaths, database: SQLite) -> Tree:
"""
load tree from packages
Args:
packages(Iterable[Package]): packages list
paths(RepositoryPaths): repository paths instance
database(SQLite): database instance
Returns:
Tree: loaded class
"""
return cls([Leaf.load(package, database) for package in packages])
return cls([Leaf.load(package, paths, database) for package in packages])
def levels(self) -> List[List[Package]]:
"""

View File

@ -0,0 +1,21 @@
#
# Copyright (c) 2021-2022 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.triggers.trigger import Trigger
from ahriman.core.triggers.trigger_loader import TriggerLoader

View File

@ -0,0 +1,79 @@
#
# Copyright (c) 2021-2022 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
from typing import Iterable
from ahriman.core.configuration import Configuration
from ahriman.models.package import Package
from ahriman.models.result import Result
class Trigger:
"""
trigger base class
Attributes:
architecture(str): repository architecture
configuration(Configuration): configuration instance
logger(logging.Logger): application logger
Examples:
This class must be used in order to create own extension. Basically idea is the following::
>>> class CustomTrigger(Trigger):
>>> def run(self, result: Result, packages: Iterable[Package]) -> None:
>>> perform_some_action()
Having this class you can pass it to ``configuration`` and it will be run on action::
>>> from ahriman.core.triggers import TriggerLoader
>>>
>>> configuration = Configuration()
>>> configuration.set_option("build", "triggers", "my.awesome.package.CustomTrigger")
>>>
>>> loader = TriggerLoader("x86_64", configuration)
>>> loader.process(Result(), [])
"""
def __init__(self, architecture: str, configuration: Configuration) -> None:
"""
default constructor
Args:
architecture(str): repository architecture
configuration(Configuration): configuration instance
"""
self.logger = logging.getLogger("root")
self.architecture = architecture
self.configuration = configuration
def run(self, result: Result, packages: Iterable[Package]) -> None:
"""
run trigger
Args:
result(Result): build result
packages(Iterable[Package]): list of all available packages
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError

View File

@ -0,0 +1,161 @@
#
# Copyright (c) 2021-2022 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import importlib
import logging
import os
from pathlib import Path
from types import ModuleType
from typing import Iterable
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import InvalidExtension
from ahriman.core.triggers import Trigger
from ahriman.models.package import Package
from ahriman.models.result import Result
class TriggerLoader:
"""
trigger loader class
Attributes:
architecture(str): repository architecture
configuration(Configuration): configuration instance
logger(logging.Logger): application logger
triggers(List[Trigger]): list of loaded triggers according to the configuration
Examples:
This class more likely must not be used directly, but the usual workflow is the following::
>>> configuration = Configuration() # create configuration
>>> configuration.set_option("build", "triggers", "ahriman.core.report.ReportTrigger") # set class for load
Having such configuration you can create instance of the loader::
>>> loader = TriggerLoader("x86_64", configuration)
>>> print(loader.triggers)
After that you are free to run triggers::
>>> loader.process(Result(), [])
"""
def __init__(self, architecture: str, configuration: Configuration) -> None:
"""
default constructor
Args:
architecture(str): repository architecture
configuration(Configuration): configuration instance
"""
self.logger = logging.getLogger("root")
self.architecture = architecture
self.configuration = configuration
self.triggers = [
self._load_trigger(trigger)
for trigger in configuration.getlist("build", "triggers")
]
def _load_module_from_file(self, module_path: str, implementation: str) -> ModuleType:
"""
load module by given file path
Args:
module_path(str): import package
implementation(str): specific trigger implementation, class name, required by import
Returns:
ModuleType: module loaded from the imported file
"""
self.logger.info("load module %s from path %s", implementation, module_path)
# basically this method is called only if ``module_path`` exists and is file.
# Thus, this method should never throw ``FileNotFoundError`` exception
loader = importlib.machinery.SourceFileLoader(implementation, module_path)
module = ModuleType(loader.name)
loader.exec_module(module)
return module
def _load_module_from_package(self, package: str) -> ModuleType:
"""
load module by given package name
Args:
package(str): package name to import
Returns:
ModuleType: module loaded from the imported module
"""
self.logger.info("load module from package %s", package)
try:
return importlib.import_module(package)
except ModuleNotFoundError:
raise InvalidExtension(f"Module {package} not found")
def _load_trigger(self, module_path: str) -> Trigger:
"""
load trigger by module path
Args:
module_path(str): module import path to load
Returns:
Trigger: loaded trigger based on settings
"""
*package_path_parts, class_name = module_path.split(".")
package_or_path = ".".join(package_path_parts)
# it works for both missing permission and file does not exist
if os.access(Path(package_or_path), os.R_OK):
module = self._load_module_from_file(package_or_path, class_name)
else:
module = self._load_module_from_package(package_or_path)
trigger_type = getattr(module, class_name, None)
if not isinstance(trigger_type, type):
raise InvalidExtension(f"{class_name} of {package_or_path} is not a type")
self.logger.info("loaded type %s of package %s", class_name, package_or_path)
try:
trigger = trigger_type(self.architecture, self.configuration)
except Exception:
raise InvalidExtension(f"Could not load instance of trigger from {class_name} of {package_or_path}")
if not isinstance(trigger, Trigger):
raise InvalidExtension(f"Class {class_name} of {package_or_path} is not a Trigger")
return trigger
def process(self, result: Result, packages: Iterable[Package]) -> None:
"""
run remote sync
Args:
result(Result): build result
packages(Iterable[Package]): list of all available packages
"""
for trigger in self.triggers:
trigger_name = type(trigger).__name__
try:
self.logger.info("executing extension %s", trigger_name)
trigger.run(result, packages)
except Exception:
self.logger.exception("got exception while run trigger %s", trigger_name)

View File

@ -17,3 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.upload.upload import Upload
from ahriman.core.upload.http_upload import HttpUpload
from ahriman.core.upload.github import Github
from ahriman.core.upload.rsync import Rsync
from ahriman.core.upload.s3 import S3
from ahriman.core.upload.upload_trigger import UploadTrigger

View File

@ -24,7 +24,7 @@ from pathlib import Path
from typing import Any, Dict, Iterable, Optional
from ahriman.core.configuration import Configuration
from ahriman.core.upload.http_upload import HttpUpload
from ahriman.core.upload import HttpUpload
from ahriman.core.util import walk
from ahriman.models.package import Package

View File

@ -24,7 +24,7 @@ from pathlib import Path
from typing import Any, Dict
from ahriman.core.configuration import Configuration
from ahriman.core.upload.upload import Upload
from ahriman.core.upload import Upload
from ahriman.core.util import exception_response_text

View File

@ -21,7 +21,7 @@ from pathlib import Path
from typing import Iterable
from ahriman.core.configuration import Configuration
from ahriman.core.upload.upload import Upload
from ahriman.core.upload import Upload
from ahriman.core.util import check_output
from ahriman.models.package import Package

View File

@ -25,7 +25,7 @@ from pathlib import Path
from typing import Any, Dict, Iterable
from ahriman.core.configuration import Configuration
from ahriman.core.upload.upload import Upload
from ahriman.core.upload import Upload
from ahriman.core.util import walk
from ahriman.models.package import Package

View File

@ -68,7 +68,7 @@ class Upload:
"""
self.logger = logging.getLogger("root")
self.architecture = architecture
self.config = configuration
self.configuration = configuration
@classmethod
def load(cls: Type[Upload], architecture: str, configuration: Configuration, target: str) -> Upload:
@ -86,13 +86,13 @@ class Upload:
section, provider_name = configuration.gettype(target, architecture)
provider = UploadSettings.from_option(provider_name)
if provider == UploadSettings.Rsync:
from ahriman.core.upload.rsync import Rsync
from ahriman.core.upload import Rsync
return Rsync(architecture, configuration, section)
if provider == UploadSettings.S3:
from ahriman.core.upload.s3 import S3
from ahriman.core.upload import S3
return S3(architecture, configuration, section)
if provider == UploadSettings.Github:
from ahriman.core.upload.github import Github
from ahriman.core.upload import Github
return Github(architecture, configuration, section)
return cls(architecture, configuration) # should never happen

View File

@ -0,0 +1,58 @@
#
# Copyright (c) 2021-2022 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from typing import Iterable
from ahriman.core.configuration import Configuration
from ahriman.core.triggers import Trigger
from ahriman.core.upload import Upload
from ahriman.models.package import Package
from ahriman.models.result import Result
class UploadTrigger(Trigger):
"""
synchronization trigger
Attributes:
targets(List[str]): upload target list
"""
def __init__(self, architecture: str, configuration: Configuration) -> None:
"""
default constructor
Args:
architecture(str): repository architecture
configuration(Configuration): configuration instance
"""
Trigger.__init__(self, architecture, configuration)
self.targets = configuration.getlist("upload", "target")
def run(self, result: Result, packages: Iterable[Package]) -> None:
"""
run trigger
Args:
result(Result): build result
packages(Iterable[Package]): list of all available packages
"""
for target in self.targets:
runner = Upload.load(self.architecture, self.configuration, target)
runner.run(self.configuration.repository_paths.repository, result.success)

View File

@ -29,9 +29,7 @@ from srcinfo.parse import parse_srcinfo # type: ignore
from typing import Any, Dict, Iterable, List, Optional, Set, Type
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.aur import AUR
from ahriman.core.alpm.remote.official import Official
from ahriman.core.alpm.remote.official_syncdb import OfficialSyncdb
from ahriman.core.alpm.remote import AUR, Official, OfficialSyncdb
from ahriman.core.exceptions import InvalidPackageInfo
from ahriman.core.util import check_output, full_version
from ahriman.models.package_description import PackageDescription
@ -284,7 +282,7 @@ class Package:
from ahriman.core.build_tools.sources import Sources
logger = logging.getLogger("build_details")
Sources.load(paths.cache_for(self.base), self.remote, None)
Sources.load(paths.cache_for(self.base), self, None, paths)
try:
# update pkgver first

View File

@ -95,7 +95,7 @@ class RemoteSource:
Optional[RemoteSource]: generated remote source if any, None otherwise
"""
if source == PackageSource.AUR:
from ahriman.core.alpm.remote.aur import AUR
from ahriman.core.alpm.remote import AUR
return RemoteSource(
git_url=AUR.remote_git_url(package_base, repository),
web_url=AUR.remote_web_url(package_base),
@ -104,7 +104,7 @@ class RemoteSource:
source=source,
)
if source == PackageSource.Repository:
from ahriman.core.alpm.remote.official import Official
from ahriman.core.alpm.remote import Official
return RemoteSource(
git_url=Official.remote_git_url(package_base, repository),
web_url=Official.remote_web_url(package_base),

View File

@ -17,4 +17,4 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__version__ = "2.0.0rc7"
__version__ = "2.0.0rc11"

View File

@ -30,7 +30,7 @@ from aiohttp_session.cookie_storage import EncryptedCookieStorage
from cryptography import fernet
from typing import Optional
from ahriman.core.auth.auth import Auth
from ahriman.core.auth import Auth
from ahriman.models.user_access import UserAccess
from ahriman.models.user_identity import UserIdentity
from ahriman.web.middlewares import HandlerType, MiddlewareType

View File

@ -22,9 +22,9 @@ from __future__ import annotations
from aiohttp.web import Request, View
from typing import Any, Dict, List, Optional, Type
from ahriman.core.auth.auth import Auth
from ahriman.core.auth import Auth
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
from ahriman.core.spawn import Spawn
from ahriman.core.status.watcher import Watcher
from ahriman.models.user_access import UserAccess

View File

@ -20,7 +20,7 @@
from aiohttp.web import HTTPNotFound, Response, json_response
from typing import Callable, List
from ahriman.core.alpm.remote.aur import AUR
from ahriman.core.alpm.remote import AUR
from ahriman.models.aur_package import AURPackage
from ahriman.models.user_access import UserAccess
from ahriman.web.views.base import BaseView

View File

@ -48,7 +48,7 @@ class LoginView(BaseView):
HTTPMethodNotAllowed: in case if method is used, but OAuth is disabled
HTTPUnauthorized: if case of authorization error
"""
from ahriman.core.auth.oauth import OAuth
from ahriman.core.auth import OAuth
oauth_provider = self.validator
if not isinstance(oauth_provider, OAuth): # there is actually property, but mypy does not like it anyway

View File

@ -23,9 +23,9 @@ import logging
from aiohttp import web
from ahriman.core.auth.auth import Auth
from ahriman.core.auth import Auth
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
from ahriman.core.database import SQLite
from ahriman.core.exceptions import InitializeException
from ahriman.core.spawn import Spawn
from ahriman.core.status.watcher import Watcher

Some files were not shown because too many files have changed in this diff Show More