Compare commits

..

13 Commits

Author SHA1 Message Date
4d1c881827 Release 2.19.4 2026-02-02 22:50:38 +02:00
833978f8b4 fix: handle permissionerror during walking over tree
Previously it tried to look into 700 directories (e.g. .gnupg) which
breaks running as non-ahriman user
2026-02-02 22:43:48 +02:00
b2e9ba3877 type: replace generator return type with iterator 2026-02-02 22:37:59 +02:00
2c913afb7a Release 2.19.3 2026-01-25 10:46:24 +02:00
65bcf819b1 fix: fallback to package name for missing bases in archive
package zoom is being generated without base, leading to None there

Closes #155
2026-01-25 10:45:56 +02:00
f13a2fde85 type: use as keyword in case match 2026-01-25 10:39:15 +02:00
847c029c46 fix: correctly process trigger repo specific settings in validator (see #154) 2026-01-25 10:24:01 +02:00
a647783252 feat: expose repository name and architecure in configuration if available
In some cases there are reference to current repository settings. In
order to handle it correctly two ro options have been added

Related to #154
2026-01-25 10:23:30 +02:00
5cfcb5c3e8 fix: careful handling of file permissions during initialization
It has been found that during cold start (e.g. in docker container),
some permissions are invalid. In order to handle that, some operations
are not guarded with RepositoryPaths.preserve_root guard

In addition, it has been also found that in some cases (e.g. web server
start) migrations are performed on empty repository identifier which may
lead to wrong data (see also 435375721d),
as well as some unexpected results during database operations. In order
to handle that, now all watcher instances have their own databases (and
configurations)
2026-01-25 10:20:29 +02:00
6280c9dbe6 Release 2.19.1 2025-07-14 21:41:02 +03:00
3d1fdd5517 fix: fix migrations on empty repositories 2025-07-14 21:37:43 +03:00
ab022071e8 fix: trim provides/depends versions and lookup provides through pkgname
(#150)

Current implementation did it in wrong way. First of all, there was a
lookup through pkgbase instead of pkgname, which lead to errors, because
aur api doesn't allow to search by pkgbase (as well as provides is
basically pkgname instead)

It also was found that dependencies resolution lookup has been performed
by using raw packages array, which can include versions, descriptions
etc
2025-07-14 21:37:35 +03:00
a01f76df42 fix: separate ua by spaces 2025-07-14 21:37:25 +03:00
125 changed files with 1092 additions and 1690 deletions

View File

@@ -215,6 +215,7 @@ Again, the most checks can be performed by `tox` command, though some additional
* It is allowed to change web API to add new fields or remove optional ones. However, in case of model changes, new API version must be introduced. * It is allowed to change web API to add new fields or remove optional ones. However, in case of model changes, new API version must be introduced.
* On the other hand, it is allowed to change method signatures, however, it is recommended to add new parameters as optional if possible. Deprecated API can be dropped during major release. * On the other hand, it is allowed to change method signatures, however, it is recommended to add new parameters as optional if possible. Deprecated API can be dropped during major release.
* Enumerations (`Enum` classes) are allowed and recommended. However, it is recommended to use `StrEnum` class if there are from/to string conversions and `IntEnum` otherwise. * Enumerations (`Enum` classes) are allowed and recommended. However, it is recommended to use `StrEnum` class if there are from/to string conversions and `IntEnum` otherwise.
* `Generator` return type is not allowed. Generator functions must return generic `Iterator` object. Documentation should be described as `Yields`, however, because of pylint checks. Unfortunately, `Iterable` return type is not available for generators also, because of specific `contextlib.contextmanager` case.
### Other checks ### Other checks

View File

@@ -184,7 +184,7 @@ digraph G {
ahriman_core_upload_upload [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nupload\.\nupload",shape="box"]; ahriman_core_upload_upload [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nupload\.\nupload",shape="box"];
ahriman_core_upload_upload_trigger [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nupload\.\nupload_trigger",shape="box"]; ahriman_core_upload_upload_trigger [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nupload\.\nupload_trigger",shape="box"];
ahriman_core_util [fillcolor="#ac6349",fontcolor="#ffffff",label="ahriman\.\ncore\.\nutil"]; ahriman_core_util [fillcolor="#ac6349",fontcolor="#ffffff",label="ahriman\.\ncore\.\nutil"];
ahriman_core_utils [fillcolor="#db3d05",fontcolor="#ffffff",label="ahriman\.\ncore\.\nutils"]; ahriman_core_utils [fillcolor="#ef4306",fontcolor="#ffffff",label="ahriman\.\ncore\.\nutils"];
ahriman_models [fillcolor="#f94d10",fontcolor="#ffffff",label="ahriman.models"]; ahriman_models [fillcolor="#f94d10",fontcolor="#ffffff",label="ahriman.models"];
ahriman_models_action [fillcolor="#e75622",fontcolor="#ffffff",label="ahriman\.\nmodels\.\naction"]; ahriman_models_action [fillcolor="#e75622",fontcolor="#ffffff",label="ahriman\.\nmodels\.\naction"];
ahriman_models_aur_package [fillcolor="#db3d05",fontcolor="#ffffff",label="ahriman\.\nmodels\.\naur_package"]; ahriman_models_aur_package [fillcolor="#db3d05",fontcolor="#ffffff",label="ahriman\.\nmodels\.\naur_package"];
@@ -215,7 +215,7 @@ digraph G {
ahriman_models_remote_source [fillcolor="#d13a05",fontcolor="#ffffff",label="ahriman\.\nmodels\.\nremote_source"]; ahriman_models_remote_source [fillcolor="#d13a05",fontcolor="#ffffff",label="ahriman\.\nmodels\.\nremote_source"];
ahriman_models_report_settings [fillcolor="#c26747",fontcolor="#ffffff",label="ahriman\.\nmodels\.\nreport_settings"]; ahriman_models_report_settings [fillcolor="#c26747",fontcolor="#ffffff",label="ahriman\.\nmodels\.\nreport_settings"];
ahriman_models_repository_id [fillcolor="#f94d10",fontcolor="#ffffff",label="ahriman\.\nmodels\.\nrepository_id"]; ahriman_models_repository_id [fillcolor="#f94d10",fontcolor="#ffffff",label="ahriman\.\nmodels\.\nrepository_id"];
ahriman_models_repository_paths [fillcolor="#d13a05",fontcolor="#ffffff",label="ahriman\.\nmodels\.\nrepository_paths"]; ahriman_models_repository_paths [fillcolor="#c73705",fontcolor="#ffffff",label="ahriman\.\nmodels\.\nrepository_paths"];
ahriman_models_repository_stats [fillcolor="#ca4516",fontcolor="#ffffff",label="ahriman\.\nmodels\.\nrepository_stats"]; ahriman_models_repository_stats [fillcolor="#ca4516",fontcolor="#ffffff",label="ahriman\.\nmodels\.\nrepository_stats"];
ahriman_models_result [fillcolor="#ef4306",fontcolor="#ffffff",label="ahriman\.\nmodels\.\nresult"]; ahriman_models_result [fillcolor="#ef4306",fontcolor="#ffffff",label="ahriman\.\nmodels\.\nresult"];
ahriman_models_scan_paths [fillcolor="#ce613b",fontcolor="#ffffff",label="ahriman\.\nmodels\.\nscan_paths"]; ahriman_models_scan_paths [fillcolor="#ce613b",fontcolor="#ffffff",label="ahriman\.\nmodels\.\nscan_paths"];
@@ -952,56 +952,57 @@ digraph G {
ahriman_core_upload_upload -> ahriman_core_upload_s3 [fillcolor="blue",weight="3"]; ahriman_core_upload_upload -> ahriman_core_upload_s3 [fillcolor="blue",weight="3"];
ahriman_core_upload_upload -> ahriman_core_upload_upload_trigger [fillcolor="blue",weight="3"]; ahriman_core_upload_upload -> ahriman_core_upload_upload_trigger [fillcolor="blue",weight="3"];
ahriman_core_upload_upload_trigger -> ahriman_core_upload [fillcolor="blue",weight="3"]; ahriman_core_upload_upload_trigger -> ahriman_core_upload [fillcolor="blue",weight="3"];
ahriman_core_utils -> ahriman_application_ahriman [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_application_ahriman [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_application_application_application_packages [fillcolor="#db3d05",minlen="3"]; ahriman_core_utils -> ahriman_application_application_application_packages [fillcolor="#ef4306",minlen="3"];
ahriman_core_utils -> ahriman_application_handlers_add [fillcolor="#db3d05",minlen="3"]; ahriman_core_utils -> ahriman_application_handlers_add [fillcolor="#ef4306",minlen="3"];
ahriman_core_utils -> ahriman_application_handlers_daemon [fillcolor="#db3d05",minlen="3"]; ahriman_core_utils -> ahriman_application_handlers_daemon [fillcolor="#ef4306",minlen="3"];
ahriman_core_utils -> ahriman_application_handlers_rebuild [fillcolor="#db3d05",minlen="3"]; ahriman_core_utils -> ahriman_application_handlers_rebuild [fillcolor="#ef4306",minlen="3"];
ahriman_core_utils -> ahriman_application_handlers_setup [fillcolor="#db3d05",minlen="3"]; ahriman_core_utils -> ahriman_application_handlers_setup [fillcolor="#ef4306",minlen="3"];
ahriman_core_utils -> ahriman_application_handlers_statistics [fillcolor="#db3d05",minlen="3"]; ahriman_core_utils -> ahriman_application_handlers_statistics [fillcolor="#ef4306",minlen="3"];
ahriman_core_utils -> ahriman_application_handlers_status [fillcolor="#db3d05",minlen="3"]; ahriman_core_utils -> ahriman_application_handlers_status [fillcolor="#ef4306",minlen="3"];
ahriman_core_utils -> ahriman_application_handlers_status_update [fillcolor="#db3d05",minlen="3"]; ahriman_core_utils -> ahriman_application_handlers_status_update [fillcolor="#ef4306",minlen="3"];
ahriman_core_utils -> ahriman_application_handlers_update [fillcolor="#db3d05",minlen="3"]; ahriman_core_utils -> ahriman_application_handlers_update [fillcolor="#ef4306",minlen="3"];
ahriman_core_utils -> ahriman_application_handlers_users [fillcolor="#db3d05",minlen="3"]; ahriman_core_utils -> ahriman_application_handlers_users [fillcolor="#ef4306",minlen="3"];
ahriman_core_utils -> ahriman_application_lock [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_application_lock [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_core_alpm_pacman [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_alpm_pacman [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_alpm_repo [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_alpm_repo [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_auth_pam [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_auth_pam [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_build_tools_package_archive [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_build_tools_package_archive [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_build_tools_sources [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_build_tools_sources [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_build_tools_task [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_build_tools_task [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_formatters_aur_printer [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_formatters_aur_printer [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_formatters_repository_stats_printer [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_formatters_repository_stats_printer [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_formatters_update_printer [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_formatters_update_printer [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_gitremote_remote_pull [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_gitremote_remote_pull [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_report_email [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_report_email [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_report_jinja_template [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_report_jinja_template [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_repository_executor [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_repository_executor [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_repository_package_info [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_repository_package_info [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_sign_gpg [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_sign_gpg [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_support_pkgbuild_pkgbuild_generator [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_support_pkgbuild_pkgbuild_generator [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_tree [fillcolor="#db3d05",weight="2"]; ahriman_core_utils -> ahriman_core_tree [fillcolor="#ef4306",weight="2"];
ahriman_core_utils -> ahriman_core_upload_github [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_upload_github [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_upload_rsync [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_upload_rsync [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_upload_s3 [fillcolor="#db3d05",minlen="2",weight="2"]; ahriman_core_utils -> ahriman_core_upload_s3 [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_utils -> ahriman_core_util [fillcolor="#db3d05",weight="2"]; ahriman_core_utils -> ahriman_core_util [fillcolor="#ef4306",weight="2"];
ahriman_core_utils -> ahriman_models_aur_package [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_models_aur_package [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_models_build_status [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_models_build_status [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_models_changes [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_models_changes [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_models_counters [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_models_counters [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_models_dependencies [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_models_dependencies [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_models_event [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_models_event [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_models_filesystem_package [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_models_filesystem_package [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_models_internal_status [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_models_internal_status [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_models_package [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_models_package [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_models_package_description [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_models_package_description [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_models_package_source [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_models_package_source [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_models_pkgbuild_patch [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_models_pkgbuild_patch [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_models_remote_source [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_models_remote_source [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_models_repository_stats [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_models_repository_paths [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_models_worker [fillcolor="#db3d05",minlen="2"]; ahriman_core_utils -> ahriman_models_repository_stats [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_web_views_api_swagger [fillcolor="#db3d05",minlen="3"]; ahriman_core_utils -> ahriman_models_worker [fillcolor="#ef4306",minlen="2"];
ahriman_core_utils -> ahriman_web_views_v1_packages_logs [fillcolor="#db3d05",minlen="3"]; ahriman_core_utils -> ahriman_web_views_api_swagger [fillcolor="#ef4306",minlen="3"];
ahriman_core_utils -> ahriman_web_views_v1_packages_logs [fillcolor="#ef4306",minlen="3"];
ahriman_models -> ahriman_application_ahriman [fillcolor="#f94d10",minlen="2"]; ahriman_models -> ahriman_application_ahriman [fillcolor="#f94d10",minlen="2"];
ahriman_models -> ahriman_application_application_application [fillcolor="#f94d10",minlen="3"]; ahriman_models -> ahriman_application_application_application [fillcolor="#f94d10",minlen="3"];
ahriman_models -> ahriman_application_application_application_packages [fillcolor="#f94d10",minlen="3"]; ahriman_models -> ahriman_application_application_application_packages [fillcolor="#f94d10",minlen="3"];
@@ -1136,7 +1137,6 @@ digraph G {
ahriman_models -> ahriman_core_upload_s3 [fillcolor="#f94d10",minlen="3"]; ahriman_models -> ahriman_core_upload_s3 [fillcolor="#f94d10",minlen="3"];
ahriman_models -> ahriman_core_upload_upload [fillcolor="#f94d10",minlen="3"]; ahriman_models -> ahriman_core_upload_upload [fillcolor="#f94d10",minlen="3"];
ahriman_models -> ahriman_core_upload_upload_trigger [fillcolor="#f94d10",minlen="3"]; ahriman_models -> ahriman_core_upload_upload_trigger [fillcolor="#f94d10",minlen="3"];
ahriman_models -> ahriman_core_utils [fillcolor="#f94d10",minlen="2"];
ahriman_models -> ahriman_web_apispec_decorators [fillcolor="#f94d10",minlen="3"]; ahriman_models -> ahriman_web_apispec_decorators [fillcolor="#f94d10",minlen="3"];
ahriman_models -> ahriman_web_keys [fillcolor="#f94d10",minlen="2"]; ahriman_models -> ahriman_web_keys [fillcolor="#f94d10",minlen="2"];
ahriman_models -> ahriman_web_middlewares_auth_handler [fillcolor="#f94d10",minlen="3"]; ahriman_models -> ahriman_web_middlewares_auth_handler [fillcolor="#f94d10",minlen="3"];
@@ -1475,17 +1475,16 @@ digraph G {
ahriman_models_repository_id -> ahriman_web_keys [fillcolor="#f94d10",minlen="2"]; ahriman_models_repository_id -> ahriman_web_keys [fillcolor="#f94d10",minlen="2"];
ahriman_models_repository_id -> ahriman_web_views_base [fillcolor="#f94d10",minlen="3"]; ahriman_models_repository_id -> ahriman_web_views_base [fillcolor="#f94d10",minlen="3"];
ahriman_models_repository_id -> ahriman_web_web [fillcolor="#f94d10",minlen="2"]; ahriman_models_repository_id -> ahriman_web_web [fillcolor="#f94d10",minlen="2"];
ahriman_models_repository_paths -> ahriman_application_handlers_handler [fillcolor="#d13a05",minlen="3"]; ahriman_models_repository_paths -> ahriman_application_handlers_handler [fillcolor="#c73705",minlen="3"];
ahriman_models_repository_paths -> ahriman_application_handlers_setup [fillcolor="#d13a05",minlen="3"]; ahriman_models_repository_paths -> ahriman_application_handlers_setup [fillcolor="#c73705",minlen="3"];
ahriman_models_repository_paths -> ahriman_application_handlers_tree_migrate [fillcolor="#d13a05",minlen="3"]; ahriman_models_repository_paths -> ahriman_application_handlers_tree_migrate [fillcolor="#c73705",minlen="3"];
ahriman_models_repository_paths -> ahriman_core_alpm_repo [fillcolor="#d13a05",minlen="3"]; ahriman_models_repository_paths -> ahriman_core_alpm_repo [fillcolor="#c73705",minlen="3"];
ahriman_models_repository_paths -> ahriman_core_build_tools_sources [fillcolor="#d13a05",minlen="3"]; ahriman_models_repository_paths -> ahriman_core_build_tools_sources [fillcolor="#c73705",minlen="3"];
ahriman_models_repository_paths -> ahriman_core_build_tools_task [fillcolor="#d13a05",minlen="3"]; ahriman_models_repository_paths -> ahriman_core_build_tools_task [fillcolor="#c73705",minlen="3"];
ahriman_models_repository_paths -> ahriman_core_configuration_configuration [fillcolor="#d13a05",minlen="3"]; ahriman_models_repository_paths -> ahriman_core_configuration_configuration [fillcolor="#c73705",minlen="3"];
ahriman_models_repository_paths -> ahriman_core_repository_repository_properties [fillcolor="#d13a05",minlen="3"]; ahriman_models_repository_paths -> ahriman_core_repository_repository_properties [fillcolor="#c73705",minlen="3"];
ahriman_models_repository_paths -> ahriman_core_utils [fillcolor="#d13a05",minlen="2"]; ahriman_models_repository_paths -> ahriman_models_package_source [fillcolor="#c73705",weight="2"];
ahriman_models_repository_paths -> ahriman_models_package_source [fillcolor="#d13a05",weight="2"]; ahriman_models_repository_paths -> ahriman_web_views_v1_service_upload [fillcolor="#c73705",minlen="3"];
ahriman_models_repository_paths -> ahriman_web_views_v1_service_upload [fillcolor="#d13a05",minlen="3"];
ahriman_models_repository_stats -> ahriman_core_formatters_repository_stats_printer [fillcolor="#ca4516",minlen="3"]; ahriman_models_repository_stats -> ahriman_core_formatters_repository_stats_printer [fillcolor="#ca4516",minlen="3"];
ahriman_models_repository_stats -> ahriman_core_status_client [fillcolor="#ca4516",minlen="3"]; ahriman_models_repository_stats -> ahriman_core_status_client [fillcolor="#ca4516",minlen="3"];
ahriman_models_repository_stats -> ahriman_models_internal_status [fillcolor="#ca4516",weight="2"]; ahriman_models_repository_stats -> ahriman_models_internal_status [fillcolor="#ca4516",weight="2"];

View File

@@ -100,14 +100,6 @@ ahriman.application.handlers.rebuild module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.application.handlers.reload module
------------------------------------------
.. automodule:: ahriman.application.handlers.reload
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.remove module ahriman.application.handlers.remove module
------------------------------------------ ------------------------------------------

View File

@@ -44,14 +44,6 @@ ahriman.web.schemas.changes\_schema module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.web.schemas.configuration\_schema module
------------------------------------------------
.. automodule:: ahriman.web.schemas.configuration_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.counters\_schema module ahriman.web.schemas.counters\_schema module
------------------------------------------- -------------------------------------------
@@ -148,14 +140,6 @@ ahriman.web.schemas.logs\_schema module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.web.schemas.logs\_search\_schema module
-----------------------------------------------
.. automodule:: ahriman.web.schemas.logs_search_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.oauth2\_schema module ahriman.web.schemas.oauth2\_schema module
----------------------------------------- -----------------------------------------

View File

@@ -12,14 +12,6 @@ ahriman.web.views.v1.service.add module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.web.views.v1.service.config module
------------------------------------------
.. automodule:: ahriman.web.views.v1.service.config
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.v1.service.logs module ahriman.web.views.v1.service.logs module
---------------------------------------- ----------------------------------------

View File

@@ -138,6 +138,8 @@ Build related configuration. Group name can refer to architecture, e.g. ``build:
Base repository settings. Base repository settings.
* ``architecture`` - repository architecture, string. This field is read-only and generated automatically from run options if possible.
* ``name`` - repository name, string. This field is read-only and generated automatically from run options if possible.
* ``root`` - root path for application, string, required. * ``root`` - root path for application, string, required.
``sign:*`` groups ``sign:*`` groups
@@ -166,7 +168,6 @@ Reporting to web service related settings. In most cases there is fallback to we
Web server settings. This feature requires ``aiohttp`` libraries to be installed. Web server 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.
* ``autorefresh_intervals`` - enable page auto refresh options, space separated list of integers, optional. The first defined interval will be used as default. If no intervals set, the auto refresh buttons will be disabled. If first element of the list equals ``0``, auto refresh will be disabled by default.
* ``enable_archive_upload`` - allow to upload packages via HTTP (i.e. call of ``/api/v1/service/upload`` uri), boolean, optional, default ``no``. * ``enable_archive_upload`` - allow to upload packages via HTTP (i.e. call of ``/api/v1/service/upload`` uri), boolean, optional, default ``no``.
* ``host`` - host to bind, string, optional. * ``host`` - host to bind, string, optional.
* ``index_url`` - full URL of the repository index page, string, optional. * ``index_url`` - full URL of the repository index page, string, optional.

View File

@@ -2,7 +2,7 @@
pkgbase='ahriman' pkgbase='ahriman'
pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web') pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web')
pkgver=2.19.0 pkgver=2.19.4
pkgrel=1 pkgrel=1
pkgdesc="ArcH linux ReposItory MANager" pkgdesc="ArcH linux ReposItory MANager"
arch=('any') arch=('any')

View File

@@ -5,7 +5,6 @@ After=network.target
[Service] [Service]
Type=simple Type=simple
ExecStart=/usr/bin/ahriman web ExecStart=/usr/bin/ahriman web
ExecReload=/usr/bin/ahriman web-reload
User=ahriman User=ahriman
Group=ahriman Group=ahriman

View File

@@ -28,10 +28,6 @@ allow_read_only = yes
; External address of the web service. Will be used for some features like OAuth. If none set will be generated as ; External address of the web service. Will be used for some features like OAuth. If none set will be generated as
; address = http://${web:host}:${web:port} ; address = http://${web:host}:${web:port}
;address = http://${web:host}:${web:port} ;address = http://${web:host}:${web:port}
; Enable page auto refresh. Intervals are given in seconds. Default interval is always the first element of the list.
; If no intervals set, auto refresh will be disabled. 0 can only be the first element and will disable auto refresh
; by default.
autorefresh_intervals = 5 1 10 30 60
; Enable file upload endpoint used by some triggers. ; Enable file upload endpoint used by some triggers.
;enable_archive_upload = no ;enable_archive_upload = no
; Address to bind the server. ; Address to bind the server.

View File

@@ -80,28 +80,10 @@
<button type="button" class="btn btn-secondary" onclick="reload()"> <button type="button" class="btn btn-secondary" onclick="reload()">
<i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span> <i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span>
</button> </button>
{% if autorefresh_intervals %}
<div class="btn-group">
<input id="table-autoreload-button" type="checkbox" class="btn-check" autocomplete="off" onclick="toggleTableAutoReload()" checked>
<label for="table-autoreload-button" class="btn btn-outline-secondary" title="toggle auto reload"><i class="bi bi-clock"></i></label>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">select interval</span>
</button>
<ul id="table-autoreload-input" class="dropdown-menu">
{% for interval in autorefresh_intervals %}
<li><a class="dropdown-item {{ "active" if interval.is_active }}" onclick="toggleTableAutoReload({{ interval.interval }})" data-interval="{{ interval.interval }}">{{ interval.text }}</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
</div> </div>
<table id="packages" <table id="packages"
data-classes="table table-hover" data-classes="table table-hover"
data-cookie="true"
data-cookie-id-table="ahriman-packages"
data-cookie-storage="localStorage"
data-export-options='{"fileName": "packages"}' data-export-options='{"fileName": "packages"}'
data-filter-control="true" data-filter-control="true"
data-filter-control-visible="false" data-filter-control-visible="false"
@@ -120,8 +102,8 @@
data-sortable="true" data-sortable="true"
data-sort-name="base" data-sort-name="base"
data-sort-order="asc" data-sort-order="asc"
data-toolbar="#toolbar" data-toggle="table"
data-unique-id="id"> data-toolbar="#toolbar">
<thead class="table-primary"> <thead class="table-primary">
<tr> <tr>
<th data-checkbox="true"></th> <th data-checkbox="true"></th>

View File

@@ -3,9 +3,7 @@
function createAlert(title, message, clz, action, id) { function createAlert(title, message, clz, action, id) {
id ??= md5(title + message); // MD5 id from the content id ??= md5(title + message); // MD5 id from the content
if (alertPlaceholder.querySelector(`#alert-${id}`)) { if (alertPlaceholder.querySelector(`#alert-${id}`)) return; // check if there are duplicates
return; // check if there are duplicates
}
const wrapper = document.createElement("div"); const wrapper = document.createElement("div");
wrapper.id = `alert-${id}`; wrapper.id = `alert-${id}`;

View File

@@ -51,87 +51,6 @@
const dashboardPackagesCountChartCanvas = document.getElementById("dashboard-packages-count-chart"); const dashboardPackagesCountChartCanvas = document.getElementById("dashboard-packages-count-chart");
let dashboardPackagesCountChart = null; let dashboardPackagesCountChart = null;
function statusLoad() {
const badgeClass = status => {
if (status === "pending") return "btn-outline-warning";
if (status === "building") return "btn-outline-warning";
if (status === "failed") return "btn-outline-danger";
if (status === "success") return "btn-outline-success";
return "btn-outline-secondary";
};
makeRequest(
"/api/v1/status",
{
query: {
architecture: repository.architecture,
repository: repository.repository,
},
convert: response => response.json(),
},
data => {
versionBadge.innerHTML = `<i class="bi bi-github"></i> ahriman ${safe(data.version)}`;
dashboardButton.classList.remove(...dashboardButton.classList);
dashboardButton.classList.add("btn");
dashboardButton.classList.add(badgeClass(data.status.status));
dashboardModalHeader.classList.remove(...dashboardModalHeader.classList);
dashboardModalHeader.classList.add("modal-header");
headerClass(data.status.status).forEach(clz => dashboardModalHeader.classList.add(clz));
dashboardName.textContent = data.repository;
dashboardArchitecture.textContent = data.architecture;
dashboardStatus.textContent = data.status.status;
dashboardStatusTimestamp.textContent = new Date(1000 * data.status.timestamp).toISOStringShort();
if (dashboardPackagesStatusesChart) {
const labels = [
"unknown",
"pending",
"building",
"failed",
"success",
];
dashboardPackagesStatusesChart.config.data = {
labels: labels,
datasets: [{
label: "packages in status",
data: labels.map(label => data.packages[label]),
backgroundColor: [
"rgb(55, 58, 60)",
"rgb(255, 117, 24)",
"rgb(255, 117, 24)",
"rgb(255, 0, 57)",
"rgb(63, 182, 24)", // copy-paste from current style
],
}],
};
dashboardPackagesStatusesChart.update();
}
if (dashboardPackagesCountChart) {
dashboardPackagesCountChart.config.data = {
labels: ["packages"],
datasets: [
{
label: "archives",
data: [data.stats.packages],
},
{
label: "bases",
data: [data.stats.bases],
},
],
};
dashboardPackagesCountChart.update();
}
dashboardCanvas.hidden = data.status.total > 0;
},
);
}
ready(_ => { ready(_ => {
dashboardPackagesStatusesChart = new Chart(dashboardPackagesStatusesChartCanvas, { dashboardPackagesStatusesChart = new Chart(dashboardPackagesStatusesChartCanvas, {
type: "pie", type: "pie",

View File

@@ -148,19 +148,8 @@
packageAddInput.addEventListener("keyup", _ => { packageAddInput.addEventListener("keyup", _ => {
clearTimeout(packageAddInput.requestTimeout); clearTimeout(packageAddInput.requestTimeout);
// do not update datalist if search string didn't change yet
const value = packageAddInput.value;
const previousValue = packageAddInput.dataset.previousValue;
if (value === previousValue) {
return;
}
// store current search string in attributes
packageAddInput.dataset.previousValue = value;
// perform data list update
packageAddInput.requestTimeout = setTimeout(_ => { packageAddInput.requestTimeout = setTimeout(_ => {
const value = packageAddInput.value;
if (value.length >= 3) { if (value.length >= 3) {
makeRequest( makeRequest(

View File

@@ -80,7 +80,8 @@
data-classes="table table-hover" data-classes="table table-hover"
data-sortable="true" data-sortable="true"
data-sort-name="timestamp" data-sort-name="timestamp"
data-sort-order="desc"> data-sort-order="desc"
data-toggle="table">
<thead class="table-primary"> <thead class="table-primary">
<tr> <tr>
<th data-align="right" data-field="timestamp">date</th> <th data-align="right" data-field="timestamp">date</th>
@@ -97,24 +98,10 @@
<input id="package-info-refresh-input" type="checkbox" class="form-check-input" value="" checked> <input id="package-info-refresh-input" type="checkbox" class="form-check-input" value="" checked>
<label for="package-info-refresh-input" class="form-check-label">update pacman databases</label> <label for="package-info-refresh-input" class="form-check-label">update pacman databases</label>
<button id="package-info-update-button" type="submit" class="btn btn-success" onclick="packageInfoUpdate()"><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button> <button id="package-info-update-button" type="submit" class="btn btn-success" onclick="packageInfoUpdate()" data-bs-dismiss="modal"><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button>
<button id="package-info-remove-button" type="submit" class="btn btn-danger" onclick="packageInfoRemove()" data-bs-dismiss="modal"><i class="bi bi-trash"></i><span class="d-none d-sm-inline"> remove</span></button> <button id="package-info-remove-button" type="submit" class="btn btn-danger" onclick="packageInfoRemove()" data-bs-dismiss="modal"><i class="bi bi-trash"></i><span class="d-none d-sm-inline"> remove</span></button>
{% endif %} {% endif %}
{% if autorefresh_intervals %} <button type="button" class="btn btn-secondary" onclick="showPackageInfo()"><i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span></button>
<button type="button" class="btn btn-secondary" onclick="showPackageInfo()"><i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span></button>
<div class="btn-group dropup">
<input id="package-info-autoreload-button" type="checkbox" class="btn-check" autocomplete="off" onclick="togglePackageInfoAutoReload()" checked>
<label for="package-info-autoreload-button" class="btn btn-outline-secondary" title="toggle auto reload"><i class="bi bi-clock"></i></label>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">select interval</span>
</button>
<ul id="package-info-autoreload-input" class="dropdown-menu">
{% for interval in autorefresh_intervals %}
<li><a class="dropdown-item {{ "active" if interval.is_active }}" onclick="togglePackageInfoAutoReload({{ interval.interval }})" data-interval="{{ interval.interval }}">{{ interval.text }}</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
<button type="button" class="btn btn-primary" data-bs-dismiss="modal"><i class="bi bi-x"></i><span class="d-none d-sm-inline"> close</span></button> <button type="button" class="btn btn-primary" data-bs-dismiss="modal"><i class="bi bi-x"></i><span class="d-none d-sm-inline"> close</span></button>
</div> </div>
</div> </div>
@@ -153,10 +140,6 @@
const packageInfoRefreshInput = document.getElementById("package-info-refresh-input"); const packageInfoRefreshInput = document.getElementById("package-info-refresh-input");
const packageInfoAutoReloadButton = document.getElementById("package-info-autoreload-button");
const packageInfoAutoReloadInput = document.getElementById("package-info-autoreload-input");
let packageInfoAutoReloadTask = null;
function clearChart() { function clearChart() {
packageInfoEventsUpdateChartCanvas.hidden = true; packageInfoEventsUpdateChartCanvas.hidden = true;
if (packageInfoEventsUpdateChart) { if (packageInfoEventsUpdateChart) {
@@ -165,13 +148,6 @@
} }
} }
function convertLogs(data, filter) {
return data
.filter((filter || Boolean))
.map(log_record => `[${new Date(1000 * log_record.created).toISOString()}] ${log_record.message}`)
.join("\n");
}
async function copyChanges() { async function copyChanges() {
const changes = packageInfoChangesInput.textContent; const changes = packageInfoChangesInput.textContent;
await copyToClipboard(changes, packageInfoChangesCopyButton); await copyToClipboard(changes, packageInfoChangesCopyButton);
@@ -315,69 +291,6 @@
} }
function loadLogs(packageBase, onFailure) { function loadLogs(packageBase, onFailure) {
const sortFn = (left, right) => left.process_id.localeCompare(right.process_id) || left.version.localeCompare(right.version);
const compareFn = (left, right) => left.process_id === right.process_id && left.version === right.version;
makeRequest(
`/api/v2/packages/${packageBase}/logs`,
{
query: {
architecture: repository.architecture,
head: true,
repository: repository.repository,
},
convert: response => response.json(),
},
data => {
const currentVersions = Array.from(packageInfoLogsVersions.children)
.map(el => {
return {
process_id: el.dataset.processId,
version: el.dataset.version,
};
})
.sort(sortFn);
const newVersions = data
.map(el => {
return {
process_id: el.process_id,
version: el.version,
};
})
.sort(sortFn);
if (currentVersions.equals(newVersions, compareFn))
loadLogsActive(packageBase);
else
loadLogsAll(packageBase, onFailure);
},
)
}
function loadLogsActive(packageBase) {
const activeLogSelector = packageInfoLogsVersions.querySelector(".active");
if (activeLogSelector) {
makeRequest(
`/api/v2/packages/${packageBase}/logs`,
{
query: {
architecture: repository.architecture,
repository: repository.repository,
version: activeLogSelector.dataset.version,
process_id: activeLogSelector.dataset.processId,
},
convert: response => response.json(),
},
data => {
activeLogSelector.dataset.logs = convertLogs(data);
activeLogSelector.click();
},
);
}
}
function loadLogsAll(packageBase, onFailure) {
makeRequest( makeRequest(
`/api/v2/packages/${packageBase}/logs`, `/api/v2/packages/${packageBase}/logs`,
{ {
@@ -406,19 +319,15 @@
const link = document.createElement("a"); const link = document.createElement("a");
link.classList.add("dropdown-item"); link.classList.add("dropdown-item");
link.dataset.version = version.version;
link.dataset.processId = version.process_id;
link.dataset.logs = convertLogs(data, log_record => log_record.version === version.version && log_record.process_id === version.process_id);
link.textContent = new Date(1000 * version.created).toISOStringShort(); link.textContent = new Date(1000 * version.created).toISOStringShort();
link.href = "#"; link.href = "#";
link.onclick = _ => { link.onclick = _ => {
// check if we are at the bottom of the code block const logs = data
const isScrolledToBottom = packageInfoLogsInput.scrollTop + packageInfoLogsInput.clientHeight >= packageInfoLogsInput.scrollHeight; .filter(log_record => log_record.version === version.version && log_record.process_id === version.process_id)
packageInfoLogsInput.textContent = link.dataset.logs; .map(log_record => `[${new Date(1000 * log_record.created).toISOString()}] ${log_record.message}`);
packageInfoLogsInput.textContent = logs.join("\n");
highlight(packageInfoLogsInput); highlight(packageInfoLogsInput);
if (isScrolledToBottom)
packageInfoLogsInput.scrollTop = packageInfoLogsInput.scrollHeight; // scroll to the new end
Array.from(packageInfoLogsVersions.children).forEach(el => el.classList.remove("active")); Array.from(packageInfoLogsVersions.children).forEach(el => el.classList.remove("active"));
link.classList.add("active"); link.classList.add("active");
@@ -494,12 +403,12 @@
} }
function packageInfoRemove() { function packageInfoRemove() {
const packageBase = packageInfoModal.dataset.package; const packageBase = packageInfoModal.package;
packagesRemove([packageBase]); packagesRemove([packageBase]);
} }
function packageInfoUpdate() { function packageInfoUpdate() {
const packageBase = packageInfoModal.dataset.package; const packageBase = packageInfoModal.package;
packagesAdd(packageBase, [], repository, {refresh: packageInfoRefreshInput.checked}); packagesAdd(packageBase, [], repository, {refresh: packageInfoRefreshInput.checked});
} }
@@ -507,10 +416,10 @@
const isPackageBaseSet = packageBase !== undefined; const isPackageBaseSet = packageBase !== undefined;
if (isPackageBaseSet) { if (isPackageBaseSet) {
// set package base as currently used // set package base as currently used
packageInfoModal.dataset.package = packageBase; packageInfoModal.package = packageBase;
} else { } else {
// read package base from the current window attribute // read package base from the current window attribute
packageBase = packageInfoModal.dataset.package; packageBase = packageInfoModal.package;
} }
const onFailure = error => { const onFailure = error => {
@@ -529,27 +438,10 @@
if (isPackageBaseSet) { if (isPackageBaseSet) {
bootstrap.Modal.getOrCreateInstance(packageInfoModal).show(); bootstrap.Modal.getOrCreateInstance(packageInfoModal).show();
{% if autorefresh_intervals %}
togglePackageInfoAutoReload();
{% endif %}
} }
} }
function togglePackageInfoAutoReload(interval) {
clearInterval(packageInfoAutoReloadTask);
packageInfoAutoReloadTask = toggleAutoReload(packageInfoAutoReloadButton, interval, packageInfoAutoReloadInput, _ => {
if (!hasActiveSelection()) {
const packageBase = packageInfoModal.dataset.package;
// we only poll status and logs here
loadPackage(packageBase);
loadLogs(packageBase);
}
});
}
ready(_ => { ready(_ => {
packageInfoEventsTable.bootstrapTable({});
packageInfoEventsUpdateChart = new Chart(packageInfoEventsUpdateChartCanvas, { packageInfoEventsUpdateChart = new Chart(packageInfoEventsUpdateChartCanvas, {
type: "line", type: "line",
data: {}, data: {},
@@ -576,11 +468,6 @@
packageInfoChangesInput.textContent = ""; packageInfoChangesInput.textContent = "";
packageInfoEventsTable.bootstrapTable("load", []); packageInfoEventsTable.bootstrapTable("load", []);
clearChart(); clearChart();
clearInterval(packageInfoAutoReloadTask);
packageInfoAutoReloadTask = null; // not really required (?) but lets clear everything
}); });
restoreAutoReloadSettings(packageInfoAutoReloadButton, packageInfoAutoReloadInput);
}); });
</script> </script>

View File

@@ -10,10 +10,6 @@
const dashboardButton = document.getElementById("dashboard-button"); const dashboardButton = document.getElementById("dashboard-button");
const versionBadge = document.getElementById("badge-version"); const versionBadge = document.getElementById("badge-version");
const tableAutoReloadButton = document.getElementById("table-autoreload-button");
const tableAutoReloadInput = document.getElementById("table-autoreload-input");
let tableAutoReloadTask = null;
function doPackageAction(uri, packages, repository, successText, failureText, data) { function doPackageAction(uri, packages, repository, successText, failureText, data) {
makeRequest( makeRequest(
uri, uri,
@@ -59,41 +55,6 @@
return table.bootstrapTable("getSelections").map(row => row.id); return table.bootstrapTable("getSelections").map(row => row.id);
} }
function packagesLoad(onFailure) {
makeRequest(
"/api/v1/packages",
{
query: {
architecture: repository.architecture,
repository: repository.repository,
},
convert: response => response.json(),
},
data => {
const payload = data
.map(description => {
const package_base = description.package.base;
const web_url = description.package.remote.web_url;
return {
id: package_base,
base: web_url ? safeLink(web_url, package_base, package_base).outerHTML : safe(package_base),
version: safe(description.package.version),
packager: description.package.packager ? safe(description.package.packager) : "",
packages: listToTable(Object.keys(description.package.packages)),
groups: listToTable(extractListProperties(description.package, "groups")),
licenses: listToTable(extractListProperties(description.package, "licenses")),
timestamp: new Date(1000 * description.status.timestamp).toISOStringShort(),
status: description.status.status,
};
});
updateTable(table, payload);
table.bootstrapTable("hideLoading");
},
onFailure,
);
}
function packagesRemove(packages) { function packagesRemove(packages) {
packages = packages ?? getSelection(); packages = packages ?? getSelection();
const onSuccess = update => `Packages ${update} have been removed`; const onSuccess = update => `Packages ${update} have been removed`;
@@ -127,22 +88,130 @@
function reload() { function reload() {
table.bootstrapTable("showLoading"); table.bootstrapTable("showLoading");
const onFailure = error => {
if ((error.status === 401) || (error.status === 403)) { const badgeClass = status => {
// authorization error if (status === "pending") return "btn-outline-warning";
const text = "In order to see statuses you must login first."; if (status === "building") return "btn-outline-warning";
table.find("tr.unauthorized").remove(); if (status === "failed") return "btn-outline-danger";
table.find("tbody").append(`<tr class="unauthorized"><td colspan="100%">${safe(text)}</td></tr>`); if (status === "success") return "btn-outline-success";
table.bootstrapTable("hideLoading"); return "btn-outline-secondary";
} else {
// other errors
const message = details => `Could not load list of packages: ${details}`;
showFailure("Load failure", message, error);
}
}; };
packagesLoad(onFailure); makeRequest(
statusLoad(); "/api/v1/packages",
{
query: {
architecture: repository.architecture,
repository: repository.repository,
},
convert: response => response.json(),
},
data => {
const payload = data.map(description => {
const package_base = description.package.base;
const web_url = description.package.remote.web_url;
return {
id: package_base,
base: web_url ? safeLink(web_url, package_base, package_base).outerHTML : safe(package_base),
version: safe(description.package.version),
packager: description.package.packager ? safe(description.package.packager) : "",
packages: listToTable(Object.keys(description.package.packages)),
groups: listToTable(extractListProperties(description.package, "groups")),
licenses: listToTable(extractListProperties(description.package, "licenses")),
timestamp: new Date(1000 * description.status.timestamp).toISOStringShort(),
status: description.status.status,
};
});
table.bootstrapTable("load", payload);
table.bootstrapTable("uncheckAll");
table.bootstrapTable("hideLoading");
},
error => {
if ((error.status === 401) || (error.status === 403)) {
// authorization error
const text = "In order to see statuses you must login first.";
table.find("tr.unauthorized").remove();
table.find("tbody").append(`<tr class="unauthorized"><td colspan="100%">${safe(text)}</td></tr>`);
table.bootstrapTable("hideLoading");
} else {
// other errors
const message = details => `Could not load list of packages: ${details}`;
showFailure("Load failure", message, error);
}
},
);
makeRequest(
"/api/v1/status",
{
query: {
architecture: repository.architecture,
repository: repository.repository,
},
convert: response => response.json(),
},
data => {
versionBadge.innerHTML = `<i class="bi bi-github"></i> ahriman ${safe(data.version)}`;
dashboardButton.classList.remove(...dashboardButton.classList);
dashboardButton.classList.add("btn");
dashboardButton.classList.add(badgeClass(data.status.status));
dashboardModalHeader.classList.remove(...dashboardModalHeader.classList);
dashboardModalHeader.classList.add("modal-header");
headerClass(data.status.status).forEach(clz => dashboardModalHeader.classList.add(clz));
dashboardName.textContent = data.repository;
dashboardArchitecture.textContent = data.architecture;
dashboardStatus.textContent = data.status.status;
dashboardStatusTimestamp.textContent = new Date(1000 * data.status.timestamp).toISOStringShort();
if (dashboardPackagesStatusesChart) {
const labels = [
"unknown",
"pending",
"building",
"failed",
"success",
];
dashboardPackagesStatusesChart.config.data = {
labels: labels,
datasets: [{
label: "packages in status",
data: labels.map(label => data.packages[label]),
backgroundColor: [
"rgb(55, 58, 60)",
"rgb(255, 117, 24)",
"rgb(255, 117, 24)",
"rgb(255, 0, 57)",
"rgb(63, 182, 24)", // copy-paste from current style
],
}],
};
dashboardPackagesStatusesChart.update();
}
if (dashboardPackagesCountChart) {
dashboardPackagesCountChart.config.data = {
labels: ["packages"],
datasets: [
{
label: "archives",
data: [data.stats.packages],
},
{
label: "bases",
data: [data.stats.bases],
},
],
};
dashboardPackagesCountChart.update();
}
dashboardCanvas.hidden = data.status.total > 0;
},
);
} }
function selectRepository() { function selectRepository() {
@@ -161,24 +230,7 @@
return {classes: cellClass(value)}; return {classes: cellClass(value)};
} }
function toggleTableAutoReload(interval) {
clearInterval(tableAutoReloadTask);
tableAutoReloadTask = toggleAutoReload(tableAutoReloadButton, interval, tableAutoReloadInput, _ => {
if (!hasActiveModal() &&
!hasActiveDropdown()) {
packagesLoad();
statusLoad();
}
});
}
ready(_ => { ready(_ => {
const onCheckFunction = function () {
if (packageRemoveButton) {
packageRemoveButton.disabled = !getSelection().length;
}
};
document.querySelectorAll("#repositories a").forEach(element => { document.querySelectorAll("#repositories a").forEach(element => {
element.onclick = _ => { element.onclick = _ => {
repository = { repository = {
@@ -193,55 +245,49 @@
}; };
}); });
table.bootstrapTable({ table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table", _ => {
onCheck: onCheckFunction, if (packageRemoveButton) {
onCheckAll: onCheckFunction, packageRemoveButton.disabled = !table.bootstrapTable("getSelections").length;
onClickRow: (data, row, cell) => { }
if (0 === cell || "base" === cell) { });
const method = data[0] === true ? "uncheckBy" : "checkBy"; // fck javascript table.on("click-row.bs.table", (self, data, row, cell) => {
table.bootstrapTable(method, {field: "id", values: [data.id]}); if (0 === cell || "base" === cell) {
} else showPackageInfo(data.id); const method = data[0] === true ? "uncheckBy" : "checkBy"; // fck javascript
}, table.bootstrapTable(method, {field: "id", values: [data.id]});
onCreatedControls: _ => { } else showPackageInfo(data.id);
new easepick.create({ });
element: document.querySelector(".bootstrap-table-filter-control-timestamp"), table.on("created-controls.bs.table", _ => {
css: [ new easepick.create({
"https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css", element: document.querySelector(".bootstrap-table-filter-control-timestamp"),
], css: [
grid: 2, "https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",
calendars: 2, ],
autoApply: false, grid: 2,
locale: { calendars: 2,
cancel: "Clear", autoApply: false,
}, locale: {
RangePlugin: { cancel: "Clear",
tooltip: false, },
}, RangePlugin: {
plugins: [ tooltip: false,
"RangePlugin", },
], plugins: [
setup: picker => { "RangePlugin",
picker.on("select", _ => { table.bootstrapTable("triggerSearch"); }); ],
// replace "Cancel" behaviour to "Clear" setup: picker => {
picker.onClickCancelButton = element => { picker.on("select", _ => { table.bootstrapTable("triggerSearch"); });
if (picker.isCancelButton(element)) { // replace "Cancel" behaviour to "Clear"
picker.clear(); picker.onClickCancelButton = element => {
picker.hide(); if (picker.isCancelButton(element)) {
table.bootstrapTable("triggerSearch"); picker.clear();
} picker.hide();
}; table.bootstrapTable("triggerSearch");
}, }
}); };
}, },
onUncheck: onCheckFunction, });
onUncheckAll: onCheckFunction,
}); });
restoreAutoReloadSettings(tableAutoReloadButton, tableAutoReloadInput);
selectRepository(); selectRepository();
{% if autorefresh_intervals %}
toggleTableAutoReload();
{% endif %}
}); });
</script> </script>

View File

@@ -53,7 +53,8 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
data-show-search-clear-button="true" data-show-search-clear-button="true"
data-sortable="true" data-sortable="true"
data-sort-name="base" data-sort-name="base"
data-sort-order="asc"> data-sort-order="asc"
data-toggle="table">
<thead class="table-primary"> <thead class="table-primary">
<tr> <tr>
<th data-sortable="true" data-switchable="false" data-field="name" data-filter-control="input" data-filter-control-placeholder="(any package)">package</th> <th data-sortable="true" data-switchable="false" data-field="name" data-filter-control="input" data-filter-control-placeholder="(any package)">package</th>
@@ -127,38 +128,36 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
} }
ready(_ => { ready(_ => {
table.bootstrapTable({ table.on("created-controls.bs.table", _ => {
onCreatedControls: _ => { new easepick.create({
new easepick.create({ element: document.querySelector(".bootstrap-table-filter-control-timestamp"),
element: document.querySelector(".bootstrap-table-filter-control-timestamp"), css: [
css: [ "https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",
"https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css", ],
], grid: 2,
grid: 2, calendars: 2,
calendars: 2, autoApply: false,
autoApply: false, locale: {
locale: { cancel: "Clear",
cancel: "Clear", },
}, RangePlugin: {
RangePlugin: { tooltip: false,
tooltip: false, },
}, plugins: [
plugins: [ "RangePlugin",
"RangePlugin", ],
], setup: picker => {
setup: picker => { picker.on("select", _ => { table.bootstrapTable("triggerSearch"); });
picker.on("select", _ => { table.bootstrapTable("triggerSearch"); }); // replace "Cancel" behaviour to "Clear"
// replace "Cancel" behaviour to "Clear" picker.onClickCancelButton = element => {
picker.onClickCancelButton = element => { if (picker.isCancelButton(element)) {
if (picker.isCancelButton(element)) { picker.clear();
picker.clear(); picker.hide();
picker.hide(); table.bootstrapTable("triggerSearch");
table.bootstrapTable("triggerSearch"); }
} };
}; },
}, });
});
},
}); });
}); });
</script> </script>

View File

@@ -1,24 +1,23 @@
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/js-md5@0.8.3/src/md5.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/js-md5@0.8.3/src/md5.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.33.0/tableExport.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.30.0/tableExport.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/bootstrap-table.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/bootstrap-table.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/export/bootstrap-table-export.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/export/bootstrap-table-export.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/resizable/bootstrap-table-resizable.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/resizable/bootstrap-table-resizable.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/filter-control/bootstrap-table-filter-control.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/filter-control/bootstrap-table-filter-control.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/cookie/bootstrap-table-cookie.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/highlight.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.10.0/build/highlight.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script> <script>
async function copyToClipboard(text, button) { async function copyToClipboard(text, button) {
@@ -59,20 +58,6 @@
return value.includes(dataList[index].toLowerCase()); return value.includes(dataList[index].toLowerCase());
} }
function hasActiveSelection() {
return !document.getSelection().isCollapsed; // not sure if it is a valid way, but I guess so
}
function hasActiveDropdown() {
return Array.from(document.querySelectorAll(".dropdown-menu"))
.some(el => el.classList.contains("show"));
}
function hasActiveModal() {
return Array.from(document.querySelectorAll(".modal"))
.some(el => el.classList.contains("show"));
}
function headerClass(status) { function headerClass(status) {
if (status === "pending") return ["bg-warning"]; if (status === "pending") return ["bg-warning"];
if (status === "building") return ["bg-warning"]; if (status === "building") return ["bg-warning"];
@@ -121,12 +106,6 @@
.catch(error => onFailure && onFailure(error)); .catch(error => onFailure && onFailure(error));
} }
function readOptional(extractor, callback) {
for (let value = extractor(); !!value; value = null) {
callback(value);
}
}
function ready(fn) { function ready(fn) {
if (document.readyState === "complete" || document.readyState === "interactive") { if (document.readyState === "complete" || document.readyState === "interactive") {
setTimeout(fn, 1); setTimeout(fn, 1);
@@ -135,11 +114,6 @@
} }
} }
function restoreAutoReloadSettings(toggle, intervalSelector) {
readOptional(() => localStorage.getItem(`ahriman-${toggle.id}-refresh-enabled`), value => toggle.checked = value === "true");
readOptional(() => localStorage.getItem(`ahriman-${toggle.id}-refresh-interval`), value => toggleActiveElement(intervalSelector, "interval", value));
}
function safe(string) { function safe(string) {
return String(string) return String(string)
.replace(/&/g, "&amp;") .replace(/&/g, "&amp;")
@@ -159,86 +133,7 @@
return element; return element;
} }
function toggleActiveElement(selector, dataType, value) { Date.prototype.toISOStringShort = function() {
const targetElement = selector.querySelector(`a[data-${dataType}="${value}"]`);
if (targetElement?.classList?.contains("active")) {
return; // element is already active, skip processing
}
Array.from(selector.children).forEach(il => {
Array.from(il.children).forEach(el => el.classList.remove("active"));
});
targetElement?.classList?.add("active");
}
function toggleAutoReload(toggle, interval, intervalSelector, callback) {
if (interval) {
toggle.checked = true; // toggle reload
} else {
interval = intervalSelector.querySelector(".active")?.dataset?.interval; // find active element
}
let intervalId = null;
if (interval) {
if (toggle.checked) {
// refresh UI
toggleActiveElement(intervalSelector, "interval", interval);
// finally create timer task
intervalId = setInterval(callback, interval);
}
} else {
toggle.checked = false; // no active interval found, disable toggle
}
localStorage.setItem(`ahriman-${toggle.id}-refresh-enabled`, toggle.checked);
localStorage.setItem(`ahriman-${toggle.id}-refresh-interval`, interval);
return intervalId;
}
function updateTable(table, rows) {
// instead of using load method here, we just update rows manually to avoid table reinitialization
const currentData = table.bootstrapTable("getData").reduce((accumulator, row) => {
accumulator[row.id] = row["0"];
return accumulator;
}, {});
// insert or update rows
rows.forEach(row => {
if (Object.hasOwn(currentData, row.id)) {
row["0"] = currentData[row.id]; // copy checkbox state
table.bootstrapTable("updateByUniqueId", {
id: row.id,
row: row,
replace: true,
});
} else {
table.bootstrapTable("insertRow", {index: 0, row: row});
}
});
// remove old rows
const newData = rows.map(value => value.id);
Object.keys(currentData).forEach(id => {
if (!newData.includes(id)) {
table.bootstrapTable("removeByUniqueId", id);
}
});
}
Array.prototype.equals = function (right, comparator) {
let index = this.length;
if (index !== right.length) {
return false;
}
while (index--) {
if (!comparator(this[index], right[index])) {
return false;
}
}
return true;
}
Date.prototype.toISOStringShort = function () {
const pad = number => String(number).padStart(2, "0"); const pad = number => String(number).padStart(2, "0");
return `${this.getFullYear()}-${pad(this.getMonth() + 1)}-${pad(this.getDate())} ${pad(this.getHours())}:${pad(this.getMinutes())}:${pad(this.getSeconds())}`; return `${this.getFullYear()}-${pad(this.getMonth() + 1)}-${pad(this.getDate())} ${pad(this.getHours())}:${pad(this.getMinutes())}:${pad(this.getSeconds())}`;
} }

View File

@@ -1,15 +1,15 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" crossorigin="anonymous" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.css" crossorigin="anonymous" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/bootstrap-table.min.css" crossorigin="anonymous" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/bootstrap-table.min.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.css" crossorigin="anonymous" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/filter-control/bootstrap-table-filter-control.css" crossorigin="anonymous" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/filter-control/bootstrap-table-filter-control.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.7/dist/cosmo/bootstrap.min.css" crossorigin="anonymous" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.3/dist/cosmo/bootstrap.min.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/github.min.css" crossorigin="anonymous" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.10.0/build/styles/github.min.css" crossorigin="anonymous" type="text/css">
<style> <style>
.pre-scrollable { .pre-scrollable {

View File

@@ -674,6 +674,7 @@ _shtab_ahriman() {
if [[ "$current_action_nargs" != "*" ]] && \ if [[ "$current_action_nargs" != "*" ]] && \
[[ "$current_action_nargs" != "+" ]] && \ [[ "$current_action_nargs" != "+" ]] && \
[[ "$current_action_nargs" != "?" ]] && \
[[ "$current_action_nargs" != *"..." ]] && \ [[ "$current_action_nargs" != *"..." ]] && \
(( $word_index + 1 - $current_action_args_start_index - $pos_only >= \ (( $word_index + 1 - $current_action_args_start_index - $pos_only >= \
$current_action_nargs )); then $current_action_nargs )); then

View File

@@ -1,9 +1,9 @@
.TH AHRIMAN "1" "2025\-06\-29" "ahriman 2.19.0" "ArcH linux ReposItory MANager" .TH AHRIMAN "1" "2026\-02\-02" "ahriman 2.19.4" "ArcH linux ReposItory MANager"
.SH NAME .SH NAME
ahriman \- ArcH linux ReposItory MANager ahriman \- ArcH linux ReposItory MANager
.SH SYNOPSIS .SH SYNOPSIS
.B ahriman .B ahriman
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [-V] [--wait-timeout WAIT_TIMEOUT] {add,aur-search,check,clean,config,config-validate,copy,daemon,help,help-commands-unsafe,help-updates,help-version,init,key-import,package-add,package-changes,package-changes-remove,package-copy,package-remove,package-status,package-status-remove,package-status-update,package-update,patch-add,patch-list,patch-remove,patch-set-add,rebuild,remove,remove-unknown,repo-backup,repo-check,repo-clean,repo-config,repo-config-validate,repo-create-keyring,repo-create-mirrorlist,repo-daemon,repo-init,repo-rebuild,repo-remove-unknown,repo-report,repo-restore,repo-setup,repo-sign,repo-statistics,repo-status-update,repo-sync,repo-tree,repo-triggers,repo-update,report,run,search,service-clean,service-config,service-config-validate,service-key-import,service-repositories,service-run,service-setup,service-shell,service-tree-migrate,setup,shell,sign,status,status-update,sync,update,user-add,user-list,user-remove,version,web} ... [-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [-V] [--wait-timeout WAIT_TIMEOUT] {add,aur-search,check,clean,config,config-validate,copy,daemon,help,help-commands-unsafe,help-updates,help-version,init,key-import,package-add,package-changes,package-changes-remove,package-copy,package-remove,package-status,package-status-remove,package-status-update,package-update,patch-add,patch-list,patch-remove,patch-set-add,rebuild,remove,remove-unknown,repo-backup,repo-check,repo-clean,repo-config,repo-config-validate,repo-create-keyring,repo-create-mirrorlist,repo-daemon,repo-init,repo-rebuild,repo-remove-unknown,repo-report,repo-restore,repo-setup,repo-sign,repo-statistics,repo-status-update,repo-sync,repo-tree,repo-triggers,repo-update,report,run,search,service-clean,service-config,service-config-validate,service-key-import,service-repositories,service-run,service-setup,service-shell,service-tree-migrate,setup,shell,sign,status,status-update,sync,update,user-add,user-list,user-remove,version,web} ...
.SH DESCRIPTION .SH DESCRIPTION
ArcH linux ReposItory MANager ArcH linux ReposItory MANager
@@ -195,9 +195,9 @@ remove user
web server web server
.SH COMMAND \fI\,'ahriman aur\-search'\/\fR .SH COMMAND \fI\,'ahriman aur\-search'\/\fR
usage: ahriman aur\-search [\-h] [\-e] [\-\-info | \-\-no\-info] usage: ahriman aur\-search [\-h] [\-e] [\-\-info | \-\-no\-info]
[\-\-sort\-by {description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,repository,submitter,url,url_path,version}] [\-\-sort\-by {description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,repository,submitter,url,url_path,version}]
search [search ...] search [search ...]
search for package in AUR using API search for package in AUR using API
@@ -220,7 +220,7 @@ sort field by this field. In case if two packages have the same value of the spe
by name by name
.SH COMMAND \fI\,'ahriman help'\/\fR .SH COMMAND \fI\,'ahriman help'\/\fR
usage: ahriman help [\-h] [subcommand] usage: ahriman help [\-h] [subcommand]
show help message for application or command and exit show help message for application or command and exit
@@ -229,7 +229,7 @@ show help message for application or command and exit
show help message for specific command show help message for specific command
.SH COMMAND \fI\,'ahriman help\-commands\-unsafe'\/\fR .SH COMMAND \fI\,'ahriman help\-commands\-unsafe'\/\fR
usage: ahriman help\-commands\-unsafe [\-h] [subcommand ...] usage: ahriman help\-commands\-unsafe [\-h] [subcommand ...]
list unsafe commands as defined in default args list unsafe commands as defined in default args
@@ -239,7 +239,7 @@ instead of showing commands, just test command line for unsafe subcommand and re
otherwise otherwise
.SH COMMAND \fI\,'ahriman help\-updates'\/\fR .SH COMMAND \fI\,'ahriman help\-updates'\/\fR
usage: ahriman help\-updates [\-h] [\-e] usage: ahriman help\-updates [\-h] [\-e]
request AUR for current version and compare with current service version request AUR for current version and compare with current service version
@@ -249,15 +249,15 @@ request AUR for current version and compare with current service version
return non\-zero exit code if updates available return non\-zero exit code if updates available
.SH COMMAND \fI\,'ahriman help\-version'\/\fR .SH COMMAND \fI\,'ahriman help\-version'\/\fR
usage: ahriman help\-version [\-h] usage: ahriman help\-version [\-h]
print application and its dependencies versions print application and its dependencies versions
.SH COMMAND \fI\,'ahriman package\-add'\/\fR .SH COMMAND \fI\,'ahriman package\-add'\/\fR
usage: ahriman package\-add [\-h] [\-\-changes | \-\-no\-changes] [\-\-dependencies | \-\-no\-dependencies] [\-e] usage: ahriman package\-add [\-h] [\-\-changes | \-\-no\-changes] [\-\-dependencies | \-\-no\-dependencies] [\-e]
[\-\-increment | \-\-no\-increment] [\-n] [\-y] [\-\-increment | \-\-no\-increment] [\-n] [\-y]
[\-s {auto,archive,aur,directory,local,remote,repository}] [\-u USERNAME] [\-v VARIABLE] [\-s {auto,archive,aur,directory,local,remote,repository}] [\-u USERNAME] [\-v VARIABLE]
package [package ...] package [package ...]
add existing or new package to the build queue add existing or new package to the build queue
@@ -303,7 +303,7 @@ build as user
apply specified makepkg variables to the next build apply specified makepkg variables to the next build
.SH COMMAND \fI\,'ahriman package\-changes'\/\fR .SH COMMAND \fI\,'ahriman package\-changes'\/\fR
usage: ahriman package\-changes [\-h] [\-e] package usage: ahriman package\-changes [\-h] [\-e] package
retrieve package changes stored in database retrieve package changes stored in database
@@ -317,7 +317,7 @@ package base
return non\-zero exit status if result is empty return non\-zero exit status if result is empty
.SH COMMAND \fI\,'ahriman package\-changes\-remove'\/\fR .SH COMMAND \fI\,'ahriman package\-changes\-remove'\/\fR
usage: ahriman package\-changes\-remove [\-h] package usage: ahriman package\-changes\-remove [\-h] package
remove the package changes stored remotely remove the package changes stored remotely
@@ -326,7 +326,7 @@ remove the package changes stored remotely
package base package base
.SH COMMAND \fI\,'ahriman package\-copy'\/\fR .SH COMMAND \fI\,'ahriman package\-copy'\/\fR
usage: ahriman package\-copy [\-h] [\-e] [\-\-remove] source package [package ...] usage: ahriman package\-copy [\-h] [\-e] [\-\-remove] source package [package ...]
copy package and its metadata from another repository copy package and its metadata from another repository
@@ -348,7 +348,7 @@ return non\-zero exit status if result is empty
remove package from the source repository after remove package from the source repository after
.SH COMMAND \fI\,'ahriman package\-remove'\/\fR .SH COMMAND \fI\,'ahriman package\-remove'\/\fR
usage: ahriman package\-remove [\-h] package [package ...] usage: ahriman package\-remove [\-h] package [package ...]
remove package from the repository remove package from the repository
@@ -357,8 +357,8 @@ remove package from the repository
package name or base package name or base
.SH COMMAND \fI\,'ahriman package\-status'\/\fR .SH COMMAND \fI\,'ahriman package\-status'\/\fR
usage: ahriman package\-status [\-h] [\-\-ahriman] [\-e] [\-\-info | \-\-no\-info] [\-s {unknown,pending,building,failed,success}] usage: ahriman package\-status [\-h] [\-\-ahriman] [\-e] [\-\-info | \-\-no\-info] [\-s {unknown,pending,building,failed,success}]
[package ...] [package ...]
request status of the package request status of the package
@@ -384,7 +384,7 @@ show additional package information
filter packages by status filter packages by status
.SH COMMAND \fI\,'ahriman package\-status\-remove'\/\fR .SH COMMAND \fI\,'ahriman package\-status\-remove'\/\fR
usage: ahriman package\-status\-remove [\-h] package [package ...] usage: ahriman package\-status\-remove [\-h] package [package ...]
remove the package from the status page remove the package from the status page
@@ -393,7 +393,7 @@ remove the package from the status page
remove specified packages from status page remove specified packages from status page
.SH COMMAND \fI\,'ahriman package\-status\-update'\/\fR .SH COMMAND \fI\,'ahriman package\-status\-update'\/\fR
usage: ahriman package\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}] [package ...] usage: ahriman package\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}] [package ...]
update package status on the status page update package status on the status page
@@ -407,7 +407,7 @@ set status for specified packages. If no packages supplied, service status will
new package build status new package build status
.SH COMMAND \fI\,'ahriman patch\-add'\/\fR .SH COMMAND \fI\,'ahriman patch\-add'\/\fR
usage: ahriman patch\-add [\-h] package variable [patch] usage: ahriman patch\-add [\-h] package variable [patch]
create or update patched PKGBUILD function or variable create or update patched PKGBUILD function or variable
@@ -424,7 +424,7 @@ PKGBUILD variable or function name. If variable is a function, it must end with
path to file which contains function or variable value. If not set, the value will be read from stdin path to file which contains function or variable value. If not set, the value will be read from stdin
.SH COMMAND \fI\,'ahriman patch\-list'\/\fR .SH COMMAND \fI\,'ahriman patch\-list'\/\fR
usage: ahriman patch\-list [\-h] [\-e] [\-v VARIABLE] package usage: ahriman patch\-list [\-h] [\-e] [\-v VARIABLE] package
list available patches for the package list available patches for the package
@@ -442,7 +442,7 @@ return non\-zero exit status if result is empty
if set, show only patches for specified PKGBUILD variables if set, show only patches for specified PKGBUILD variables
.SH COMMAND \fI\,'ahriman patch\-remove'\/\fR .SH COMMAND \fI\,'ahriman patch\-remove'\/\fR
usage: ahriman patch\-remove [\-h] [\-v VARIABLE] package usage: ahriman patch\-remove [\-h] [\-v VARIABLE] package
remove patches for the package remove patches for the package
@@ -457,7 +457,7 @@ should be used for single\-function patches in case if you wold like to remove o
if not set, it will remove all patches related to the package if not set, it will remove all patches related to the package
.SH COMMAND \fI\,'ahriman patch\-set\-add'\/\fR .SH COMMAND \fI\,'ahriman patch\-set\-add'\/\fR
usage: ahriman patch\-set\-add [\-h] [\-t TRACK] package usage: ahriman patch\-set\-add [\-h] [\-t TRACK] package
create or update source patches create or update source patches
@@ -471,7 +471,7 @@ path to directory with changed files for patch addition/update
files which has to be tracked files which has to be tracked
.SH COMMAND \fI\,'ahriman repo\-backup'\/\fR .SH COMMAND \fI\,'ahriman repo\-backup'\/\fR
usage: ahriman repo\-backup [\-h] path usage: ahriman repo\-backup [\-h] path
backup repository settings and database backup repository settings and database
@@ -480,9 +480,9 @@ backup repository settings and database
path of the output archive path of the output archive
.SH COMMAND \fI\,'ahriman repo\-check'\/\fR .SH COMMAND \fI\,'ahriman repo\-check'\/\fR
usage: ahriman repo\-check [\-h] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files] [\-e] [\-\-vcs | \-\-no\-vcs] usage: ahriman repo\-check [\-h] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files] [\-e] [\-\-vcs | \-\-no\-vcs]
[\-y] [\-y]
[package ...] [package ...]
check for packages updates. Same as repo\-update \-\-dry\-run \-\-no\-manual check for packages updates. Same as repo\-update \-\-dry\-run \-\-no\-manual
@@ -512,20 +512,20 @@ fetch actual version of VCS packages
download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date
.SH COMMAND \fI\,'ahriman repo\-create\-keyring'\/\fR .SH COMMAND \fI\,'ahriman repo\-create\-keyring'\/\fR
usage: ahriman repo\-create\-keyring [\-h] usage: ahriman repo\-create\-keyring [\-h]
create package which contains list of trusted keys as set by configuration. Note, that this action will only create package, the package itself has to be built manually create package which contains list of trusted keys as set by configuration. Note, that this action will only create package, the package itself has to be built manually
.SH COMMAND \fI\,'ahriman repo\-create\-mirrorlist'\/\fR .SH COMMAND \fI\,'ahriman repo\-create\-mirrorlist'\/\fR
usage: ahriman repo\-create\-mirrorlist [\-h] usage: ahriman repo\-create\-mirrorlist [\-h]
create package which contains list of available mirrors as set by configuration. Note, that this action will only create package, the package itself has to be built manually create package which contains list of available mirrors as set by configuration. Note, that this action will only create package, the package itself has to be built manually
.SH COMMAND \fI\,'ahriman repo\-daemon'\/\fR .SH COMMAND \fI\,'ahriman repo\-daemon'\/\fR
usage: ahriman repo\-daemon [\-h] [\-i INTERVAL] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes] usage: ahriman repo\-daemon [\-h] [\-i INTERVAL] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes]
[\-\-check\-files | \-\-no\-check\-files] [\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run] [\-\-check\-files | \-\-no\-check\-files] [\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run]
[\-\-increment | \-\-no\-increment] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-\-increment | \-\-no\-increment] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual]
[\-\-partitions | \-\-no\-partitions] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y] [\-\-partitions | \-\-no\-partitions] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y]
start process which periodically will run update process start process which periodically will run update process
@@ -583,8 +583,8 @@ fetch actual version of VCS packages
download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date
.SH COMMAND \fI\,'ahriman repo\-rebuild'\/\fR .SH COMMAND \fI\,'ahriman repo\-rebuild'\/\fR
usage: ahriman repo\-rebuild [\-h] [\-\-depends\-on DEPENDS_ON] [\-\-dry\-run] [\-\-from\-database] [\-\-increment | \-\-no\-increment] usage: ahriman repo\-rebuild [\-h] [\-\-depends\-on DEPENDS_ON] [\-\-dry\-run] [\-\-from\-database] [\-\-increment | \-\-no\-increment]
[\-e] [\-s {unknown,pending,building,failed,success}] [\-u USERNAME] [\-e] [\-s {unknown,pending,building,failed,success}] [\-u USERNAME]
force rebuild whole repository force rebuild whole repository
@@ -620,7 +620,7 @@ filter packages by status. Requires \-\-from\-database to be set
build as user build as user
.SH COMMAND \fI\,'ahriman repo\-remove\-unknown'\/\fR .SH COMMAND \fI\,'ahriman repo\-remove\-unknown'\/\fR
usage: ahriman repo\-remove\-unknown [\-h] [\-\-dry\-run] usage: ahriman repo\-remove\-unknown [\-h] [\-\-dry\-run]
remove packages which are missing in AUR and do not have local PKGBUILDs remove packages which are missing in AUR and do not have local PKGBUILDs
@@ -630,12 +630,12 @@ remove packages which are missing in AUR and do not have local PKGBUILDs
just perform check for packages without removal just perform check for packages without removal
.SH COMMAND \fI\,'ahriman repo\-report'\/\fR .SH COMMAND \fI\,'ahriman repo\-report'\/\fR
usage: ahriman repo\-report [\-h] usage: ahriman repo\-report [\-h]
generate repository report according to current settings generate repository report according to current settings
.SH COMMAND \fI\,'ahriman repo\-restore'\/\fR .SH COMMAND \fI\,'ahriman repo\-restore'\/\fR
usage: ahriman repo\-restore [\-h] [\-o OUTPUT] path usage: ahriman repo\-restore [\-h] [\-o OUTPUT] path
restore settings and database restore settings and database
@@ -649,7 +649,7 @@ path of the input archive
root path of the extracted files root path of the extracted files
.SH COMMAND \fI\,'ahriman repo\-sign'\/\fR .SH COMMAND \fI\,'ahriman repo\-sign'\/\fR
usage: ahriman repo\-sign [\-h] [package ...] usage: ahriman repo\-sign [\-h] [package ...]
(re\-)sign packages and repository database according to current settings (re\-)sign packages and repository database according to current settings
@@ -658,10 +658,10 @@ usage: ahriman repo\-sign [\-h] [package ...]
sign only specified packages sign only specified packages
.SH COMMAND \fI\,'ahriman repo\-statistics'\/\fR .SH COMMAND \fI\,'ahriman repo\-statistics'\/\fR
usage: ahriman repo\-statistics [\-h] [\-\-chart CHART] usage: ahriman repo\-statistics [\-h] [\-\-chart CHART]
[\-e {package\-outdated,package\-removed,package\-update\-failed,package\-updated}] [\-e {package\-outdated,package\-removed,package\-update\-failed,package\-updated}]
[\-\-from\-date FROM_DATE] [\-\-limit LIMIT] [\-\-offset OFFSET] [\-\-to\-date TO_DATE] [\-\-from\-date FROM_DATE] [\-\-limit LIMIT] [\-\-offset OFFSET] [\-\-to\-date TO_DATE]
[package] [package]
fetch repository statistics fetch repository statistics
@@ -695,7 +695,7 @@ skip specified amount of events
only fetch events which are older than the date only fetch events which are older than the date
.SH COMMAND \fI\,'ahriman repo\-status\-update'\/\fR .SH COMMAND \fI\,'ahriman repo\-status\-update'\/\fR
usage: ahriman repo\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}] usage: ahriman repo\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}]
update repository status on the status page update repository status on the status page
@@ -705,12 +705,12 @@ update repository status on the status page
new status new status
.SH COMMAND \fI\,'ahriman repo\-sync'\/\fR .SH COMMAND \fI\,'ahriman repo\-sync'\/\fR
usage: ahriman repo\-sync [\-h] usage: ahriman repo\-sync [\-h]
sync repository files to remote server according to current settings sync repository files to remote server according to current settings
.SH COMMAND \fI\,'ahriman repo\-tree'\/\fR .SH COMMAND \fI\,'ahriman repo\-tree'\/\fR
usage: ahriman repo\-tree [\-h] [\-p PARTITIONS] usage: ahriman repo\-tree [\-h] [\-p PARTITIONS]
dump repository tree based on packages dependencies dump repository tree based on packages dependencies
@@ -720,7 +720,7 @@ dump repository tree based on packages dependencies
also divide packages by independent partitions also divide packages by independent partitions
.SH COMMAND \fI\,'ahriman repo\-triggers'\/\fR .SH COMMAND \fI\,'ahriman repo\-triggers'\/\fR
usage: ahriman repo\-triggers [\-h] [trigger ...] usage: ahriman repo\-triggers [\-h] [trigger ...]
run triggers on empty build result as configured by settings run triggers on empty build result as configured by settings
@@ -729,10 +729,10 @@ run triggers on empty build result as configured by settings
instead of running all triggers as set by configuration, just process specified ones in order of mention instead of running all triggers as set by configuration, just process specified ones in order of mention
.SH COMMAND \fI\,'ahriman repo\-update'\/\fR .SH COMMAND \fI\,'ahriman repo\-update'\/\fR
usage: ahriman repo\-update [\-h] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files] usage: ahriman repo\-update [\-h] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files]
[\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run] [\-e] [\-\-increment | \-\-no\-increment] [\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run] [\-e] [\-\-increment | \-\-no\-increment]
[\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y]
[package ...] [package ...]
check for packages updates and run build process if requested check for packages updates and run build process if requested
@@ -790,8 +790,8 @@ fetch actual version of VCS packages
download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date
.SH COMMAND \fI\,'ahriman service\-clean'\/\fR .SH COMMAND \fI\,'ahriman service\-clean'\/\fR
usage: ahriman service\-clean [\-h] [\-\-cache | \-\-no\-cache] [\-\-chroot | \-\-no\-chroot] [\-\-manual | \-\-no\-manual] usage: ahriman service\-clean [\-h] [\-\-cache | \-\-no\-cache] [\-\-chroot | \-\-no\-chroot] [\-\-manual | \-\-no\-manual]
[\-\-packages | \-\-no\-packages] [\-\-pacman | \-\-no\-pacman] [\-\-packages | \-\-no\-packages] [\-\-pacman | \-\-no\-pacman]
remove local caches remove local caches
@@ -817,7 +817,7 @@ clear directory with built packages
clear directory with pacman local database cache clear directory with pacman local database cache
.SH COMMAND \fI\,'ahriman service\-config'\/\fR .SH COMMAND \fI\,'ahriman service\-config'\/\fR
usage: ahriman service\-config [\-h] [\-\-info | \-\-no\-info] [\-\-secure | \-\-no\-secure] [section] [key] usage: ahriman service\-config [\-h] [\-\-info | \-\-no\-info] [\-\-secure | \-\-no\-secure] [section] [key]
dump configuration for the specified architecture dump configuration for the specified architecture
@@ -839,7 +839,7 @@ show additional information, e.g. configuration files
hide passwords and secrets from output hide passwords and secrets from output
.SH COMMAND \fI\,'ahriman service\-config\-validate'\/\fR .SH COMMAND \fI\,'ahriman service\-config\-validate'\/\fR
usage: ahriman service\-config\-validate [\-h] [\-e] usage: ahriman service\-config\-validate [\-h] [\-e]
validate configuration and print found errors validate configuration and print found errors
@@ -849,7 +849,7 @@ validate configuration and print found errors
return non\-zero exit status if configuration is invalid return non\-zero exit status if configuration is invalid
.SH COMMAND \fI\,'ahriman service\-key\-import'\/\fR .SH COMMAND \fI\,'ahriman service\-key\-import'\/\fR
usage: ahriman service\-key\-import [\-h] [\-\-key\-server KEY_SERVER] key usage: ahriman service\-key\-import [\-h] [\-\-key\-server KEY_SERVER] key
import PGP key from public sources to the repository user import PGP key from public sources to the repository user
@@ -863,7 +863,7 @@ PGP key to import from public server
key server for key import key server for key import
.SH COMMAND \fI\,'ahriman service\-repositories'\/\fR .SH COMMAND \fI\,'ahriman service\-repositories'\/\fR
usage: ahriman service\-repositories [\-h] [\-\-id\-only | \-\-no\-id\-only] usage: ahriman service\-repositories [\-h] [\-\-id\-only | \-\-no\-id\-only]
list all available repositories list all available repositories
@@ -873,7 +873,7 @@ list all available repositories
show machine readable identifier instead show machine readable identifier instead
.SH COMMAND \fI\,'ahriman service\-run'\/\fR .SH COMMAND \fI\,'ahriman service\-run'\/\fR
usage: ahriman service\-run [\-h] command [command ...] usage: ahriman service\-run [\-h] command [command ...]
run multiple commands on success run of the previous command run multiple commands on success run of the previous command
@@ -882,11 +882,11 @@ run multiple commands on success run of the previous command
command to be run (quoted) without ``ahriman`` command to be run (quoted) without ``ahriman``
.SH COMMAND \fI\,'ahriman service\-setup'\/\fR .SH COMMAND \fI\,'ahriman service\-setup'\/\fR
usage: ahriman service\-setup [\-h] [\-\-build\-as\-user BUILD_AS_USER] [\-\-from\-configuration FROM_CONFIGURATION] usage: ahriman service\-setup [\-h] [\-\-build\-as\-user BUILD_AS_USER] [\-\-from\-configuration FROM_CONFIGURATION]
[\-\-generate\-salt | \-\-no\-generate\-salt] [\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs] [\-\-generate\-salt | \-\-no\-generate\-salt] [\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs]
[\-\-mirror MIRROR] [\-\-multilib | \-\-no\-multilib] \-\-packager PACKAGER [\-\-server SERVER] [\-\-mirror MIRROR] [\-\-multilib | \-\-no\-multilib] \-\-packager PACKAGER [\-\-server SERVER]
[\-\-sign\-key SIGN_KEY] [\-\-sign\-target {disabled,packages,repository}] [\-\-web\-port WEB_PORT] [\-\-sign\-key SIGN_KEY] [\-\-sign\-target {disabled,packages,repository}] [\-\-web\-port WEB_PORT]
[\-\-web\-unix\-socket WEB_UNIX_SOCKET] [\-\-web\-unix\-socket WEB_UNIX_SOCKET]
create initial service configuration, requires root create initial service configuration, requires root
@@ -940,7 +940,7 @@ port of the web service
path to unix socket used for interprocess communications path to unix socket used for interprocess communications
.SH COMMAND \fI\,'ahriman service\-shell'\/\fR .SH COMMAND \fI\,'ahriman service\-shell'\/\fR
usage: ahriman service\-shell [\-h] [\-o OUTPUT] [code] usage: ahriman service\-shell [\-h] [\-o OUTPUT] [code]
drop into python shell drop into python shell
@@ -954,13 +954,13 @@ instead of dropping into shell, just execute the specified code
output commands and result to the file output commands and result to the file
.SH COMMAND \fI\,'ahriman service\-tree\-migrate'\/\fR .SH COMMAND \fI\,'ahriman service\-tree\-migrate'\/\fR
usage: ahriman service\-tree\-migrate [\-h] usage: ahriman service\-tree\-migrate [\-h]
migrate repository tree between versions migrate repository tree between versions
.SH COMMAND \fI\,'ahriman user\-add'\/\fR .SH COMMAND \fI\,'ahriman user\-add'\/\fR
usage: ahriman user\-add [\-h] [\-\-key KEY] [\-\-packager PACKAGER] [\-p PASSWORD] [\-R {unauthorized,read,reporter,full}] usage: ahriman user\-add [\-h] [\-\-key KEY] [\-\-packager PACKAGER] [\-p PASSWORD] [\-R {unauthorized,read,reporter,full}]
username username
update user for web services with the given password and role. In case if password was not entered it will be asked interactively update user for web services with the given password and role. In case if password was not entered it will be asked interactively
@@ -987,7 +987,7 @@ authorization type.
user access level user access level
.SH COMMAND \fI\,'ahriman user\-list'\/\fR .SH COMMAND \fI\,'ahriman user\-list'\/\fR
usage: ahriman user\-list [\-h] [\-e] [\-R {unauthorized,read,reporter,full}] [username] usage: ahriman user\-list [\-h] [\-e] [\-R {unauthorized,read,reporter,full}] [username]
list users from the user mapping and their roles list users from the user mapping and their roles
@@ -1005,7 +1005,7 @@ return non\-zero exit status if result is empty
filter users by role filter users by role
.SH COMMAND \fI\,'ahriman user\-remove'\/\fR .SH COMMAND \fI\,'ahriman user\-remove'\/\fR
usage: ahriman user\-remove [\-h] username usage: ahriman user\-remove [\-h] username
remove user from the user mapping and update the configuration remove user from the user mapping and update the configuration
@@ -1014,7 +1014,7 @@ remove user from the user mapping and update the configuration
username for web service username for web service
.SH COMMAND \fI\,'ahriman web'\/\fR .SH COMMAND \fI\,'ahriman web'\/\fR
usage: ahriman web [\-h] usage: ahriman web [\-h]
start web server start web server

View File

@@ -99,6 +99,9 @@ _shtab_ahriman_options=(
"--wait-timeout[wait for lock to be free. Negative value will lead to immediate application run even if there is lock file. In case of zero value, the application will wait infinitely (default\: -1)]:wait_timeout:" "--wait-timeout[wait for lock to be free. Negative value will lead to immediate application run even if there is lock file. In case of zero value, the application will wait infinitely (default\: -1)]:wait_timeout:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_defaults_added=0
_shtab_ahriman_add_options=( _shtab_ahriman_add_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:" {--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:"
@@ -113,6 +116,9 @@ _shtab_ahriman_add_options=(
"(*):package source (base name, path to local files, remote URL):" "(*):package source (base name, path to local files, remote URL):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_add_defaults_added=0
_shtab_ahriman_aur_search_options=( _shtab_ahriman_aur_search_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
@@ -121,6 +127,9 @@ _shtab_ahriman_aur_search_options=(
"(*):search terms, can be specified multiple times, the result will match all terms:" "(*):search terms, can be specified multiple times, the result will match all terms:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_aur_search_defaults_added=0
_shtab_ahriman_check_options=( _shtab_ahriman_check_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:" {--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:"
@@ -131,6 +140,9 @@ _shtab_ahriman_check_options=(
"(*)::filter check by package base (default\: None):" "(*)::filter check by package base (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_check_defaults_added=0
_shtab_ahriman_clean_options=( _shtab_ahriman_clean_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--cache,--no-cache}"[clear directory with package caches (default\: False)]:cache:" {--cache,--no-cache}"[clear directory with package caches (default\: False)]:cache:"
@@ -140,6 +152,9 @@ _shtab_ahriman_clean_options=(
{--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: False)]:pacman:" {--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: False)]:pacman:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_clean_defaults_added=0
_shtab_ahriman_config_options=( _shtab_ahriman_config_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:" {--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:"
@@ -148,11 +163,17 @@ _shtab_ahriman_config_options=(
":filter settings by key (default\: None):" ":filter settings by key (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_config_defaults_added=0
_shtab_ahriman_config_validate_options=( _shtab_ahriman_config_validate_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if configuration is invalid (default\: False)]" {-e,--exit-code}"[return non-zero exit status if configuration is invalid (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_config_validate_defaults_added=0
_shtab_ahriman_copy_options=( _shtab_ahriman_copy_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
@@ -161,6 +182,9 @@ _shtab_ahriman_copy_options=(
"(*):package base:" "(*):package base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_copy_defaults_added=0
_shtab_ahriman_daemon_options=( _shtab_ahriman_daemon_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:" {-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:"
@@ -178,25 +202,40 @@ _shtab_ahriman_daemon_options=(
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_daemon_defaults_added=0
_shtab_ahriman_help_options=( _shtab_ahriman_help_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
":show help message for specific command (default\: None):" ":show help message for specific command (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_help_defaults_added=0
_shtab_ahriman_help_commands_unsafe_options=( _shtab_ahriman_help_commands_unsafe_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*)::instead of showing commands, just test command line for unsafe subcommand and return 0 in case if command is safe and 1 otherwise (default\: None):" "(*)::instead of showing commands, just test command line for unsafe subcommand and return 0 in case if command is safe and 1 otherwise (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_help_commands_unsafe_defaults_added=0
_shtab_ahriman_help_updates_options=( _shtab_ahriman_help_updates_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit code if updates available (default\: False)]" {-e,--exit-code}"[return non-zero exit code if updates available (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_help_updates_defaults_added=0
_shtab_ahriman_help_version_options=( _shtab_ahriman_help_version_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_help_version_defaults_added=0
_shtab_ahriman_init_options=( _shtab_ahriman_init_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:" "--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
@@ -213,12 +252,18 @@ _shtab_ahriman_init_options=(
"--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:" "--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_init_defaults_added=0
_shtab_ahriman_key_import_options=( _shtab_ahriman_key_import_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--key-server[key server for key import (default\: keyserver.ubuntu.com)]:key_server:" "--key-server[key server for key import (default\: keyserver.ubuntu.com)]:key_server:"
":PGP key to import from public server:" ":PGP key to import from public server:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_key_import_defaults_added=0
_shtab_ahriman_package_add_options=( _shtab_ahriman_package_add_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:" {--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:"
@@ -233,17 +278,26 @@ _shtab_ahriman_package_add_options=(
"(*):package source (base name, path to local files, remote URL):" "(*):package source (base name, path to local files, remote URL):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_add_defaults_added=0
_shtab_ahriman_package_changes_options=( _shtab_ahriman_package_changes_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
":package base:" ":package base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_changes_defaults_added=0
_shtab_ahriman_package_changes_remove_options=( _shtab_ahriman_package_changes_remove_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
":package base:" ":package base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_changes_remove_defaults_added=0
_shtab_ahriman_package_copy_options=( _shtab_ahriman_package_copy_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
@@ -252,11 +306,17 @@ _shtab_ahriman_package_copy_options=(
"(*):package base:" "(*):package base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_copy_defaults_added=0
_shtab_ahriman_package_remove_options=( _shtab_ahriman_package_remove_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*):package name or base:" "(*):package name or base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_remove_defaults_added=0
_shtab_ahriman_package_status_options=( _shtab_ahriman_package_status_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--ahriman[get service status itself (default\: False)]" "--ahriman[get service status itself (default\: False)]"
@@ -266,17 +326,26 @@ _shtab_ahriman_package_status_options=(
"(*)::filter status by package base (default\: None):" "(*)::filter status by package base (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_status_defaults_added=0
_shtab_ahriman_package_status_remove_options=( _shtab_ahriman_package_status_remove_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*):remove specified packages from status page:" "(*):remove specified packages from status page:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_status_remove_defaults_added=0
_shtab_ahriman_package_status_update_options=( _shtab_ahriman_package_status_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-s,--status}"[new package build status (default\: success)]:status:(unknown pending building failed success)" {-s,--status}"[new package build status (default\: success)]:status:(unknown pending building failed success)"
"(*)::set status for specified packages. If no packages supplied, service status will be updated (default\: None):" "(*)::set status for specified packages. If no packages supplied, service status will be updated (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_status_update_defaults_added=0
_shtab_ahriman_package_update_options=( _shtab_ahriman_package_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:" {--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:"
@@ -291,6 +360,9 @@ _shtab_ahriman_package_update_options=(
"(*):package source (base name, path to local files, remote URL):" "(*):package source (base name, path to local files, remote URL):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_update_defaults_added=0
_shtab_ahriman_patch_add_options=( _shtab_ahriman_patch_add_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
":package base:" ":package base:"
@@ -298,6 +370,9 @@ _shtab_ahriman_patch_add_options=(
":path to file which contains function or variable value. If not set, the value will be read from stdin (default\: None):" ":path to file which contains function or variable value. If not set, the value will be read from stdin (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_patch_add_defaults_added=0
_shtab_ahriman_patch_list_options=( _shtab_ahriman_patch_list_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
@@ -305,18 +380,27 @@ _shtab_ahriman_patch_list_options=(
":package base:" ":package base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_patch_list_defaults_added=0
_shtab_ahriman_patch_remove_options=( _shtab_ahriman_patch_remove_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"*"{-v,--variable}"[should be used for single-function patches in case if you wold like to remove only specified PKGBUILD variables. In case if not set, it will remove all patches related to the package (default\: None)]:variable:" "*"{-v,--variable}"[should be used for single-function patches in case if you wold like to remove only specified PKGBUILD variables. In case if not set, it will remove all patches related to the package (default\: None)]:variable:"
":package base:" ":package base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_patch_remove_defaults_added=0
_shtab_ahriman_patch_set_add_options=( _shtab_ahriman_patch_set_add_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"*"{-t,--track}"[files which has to be tracked (default\: \[\'\*.diff\', \'\*.patch\'\])]:track:" "*"{-t,--track}"[files which has to be tracked (default\: \[\'\*.diff\', \'\*.patch\'\])]:track:"
":path to directory with changed files for patch addition\/update:" ":path to directory with changed files for patch addition\/update:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_patch_set_add_defaults_added=0
_shtab_ahriman_rebuild_options=( _shtab_ahriman_rebuild_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"*--depends-on[only rebuild packages that depend on specified packages (default\: None)]:depends_on:" "*--depends-on[only rebuild packages that depend on specified packages (default\: None)]:depends_on:"
@@ -328,21 +412,33 @@ _shtab_ahriman_rebuild_options=(
{-u,--username}"[build as user (default\: None)]:username:" {-u,--username}"[build as user (default\: None)]:username:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_rebuild_defaults_added=0
_shtab_ahriman_remove_options=( _shtab_ahriman_remove_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*):package name or base:" "(*):package name or base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_remove_defaults_added=0
_shtab_ahriman_remove_unknown_options=( _shtab_ahriman_remove_unknown_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--dry-run[just perform check for packages without removal (default\: False)]" "--dry-run[just perform check for packages without removal (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_remove_unknown_defaults_added=0
_shtab_ahriman_repo_backup_options=( _shtab_ahriman_repo_backup_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
":path of the output archive:" ":path of the output archive:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_backup_defaults_added=0
_shtab_ahriman_repo_check_options=( _shtab_ahriman_repo_check_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:" {--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:"
@@ -353,6 +449,9 @@ _shtab_ahriman_repo_check_options=(
"(*)::filter check by package base (default\: None):" "(*)::filter check by package base (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_check_defaults_added=0
_shtab_ahriman_repo_clean_options=( _shtab_ahriman_repo_clean_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--cache,--no-cache}"[clear directory with package caches (default\: False)]:cache:" {--cache,--no-cache}"[clear directory with package caches (default\: False)]:cache:"
@@ -362,6 +461,9 @@ _shtab_ahriman_repo_clean_options=(
{--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: False)]:pacman:" {--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: False)]:pacman:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_clean_defaults_added=0
_shtab_ahriman_repo_config_options=( _shtab_ahriman_repo_config_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:" {--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:"
@@ -370,19 +472,31 @@ _shtab_ahriman_repo_config_options=(
":filter settings by key (default\: None):" ":filter settings by key (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_config_defaults_added=0
_shtab_ahriman_repo_config_validate_options=( _shtab_ahriman_repo_config_validate_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if configuration is invalid (default\: False)]" {-e,--exit-code}"[return non-zero exit status if configuration is invalid (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_config_validate_defaults_added=0
_shtab_ahriman_repo_create_keyring_options=( _shtab_ahriman_repo_create_keyring_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_create_keyring_defaults_added=0
_shtab_ahriman_repo_create_mirrorlist_options=( _shtab_ahriman_repo_create_mirrorlist_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_create_mirrorlist_defaults_added=0
_shtab_ahriman_repo_daemon_options=( _shtab_ahriman_repo_daemon_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:" {-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:"
@@ -400,6 +514,9 @@ _shtab_ahriman_repo_daemon_options=(
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_daemon_defaults_added=0
_shtab_ahriman_repo_init_options=( _shtab_ahriman_repo_init_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:" "--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
@@ -416,6 +533,9 @@ _shtab_ahriman_repo_init_options=(
"--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:" "--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_init_defaults_added=0
_shtab_ahriman_repo_rebuild_options=( _shtab_ahriman_repo_rebuild_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"*--depends-on[only rebuild packages that depend on specified packages (default\: None)]:depends_on:" "*--depends-on[only rebuild packages that depend on specified packages (default\: None)]:depends_on:"
@@ -427,21 +547,33 @@ _shtab_ahriman_repo_rebuild_options=(
{-u,--username}"[build as user (default\: None)]:username:" {-u,--username}"[build as user (default\: None)]:username:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_rebuild_defaults_added=0
_shtab_ahriman_repo_remove_unknown_options=( _shtab_ahriman_repo_remove_unknown_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--dry-run[just perform check for packages without removal (default\: False)]" "--dry-run[just perform check for packages without removal (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_remove_unknown_defaults_added=0
_shtab_ahriman_repo_report_options=( _shtab_ahriman_repo_report_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_report_defaults_added=0
_shtab_ahriman_repo_restore_options=( _shtab_ahriman_repo_restore_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-o,--output}"[root path of the extracted files (default\: \/)]:output:" {-o,--output}"[root path of the extracted files (default\: \/)]:output:"
":path of the input archive:" ":path of the input archive:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_restore_defaults_added=0
_shtab_ahriman_repo_setup_options=( _shtab_ahriman_repo_setup_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:" "--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
@@ -458,11 +590,17 @@ _shtab_ahriman_repo_setup_options=(
"--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:" "--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_setup_defaults_added=0
_shtab_ahriman_repo_sign_options=( _shtab_ahriman_repo_sign_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*)::sign only specified packages (default\: None):" "(*)::sign only specified packages (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_sign_defaults_added=0
_shtab_ahriman_repo_statistics_options=( _shtab_ahriman_repo_statistics_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--chart[create updates chart and save it to the specified path (default\: None)]:chart:" "--chart[create updates chart and save it to the specified path (default\: None)]:chart:"
@@ -474,25 +612,40 @@ _shtab_ahriman_repo_statistics_options=(
":fetch only events for the specified package (default\: None):" ":fetch only events for the specified package (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_statistics_defaults_added=0
_shtab_ahriman_repo_status_update_options=( _shtab_ahriman_repo_status_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-s,--status}"[new status (default\: success)]:status:(unknown pending building failed success)" {-s,--status}"[new status (default\: success)]:status:(unknown pending building failed success)"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_status_update_defaults_added=0
_shtab_ahriman_repo_sync_options=( _shtab_ahriman_repo_sync_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_sync_defaults_added=0
_shtab_ahriman_repo_tree_options=( _shtab_ahriman_repo_tree_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-p,--partitions}"[also divide packages by independent partitions (default\: 1)]:partitions:" {-p,--partitions}"[also divide packages by independent partitions (default\: 1)]:partitions:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_tree_defaults_added=0
_shtab_ahriman_repo_triggers_options=( _shtab_ahriman_repo_triggers_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*)::instead of running all triggers as set by configuration, just process specified ones in order of mention (default\: None):" "(*)::instead of running all triggers as set by configuration, just process specified ones in order of mention (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_triggers_defaults_added=0
_shtab_ahriman_repo_update_options=( _shtab_ahriman_repo_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:" {--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:"
@@ -510,15 +663,24 @@ _shtab_ahriman_repo_update_options=(
"(*)::filter check by package base (default\: None):" "(*)::filter check by package base (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_update_defaults_added=0
_shtab_ahriman_report_options=( _shtab_ahriman_report_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_report_defaults_added=0
_shtab_ahriman_run_options=( _shtab_ahriman_run_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*):command to be run (quoted) without \`\`ahriman\`\`:" "(*):command to be run (quoted) without \`\`ahriman\`\`:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_run_defaults_added=0
_shtab_ahriman_search_options=( _shtab_ahriman_search_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
@@ -527,6 +689,9 @@ _shtab_ahriman_search_options=(
"(*):search terms, can be specified multiple times, the result will match all terms:" "(*):search terms, can be specified multiple times, the result will match all terms:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_search_defaults_added=0
_shtab_ahriman_service_clean_options=( _shtab_ahriman_service_clean_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--cache,--no-cache}"[clear directory with package caches (default\: False)]:cache:" {--cache,--no-cache}"[clear directory with package caches (default\: False)]:cache:"
@@ -536,6 +701,9 @@ _shtab_ahriman_service_clean_options=(
{--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: False)]:pacman:" {--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: False)]:pacman:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_clean_defaults_added=0
_shtab_ahriman_service_config_options=( _shtab_ahriman_service_config_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:" {--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:"
@@ -544,27 +712,42 @@ _shtab_ahriman_service_config_options=(
":filter settings by key (default\: None):" ":filter settings by key (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_config_defaults_added=0
_shtab_ahriman_service_config_validate_options=( _shtab_ahriman_service_config_validate_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if configuration is invalid (default\: False)]" {-e,--exit-code}"[return non-zero exit status if configuration is invalid (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_config_validate_defaults_added=0
_shtab_ahriman_service_key_import_options=( _shtab_ahriman_service_key_import_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--key-server[key server for key import (default\: keyserver.ubuntu.com)]:key_server:" "--key-server[key server for key import (default\: keyserver.ubuntu.com)]:key_server:"
":PGP key to import from public server:" ":PGP key to import from public server:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_key_import_defaults_added=0
_shtab_ahriman_service_repositories_options=( _shtab_ahriman_service_repositories_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--id-only,--no-id-only}"[show machine readable identifier instead (default\: False)]:id_only:" {--id-only,--no-id-only}"[show machine readable identifier instead (default\: False)]:id_only:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_repositories_defaults_added=0
_shtab_ahriman_service_run_options=( _shtab_ahriman_service_run_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*):command to be run (quoted) without \`\`ahriman\`\`:" "(*):command to be run (quoted) without \`\`ahriman\`\`:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_run_defaults_added=0
_shtab_ahriman_service_setup_options=( _shtab_ahriman_service_setup_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:" "--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
@@ -581,16 +764,25 @@ _shtab_ahriman_service_setup_options=(
"--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:" "--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_setup_defaults_added=0
_shtab_ahriman_service_shell_options=( _shtab_ahriman_service_shell_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-o,--output}"[output commands and result to the file (default\: None)]:output:" {-o,--output}"[output commands and result to the file (default\: None)]:output:"
":instead of dropping into shell, just execute the specified code (default\: None):" ":instead of dropping into shell, just execute the specified code (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_shell_defaults_added=0
_shtab_ahriman_service_tree_migrate_options=( _shtab_ahriman_service_tree_migrate_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_tree_migrate_defaults_added=0
_shtab_ahriman_setup_options=( _shtab_ahriman_setup_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:" "--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
@@ -607,17 +799,26 @@ _shtab_ahriman_setup_options=(
"--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:" "--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_setup_defaults_added=0
_shtab_ahriman_shell_options=( _shtab_ahriman_shell_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-o,--output}"[output commands and result to the file (default\: None)]:output:" {-o,--output}"[output commands and result to the file (default\: None)]:output:"
":instead of dropping into shell, just execute the specified code (default\: None):" ":instead of dropping into shell, just execute the specified code (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_shell_defaults_added=0
_shtab_ahriman_sign_options=( _shtab_ahriman_sign_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*)::sign only specified packages (default\: None):" "(*)::sign only specified packages (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_sign_defaults_added=0
_shtab_ahriman_status_options=( _shtab_ahriman_status_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--ahriman[get service status itself (default\: False)]" "--ahriman[get service status itself (default\: False)]"
@@ -627,16 +828,25 @@ _shtab_ahriman_status_options=(
"(*)::filter status by package base (default\: None):" "(*)::filter status by package base (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_status_defaults_added=0
_shtab_ahriman_status_update_options=( _shtab_ahriman_status_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-s,--status}"[new package build status (default\: success)]:status:(unknown pending building failed success)" {-s,--status}"[new package build status (default\: success)]:status:(unknown pending building failed success)"
"(*)::set status for specified packages. If no packages supplied, service status will be updated (default\: None):" "(*)::set status for specified packages. If no packages supplied, service status will be updated (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_status_update_defaults_added=0
_shtab_ahriman_sync_options=( _shtab_ahriman_sync_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_sync_defaults_added=0
_shtab_ahriman_update_options=( _shtab_ahriman_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:" {--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:"
@@ -654,6 +864,9 @@ _shtab_ahriman_update_options=(
"(*)::filter check by package base (default\: None):" "(*)::filter check by package base (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_update_defaults_added=0
_shtab_ahriman_user_add_options=( _shtab_ahriman_user_add_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--key[optional PGP key used by this user. The private key must be imported (default\: None)]:key:" "--key[optional PGP key used by this user. The private key must be imported (default\: None)]:key:"
@@ -663,6 +876,9 @@ _shtab_ahriman_user_add_options=(
":username for web service:" ":username for web service:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_user_add_defaults_added=0
_shtab_ahriman_user_list_options=( _shtab_ahriman_user_list_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
@@ -670,25 +886,41 @@ _shtab_ahriman_user_list_options=(
":filter users by username (default\: None):" ":filter users by username (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_user_list_defaults_added=0
_shtab_ahriman_user_remove_options=( _shtab_ahriman_user_remove_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
":username for web service:" ":username for web service:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_user_remove_defaults_added=0
_shtab_ahriman_version_options=( _shtab_ahriman_version_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_version_defaults_added=0
_shtab_ahriman_web_options=( _shtab_ahriman_web_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_web_defaults_added=0
_shtab_ahriman() { _shtab_ahriman() {
local context state line curcontext="$curcontext" one_or_more='(-)*' remainder='(*)' local context state line curcontext="$curcontext" one_or_more='(*)' remainder='(-)*' default='*::: :->ahriman'
if ((${_shtab_ahriman_options[(I)${(q)one_or_more}*]} + ${_shtab_ahriman_options[(I)${(q)remainder}*]} == 0)); then # noqa: E501 # Add default positional/remainder specs only if none exist, and only once per session
_shtab_ahriman_options+=(': :_shtab_ahriman_commands' '*::: :->ahriman') if (( ! _shtab_ahriman_defaults_added )); then
if (( ${_shtab_ahriman_options[(I)${(q)one_or_more}*]} + ${_shtab_ahriman_options[(I)${(q)remainder}*]} + ${_shtab_ahriman_options[(I)${(q)default}]} == 0 )); then
_shtab_ahriman_options+=(': :_shtab_ahriman_commands' '*::: :->ahriman')
fi
_shtab_ahriman_defaults_added=1
fi fi
_arguments -C -s $_shtab_ahriman_options _arguments -C -s $_shtab_ahriman_options

View File

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

View File

@@ -1,70 +0,0 @@
#
# Copyright (c) 2021-2025 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import argparse
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.core.configuration import Configuration
from ahriman.models.repository_id import RepositoryId
class Reload(Handler):
"""
web server reload handler
"""
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
"""
application = Application(repository_id, configuration, report=True)
client = application.repository.reporter
client.configuration_reload()
@staticmethod
def _set_web_reload_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for web reload subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("web-reload", help="reload configuration",
description="reload web server configuration",
epilog="This method forces the web server to reload its configuration. "
"Note, however, that this method does not apply all configuration changes "
"(like ports, authentication, etc)")
parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True)
return parser
arguments = [_set_web_reload_parser]

View File

@@ -52,7 +52,7 @@ class Validate(Handler):
""" """
from ahriman.core.configuration.validator import Validator from ahriman.core.configuration.validator import Validator
schema = Validate.schema(repository_id, configuration) schema = Validate.schema(configuration)
validator = Validator(configuration=configuration, schema=schema) validator = Validator(configuration=configuration, schema=schema)
if validator.validate(configuration.dump()): if validator.validate(configuration.dump()):
@@ -83,12 +83,11 @@ class Validate(Handler):
return parser return parser
@staticmethod @staticmethod
def schema(repository_id: RepositoryId, configuration: Configuration) -> ConfigurationSchema: def schema(configuration: Configuration) -> ConfigurationSchema:
""" """
get schema with triggers get schema with triggers
Args: Args:
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
Returns: Returns:
@@ -107,12 +106,12 @@ class Validate(Handler):
continue continue
# default settings if any # default settings if any
for schema_name, schema in trigger_class.configuration_schema(repository_id, None).items(): for schema_name, schema in trigger_class.configuration_schema(None).items():
erased = Validate.schema_erase_required(copy.deepcopy(schema)) erased = Validate.schema_erase_required(copy.deepcopy(schema))
root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), erased) root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), erased)
# settings according to enabled triggers # settings according to enabled triggers
for schema_name, schema in trigger_class.configuration_schema(repository_id, configuration).items(): for schema_name, schema in trigger_class.configuration_schema(configuration).items():
root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), copy.deepcopy(schema)) root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), copy.deepcopy(schema))
return root return root

View File

@@ -21,7 +21,7 @@ import argparse
import re import re
import sys import sys
from collections.abc import Generator from collections.abc import Iterator
from importlib import metadata from importlib import metadata
from typing import ClassVar from typing import ClassVar
@@ -77,7 +77,7 @@ class Versions(Handler):
return parser return parser
@staticmethod @staticmethod
def package_dependencies(root: str) -> Generator[tuple[str, str], None, None]: def package_dependencies(root: str) -> Iterator[tuple[str, str]]:
""" """
extract list of ahriman package dependencies installed into system with their versions extract list of ahriman package dependencies installed into system with their versions
@@ -87,7 +87,7 @@ class Versions(Handler):
Yields: Yields:
tuple[str, str]: map of installed dependency to its version tuple[str, str]: map of installed dependency to its version
""" """
def dependencies_by_key(key: str) -> Generator[str, None, None]: def dependencies_by_key(key: str) -> Iterator[str]:
# in importlib it returns requires in the following format # in importlib it returns requires in the following format
# ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"] # ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
try: try:

View File

@@ -19,7 +19,7 @@
# #
import argparse import argparse
from collections.abc import Generator from collections.abc import Iterator
from pathlib import Path from pathlib import Path
from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.application.handlers.handler import Handler, SubParserAction
@@ -86,7 +86,7 @@ class Web(Handler):
return parser return parser
@staticmethod @staticmethod
def extract_arguments(args: argparse.Namespace, configuration: Configuration) -> Generator[str, None, None]: def extract_arguments(args: argparse.Namespace, configuration: Configuration) -> Iterator[str]:
""" """
extract list of arguments used for current command, except for command specific ones extract list of arguments used for current command, except for command specific ones

View File

@@ -158,7 +158,7 @@ class Lock(LazyLogging):
""" """
check if current user is actually owner of ahriman root check if current user is actually owner of ahriman root
""" """
check_user(self.paths, unsafe=self.unsafe) check_user(self.paths.root, unsafe=self.unsafe)
self.paths.tree_create() self.paths.tree_create()
def check_version(self) -> None: def check_version(self) -> None:

View File

@@ -21,7 +21,7 @@ import itertools
import shutil import shutil
import tarfile import tarfile
from collections.abc import Generator, Iterable from collections.abc import Iterable, Iterator
from functools import cached_property from functools import cached_property
from pathlib import Path from pathlib import Path
from pyalpm import DB, Handle, Package, SIG_DATABASE_OPTIONAL, SIG_PACKAGE_OPTIONAL # type: ignore[import-not-found] from pyalpm import DB, Handle, Package, SIG_DATABASE_OPTIONAL, SIG_PACKAGE_OPTIONAL # type: ignore[import-not-found]
@@ -188,7 +188,7 @@ class Pacman(LazyLogging):
Returns: Returns:
dict[str, set[str]]: map of package name to its list of files dict[str, set[str]]: map of package name to its list of files
""" """
def extract(tar: tarfile.TarFile, versions: dict[str, str]) -> Generator[tuple[str, set[str]], None, None]: def extract(tar: tarfile.TarFile, versions: dict[str, str]) -> Iterator[tuple[str, set[str]]]:
for package_name, version in versions.items(): for package_name, version in versions.items():
path = Path(f"{package_name}-{version}") / "files" path = Path(f"{package_name}-{version}") / "files"
try: try:
@@ -223,7 +223,7 @@ class Pacman(LazyLogging):
return result return result
def package(self, package_name: str) -> Generator[Package, None, None]: def package(self, package_name: str) -> Iterator[Package]:
""" """
retrieve list of the packages from the repository by name retrieve list of the packages from the repository by name
@@ -256,7 +256,7 @@ class Pacman(LazyLogging):
return result return result
def provided_by(self, package_name: str) -> Generator[Package, None, None]: def provided_by(self, package_name: str) -> Iterator[Package]:
""" """
search through databases and emit packages which provides the ``package_name`` search through databases and emit packages which provides the ``package_name``

View File

@@ -21,7 +21,7 @@ import itertools
import re import re
import shlex import shlex
from collections.abc import Generator from collections.abc import Iterator
from enum import StrEnum from enum import StrEnum
from typing import IO from typing import IO
@@ -209,7 +209,7 @@ class PkgbuildParser(shlex.shlex):
Raises: Raises:
PkgbuildParserError: if array is not closed PkgbuildParserError: if array is not closed
""" """
def extract() -> Generator[str, None, None]: def extract() -> Iterator[str]:
while token := self.get_token(): while token := self.get_token():
match token: match token:
case _ if self._is_escaped(): case _ if self._is_escaped():
@@ -276,7 +276,7 @@ class PkgbuildParser(shlex.shlex):
return content return content
def _parse_token(self, token: str) -> Generator[PkgbuildPatch, None, None]: def _parse_token(self, token: str) -> Iterator[PkgbuildPatch]:
""" """
parse single token to the PKGBUILD field parse single token to the PKGBUILD field
@@ -360,7 +360,7 @@ class PkgbuildParser(shlex.shlex):
raise PkgbuildParserError("reached starting position, no valid symbols found") raise PkgbuildParserError("reached starting position, no valid symbols found")
def parse(self) -> Generator[PkgbuildPatch, None, None]: def parse(self) -> Iterator[PkgbuildPatch]:
""" """
parse source stream and yield parsed entries parse source stream and yield parsed entries

View File

@@ -94,15 +94,6 @@ class Remote(SyncHttpClient):
for package in portion for package in portion
if package.name in packages or not packages if package.name in packages or not packages
} }
# simple check for duplicates. This method will remove all packages under base if there is
# a package named exactly as its base
packages = {
package.name: package
for package in packages.values()
if package.package_base not in packages or package.package_base == package.name
}
return list(packages.values()) return list(packages.values())
@classmethod @classmethod

View File

@@ -19,7 +19,7 @@
# #
import shutil import shutil
from collections.abc import Generator from collections.abc import Iterator
from pathlib import Path from pathlib import Path
from typing import ClassVar from typing import ClassVar
@@ -347,7 +347,7 @@ class Sources(LazyLogging):
""" """
gitconfig = gitconfig or {} gitconfig = gitconfig or {}
def configuration_flags() -> Generator[str, None, None]: def configuration_flags() -> Iterator[str]:
for option, value in (self.GITCONFIG | gitconfig).items(): for option, value in (self.GITCONFIG | gitconfig).items():
yield "-c" yield "-c"
yield f"{option}=\"{value}\"" yield f"{option}=\"{value}\""

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from collections.abc import Generator from collections.abc import Iterator
from pathlib import Path from pathlib import Path
from ahriman.core.build_tools.sources import Sources from ahriman.core.build_tools.sources import Sources
@@ -77,7 +77,7 @@ class Task(LazyLogging):
Returns: Returns:
list[Path]: list of file paths which looks like freshly generated archives list[Path]: list of file paths which looks like freshly generated archives
""" """
def files() -> Generator[Path, None, None]: def files() -> Iterator[Path]:
for filepath in sources_dir.iterdir(): for filepath in sources_dir.iterdir():
if filepath in source_files: if filepath in source_files:
continue # skip files which were already there continue # skip files which were already there

View File

@@ -17,7 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
# pylint: disable=too-many-public-methods
import configparser import configparser
import shlex import shlex
import sys import sys
@@ -42,7 +41,6 @@ class Configuration(configparser.RawConfigParser):
SYSTEM_CONFIGURATION_PATH(Path): (class attribute) default system configuration path distributed by package SYSTEM_CONFIGURATION_PATH(Path): (class attribute) default system configuration path distributed by package
includes(list[Path]): list of includes which were read includes(list[Path]): list of includes which were read
path(Path | None): path to root configuration file path(Path | None): path to root configuration file
repository_id(RepositoryId | None): repository unique identifier
Examples: Examples:
Configuration class provides additional method in order to handle application configuration. Since this class is Configuration class provides additional method in order to handle application configuration. Since this class is
@@ -86,14 +84,13 @@ class Configuration(configparser.RawConfigParser):
empty_lines_in_values=not allow_multi_key, empty_lines_in_values=not allow_multi_key,
interpolation=ShellInterpolator(), interpolation=ShellInterpolator(),
converters={ converters={
"intlist": lambda value: list(map(int, shlex.split(value))),
"list": shlex.split, "list": shlex.split,
"path": self._convert_path, "path": self._convert_path,
"pathlist": lambda value: list(map(self._convert_path, shlex.split(value))), "pathlist": lambda value: [self._convert_path(element) for element in shlex.split(value)],
}, },
) )
self.repository_id: RepositoryId | None = None self._repository_id: RepositoryId | None = None
self.path: Path | None = None self.path: Path | None = None
self.includes: list[Path] = [] self.includes: list[Path] = []
@@ -128,6 +125,32 @@ class Configuration(configparser.RawConfigParser):
""" """
return self.getpath("settings", "logging") return self.getpath("settings", "logging")
@property
def repository_id(self) -> RepositoryId | None:
"""
repository identifier
Returns:
RepositoryId: repository unique identifier
"""
return self._repository_id
@repository_id.setter
def repository_id(self, repository_id: RepositoryId | None) -> None:
"""
setter for repository identifier
Args:
repository_id(RepositoryId | None): repository unique identifier
"""
self._repository_id = repository_id
if repository_id is None or repository_id.is_empty:
self.remove_option("repository", "name")
self.remove_option("repository", "architecture")
else:
self.set_option("repository", "name", repository_id.name)
self.set_option("repository", "architecture", repository_id.architecture)
@property @property
def repository_name(self) -> str: def repository_name(self) -> str:
""" """
@@ -238,8 +261,6 @@ class Configuration(configparser.RawConfigParser):
# pylint and mypy are too stupid to find these methods # pylint and mypy are too stupid to find these methods
# pylint: disable=missing-function-docstring,unused-argument # pylint: disable=missing-function-docstring,unused-argument
def getintlist(self, *args: Any, **kwargs: Any) -> list[int]: ... # type: ignore[empty-body]
def getlist(self, *args: Any, **kwargs: Any) -> list[str]: ... # type: ignore[empty-body] def getlist(self, *args: Any, **kwargs: Any) -> list[str]: ... # type: ignore[empty-body]
def getpath(self, *args: Any, **kwargs: Any) -> Path: ... # type: ignore[empty-body] def getpath(self, *args: Any, **kwargs: Any) -> Path: ... # type: ignore[empty-body]

View File

@@ -57,7 +57,7 @@ class ConfigurationMultiDict(dict[str, Any]):
OptionError: if the key already exists in the dictionary, but not a single value list or a string OptionError: if the key already exists in the dictionary, but not a single value list or a string
""" """
match self.get(key): match self.get(key):
case [current_value] | str(current_value): case [current_value] | (str() as current_value):
value = f"{current_value} {value}" value = f"{current_value} {value}"
case None: case None:
pass pass

View File

@@ -254,6 +254,10 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
"repository": { "repository": {
"type": "dict", "type": "dict",
"schema": { "schema": {
"architecture": {
"type": "string",
"empty": False,
},
"name": { "name": {
"type": "string", "type": "string",
"empty": False, "empty": False,
@@ -324,15 +328,6 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
"empty": False, "empty": False,
"is_url": ["http", "https"], "is_url": ["http", "https"],
}, },
"autorefresh_intervals": {
"type": "list",
"coerce": "list",
"schema": {
"type": "integer",
"coerce": "integer",
"min": 0,
},
},
"enable_archive_upload": { "enable_archive_upload": {
"type": "boolean", "type": "boolean",
"coerce": "boolean", "coerce": "boolean",

View File

@@ -21,7 +21,7 @@ import configparser
import os import os
import sys import sys
from collections.abc import Generator, Mapping, MutableMapping from collections.abc import Iterator, Mapping, MutableMapping
from string import Template from string import Template
from typing import Any, ClassVar from typing import Any, ClassVar
@@ -37,7 +37,7 @@ class ShellInterpolator(configparser.Interpolation):
@staticmethod @staticmethod
def _extract_variables(parser: MutableMapping[str, Mapping[str, str]], value: str, def _extract_variables(parser: MutableMapping[str, Mapping[str, str]], value: str,
defaults: Mapping[str, str]) -> Generator[tuple[str, str], None, None]: defaults: Mapping[str, str]) -> Iterator[tuple[str, str]]:
""" """
extract keys and values (if available) from the configuration. In case if a key is not available, it will be extract keys and values (if available) from the configuration. In case if a key is not available, it will be
silently skipped from the result silently skipped from the result
@@ -50,7 +50,7 @@ class ShellInterpolator(configparser.Interpolation):
Yields: Yields:
tuple[str, str]: variable name used for substitution and its value tuple[str, str]: variable name used for substitution and its value
""" """
def identifiers() -> Generator[tuple[str | None, str], None, None]: def identifiers() -> Iterator[tuple[str | None, str]]:
# extract all found identifiers and parse them # extract all found identifiers and parse them
for identifier in ShellTemplate(value).get_identifiers(): for identifier in ShellTemplate(value).get_identifiers():
match identifier.rsplit(":", maxsplit=1): match identifier.rsplit(":", maxsplit=1):

View File

@@ -20,7 +20,7 @@
import fnmatch import fnmatch
import re import re
from collections.abc import Generator, Mapping from collections.abc import Iterator, Mapping
from string import Template from string import Template
@@ -132,7 +132,7 @@ class ShellTemplate(Template):
(self._REPLACE, self._replace, "/"), (self._REPLACE, self._replace, "/"),
) )
def generator(variables: dict[str, str]) -> Generator[tuple[str, str], None, None]: def generator(variables: dict[str, str]) -> Iterator[tuple[str, str]]:
for identifier in self.get_identifiers(): for identifier in self.get_identifiers():
for regex, function, greediness in substitutions: for regex, function, greediness in substitutions:
if m := regex.match(identifier): if m := regex.match(identifier):

View File

@@ -29,15 +29,13 @@ class LogsOperations(Operations):
logs operations logs operations
""" """
def logs_get(self, package_base: str, version: str | None = None, process_id: str | None = None, def logs_get(self, package_base: str, limit: int = -1, offset: int = 0,
limit: int = -1, offset: int = 0, repository_id: RepositoryId | None = None) -> list[LogRecord]: repository_id: RepositoryId | None = None) -> list[LogRecord]:
""" """
extract logs for specified package base extract logs for specified package base
Args: Args:
package_base(str): package base to extract logs package_base(str): package base to extract logs
version(str | None, optional): package version to filter (Default value = None)
process_id(str | None, optional): process identifier to filter (Default value = None)
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1) limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
offset(int, optional): records offset (Default value = 0) offset(int, optional): records offset (Default value = 0)
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None) repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
@@ -54,17 +52,12 @@ class LogsOperations(Operations):
""" """
select created, message, version, process_id from ( select created, message, version, process_id from (
select * from logs select * from logs
where package_base = :package_base where package_base = :package_base and repository = :repository
and repository = :repository
and (:version is null or version = :version)
and (:process_id is null or process_id = :process_id)
order by created desc limit :limit offset :offset order by created desc limit :limit offset :offset
) order by created asc ) order by created asc
""", """,
{ {
"package_base": package_base, "package_base": package_base,
"version": version,
"process_id": process_id,
"repository": repository_id.id, "repository": repository_id.id,
"limit": limit, "limit": limit,
"offset": offset, "offset": offset,

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from collections.abc import Generator, Iterable from collections.abc import Iterable, Iterator
from sqlite3 import Connection from sqlite3 import Connection
from ahriman.core.database.operations.operations import Operations from ahriman.core.database.operations.operations import Operations
@@ -263,7 +263,7 @@ class PackageOperations(Operations):
""" """
repository_id = repository_id or self._repository_id repository_id = repository_id or self._repository_id
def run(connection: Connection) -> Generator[tuple[Package, BuildStatus], None, None]: def run(connection: Connection) -> Iterator[tuple[Package, BuildStatus]]:
packages = self._packages_get_select_package_bases(connection, repository_id) packages = self._packages_get_select_package_bases(connection, repository_id)
statuses = self._packages_get_select_statuses(connection, repository_id) statuses = self._packages_get_select_statuses(connection, repository_id)
per_package_base = self._packages_get_select_packages(connection, packages, repository_id) per_package_base = self._packages_get_select_packages(connection, packages, repository_id)

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from collections.abc import Generator from collections.abc import Iterator
from typing import Any from typing import Any
from ahriman.core.formatters.string_printer import StringPrinter from ahriman.core.formatters.string_printer import StringPrinter
@@ -44,8 +44,7 @@ class ValidationPrinter(StringPrinter):
self.errors = errors self.errors = errors
@staticmethod @staticmethod
def get_error_messages(node: str, errors: list[str | dict[str, Any]], def get_error_messages(node: str, errors: list[str | dict[str, Any]], current_level: int = 1) -> Iterator[Property]:
current_level: int = 1) -> Generator[Property, None, None]:
""" """
extract default error message from cerberus class extract default error message from cerberus class

View File

@@ -19,7 +19,7 @@
# #
import shutil import shutil
from collections.abc import Generator from collections.abc import Iterator
from pathlib import Path from pathlib import Path
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
@@ -96,7 +96,7 @@ class RemotePush(LazyLogging):
# ...and finally return path to the copied directory # ...and finally return path to the copied directory
return package.base return package.base
def packages_update(self, result: Result, target_dir: Path) -> Generator[str, None, None]: def packages_update(self, result: Result, target_dir: Path) -> Iterator[str]:
""" """
update all packages from the build result update all packages from the build result

View File

@@ -20,7 +20,7 @@
import contextlib import contextlib
import logging import logging
from collections.abc import Generator from collections.abc import Iterator
from functools import cached_property from functools import cached_property
from typing import Any from typing import Any
@@ -80,7 +80,7 @@ class LazyLogging:
logging.setLogRecordFactory(package_record_factory) logging.setLogRecordFactory(package_record_factory)
@contextlib.contextmanager @contextlib.contextmanager
def in_package_context(self, package_base: str, version: str | None) -> Generator[None, None, None]: def in_package_context(self, package_base: str, version: str | None) -> Iterator[None]:
""" """
execute function while setting package context execute function while setting package context

View File

@@ -19,7 +19,7 @@
# #
import inspect import inspect
from collections.abc import Generator from collections.abc import Iterator
from importlib import import_module from importlib import import_module
from pathlib import Path from pathlib import Path
from pkgutil import ModuleInfo, walk_packages from pkgutil import ModuleInfo, walk_packages
@@ -33,7 +33,7 @@ __all__ = ["implementations"]
T = TypeVar("T") T = TypeVar("T")
def _modules(module_root: Path, prefix: str) -> Generator[ModuleInfo, None, None]: def _modules(module_root: Path, prefix: str) -> Iterator[ModuleInfo]:
""" """
extract available modules from package extract available modules from package
@@ -52,7 +52,7 @@ def _modules(module_root: Path, prefix: str) -> Generator[ModuleInfo, None, None
yield module_info yield module_info
def implementations(root_module: ModuleType, base_class: type[T]) -> Generator[type[T], None, None]: def implementations(root_module: ModuleType, base_class: type[T]) -> Iterator[type[T]]:
""" """
extract implementations of the ``base_class`` from the module extract implementations of the ``base_class`` from the module

View File

@@ -19,7 +19,7 @@
# #
import contextlib import contextlib
from typing import Generator from collections.abc import Iterator
from ahriman.core.status import Client from ahriman.core.status import Client
from ahriman.models.event import Event, EventType from ahriman.models.event import Event, EventType
@@ -55,7 +55,7 @@ class EventLogger:
@contextlib.contextmanager @contextlib.contextmanager
def in_event(self, package_base: str, event: EventType, message: str | None = None, def in_event(self, package_base: str, event: EventType, message: str | None = None,
failure: EventType | None = None) -> Generator[None, None, None]: failure: EventType | None = None) -> Iterator[None]:
""" """
perform action in package context and log event with time elapsed perform action in package context and log event with time elapsed

View File

@@ -81,11 +81,6 @@ class Client:
return make_local_client() return make_local_client()
def configuration_reload(self) -> None:
"""
reload configuration
"""
def event_add(self, event: Event) -> None: def event_add(self, event: Event) -> None:
""" """
create new event create new event
@@ -208,15 +203,12 @@ class Client:
""" """
# this method does not raise NotImplementedError because it is actively used as dummy client for http log # this method does not raise NotImplementedError because it is actively used as dummy client for http log
def package_logs_get(self, package_base: str, version: str | None = None, process_id: str | None = None, def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[LogRecord]:
limit: int = -1, offset: int = 0) -> list[LogRecord]:
""" """
get package logs get package logs
Args: Args:
package_base(str): package base package_base(str): package base
version(str | None, optional): package version to search (Default value = None)
process_id(str | None, optional): process identifier to search (Default value = None)
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1) limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
offset(int, optional): records offset (Default value = 0) offset(int, optional): records offset (Default value = 0)

View File

@@ -152,22 +152,19 @@ class LocalClient(Client):
""" """
self.database.logs_insert(log_record, self.repository_id) self.database.logs_insert(log_record, self.repository_id)
def package_logs_get(self, package_base: str, version: str | None = None, process_id: str | None = None, def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[LogRecord]:
limit: int = -1, offset: int = 0) -> list[LogRecord]:
""" """
get package logs get package logs
Args: Args:
package_base(str): package base package_base(str): package base
version(str | None, optional): package version to search (Default value = None)
process_id(str | None, optional): process identifier to search (Default value = None)
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1) limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
offset(int, optional): records offset (Default value = 0) offset(int, optional): records offset (Default value = 0)
Returns: Returns:
list[LogRecord]: package logs list[LogRecord]: package logs
""" """
return self.database.logs_get(package_base, version, process_id, limit, offset, self.repository_id) return self.database.logs_get(package_base, limit, offset, self.repository_id)
def package_logs_remove(self, package_base: str, version: str | None) -> None: def package_logs_remove(self, package_base: str, version: str | None) -> None:
""" """

View File

@@ -109,7 +109,7 @@ class Watcher(LazyLogging):
package_logs_add: Callable[[LogRecord], None] package_logs_add: Callable[[LogRecord], None]
package_logs_get: Callable[[str, str | None, str | None, int, int], list[LogRecord]] package_logs_get: Callable[[str, int, int], list[LogRecord]]
package_logs_remove: Callable[[str, str | None], None] package_logs_remove: Callable[[str, str | None], None]

View File

@@ -17,7 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
# pylint: disable=too-many-public-methods
import contextlib import contextlib
from urllib.parse import quote_plus as url_encode from urllib.parse import quote_plus as url_encode
@@ -166,13 +165,6 @@ class WebClient(Client, SyncAhrimanClient):
""" """
return f"{self.address}/api/v1/status" return f"{self.address}/api/v1/status"
def configuration_reload(self) -> None:
"""
reload configuration
"""
with contextlib.suppress(Exception):
self.make_request("POST", f"{self.address}/api/v1/service/config")
def event_add(self, event: Event) -> None: def event_add(self, event: Event) -> None:
""" """
create new event create new event
@@ -334,15 +326,12 @@ class WebClient(Client, SyncAhrimanClient):
self.make_request("POST", self._logs_url(log_record.log_record_id.package_base), self.make_request("POST", self._logs_url(log_record.log_record_id.package_base),
params=self.repository_id.query(), json=payload, suppress_errors=True) params=self.repository_id.query(), json=payload, suppress_errors=True)
def package_logs_get(self, package_base: str, version: str | None = None, process_id: str | None = None, def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[LogRecord]:
limit: int = -1, offset: int = 0) -> list[LogRecord]:
""" """
get package logs get package logs
Args: Args:
package_base(str): package base package_base(str): package base
version(str | None, optional): package version to search (Default value = None)
process_id(str | None, optional): process identifier to search (Default value = None)
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1) limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
offset(int, optional): records offset (Default value = 0) offset(int, optional): records offset (Default value = 0)
@@ -350,10 +339,6 @@ class WebClient(Client, SyncAhrimanClient):
list[LogRecord]: package logs list[LogRecord]: package logs
""" """
query = self.repository_id.query() + [("limit", str(limit)), ("offset", str(offset))] query = self.repository_id.query() + [("limit", str(limit)), ("offset", str(offset))]
if version is not None:
query.append(("version", version))
if process_id is not None:
query.append(("process_id", process_id))
with contextlib.suppress(Exception): with contextlib.suppress(Exception):
response = self.make_request("GET", self._logs_url(package_base), params=query) response = self.make_request("GET", self._logs_url(package_base), params=query)

View File

@@ -20,7 +20,7 @@
import hashlib import hashlib
import itertools import itertools
from collections.abc import Callable, Generator from collections.abc import Callable, Iterator
from pathlib import Path from pathlib import Path
from typing import ClassVar from typing import ClassVar
@@ -187,7 +187,7 @@ class PkgbuildGenerator:
Returns: Returns:
list[PkgbuildPatch]: list of patches to be applied to the PKGBUILD list[PkgbuildPatch]: list of patches to be applied to the PKGBUILD
""" """
def sources_generator() -> Generator[tuple[str, str], None, None]: def sources_generator() -> Iterator[tuple[str, str]]:
for source, generator in sorted(self.sources().items()): for source, generator in sorted(self.sources().items()):
source_path = source_dir / source source_path = source_dir / source
generator(source_path) generator(source_path)

View File

@@ -80,8 +80,7 @@ class Trigger(LazyLogging):
return self.repository_id.architecture return self.repository_id.architecture
@classmethod @classmethod
def configuration_schema(cls, repository_id: RepositoryId, def configuration_schema(cls, configuration: Configuration | None) -> ConfigurationSchema:
configuration: Configuration | None) -> ConfigurationSchema:
""" """
configuration schema based on supplied service configuration configuration schema based on supplied service configuration
@@ -89,7 +88,6 @@ class Trigger(LazyLogging):
Schema must be in cerberus format, for details and examples you can check built-in triggers. Schema must be in cerberus format, for details and examples you can check built-in triggers.
Args: Args:
repository_id(str): repository unique identifier
configuration(Configuration | None): configuration instance. If set to None, the default schema configuration(Configuration | None): configuration instance. If set to None, the default schema
should be returned should be returned
@@ -101,13 +99,15 @@ class Trigger(LazyLogging):
result: ConfigurationSchema = {} result: ConfigurationSchema = {}
for target in cls.configuration_sections(configuration): for target in cls.configuration_sections(configuration):
if not configuration.has_section(target): for section in configuration.sections():
continue if not (section == target or section.startswith(f"{target}:")):
section, schema_name = configuration.gettype( # either repository specific or exact name
target, repository_id, fallback=cls.CONFIGURATION_SCHEMA_FALLBACK) continue
if schema_name not in cls.CONFIGURATION_SCHEMA: schema_name = configuration.get(section, "type", fallback=section)
continue
result[section] = cls.CONFIGURATION_SCHEMA[schema_name] if schema_name not in cls.CONFIGURATION_SCHEMA:
continue
result[section] = cls.CONFIGURATION_SCHEMA[schema_name]
return result return result

View File

@@ -20,7 +20,7 @@
import contextlib import contextlib
import os import os
from collections.abc import Generator from collections.abc import Iterator
from importlib import import_module, machinery from importlib import import_module, machinery
from pathlib import Path from pathlib import Path
from types import ModuleType from types import ModuleType
@@ -119,7 +119,7 @@ class TriggerLoader(LazyLogging):
return configuration.getlist("build", "triggers", fallback=[]) return configuration.getlist("build", "triggers", fallback=[])
@contextlib.contextmanager @contextlib.contextmanager
def __execute_trigger(self, trigger: Trigger) -> Generator[None, None, None]: def __execute_trigger(self, trigger: Trigger) -> Iterator[None]:
""" """
decorator for calling triggers decorator for calling triggers

View File

@@ -27,7 +27,7 @@ import re
import selectors import selectors
import subprocess import subprocess
from collections.abc import Callable, Generator, Iterable, Mapping from collections.abc import Callable, Iterable, Iterator, Mapping
from dataclasses import asdict from dataclasses import asdict
from enum import Enum from enum import Enum
from pathlib import Path from pathlib import Path
@@ -35,7 +35,6 @@ from pwd import getpwuid
from typing import Any, IO, TypeVar from typing import Any, IO, TypeVar
from ahriman.core.exceptions import CalledProcessError, OptionError, UnsafeRunError from ahriman.core.exceptions import CalledProcessError, OptionError, UnsafeRunError
from ahriman.models.repository_paths import RepositoryPaths
__all__ = [ __all__ = [
@@ -47,13 +46,14 @@ __all__ = [
"filter_json", "filter_json",
"full_version", "full_version",
"minmax", "minmax",
"owner",
"package_like", "package_like",
"parse_version", "parse_version",
"partition", "partition",
"pretty_datetime", "pretty_datetime",
"pretty_interval",
"pretty_size", "pretty_size",
"safe_filename", "safe_filename",
"safe_iterdir",
"srcinfo_property", "srcinfo_property",
"srcinfo_property_list", "srcinfo_property_list",
"trim_package", "trim_package",
@@ -113,7 +113,7 @@ def check_output(*args: str, exception: Exception | Callable[[int, list[str], st
return channel if channel is not None else io.StringIO() return channel if channel is not None else io.StringIO()
# wrapper around selectors polling # wrapper around selectors polling
def poll(sel: selectors.BaseSelector) -> Generator[tuple[str, str], None, None]: def poll(sel: selectors.BaseSelector) -> Iterator[tuple[str, str]]:
for key, _ in sel.select(): # we don't need to check mask here because we have only subscribed on reading for key, _ in sel.select(): # we don't need to check mask here because we have only subscribed on reading
line = key.fileobj.readline() # type: ignore[union-attr] line = key.fileobj.readline() # type: ignore[union-attr]
if not line: # in case of empty line we remove selector as there is no data here anymore if not line: # in case of empty line we remove selector as there is no data here anymore
@@ -170,12 +170,13 @@ def check_output(*args: str, exception: Exception | Callable[[int, list[str], st
return stdout return stdout
def check_user(paths: RepositoryPaths, *, unsafe: bool) -> None: def check_user(root: Path, *, unsafe: bool) -> None:
""" """
check if current user is the owner of the root check if current user is the owner of the root
Args: Args:
paths(RepositoryPaths): repository paths object root(Path): path to root directory (e.g. repository root
:attr:`ahriman.models.repository_paths.RepositoryPaths.root`)
unsafe(bool): if set no user check will be performed before path creation unsafe(bool): if set no user check will be performed before path creation
Raises: Raises:
@@ -184,14 +185,16 @@ def check_user(paths: RepositoryPaths, *, unsafe: bool) -> None:
Examples: Examples:
Simply run function with arguments:: Simply run function with arguments::
>>> check_user(paths, unsafe=False) >>> check_user(root, unsafe=False)
""" """
if not paths.root.exists(): if not root.exists():
return # no directory found, skip check return # no directory found, skip check
if unsafe: if unsafe:
return # unsafe flag is enabled, no check performed return # unsafe flag is enabled, no check performed
current_uid = os.getuid() current_uid = os.getuid()
root_uid, _ = paths.root_owner root_uid, _ = owner(root)
if current_uid != root_uid: if current_uid != root_uid:
raise UnsafeRunError(current_uid, root_uid) raise UnsafeRunError(current_uid, root_uid)
@@ -289,6 +292,20 @@ def minmax(source: Iterable[T], *, key: Callable[[T], Any] | None = None) -> tup
return min(first_iter, key=key), max(second_iter, key=key) # type: ignore return min(first_iter, key=key), max(second_iter, key=key) # type: ignore
def owner(path: Path) -> tuple[int, int]:
"""
retrieve owner information by path
Args:
path(Path): path for which extract ids
Returns:
tuple[int, int]: owner user and group ids of the directory
"""
stat = path.stat()
return stat.st_uid, stat.st_gid
def package_like(filename: Path) -> bool: def package_like(filename: Path) -> bool:
""" """
check if file looks like package check if file looks like package
@@ -354,28 +371,6 @@ def pretty_datetime(timestamp: datetime.datetime | float | int | None) -> str:
return timestamp.strftime("%Y-%m-%d %H:%M:%S") return timestamp.strftime("%Y-%m-%d %H:%M:%S")
def pretty_interval(interval: int) -> str:
"""
convert time interval to string
Args:
interval(int): time interval in seconds
Returns:
str: pretty printable interval as string
"""
minutes, seconds = divmod(interval, 60)
hours, minutes = divmod(minutes, 60)
return " ".join([
f"{value} {description}{"s" if value > 1 else ""}"
for value, description in [
(hours, "hour"),
(minutes, "minute"),
(seconds, "second"),
] if value > 0
])
def pretty_size(size: float | None, level: int = 0) -> str: def pretty_size(size: float | None, level: int = 0) -> str:
""" """
convert size to string convert size to string
@@ -431,6 +426,22 @@ def safe_filename(source: str) -> str:
return re.sub(r"[^A-Za-z\d\-._~:\[\]@]", "-", source) return re.sub(r"[^A-Za-z\d\-._~:\[\]@]", "-", source)
def safe_iterdir(path: Path) -> Iterator[Path]:
"""
wrapper around :func:`pathlib.Path.iterdir`, which suppresses :exc:`PermissionError`
Args:
path(Path): path to iterate
Yields:
Path: content of the directory
"""
try:
yield from path.iterdir()
except PermissionError:
pass
def srcinfo_property(key: str, srcinfo: Mapping[str, Any], package_srcinfo: Mapping[str, Any], *, def srcinfo_property(key: str, srcinfo: Mapping[str, Any], package_srcinfo: Mapping[str, Any], *,
default: Any = None) -> Any: default: Any = None) -> Any:
""" """
@@ -499,7 +510,7 @@ def utcnow() -> datetime.datetime:
return datetime.datetime.now(datetime.UTC) return datetime.datetime.now(datetime.UTC)
def walk(directory_path: Path) -> Generator[Path, None, None]: def walk(directory_path: Path) -> Iterator[Path]:
""" """
list all file paths in given directory list all file paths in given directory

View File

@@ -22,7 +22,7 @@ from __future__ import annotations
import copy import copy
from collections.abc import Callable, Generator, Iterable from collections.abc import Callable, Iterable, Iterator
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from pyalpm import vercmp # type: ignore[import-not-found] from pyalpm import vercmp # type: ignore[import-not-found]
@@ -205,7 +205,7 @@ class Package(LazyLogging):
package = pacman.handle.load_pkg(str(path)) package = pacman.handle.load_pkg(str(path))
description = PackageDescription.from_package(package, path) description = PackageDescription.from_package(package, path)
return cls( return cls(
base=package.base, base=package.base or package.name,
version=package.version, version=package.version,
remote=RemoteSource(source=PackageSource.Archive), remote=RemoteSource(source=PackageSource.Archive),
packages={package.name: description}, packages={package.name: description},
@@ -346,7 +346,7 @@ class Package(LazyLogging):
) )
@staticmethod @staticmethod
def local_files(path: Path) -> Generator[Path, None, None]: def local_files(path: Path) -> Iterator[Path]:
""" """
extract list of local files extract list of local files
@@ -407,7 +407,7 @@ class Package(LazyLogging):
Returns: Returns:
list[str]: combined list of unique entries in properties list list[str]: combined list of unique entries in properties list
""" """
def generator() -> Generator[str, None, None]: def generator() -> Iterator[str]:
for package in self.packages.values(): for package in self.packages.values():
yield from extractor(package) yield from extractor(package)
@@ -570,7 +570,7 @@ class Package(LazyLogging):
""" """
return dataclass_view(self) return dataclass_view(self)
def with_packages(self, packages: list[Path], pacman: Pacman) -> None: def with_packages(self, packages: Iterable[Path], pacman: Pacman) -> None:
""" """
replace packages descriptions with ones from archives replace packages descriptions with ones from archives

View File

@@ -22,7 +22,7 @@ import shlex
from dataclasses import dataclass, fields from dataclasses import dataclass, fields
from pathlib import Path from pathlib import Path
from typing import Any, Generator, Self from typing import Any, Iterator, Self
from ahriman.core.configuration.shell_template import ShellTemplate from ahriman.core.configuration.shell_template import ShellTemplate
from ahriman.core.utils import dataclass_view, filter_json from ahriman.core.utils import dataclass_view, filter_json
@@ -166,7 +166,7 @@ class PkgbuildPatch:
ValueError: if no closing quotation ValueError: if no closing quotation
""" """
def generator() -> Generator[str, None, None]: def generator() -> Iterator[str]:
token = None token = None
for char in source: for char in source:
if token is not None: if token is not None:

View File

@@ -21,7 +21,7 @@ import contextlib
import os import os
import shutil import shutil
from collections.abc import Generator from collections.abc import Iterator
from dataclasses import dataclass, field from dataclasses import dataclass, field
from functools import cached_property from functools import cached_property
from pathlib import Path from pathlib import Path
@@ -29,6 +29,7 @@ from pwd import getpwuid
from ahriman.core.exceptions import PathError from ahriman.core.exceptions import PathError
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
from ahriman.core.utils import owner, safe_iterdir
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId
@@ -93,7 +94,7 @@ class RepositoryPaths(LazyLogging):
Returns: Returns:
Path: path to directory in which build process is run Path: path to directory in which build process is run
""" """
uid, _ = self.owner(self.root) uid, _ = owner(self.root)
return self.chroot / f"{self.repository_id.name}-{self.repository_id.architecture}" / getpwuid(uid).pw_name return self.chroot / f"{self.repository_id.name}-{self.repository_id.architecture}" / getpwuid(uid).pw_name
@property @property
@@ -155,7 +156,7 @@ class RepositoryPaths(LazyLogging):
Returns: Returns:
tuple[int, int]: owner user and group of the root directory tuple[int, int]: owner user and group of the root directory
""" """
return self.owner(self.root) return owner(self.root)
# pylint: disable=protected-access # pylint: disable=protected-access
@classmethod @classmethod
@@ -170,7 +171,7 @@ class RepositoryPaths(LazyLogging):
Returns: Returns:
set[str]: list of repository architectures for which there is created tree set[str]: list of repository architectures for which there is created tree
""" """
def walk(repository_dir: Path) -> Generator[str, None, None]: def walk(repository_dir: Path) -> Iterator[str]:
for architecture in filter(lambda path: path.is_dir(), repository_dir.iterdir()): for architecture in filter(lambda path: path.is_dir(), repository_dir.iterdir()):
yield architecture.name yield architecture.name
@@ -197,7 +198,7 @@ class RepositoryPaths(LazyLogging):
is loaded in legacy mode is loaded in legacy mode
""" """
# simply walk through the root. In case if there are subdirectories, emit the name # simply walk through the root. In case if there are subdirectories, emit the name
def walk(paths: RepositoryPaths) -> Generator[str, None, None]: def walk(paths: RepositoryPaths) -> Iterator[str]:
for repository in filter(lambda path: path.is_dir(), paths._repository_root.iterdir()): for repository in filter(lambda path: path.is_dir(), paths._repository_root.iterdir()):
if any(path.is_dir() for path in repository.iterdir()): if any(path.is_dir() for path in repository.iterdir()):
yield repository.name yield repository.name
@@ -208,20 +209,6 @@ class RepositoryPaths(LazyLogging):
return set(walk(instance)) return set(walk(instance))
@staticmethod
def owner(path: Path) -> tuple[int, int]:
"""
retrieve owner information by path
Args:
path(Path): path for which extract ids
Returns:
tuple[int, int]: owner user and group ids of the directory
"""
stat = path.stat()
return stat.st_uid, stat.st_gid
def _chown(self, path: Path) -> None: def _chown(self, path: Path) -> None:
""" """
set owner of path recursively (from root) to root owner set owner of path recursively (from root) to root owner
@@ -237,7 +224,7 @@ class RepositoryPaths(LazyLogging):
PathError: if path does not belong to root PathError: if path does not belong to root
""" """
def set_owner(current: Path) -> None: def set_owner(current: Path) -> None:
uid, gid = self.owner(current) uid, gid = owner(current)
if uid == root_uid and gid == root_gid: if uid == root_uid and gid == root_gid:
return return
os.chown(current, root_uid, root_gid, follow_symlinks=False) os.chown(current, root_uid, root_gid, follow_symlinks=False)
@@ -262,7 +249,7 @@ class RepositoryPaths(LazyLogging):
return self.cache / package_base return self.cache / package_base
@contextlib.contextmanager @contextlib.contextmanager
def preserve_owner(self, path: Path | None = None) -> Generator[None, None, None]: def preserve_owner(self, path: Path | None = None) -> Iterator[None]:
""" """
perform any action preserving owner for any newly created file or directory perform any action preserving owner for any newly created file or directory
@@ -281,12 +268,12 @@ class RepositoryPaths(LazyLogging):
""" """
path = path or self.root path = path or self.root
def walk(root: Path) -> Generator[Path, None, None]: def walk(root: Path) -> Iterator[Path]:
# basically walk, but skipping some content # basically walk, but skipping some content
for child in root.iterdir(): for child in safe_iterdir(root):
yield child yield child
if child in (self.chroot.parent,): if child in (self.chroot.parent,):
yield from child.iterdir() # we only yield top-level in chroot directory yield from safe_iterdir(child) # we only yield top-level in chroot directory
elif child.is_dir(): elif child.is_dir():
yield from walk(child) yield from walk(child)

View File

@@ -20,7 +20,7 @@
import re import re
from aiohttp.web import Application, View from aiohttp.web import Application, View
from collections.abc import Generator from collections.abc import Iterator
import ahriman.web.views import ahriman.web.views
@@ -32,7 +32,7 @@ from ahriman.web.views.base import BaseView
__all__ = ["setup_routes"] __all__ = ["setup_routes"]
def _dynamic_routes(configuration: Configuration) -> Generator[tuple[str, type[View]], None, None]: def _dynamic_routes(configuration: Configuration) -> Iterator[tuple[str, type[View]]]:
""" """
extract dynamic routes based on views extract dynamic routes based on views

View File

@@ -22,7 +22,6 @@ from ahriman.web.schemas.aur_package_schema import AURPackageSchema
from ahriman.web.schemas.auth_schema import AuthSchema from ahriman.web.schemas.auth_schema import AuthSchema
from ahriman.web.schemas.build_options_schema import BuildOptionsSchema from ahriman.web.schemas.build_options_schema import BuildOptionsSchema
from ahriman.web.schemas.changes_schema import ChangesSchema from ahriman.web.schemas.changes_schema import ChangesSchema
from ahriman.web.schemas.configuration_schema import ConfigurationSchema
from ahriman.web.schemas.counters_schema import CountersSchema from ahriman.web.schemas.counters_schema import CountersSchema
from ahriman.web.schemas.dependencies_schema import DependenciesSchema from ahriman.web.schemas.dependencies_schema import DependenciesSchema
from ahriman.web.schemas.error_schema import ErrorSchema from ahriman.web.schemas.error_schema import ErrorSchema
@@ -35,7 +34,6 @@ from ahriman.web.schemas.log_schema import LogSchema
from ahriman.web.schemas.login_schema import LoginSchema from ahriman.web.schemas.login_schema import LoginSchema
from ahriman.web.schemas.logs_rotate_schema import LogsRotateSchema from ahriman.web.schemas.logs_rotate_schema import LogsRotateSchema
from ahriman.web.schemas.logs_schema import LogsSchema from ahriman.web.schemas.logs_schema import LogsSchema
from ahriman.web.schemas.logs_search_schema import LogsSearchSchema
from ahriman.web.schemas.oauth2_schema import OAuth2Schema from ahriman.web.schemas.oauth2_schema import OAuth2Schema
from ahriman.web.schemas.package_name_schema import PackageNameSchema from ahriman.web.schemas.package_name_schema import PackageNameSchema
from ahriman.web.schemas.package_names_schema import PackageNamesSchema from ahriman.web.schemas.package_names_schema import PackageNamesSchema

View File

@@ -25,11 +25,11 @@ class AURPackageSchema(Schema):
response AUR package schema response AUR package schema
""" """
description = fields.String(required=True, metadata={
"description": "Package description",
"example": "ArcH linux ReposItory MANager",
})
package = fields.String(required=True, metadata={ package = fields.String(required=True, metadata={
"description": "Package base", "description": "Package base",
"example": "ahriman", "example": "ahriman",
}) })
description = fields.String(required=True, metadata={
"description": "Package description",
"example": "ArcH linux ReposItory MANager",
})

View File

@@ -25,10 +25,10 @@ class ChangesSchema(Schema):
response package changes schema response package changes schema
""" """
changes = fields.String(metadata={
"description": "Package changes in patch format",
})
last_commit_sha = fields.String(metadata={ last_commit_sha = fields.String(metadata={
"description": "Last recorded commit hash", "description": "Last recorded commit hash",
"example": "f1875edca1eb8fc0e55c41d1cae5fa05b6b7c6", "example": "f1875edca1eb8fc0e55c41d1cae5fa05b6b7c6",
}) })
changes = fields.String(metadata={
"description": "Package changes in patch format",
})

View File

@@ -1,39 +0,0 @@
#
# Copyright (c) 2021-2025 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.web.apispec import Schema, fields
class ConfigurationSchema(Schema):
"""
response configuration schema
"""
key = fields.String(required=True, metadata={
"description": "Configuration key",
"example": "host",
})
section = fields.String(required=True, metadata={
"description": "Configuration section",
"example": "web",
})
value = fields.String(required=True, metadata={
"description": "Configuration value",
"example": "127.0.0.1",
})

View File

@@ -25,6 +25,18 @@ class CountersSchema(Schema):
response package counters schema response package counters schema
""" """
total = fields.Integer(required=True, metadata={
"description": "Total amount of packages",
"example": 6,
})
_unknown = fields.Integer(data_key="unknown", required=True, metadata={
"description": "Amount of packages in unknown state",
"example": 0,
})
pending = fields.Integer(required=True, metadata={
"description": "Amount of packages in pending state",
"example": 2,
})
building = fields.Integer(required=True, metadata={ building = fields.Integer(required=True, metadata={
"description": "Amount of packages in building state", "description": "Amount of packages in building state",
"example": 1, "example": 1,
@@ -33,19 +45,7 @@ class CountersSchema(Schema):
"description": "Amount of packages in failed state", "description": "Amount of packages in failed state",
"example": 1, "example": 1,
}) })
pending = fields.Integer(required=True, metadata={
"description": "Amount of packages in pending state",
"example": 2,
})
success = fields.Integer(required=True, metadata={ success = fields.Integer(required=True, metadata={
"description": "Amount of packages in success state", "description": "Amount of packages in success state",
"example": 3, "example": 3,
}) })
total = fields.Integer(required=True, metadata={
"description": "Total amount of packages",
"example": 6,
})
unknown_ = fields.Integer(data_key="unknown", required=True, metadata={
"description": "Amount of packages in unknown state",
"example": 0,
})

View File

@@ -30,17 +30,17 @@ class EventSchema(Schema):
"description": "Event creation timestamp", "description": "Event creation timestamp",
"example": 1680537091, "example": 1680537091,
}) })
data = fields.Dict(keys=fields.String(), metadata={
"description": "Event metadata if available",
})
event = fields.String(required=True, metadata={ event = fields.String(required=True, metadata={
"description": "Event type", "description": "Event type",
"example": EventType.PackageUpdated, "example": EventType.PackageUpdated,
}) })
message = fields.String(metadata={
"description": "Event message if available",
})
object_id = fields.String(required=True, metadata={ object_id = fields.String(required=True, metadata={
"description": "Event object identifier", "description": "Event object identifier",
"example": "ahriman", "example": "ahriman",
}) })
message = fields.String(metadata={
"description": "Event message if available",
})
data = fields.Dict(keys=fields.String(), metadata={
"description": "Event metadata if available",
})

View File

@@ -31,14 +31,14 @@ class EventSearchSchema(PaginationSchema):
"description": "Event type", "description": "Event type",
"example": EventType.PackageUpdated, "example": EventType.PackageUpdated,
}) })
from_date = fields.Integer(metadata={
"description": "Minimal creation timestamp, inclusive",
"example": 1680537091,
})
object_id = fields.String(metadata={ object_id = fields.String(metadata={
"description": "Event object identifier", "description": "Event object identifier",
"example": "ahriman", "example": "ahriman",
}) })
from_date = fields.Integer(metadata={
"description": "Minimal creation timestamp, inclusive",
"example": 1680537091,
})
to_date = fields.Integer(metadata={ to_date = fields.Integer(metadata={
"description": "Maximal creation timestamp, exclusive", "description": "Maximal creation timestamp, exclusive",
"example": 1680537091, "example": 1680537091,

View File

@@ -33,10 +33,10 @@ class LogSchema(Schema):
message = fields.String(required=True, metadata={ message = fields.String(required=True, metadata={
"description": "Log message", "description": "Log message",
}) })
process_id = fields.String(metadata={
"description": "Process unique identifier",
})
version = fields.String(required=True, metadata={ version = fields.String(required=True, metadata={
"description": "Package version to tag", "description": "Package version to tag",
"example": __version__, "example": __version__,
}) })
process_id = fields.String(metadata={
"description": "Process unique identifier",
})

View File

@@ -25,11 +25,11 @@ class LoginSchema(Schema):
request login schema request login schema
""" """
password = fields.String(required=True, metadata={
"description": "Login password",
"example": "pa55w0rd",
})
username = fields.String(required=True, metadata={ username = fields.String(required=True, metadata={
"description": "Login username", "description": "Login username",
"example": "user", "example": "user",
}) })
password = fields.String(required=True, metadata={
"description": "Login password",
"example": "pa55w0rd",
})

View File

@@ -1,39 +0,0 @@
#
# Copyright (c) 2021-2025 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 import __version__
from ahriman.web.apispec import fields
from ahriman.web.schemas.pagination_schema import PaginationSchema
class LogsSearchSchema(PaginationSchema):
"""
request log search schema
"""
head = fields.Boolean(metadata={
"description": "Return versions only without fetching logs themselves",
})
process_id = fields.String(metadata={
"description": "Process unique identifier to search",
})
version = fields.String(metadata={
"description": "Package version to search",
"example": __version__,
})

View File

@@ -37,14 +37,22 @@ class PackagePropertiesSchema(Schema):
"description": "Package build timestamp", "description": "Package build timestamp",
"example": 1680537091, "example": 1680537091,
}) })
check_depends = fields.List(fields.String(), metadata={
"description": "Package test dependencies list",
"example": ["python-pytest"],
})
depends = fields.List(fields.String(), metadata={ depends = fields.List(fields.String(), metadata={
"description": "Package dependencies list", "description": "Package dependencies list",
"example": ["devtools"], "example": ["devtools"],
}) })
make_depends = fields.List(fields.String(), metadata={
"description": "Package make dependencies list",
"example": ["python-build"],
})
opt_depends = fields.List(fields.String(), metadata={
"description": "Package optional dependencies list",
"example": ["python-aiohttp"],
})
check_depends = fields.List(fields.String(), metadata={
"description": "Package test dependencies list",
"example": ["python-pytest"],
})
description = fields.String(metadata={ description = fields.String(metadata={
"description": "Package description", "description": "Package description",
"example": "ArcH linux ReposItory MANager", "example": "ArcH linux ReposItory MANager",
@@ -65,14 +73,6 @@ class PackagePropertiesSchema(Schema):
"description": "Package licenses", "description": "Package licenses",
"example": ["GPL3"], "example": ["GPL3"],
}) })
make_depends = fields.List(fields.String(), metadata={
"description": "Package make dependencies list",
"example": ["python-build"],
})
opt_depends = fields.List(fields.String(), metadata={
"description": "Package optional dependencies list",
"example": ["python-aiohttp"],
})
provides = fields.List(fields.String(), metadata={ provides = fields.List(fields.String(), metadata={
"description": "Package provides list", "description": "Package provides list",
"example": ["ahriman-git"], "example": ["ahriman-git"],

View File

@@ -32,18 +32,18 @@ class PackageSchema(Schema):
"description": "Package base", "description": "Package base",
"example": "ahriman", "example": "ahriman",
}) })
packager = fields.String(metadata={ version = fields.String(required=True, metadata={
"description": "packager for the last success package build", "description": "Package version",
"example": "ahriman bot <ahriman@example.com>", "example": __version__,
})
remote = fields.Nested(RemoteSchema(), required=True, metadata={
"description": "Package remote properties",
}) })
packages = fields.Dict( packages = fields.Dict(
keys=fields.String(), values=fields.Nested(PackagePropertiesSchema()), required=True, metadata={ keys=fields.String(), values=fields.Nested(PackagePropertiesSchema()), required=True, metadata={
"description": "Packages which belong to this base", "description": "Packages which belong to this base",
}) })
remote = fields.Nested(RemoteSchema(), required=True, metadata={ packager = fields.String(metadata={
"description": "Package remote properties", "description": "packager for the last success package build",
}) "example": "ahriman bot <ahriman@example.com>",
version = fields.String(required=True, metadata={
"description": "Package version",
"example": __version__,
}) })

View File

@@ -25,19 +25,19 @@ class RepositoryStatsSchema(Schema):
response repository stats schema response repository stats schema
""" """
archive_size = fields.Int(metadata={
"description": "Total archive size of the packages in bytes",
"example": 42000,
})
bases = fields.Int(metadata={ bases = fields.Int(metadata={
"description": "Amount of unique packages bases", "description": "Amount of unique packages bases",
"example": 2, "example": 2,
}) })
installed_size = fields.Int(metadata={
"description": "Total installed size of the packages in bytes",
"example": 42000000,
})
packages = fields.Int(metadata={ packages = fields.Int(metadata={
"description": "Amount of unique packages", "description": "Amount of unique packages",
"example": 4, "example": 4,
}) })
archive_size = fields.Int(metadata={
"description": "Total archive size of the packages in bytes",
"example": 42000,
})
installed_size = fields.Int(metadata={
"description": "Total installed size of the packages in bytes",
"example": 42000000,
})

View File

@@ -25,7 +25,7 @@ class SearchSchema(Schema):
request package search schema request package search schema
""" """
for_ = fields.List(fields.String(), data_key="for", required=True, metadata={ _for = fields.List(fields.String(), data_key="for", required=True, metadata={
"description": "Keyword for search", "description": "Keyword for search",
"example": ["ahriman"], "example": ["ahriman"],
}) })

View File

@@ -22,7 +22,6 @@ import aiohttp_jinja2
from typing import Any, ClassVar from typing import Any, ClassVar
from ahriman.core.auth.helpers import authorized_userid from ahriman.core.auth.helpers import authorized_userid
from ahriman.core.utils import pretty_interval
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.apispec import aiohttp_apispec from ahriman.web.apispec import aiohttp_apispec
from ahriman.web.views.base import BaseView from ahriman.web.views.base import BaseView
@@ -38,10 +37,6 @@ class IndexView(BaseView):
* control - HTML to insert for login control, HTML string, required * control - HTML to insert for login control, HTML string, required
* enabled - whether authorization is enabled by configuration or not, boolean, required * enabled - whether authorization is enabled by configuration or not, boolean, required
* username - authenticated username if any, string, null means not authenticated * username - authenticated username if any, string, null means not authenticated
* autorefresh_intervals - auto refresh intervals, optional
* interval - auto refresh interval in milliseconds, integer, required
* is_active - is current interval active or not, boolean, required
* text - text representation of the interval (e.g. "30 seconds"), string, required
* docs_enabled - indicates if api docs is enabled, boolean, required * docs_enabled - indicates if api docs is enabled, boolean, required
* index_url - url to the repository index, string, optional * index_url - url to the repository index, string, optional
* repositories - list of repositories unique identifiers, required * repositories - list of repositories unique identifiers, required
@@ -71,19 +66,8 @@ class IndexView(BaseView):
"username": auth_username, "username": auth_username,
} }
autorefresh_intervals = [
{
"interval": interval * 1000, # milliseconds
"is_active": index == 0, # first element is always default
"text": pretty_interval(interval),
}
for index, interval in enumerate(self.configuration.getintlist("web", "autorefresh_intervals", fallback=[]))
if interval > 0 # special case if 0 exists and first, refresh will not be turned on by default
]
return { return {
"auth": auth, "auth": auth,
"autorefresh_intervals": sorted(autorefresh_intervals, key=lambda interval: interval["interval"]),
"docs_enabled": aiohttp_apispec is not None, "docs_enabled": aiohttp_apispec is not None,
"index_url": self.configuration.get("web", "index_url", fallback=None), "index_url": self.configuration.get("web", "index_url", fallback=None),
"repositories": [ "repositories": [

View File

@@ -90,7 +90,7 @@ class LogsView(StatusViewGuard, BaseView):
try: try:
_, status = self.service().package_get(package_base) _, status = self.service().package_get(package_base)
logs = self.service(package_base=package_base).package_logs_get(package_base, None, None, -1, 0) logs = self.service(package_base=package_base).package_logs_get(package_base, -1, 0)
except UnknownPackageError: except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown") raise HTTPNotFound(reason=f"Package {package_base} is unknown")

View File

@@ -1,84 +0,0 @@
#
# Copyright (c) 2021-2025 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 aiohttp.web import HTTPNoContent, Response, json_response
from typing import ClassVar
from ahriman.core.formatters import ConfigurationPrinter
from ahriman.models.user_access import UserAccess
from ahriman.web.apispec.decorators import apidocs
from ahriman.web.schemas import ConfigurationSchema
from ahriman.web.views.base import BaseView
class ConfigView(BaseView):
"""
configuration control view
Attributes:
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
GET_PERMISSION = POST_PERMISSION = UserAccess.Full # type: ClassVar[UserAccess]
ROUTES = ["/api/v1/service/config"]
@apidocs(
tags=["Actions"],
summary="Get configuration",
description="Get current web service configuration as nested dictionary",
permission=GET_PERMISSION,
schema=ConfigurationSchema(many=True),
)
async def get(self) -> Response:
"""
get current web service configuration
Returns:
Response: current web service configuration as nested dictionary
"""
dump = self.configuration.dump()
response = [
{
"section": section,
"key": key,
"value": value,
} for section, values in dump.items()
for key, value in values.items()
if key not in ConfigurationPrinter.HIDE_KEYS
]
return json_response(response)
@apidocs(
tags=["Actions"],
summary="Reload configuration",
description="Reload configuration from current files",
permission=POST_PERMISSION,
)
async def post(self) -> None:
"""
reload web service configuration
Raises:
HTTPNoContent: on success response
"""
self.configuration.reload()
raise HTTPNoContent

View File

@@ -70,11 +70,7 @@ class SearchView(BaseView):
if not packages: if not packages:
raise HTTPNotFound(reason=f"No packages found for terms: {search}") raise HTTPNotFound(reason=f"No packages found for terms: {search}")
comparator: Callable[[AURPackage], tuple[bool, bool, str]] = lambda item: ( comparator: Callable[[AURPackage], str] = lambda item: str(item.package_base)
item.package_base not in search, # inverted because False < True
not any(item.package_base.startswith(term) for term in search), # same as above
item.package_base,
)
response = [ response = [
{ {
"package": package.package_base, "package": package.package_base,

View File

@@ -17,22 +17,18 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import itertools
from aiohttp.web import Response, json_response from aiohttp.web import Response, json_response
from dataclasses import replace
from typing import ClassVar from typing import ClassVar
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.apispec.decorators import apidocs from ahriman.web.apispec.decorators import apidocs
from ahriman.web.schemas import LogSchema, LogsSearchSchema, PackageNameSchema from ahriman.web.schemas import LogSchema, PackageNameSchema, PaginationSchema
from ahriman.web.views.base import BaseView from ahriman.web.views.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard from ahriman.web.views.status_view_guard import StatusViewGuard
class LogsView(StatusViewGuard, BaseView): class LogsView(StatusViewGuard, BaseView):
""" else: """
package logs web view package logs web view
Attributes: Attributes:
@@ -51,7 +47,7 @@ class LogsView(StatusViewGuard, BaseView):
error_404_description="Package base and/or repository are unknown", error_404_description="Package base and/or repository are unknown",
schema=LogSchema(many=True), schema=LogSchema(many=True),
match_schema=PackageNameSchema, match_schema=PackageNameSchema,
query_schema=LogsSearchSchema, query_schema=PaginationSchema,
) )
async def get(self) -> Response: async def get(self) -> Response:
""" """
@@ -65,19 +61,8 @@ class LogsView(StatusViewGuard, BaseView):
""" """
package_base = self.request.match_info["package"] package_base = self.request.match_info["package"]
limit, offset = self.page() limit, offset = self.page()
version = self.request.query.get("version", None)
process = self.request.query.get("process_id", None)
logs = self.service(package_base=package_base).package_logs_get(package_base, version, process, limit, offset) logs = self.service(package_base=package_base).package_logs_get(package_base, limit, offset)
head = self.request.query.get("head", "false")
# pylint: disable=protected-access
if self.configuration._convert_to_boolean(head): # type: ignore[attr-defined]
# logs should be sorted already
logs = [
replace(next(log_records), message="") # remove messages
for _, log_records in itertools.groupby(logs, lambda log_record: log_record.log_record_id)
]
response = [log_record.view() for log_record in logs] response = [log_record.view() for log_record in logs]
return json_response(response) return json_response(response)

View File

@@ -141,7 +141,7 @@ def test_add_remote_missing(application_packages: ApplicationPackages, mocker: M
""" """
must raise UnknownPackageError if remote package wasn't found must raise UnknownPackageError if remote package wasn't found
""" """
mocker.patch("requests.get", side_effect=Exception) mocker.patch("requests.get", side_effect=Exception())
with pytest.raises(UnknownPackageError): with pytest.raises(UnknownPackageError):
application_packages._add_remote("url") application_packages._add_remote("url")

View File

@@ -135,7 +135,7 @@ def test_unknown_no_aur(application_repository: ApplicationRepository, package_a
must return empty list in case if there is locally stored PKGBUILD must return empty list in case if there is locally stored PKGBUILD
""" """
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman]) mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception) mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception())
mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman) mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
mocker.patch("pathlib.Path.is_dir", return_value=True) mocker.patch("pathlib.Path.is_dir", return_value=True)
mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=False) mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=False)
@@ -149,7 +149,7 @@ def test_unknown_no_aur_no_local(application_repository: ApplicationRepository,
must return list of packages missing in aur and in local storage must return list of packages missing in aur and in local storage
""" """
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman]) mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception) mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception())
mocker.patch("pathlib.Path.is_dir", return_value=False) mocker.patch("pathlib.Path.is_dir", return_value=False)
packages = application_repository.unknown() packages = application_repository.unknown()

View File

@@ -46,7 +46,7 @@ def test_call_exception(args: argparse.Namespace, configuration: Configuration,
""" """
args.configuration = Path("") args.configuration = Path("")
args.quiet = False args.quiet = False
mocker.patch("ahriman.core.configuration.Configuration.from_path", side_effect=Exception) mocker.patch("ahriman.core.configuration.Configuration.from_path", side_effect=Exception())
logging_mock = mocker.patch("logging.Logger.exception") logging_mock = mocker.patch("logging.Logger.exception")
_, repository_id = configuration.check_loaded() _, repository_id = configuration.check_loaded()
@@ -60,7 +60,7 @@ def test_call_exit_code(args: argparse.Namespace, configuration: Configuration,
""" """
args.configuration = Path("") args.configuration = Path("")
args.quiet = False args.quiet = False
mocker.patch("ahriman.core.configuration.Configuration.from_path", side_effect=ExitCode) mocker.patch("ahriman.core.configuration.Configuration.from_path", side_effect=ExitCode())
logging_mock = mocker.patch("logging.Logger.exception") logging_mock = mocker.patch("logging.Logger.exception")
_, repository_id = configuration.check_loaded() _, repository_id = configuration.check_loaded()

View File

@@ -1,27 +0,0 @@
import argparse
from pytest_mock import MockerFixture
from ahriman.application.handlers.reload import Reload
from ahriman.core.configuration import Configuration
from ahriman.core.repository import Repository
def test_run(args: argparse.Namespace, configuration: Configuration, repository: Repository,
mocker: MockerFixture) -> None:
"""
must run command
"""
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
run_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.configuration_reload")
_, repository_id = configuration.check_loaded()
Reload.run(args, repository_id, configuration, report=False)
run_mock.assert_called_once_with()
def test_disallow_multi_architecture_run() -> None:
"""
must not allow multi architecture run
"""
assert not Reload.ALLOW_MULTI_ARCHITECTURE_RUN

View File

@@ -2,6 +2,7 @@ import argparse
import json import json
import pytest import pytest
from pathlib import Path
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from ahriman.application.handlers.validate import Validate from ahriman.application.handlers.validate import Validate
@@ -53,12 +54,50 @@ def test_run_skip(args: argparse.Namespace, configuration: Configuration, mocker
print_mock.assert_not_called() print_mock.assert_not_called()
def test_run_default(args: argparse.Namespace, configuration: Configuration) -> None:
"""
must run on default configuration without errors
"""
args.exit_code = True
_, repository_id = configuration.check_loaded()
default = Configuration.from_path(Configuration.SYSTEM_CONFIGURATION_PATH, repository_id)
# copy autogenerated values
for section, key in (("build", "build_command"), ("repository", "root")):
value = configuration.get(section, key)
default.set_option(section, key, value)
Validate.run(args, repository_id, default, report=False)
def test_run_repo_specific_triggers(args: argparse.Namespace, configuration: Configuration,
resource_path_root: Path) -> None:
"""
must correctly insert repo specific triggers
"""
args.exit_code = True
_, repository_id = configuration.check_loaded()
# remove unused sections
for section in ("customs3", "github:x86_64", "logs-rotation", "mirrorlist"):
configuration.remove_section(section)
configuration.set_option("report", "target", "test")
for section in ("test", "test:i686", "test:another-repo:x86_64"):
configuration.set_option(section, "type", "html")
configuration.set_option(section, "link_path", "http://link_path")
configuration.set_option(section, "path", "path")
configuration.set_option(section, "template", "template")
configuration.set_option(section, "templates", str(resource_path_root))
Validate.run(args, repository_id, configuration, report=False)
def test_schema(configuration: Configuration) -> None: def test_schema(configuration: Configuration) -> None:
""" """
must generate full schema correctly must generate full schema correctly
""" """
_, repository_id = configuration.check_loaded() schema = Validate.schema(configuration)
schema = Validate.schema(repository_id, configuration)
# defaults # defaults
assert schema.pop("console") assert schema.pop("console")
@@ -91,9 +130,7 @@ def test_schema_invalid_trigger(configuration: Configuration) -> None:
""" """
configuration.set_option("build", "triggers", "some.invalid.trigger.path.Trigger") configuration.set_option("build", "triggers", "some.invalid.trigger.path.Trigger")
configuration.remove_option("build", "triggers_known") configuration.remove_option("build", "triggers_known")
_, repository_id = configuration.check_loaded() assert Validate.schema(configuration) == CONFIGURATION_SCHEMA
assert Validate.schema(repository_id, configuration) == CONFIGURATION_SCHEMA
def test_schema_erase_required() -> None: def test_schema_erase_required() -> None:

View File

@@ -1563,35 +1563,6 @@ def test_subparsers_web_option_repository(parser: argparse.ArgumentParser) -> No
assert args.repository == "" assert args.repository == ""
def test_subparsers_web_reload(parser: argparse.ArgumentParser) -> None:
"""
web-reload command must imply architecture, lock, quiet, report, repository and unsafe
"""
args = parser.parse_args(["web-reload"])
assert args.architecture == ""
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == ""
assert args.unsafe
def test_subparsers_web_reload_option_architecture(parser: argparse.ArgumentParser) -> None:
"""
web-reload command must correctly parse architecture list
"""
args = parser.parse_args(["-a", "x86_64", "web-reload"])
assert args.architecture == ""
def test_subparsers_web_reload_option_repository(parser: argparse.ArgumentParser) -> None:
"""
web-reload command must correctly parse repository list
"""
args = parser.parse_args(["-r", "repo", "web-reload"])
assert args.repository == ""
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
""" """
application must be run application must be run

View File

@@ -142,7 +142,7 @@ def test_check_user(lock: Lock, mocker: MockerFixture) -> None:
tree_create = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") tree_create = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
lock.check_user() lock.check_user()
check_user_patch.assert_called_once_with(lock.paths, unsafe=False) check_user_patch.assert_called_once_with(lock.paths.root, unsafe=False)
tree_create.assert_called_once_with() tree_create.assert_called_once_with()
@@ -230,7 +230,7 @@ def test_clear_close_exception(lock: Lock) -> None:
must suppress IO exception on file closure must suppress IO exception on file closure
""" """
close_mock = lock._pid_file = MagicMock() close_mock = lock._pid_file = MagicMock()
close_mock.close.side_effect = IOError close_mock.close.side_effect = IOError()
lock.clear() lock.clear()

View File

@@ -108,7 +108,7 @@ def test_aur_request_failed(aur: AUR, mocker: MockerFixture) -> None:
""" """
must reraise generic exception must reraise generic exception
""" """
mocker.patch("requests.Session.request", side_effect=Exception) mocker.patch("requests.Session.request", side_effect=Exception())
with pytest.raises(Exception): with pytest.raises(Exception):
aur.aur_request("info", "ahriman") aur.aur_request("info", "ahriman")
@@ -116,7 +116,7 @@ def test_aur_request_failed(aur: AUR, mocker: MockerFixture) -> None:
def test_aur_request_failed_http_error(aur: AUR, mocker: MockerFixture) -> None: def test_aur_request_failed_http_error(aur: AUR, mocker: MockerFixture) -> None:
""" must reraise http exception """ must reraise http exception
""" """
mocker.patch("requests.Session.request", side_effect=requests.HTTPError) mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
with pytest.raises(requests.HTTPError): with pytest.raises(requests.HTTPError):
aur.aur_request("info", "ahriman") aur.aur_request("info", "ahriman")

View File

@@ -80,7 +80,7 @@ def test_arch_request_failed(official: Official, mocker: MockerFixture) -> None:
""" """
must reraise generic exception must reraise generic exception
""" """
mocker.patch("requests.Session.request", side_effect=Exception) mocker.patch("requests.Session.request", side_effect=Exception())
with pytest.raises(Exception): with pytest.raises(Exception):
official.arch_request("akonadi", by="q") official.arch_request("akonadi", by="q")
@@ -89,7 +89,7 @@ def test_arch_request_failed_http_error(official: Official, mocker: MockerFixtur
""" """
must reraise http exception must reraise http exception
""" """
mocker.patch("requests.Session.request", side_effect=requests.HTTPError) mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
with pytest.raises(requests.HTTPError): with pytest.raises(requests.HTTPError):
official.arch_request("akonadi", by="q") official.arch_request("akonadi", by="q")

View File

@@ -1,6 +1,5 @@
import pytest import pytest
from dataclasses import replace
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from unittest.mock import call as MockCall from unittest.mock import call as MockCall
@@ -89,17 +88,6 @@ def test_multisearch_single(aur_package_ahriman: AURPackage, pacman: Pacman, moc
search_mock.assert_called_once_with("ahriman", pacman=pacman, search_by=None) search_mock.assert_called_once_with("ahriman", pacman=pacman, search_by=None)
def test_multisearch_remove_duplicates(aur_package_ahriman: AURPackage, pacman: Pacman, mocker: MockerFixture) -> None:
"""
must remove duplicates from search result
"""
package1 = replace(aur_package_ahriman)
package2 = replace(aur_package_ahriman, name="ahriman-triggers")
mocker.patch("ahriman.core.alpm.remote.Remote.package_search", return_value=[package1, package2])
assert Remote.multisearch("ahriman", pacman=pacman) == [package1]
def test_remote_git_url(remote: Remote) -> None: def test_remote_git_url(remote: Remote) -> None:
""" """
must raise NotImplemented for missing remote git url must raise NotImplemented for missing remote git url

View File

@@ -242,7 +242,7 @@ def test_files_no_entry(pacman: Pacman, pyalpm_package_ahriman: pyalpm.Package,
pacman.handle = handle_mock pacman.handle = handle_mock
tar_mock = MagicMock() tar_mock = MagicMock()
tar_mock.extractfile.side_effect = KeyError tar_mock.extractfile.side_effect = KeyError()
open_mock = MagicMock() open_mock = MagicMock()
open_mock.__enter__.return_value = tar_mock open_mock.__enter__.return_value = tar_mock

View File

@@ -131,7 +131,7 @@ def test_sync_exception(pacman_database: PacmanDatabase, mocker: MockerFixture)
""" """
must suppress all exceptions on failure must suppress all exceptions on failure
""" """
mocker.patch("ahriman.core.alpm.pacman_database.PacmanDatabase.sync_packages", side_effect=Exception) mocker.patch("ahriman.core.alpm.pacman_database.PacmanDatabase.sync_packages", side_effect=Exception())
pacman_database.sync(force=True) pacman_database.sync(force=True)

View File

@@ -52,7 +52,7 @@ def test_repo_remove_fail_no_file(repo: Repo, mocker: MockerFixture) -> None:
must fail on missing file must fail on missing file
""" """
mocker.patch("pathlib.Path.glob", return_value=[Path("package.pkg.tar.xz")]) mocker.patch("pathlib.Path.glob", return_value=[Path("package.pkg.tar.xz")])
mocker.patch("pathlib.Path.unlink", side_effect=FileNotFoundError) mocker.patch("pathlib.Path.unlink", side_effect=FileNotFoundError())
with pytest.raises(FileNotFoundError): with pytest.raises(FileNotFoundError):
repo.remove("package", Path("package.pkg.tar.xz")) repo.remove("package", Path("package.pkg.tar.xz"))

View File

@@ -90,7 +90,7 @@ async def test_get_oauth_username_exception_1(oauth: OAuth, mocker: MockerFixtur
""" """
must return None in case of OAuth request error (get_access_token) must return None in case of OAuth request error (get_access_token)
""" """
mocker.patch("aioauth_client.GoogleClient.get_access_token", side_effect=Exception) mocker.patch("aioauth_client.GoogleClient.get_access_token", side_effect=Exception())
user_info_mock = mocker.patch("aioauth_client.GoogleClient.user_info") user_info_mock = mocker.patch("aioauth_client.GoogleClient.user_info")
email = await oauth.get_oauth_username("code") email = await oauth.get_oauth_username("code")
@@ -103,7 +103,7 @@ async def test_get_oauth_username_exception_2(oauth: OAuth, mocker: MockerFixtur
must return None in case of OAuth request error (user_info) must return None in case of OAuth request error (user_info)
""" """
mocker.patch("aioauth_client.GoogleClient.get_access_token", return_value=("token", "")) mocker.patch("aioauth_client.GoogleClient.get_access_token", return_value=("token", ""))
mocker.patch("aioauth_client.GoogleClient.user_info", side_effect=Exception) mocker.patch("aioauth_client.GoogleClient.user_info", side_effect=Exception())
email = await oauth.get_oauth_username("code") email = await oauth.get_oauth_username("code")
assert email is None assert email is None

View File

@@ -20,6 +20,40 @@ def test_architecture(configuration: Configuration) -> None:
assert configuration.architecture == "x86_64" assert configuration.architecture == "x86_64"
def test_repository_id(configuration: Configuration, repository_id: RepositoryId) -> None:
"""
must return repository identifier
"""
assert configuration.repository_id == repository_id
assert configuration.get("repository", "name") == repository_id.name
assert configuration.get("repository", "architecture") == repository_id.architecture
def test_repository_id_erase(configuration: Configuration) -> None:
"""
must remove repository identifier properties if empty identifier supplied
"""
configuration.repository_id = None
assert configuration.get("repository", "name", fallback=None) is None
assert configuration.get("repository", "architecture", fallback=None) is None
configuration.repository_id = RepositoryId("", "")
assert configuration.get("repository", "name", fallback=None) is None
assert configuration.get("repository", "architecture", fallback=None) is None
def test_repository_id_update(configuration: Configuration, repository_id: RepositoryId) -> None:
"""
must update repository identifier and related configuration options
"""
repository_id = RepositoryId("i686", repository_id.name)
configuration.repository_id = repository_id
assert configuration.repository_id == repository_id
assert configuration.get("repository", "name") == repository_id.name
assert configuration.get("repository", "architecture") == repository_id.architecture
def test_repository_name(configuration: Configuration) -> None: def test_repository_name(configuration: Configuration) -> None:
""" """
must return valid repository name must return valid repository name
@@ -133,14 +167,6 @@ def test_dump_architecture_specific(configuration: Configuration) -> None:
assert dump["build"]["archbuild_flags"] == "hello flag" assert dump["build"]["archbuild_flags"] == "hello flag"
def test_getintlist(configuration: Configuration) -> None:
"""
must extract list of integers
"""
configuration.set_option("build", "test_int_list", "1 42 3")
assert configuration.getintlist("build", "test_int_list") == [1, 42, 3]
def test_getlist(configuration: Configuration) -> None: def test_getlist(configuration: Configuration) -> None:
""" """
must return list of string correctly must return list of string correctly

View File

@@ -42,7 +42,7 @@ def test_apply_migration_exception(migrations: Migrations, mocker: MockerFixture
must roll back and close cursor on exception during migration must roll back and close cursor on exception during migration
""" """
cursor = MagicMock() cursor = MagicMock()
mocker.patch("logging.Logger.info", side_effect=Exception) mocker.patch("logging.Logger.info", side_effect=Exception())
migrations.connection.cursor.return_value = cursor migrations.connection.cursor.return_value = cursor
with pytest.raises(Exception): with pytest.raises(Exception):
@@ -59,7 +59,7 @@ def test_apply_migration_sql_exception(migrations: Migrations) -> None:
must close cursor on general migration error must close cursor on general migration error
""" """
cursor = MagicMock() cursor = MagicMock()
cursor.execute.side_effect = Exception cursor.execute.side_effect = Exception()
migrations.connection.cursor.return_value = cursor migrations.connection.cursor.return_value = cursor
with pytest.raises(Exception): with pytest.raises(Exception):

View File

@@ -71,35 +71,11 @@ def test_logs_insert_get_pagination(database: SQLite, package_ahriman: Package)
""" """
database.logs_insert(LogRecord(LogRecordId(package_ahriman.base, "1"), 42.0, "message 1")) database.logs_insert(LogRecord(LogRecordId(package_ahriman.base, "1"), 42.0, "message 1"))
database.logs_insert(LogRecord(LogRecordId(package_ahriman.base, "1"), 43.0, "message 2")) database.logs_insert(LogRecord(LogRecordId(package_ahriman.base, "1"), 43.0, "message 2"))
assert database.logs_get(package_ahriman.base, None, None, 1, 1) == [ assert database.logs_get(package_ahriman.base, 1, 1) == [
LogRecord(LogRecordId(package_ahriman.base, "1"), 42.0, "message 1"), LogRecord(LogRecordId(package_ahriman.base, "1"), 42.0, "message 1"),
] ]
def test_logs_insert_get_filter_by_version(database: SQLite, package_ahriman: Package) -> None:
"""
must insert and get package logs with pagination
"""
database.logs_insert(LogRecord(LogRecordId(package_ahriman.base, "1", "p1"), 42.0, "message 1"))
database.logs_insert(LogRecord(LogRecordId(package_ahriman.base, "1", "p2"), 43.0, "message 2"))
database.logs_insert(LogRecord(LogRecordId(package_ahriman.base, "2", "p1"), 44.0, "message 3"))
assert database.logs_get(package_ahriman.base, "1", None) == [
LogRecord(LogRecordId(package_ahriman.base, "1", "p1"), 42.0, "message 1"),
LogRecord(LogRecordId(package_ahriman.base, "1", "p2"), 43.0, "message 2"),
]
assert database.logs_get(package_ahriman.base, "2", None) == [
LogRecord(LogRecordId(package_ahriman.base, "2", "p1"), 44.0, "message 3"),
]
assert database.logs_get(package_ahriman.base, None, "p1") == [
LogRecord(LogRecordId(package_ahriman.base, "1", "p1"), 42.0, "message 1"),
LogRecord(LogRecordId(package_ahriman.base, "2", "p1"), 44.0, "message 3"),
]
assert database.logs_get(package_ahriman.base, "1", "p1") == [
LogRecord(LogRecordId(package_ahriman.base, "1", "p1"), 42.0, "message 1"),
]
def test_logs_insert_get_multi(database: SQLite, package_ahriman: Package) -> None: def test_logs_insert_get_multi(database: SQLite, package_ahriman: Package) -> None:
""" """
must insert and get package logs for multiple repositories must insert and get package logs for multiple repositories

View File

@@ -37,7 +37,7 @@ def test_register_failed(distributed_system: DistributedSystem, mocker: MockerFi
""" """
must suppress any exception happened during worker registration must suppress any exception happened during worker registration
""" """
mocker.patch("requests.Session.request", side_effect=Exception) mocker.patch("requests.Session.request", side_effect=Exception())
distributed_system.register() distributed_system.register()
@@ -45,7 +45,7 @@ def test_register_failed_http_error(distributed_system: DistributedSystem, mocke
""" """
must suppress HTTP exception happened during worker registration must suppress HTTP exception happened during worker registration
""" """
mocker.patch("requests.Session.request", side_effect=requests.HTTPError) mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
distributed_system.register() distributed_system.register()
@@ -70,7 +70,7 @@ def test_workers_failed(distributed_system: DistributedSystem, mocker: MockerFix
""" """
must suppress any exception happened during worker extraction must suppress any exception happened during worker extraction
""" """
mocker.patch("requests.Session.request", side_effect=Exception) mocker.patch("requests.Session.request", side_effect=Exception())
distributed_system.workers() distributed_system.workers()
@@ -78,5 +78,5 @@ def test_workers_failed_http_error(distributed_system: DistributedSystem, mocker
""" """
must suppress HTTP exception happened during worker extraction must suppress HTTP exception happened during worker extraction
""" """
mocker.patch("requests.Session.request", side_effect=requests.HTTPError) mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
distributed_system.workers() distributed_system.workers()

View File

@@ -85,7 +85,7 @@ def test_run_failed(configuration: Configuration, mocker: MockerFixture) -> None
""" """
must reraise exception on error occurred must reraise exception on error occurred
""" """
mocker.patch("ahriman.core.gitremote.remote_pull.RemotePull.repo_clone", side_effect=Exception) mocker.patch("ahriman.core.gitremote.remote_pull.RemotePull.repo_clone", side_effect=Exception())
_, repository_id = configuration.check_loaded() _, repository_id = configuration.check_loaded()
runner = RemotePull(repository_id, configuration, "gitremote") runner = RemotePull(repository_id, configuration, "gitremote")

View File

@@ -82,7 +82,7 @@ def test_run_failed(local_client: Client, configuration: Configuration, result:
""" """
must reraise exception on error occurred must reraise exception on error occurred
""" """
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch", side_effect=Exception) mocker.patch("ahriman.core.build_tools.sources.Sources.fetch", side_effect=Exception())
runner = RemotePush(local_client, configuration, "gitremote") runner = RemotePush(local_client, configuration, "gitremote")
with pytest.raises(GitRemoteError): with pytest.raises(GitRemoteError):

View File

@@ -51,7 +51,7 @@ def test_login_failed(ahriman_client: SyncAhrimanClient, user: User, mocker: Moc
must suppress any exception happened during login must suppress any exception happened during login
""" """
ahriman_client.user = user ahriman_client.user = user
mocker.patch("requests.Session.request", side_effect=Exception) mocker.patch("requests.Session.request", side_effect=Exception())
ahriman_client._login(requests.Session()) ahriman_client._login(requests.Session())
@@ -60,7 +60,7 @@ def test_login_failed_http_error(ahriman_client: SyncAhrimanClient, user: User,
must suppress HTTP exception happened during login must suppress HTTP exception happened during login
""" """
ahriman_client.user = user ahriman_client.user = user
mocker.patch("requests.Session.request", side_effect=requests.HTTPError) mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
ahriman_client._login(requests.Session()) ahriman_client._login(requests.Session())

View File

@@ -124,7 +124,7 @@ def test_make_request_failed(mocker: MockerFixture) -> None:
""" """
must process request errors must process request errors
""" """
mocker.patch("requests.Session.request", side_effect=Exception) mocker.patch("requests.Session.request", side_effect=Exception())
logging_mock = mocker.patch("logging.Logger.exception") logging_mock = mocker.patch("logging.Logger.exception")
with pytest.raises(Exception): with pytest.raises(Exception):
@@ -136,7 +136,7 @@ def test_make_request_suppress_errors(mocker: MockerFixture) -> None:
""" """
must suppress request errors correctly must suppress request errors correctly
""" """
mocker.patch("requests.Session.request", side_effect=Exception) mocker.patch("requests.Session.request", side_effect=Exception())
logging_mock = mocker.patch("logging.Logger.exception") logging_mock = mocker.patch("logging.Logger.exception")
with pytest.raises(Exception): with pytest.raises(Exception):

View File

@@ -61,7 +61,7 @@ def test_emit_failed(configuration: Configuration, log_record: logging.LogRecord
must call handle error on exception must call handle error on exception
""" """
log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version) log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version)
mocker.patch("ahriman.core.status.Client.package_logs_add", side_effect=Exception) mocker.patch("ahriman.core.status.Client.package_logs_add", side_effect=Exception())
handle_error_mock = mocker.patch("logging.Handler.handleError") handle_error_mock = mocker.patch("logging.Handler.handleError")
_, repository_id = configuration.check_loaded() _, repository_id = configuration.check_loaded()
handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=False) handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=False)
@@ -76,7 +76,7 @@ def test_emit_suppress_failed(configuration: Configuration, log_record: logging.
must not call handle error on exception if suppress flag is set must not call handle error on exception if suppress flag is set
""" """
log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version) log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version)
mocker.patch("ahriman.core.status.Client.package_logs_add", side_effect=Exception) mocker.patch("ahriman.core.status.Client.package_logs_add", side_effect=Exception())
handle_error_mock = mocker.patch("logging.Handler.handleError") handle_error_mock = mocker.patch("logging.Handler.handleError")
_, repository_id = configuration.check_loaded() _, repository_id = configuration.check_loaded()
handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=True) handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=True)

View File

@@ -61,7 +61,7 @@ def test_load_fallback(configuration: Configuration, mocker: MockerFixture) -> N
""" """
must fall back to stderr without errors must fall back to stderr without errors
""" """
mocker.patch("ahriman.core.log.log_loader.fileConfig", side_effect=PermissionError) mocker.patch("ahriman.core.log.log_loader.fileConfig", side_effect=PermissionError())
_, repository_id = configuration.check_loaded() _, repository_id = configuration.check_loaded()
LogLoader.load(repository_id, configuration, LogHandler.Journald, quiet=False, report=False) LogLoader.load(repository_id, configuration, LogHandler.Journald, quiet=False, report=False)

View File

@@ -13,7 +13,7 @@ def test_report_failure(configuration: Configuration, mocker: MockerFixture) ->
""" """
must raise ReportFailed on errors must raise ReportFailed on errors
""" """
mocker.patch("ahriman.core.report.html.HTML.generate", side_effect=Exception) mocker.patch("ahriman.core.report.html.HTML.generate", side_effect=Exception())
_, repository_id = configuration.check_loaded() _, repository_id = configuration.check_loaded()
with pytest.raises(ReportError): with pytest.raises(ReportError):

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